## Problem
`GetMonsterAbilities()` answers three questions through one virtual: which abilities a creature has (`GetAbility`, `AOS.cs` reflect/barrier, bard fire-breath difficulty), which abilities are enabled right now, and where cooldown state must be cleaned up (`InvalidateNextAbilityTriggers` on death and delete). Every caller assumes it is a pure, stable list. About 70 creatures return a static array; only LordOaks computed it:
```
Mobile.Delete()
→ BaseCreature.OnDelete() → CreatureDeletedEvent
→ MonsterAbility.InvalidateNextAbilityTriggers()
→ LordOaks.GetMonsterAbilities() → CheckQueen() → new Silvani().MoveToWorld(...)
```
- Deleting a Lord Oaks nobody had fought (champion reset, `DeleteCreatures`, GM delete) spawned an orphaned Silvani where he stood.
- The spell damage scalar (`AlterDamageScalarFrom`) spawned her too.
- With the queen dead his list was empty, so cleanup skipped his cooldown entries. The static `Dictionary<BaseCreature, long>` on the ability singleton kept a reference to the deleted creature. `MonsterAbilityGroup` children's entries were never cleaned either.
- Neira threw unholy bones from `AlterDamageScalarFrom`, so any caller asking for a damage scalar (a preview gump, for example) could fire the effect.
## Changes
- **Per-creature cooldowns:** a lazy `Dictionary<MonsterAbility, long>` on `BaseCreature` (`IsAbilityOnCooldown`, `SetAbilityCooldown`, `ClearAbilityCooldowns`). The static dictionaries and the query-driven cleanup are gone, and death still resets cooldowns. Tick comparisons are wraparound-safe.
- **Pure contract:** `GetMonsterAbilities()` is documented as pure. The new `virtual bool CanUseAbility(MonsterAbility)` gates abilities that only work some of the time. Every `TriggerAbility*` loop and `GetAbility` check it.
- **LordOaks:** always lists his abilities; `CanUseAbility` and the 10% spell shield read "queen alive". The queen spawns when he is first aggressed or first given a combatant, never on the internal map. Aggression lands before the damage it causes, so the first spell is still shielded, as before.
- **Neira:** the unholy bone throw moves to `OnDamagedBySpell`.
A follow-up PR makes minions such as the queen, pixies and Barracoon's rats belong to their champion spawn, so a spawn reset cleans them up.
## Testing
- `LordOaksTests` covers three cases:
- querying or deleting an unengaged Lord Oaks spawns nothing;
- aggression spawns the queen exactly once, and the shield and abilities follow her life;
- an Oaks on the internal map never spawns her.
- UOContent.Tests: 1234 passed, 2 skipped.
- A local constructor sweep (every Mobile built and deleted repeatedly) no longer reports LordOaks.
The two `Spawner (225)` entries at 1333,1808 in `shared/felucca/Vendors.json` and `shared/trammel/Vendors.json` name `escortablewanderinghealer`, which no class has. `BaseSpawner` marks the entry `InvalidType`, so the spawner never spawns anything.
This changes the entry to `EscortableMage`, the only `Escortable` class. It already spawns in Britain in `TownsPeople.json`, and a vendor spawner at 1351,1816 nearby uses it too.
If the original intent was a different type, or the spawner should go, I can change that.
Part of #2687, after #2709. One commit per section, in the same order.
### Failures, maps, bottles and colored nets
A GM's cast fails about a quarter of the time in any water, from the fishing table's last row, and deep water is no harder than a river. Maps and bottles are 0.5% per roll from T2A on, and 1 net in 100 is colored in every era.
This removes the last row, so a cast only fails its skill check. From UO:R a cast in deep water checks skill from 65 to 115, through a `Fishing.MutateResource` override. Publish 5, which "was known as the Renaissance Publish", made deep water harder: "because the difficulty level is higher, the rate of success for pulling up SoS bottles will be lower." [O3] From LBR maps and bottles come up more often and 1 net in 10 is colored, both from Publish 16, which came out in parts during LBR [O14].
| Change | Era | Source |
|---|---|---|
| No "nothing" row | All | #2687 found no source for it; "A prepatch, you never failed catching something at GM" [S15]; a GM succeeds every time in lakes and rivers [C1] |
| Deep water: nothing below 65 skill | UO:R on | "The minimum required skill level to catch *anything* in deep water is 65." [S5–S7] |
| Deep water: 10% at 70, 70% at GM | UO:R on | "Even at 70, it is only about a 10% chance of success in deep water." [S14]; 29% of casts fail before Publish 16 and 30% after [F1, F2]; 70% success at 100 offshore [C1] |
| Maps (skill − 80) / 2400, bottles (skill − 80) / 1500 | LBR on | A bottle "about once per hour" until October 2002 [S1–S6], then "a special item (a net, a message in a bottle or a treasure map) about five times per hour" [S7]; bottles went from 0.4% of catches before Publish 16 [F1] to 1.7% after [F2] |
| Colored nets: none, then 10% | LBR on | "With Publish 16, we got a nice surprise--colored special fish nets!" [F4]; "Any net created has a 10% chance of being colored." [F3] |
- From LBR a GM's deep-water catches are 0.8% maps and 1.3% bottles. Chrome counted 0.9% and 1.3% [C1], the FCB 0.7% and 1.7% after Publish 16 [F2]. Nets and big fish stay at 0.5%, close to both counts.
- At Stratics' 280 casts an hour [S1–S6], that is about one bottle an hour from UO:R to UOTD and five nets, maps or bottles an hour from LBR, the two rates Stratics gives.
- The gain chance rises as the success chance falls, so above 65 a cast in deep water gains faster than one near land, Publish 5's "best method for gaining at high levels" [O3]. Below 65 it neither catches nor gains.
- Chrome's table also has three kinds of water near land, with difficulties of 15, 35 and 55. No other source has them, so near land stays at 0–100.
### Deep water is at least 14 tiles from land
`SpecialFishingNet.FullValidation` decides what counts as deep water: where a special fishing net works, where maps, bottles, nets and big fish can be caught, and from UO:R where a cast is harder. It checks the target and 20 points on its four diagonals, 5 to 25 tiles out. Nothing off those points is read, so a spot 8 tiles from shore can count as deep, while one with land 15 to 25 tiles away on a diagonal does not.
The sources give one rule in every era: land at least 14 tiles away. This checks every land tile within 13 of the target. ModernUO has no server lines inside a map, so the only boundary is the map's edge, which counts as land as before. Only land tiles are read, so statics such as rocks are ignored. `SOS.FindLocation` had its own copy of the old check and now calls `FullValidation`, so wrecks follow the same rule.
- 1999 to 2000: "which means you must be at least 14 tiles away from all land" [S1–S4]
- March 2001: "14 tiles away from any land mass" [F5]
- 2001 to 2002: "which means you must be at least 14 tiles away from all land and server lines" [S5–S7]
- Today: "You need to be a minimum of 14 tiles from land or a server boundary to be in ‘deep water’." [O9]
One guide from January 2000 says "16 tiles away from land" [S9]; every other source says 14.
- In the area an SOS picks from on Felucca, 59% of spots pass the old check and 63% pass the new one; in Ilshenar's two sea regions, 16% and 30%.
- The check reads up to 729 land tiles instead of 21: about 6 µs per check in a Release build, against under 0.1 µs before; a cast makes at most two.
### Sea serpents
In ModernUO a sea serpent comes up from fishing only with a treasure map, a bottle or a net, which it carries. A quarter are deep sea serpents. It has no timer, and nothing makes it attack the fisherman first.
This follows the notes of March 28, 1999 and Publish 4. A fished serpent attacks the fisherman at once. Five minutes after it surfaced it goes back to deep water, taking what it carries, unless someone is still fighting it: it took damage in the last two minutes and has not healed to full since, which is what ModernUO's damage entries record. From T2A, at 80 skill or more in deep water, 1% of casts also bring up a serpent that carries nothing, whether the cast catches something or not. 1 serpent in 10 is a deep sea serpent.
| Change | Era | Source |
|---|---|---|
| Attacks the fisherman at once | All | "Sea Serpents will now immediately attack the fisherman that brought them from the deep." [O1] |
| Goes back after 5 minutes unless fought | All | "Sea Serpents will return to deep water after 5 minutes if not attacked." [O1]; "will return to the depths after a brief period if they are not engaged" [S10] |
| 1% of casts bring up an empty serpent | T2A on | "Fishermen at over 80 skill will have a chance to pull up a sea serpent regardless of whether they succeed or fail at fishing when in deep sea." [O2]; 1% at 80 or more, before the skill check [B1] |
| 1 in 10 is a deep sea serpent | All | "A small percentage of Sea Serpents will be Deep-Sea Serpents." [O1]; about 10% [B1]; 9.8% and 10.8% of the serpents counted [F1, F2] |
- Publish 4 gives no rate. The 1% comes from a post by Hance, a UO developer, in December 2000 [B1]; T2A uses it too, since it is the rule Publish 4 brought in (our inference). The FCB's counts agree: 1.76% of casts brought up a serpent and 55% of serpents carried nothing, so about 1% of casts brought up an empty one [F1] (our calculation).
- Before Publish 4 serpents came up "on failure" [O1], and Stratics kept "if you are fishing with 80+ skill and you fail" from July 1999 to 2002 [S1–S7]. Publish 4 is the last rule in T2A, so it is the one used from T2A.
- Hance also relays a side comment that the empty serpent cancels the rest of the cast [B1]. If it did, no cast could bring up two serpents, but the FCB saw two from one cast on 0.96% of serpents [F1], and Chrome caught empty serpents together with fish and footwear [C1]. So the cast goes on.
- Hance's post also needs room in the backpack for a fish and fish left in the spot [B1]. A spot with no fish left already ends the cast before the roll, unless an SOS keeps the cast going (next section). A full backpack doesn't: ModernUO doesn't check for room before a cast, so neither does the serpent.
- Serpents from a special fishing net are unchanged; Hance's post says nets don't follow its formula [B1].
### The SOS chest
In ModernUO, a fisherman carrying an SOS within 60 tiles of its spot brings up the chest on 1 catch in 8 and a shipwrecked item on the other 7. The SOS also lets a cast go on where the fish have run out.
This makes the chest 1 catch in 4, in every era. From UO:R an SOS needs 65 skill, and below that the cast is an ordinary one.
| Change | Era | Source |
|---|---|---|
| The chest on 1 catch in 4 | All | "You have a 25% chance of getting the chest and a 75% chance of getting something else with each cast." [F3]; "a 25% chance to fish up the actual treasure chest" [S7] |
| An SOS needs 65 skill | UO:R on | 65 in the chest's row of Stratics' table: "The skills mentioned above are minimum skills needed to fish up the corresponding item." [S5–S7] |
- No source gives a number before October 2002, or any for ModernUO's 1 in 8, so the 25% applies in every era. The other catches keep ModernUO's seven kinds of shipwrecked items, equally likely. Hanse's die splits them in three: "One roll is body parts; another roll is shells, barrel staves, globes, etc.; another roll is paintings, broken clocks, etc.; and another is the chest itself." [F3]
- Stratics' guide of November 2000 has neither the chest's row nor the 65 for deep water [S4]. The May 2001 version, during Third Dawn, has both: "The minimum required skill level to catch *anything* in deep water is 65." [S5] The first section applies that from UO:R, after Publish 5 made deep water harder [O3], so the chest follows it (our inference). An SOS's spot is picked in deep water, but a cast within 60 tiles of it can be nearer land, so the 65 is checked on its own.
- Below 65 there are no shipwrecked items either, because Hanse's die is rolled once you "can even attempt to get a treasure chest" [F3] (our reading). Before UO:R an SOS still works at any skill; the only period line on it is "You do not have to be GM Fisherman to actually fish up the treasure from a bottle, only to fish up A bottle." [S9]
### Fishing reveals from T2A
ModernUO reveals a hidden fisherman from ML. The notes of March 28, 1999 already say "If you are hidden, fishing will now unhide you." [O1], so this reveals from T2A, at the same two points: the splash and the end of the cast.
### Big fish and the leviathan's artifact
In ModernUO every big fish weighs 3 to 200 stones and gives max(16, weight) / 4 steaks, and the net thrower has a 1 in 4 chance at an artifact at any skill.
| Change | Era | Source |
|---|---|---|
| Big fish weigh 100 stones | Before SE | "They weigh 100 stones each" [S5–S7]; Publish 28 "Added variable weight and fisherman's mark on big fish" [O6] |
| A steak per 5 stones, at least 4 | SE on | "now its weight / 5 will determine number of fish steaks (minimum of 4)" [O7] |
| The net thrower's own chance at an artifact: 1 in 4, only at GM | SE on | "Give the fabled net thrower (if a GM Fisherman) a 1 in 4 chance of getting a minor artifact (not modified by luck or damage)." [O6] |
- Before SE a big fish keeps ModernUO's weight / 4 steaks, 25 for 100 stones: no source gives a number before Publish 29.
- Publish 27 names only the attackers' chance: "Minor Artifacts will have a chance of spawning in each attacker's backpack after a paragon creature or a leviathan is slain." [O5] Publish 28 adds the thrower's roll [O6], so before SE there is none (our inference). The attackers' chance is unchanged.
### Magic fish
From SE the prized, wondrous and truly rare fish give +1 for 12 seconds instead of +5 for a minute [C1]. A Stratics FAQ of January 5, 2005 already says "The stat boost fish used to provide the person who ate it a +5 boost, but now it's only one or two points, and lasts only a short while." [S16]
- Stratics still gives +5 in December 2002 [S7] and no note dates the change. [S16], during SE, is the earliest source with the new values, so the eras before SE keep +5 and ModernUO's minute (our inference).
- Today's guide gives "Adds 8 – 12% to your Dexterity for 150 seconds when eaten" [O9]. No note dates that change either, so it is left out.
### The white pearl at an SOS's spot
From ML the white pearl is a bonus on top of the catch, but it went through `Fishing.Construct`'s SOS branch: at an SOS's spot the fisherman read "You have found a white pearl!" and got a shipwrecked item or the chest, a second SOS roll in the same cast. The pearl now skips that branch.
The keys are #2687's, plus:
- [O14] uo.com, the 2002 publishes: Publish 16 in parts on April 23, July 23, September 20 and December 2, 2002. https://uo.com/wiki/ultima-online-wiki/technical/previous-publishes/2002-2/
- [S14] Stratics fishing board, FAQ 1.2 "How do I train fishing?", January 5, 2005. https://web.archive.org/web/20050410171430/http://boards.stratics.com/php-bin/uo/showthreaded.php?Cat=&Board=uofisherman&Number=5622892
- [S15] Stratics fishing board, a post on Publish 5's SOS change, April 25, 2000. https://web.archive.org/web/20000511000424/http://boards.stratics.com:80/cgi-bin/forums/showflat.pl?Cat=21&Board=uofisherman&Number=144300&page=0&view=collapsed&sb=5
- [S16] The FAQ of [S14], 1.4 "What's with the horned leather and scales? (Resources)", January 5, 2005, in the FAQ's flat view. https://web.archive.org/web/20050410171356/http://boards.stratics.com/php-bin/uo/showflat.php?Cat=&Number=5622877&page=0&view=collapsed&sb=5&o=&fpart=1
- [F1] The Fishin' Statistician, 11,101 casts from August 2001 to April 2002, before Publish 16. Skill and water not stated; most likely GM and mostly deep water, since the counts include big fish and serpents. https://web.archive.org/web/20020609120859/http://www.thefcb.com:80/novellettes/stats.html
- [F2] The same page, 7,371 casts from July 2002 to February 2003, after Publish 16, with nets, maps and bottles counted too. https://web.archive.org/web/20030216074120/http://www.thefcb.com:80/novellettes/stats.html
- [F3] The FCB's interview with Hanse, an OSI designer, October 21, 2002. https://web.archive.org/web/20021104194424/http://www.thefcb.com:80/Hanse.htm
- [F4] The FCB's encyclopaedia, colored nets, last modified July 13, 2002. https://web.archive.org/web/20020715111230/http://www.thefcb.com:80/encyclopaedia/colornets.html
- [F5] The FCB's "Illustrated Treatise on Deep Water Defined", by Captain Pickles and Mason, last modified March 20, 2001. https://web.archive.org/web/20010419135823/http://www.thefcb.com:80/novellettes/deep-water.html
- [B1] Brikan's skill guide, which quotes a post on Origin's boards by Hance, a UO developer, on December 20, 2000; most likely Hanse, the OSI designer the FCB interviewed in 2002. Page last modified February 3, 2007. https://web.archive.org/web/20070715201811/http://www.jgnn.com:80/uo/bk/2skill/skill.htm
- [C1] Chrome's fishing page on OSI's Yamato shard, captured November 25, 2007: 5,000 offshore catches at GM, a table of success by water and what each magic fish does. https://web.archive.org/web/20071125144104/http://www.geocities.co.jp:80/Playtown-Yoyo/4812/fishing02.html
Companion to #2712, which covers HireRanger's refused `EquipItem`. A sweep that built every constructible Mobile 2,000 times under each expansion found these other outfits that put two items on one layer. They don't leak, because `AddItem` skips `CheckEquip`, but the NPC wears both items and the client draws only one.
| NPC | Conflict |
|---|---|
| Miner, Ranger, Thief | Own shirt/pants/ThighBoots added on top of the random `BaseVendor.InitOutfit` (shoes doubled on every spawn) |
| MasterTheophilus | Shoes + Robe added over `EvilMageLord`'s Sandals + Robe |
| Merchant | Shirt + FancyShirt, or PlainDress + FancyDress |
| IronWorker | Bandana/JesterHat + Bascinet |
| MeerCaptain | One-handed sword + Crossbow |
| BakeKitsune | `BodyMod` isn't saved, so after a restart `RemoveDisguise` bailed and the next `Disguise()` stacked a second robe and pair of shoes |
## Changes
- `BaseCreature.ReplaceEquipment(item)` equips an item in place of whatever an inherited outfit put on its layer, and deletes the item if it still can't be equipped.
- Miner, Ranger, Thief, MasterTheophilus: their own pieces replace the inherited ones.
- Merchant and IronWorker: dropped the duplicate roll. Each kept the item the client was already drawing, which is the last one sent: the fancy set and the Bascinet.
- MeerCaptain: the sword goes into its pack as loot. **Behavior change:** `Mobile.Weapon` checks the one-handed layer first, so it was fighting with the sword despite its archer AI, its Archery skill and the bolts in its pack. It now uses the crossbow.
- BakeKitsune: `RemoveDisguise` strips the disguise without checking `IsBodyMod`.
## Testing
- I re-ran the sweep (local diagnostic, not committed; needs client tiledata) at 500 rolls × 12 expansions. Only HireRanger (#2712) and LordOaks (separate fix coming) remain.
- UOContent.Tests: 1218 passed, 2 skipped.
ShortPants and StuddedLegs both occupy Layer.Pants, so the second EquipItem on a male ranger is refused and its item stays in the world with no parent. Nothing deletes such an item, so every male ranger created leaves one behind in the save.
StuddedLegs already cover the layer and female rangers wear them, so the male ranger now skips the shorts. A test creates and deletes rangers and checks that World.Items does not grow.
Part of #2687. Magic fish and footwear roll the same in every era: magic fish get rarer with skill (5% at 0 skill, 1% at GM) and footwear has a 20% chance at 0 skill. The leviathan rolls the generic 1% for its level 5 map.
A fishing table row can now end at an expansion, so each era gets its own magic fish and footwear rows. The chances at GM come from the Fishing Council of Britannia's cast counts before and after Publish 16, which came out in parts from April to December 2002, during LBR [O14]. The leviathan gets its 25% through a `BaseCreature.TreasureMapChance` override; leviathans only come from the fabled net, which already needs AOS.
| Change | Era | Source |
|---|---|---|
| Footwear: 75% at 0 skill, 2.5% at GM | T2A to UOTD | "The beginning fisherman will find this item on his hook most of the times" [S1–S7]; 2.5% of catches [F1] |
| Footwear: 75% at 0 skill, 10% at GM | LBR on | The same quote; 10.5% of catches [F2] |
| Magic fish: 80 skill, 2.5% at GM | T2A to UOTD | 80 from July 1999 to October 2002 [S1–S6, S9]; 2.4% of catches [F1] |
| Magic fish: no minimum, 5% at GM | LBR to SA | 0 from December 2002 [S7], so the minimum went in late 2002, during LBR; 5.6% of catches [F2] |
| Magic fish: 80 skill again | HS on | Still 0 in June 2010, during SA [S13]; 80 today [O9]. HS is the first expansion after that |
| Leviathan's map: 25% | AOS | Publish 27: "It also has a 25% chance to carry a level 5 treasure map." [O5] |
- These are chances per roll. The FCB's counts leave out empty casts, so they line up once the "nothing" row #2687 lists is gone (a follow-up PR removes it); while it stays, footwear is 3.2% of a GM's catches before LBR and 12% from LBR.
- No source counts footwear below GM, so 75% at 0 skill only follows Stratics' "most of the times". The numbers in my comment were placeholders.
- 80 again from HS is the one change beyond my comment, which proposed no minimum from LBR on.
The keys are #2687's, plus:
- [O14] uo.com, the 2002 publishes: Publish 16 in parts on April 23, July 23, September 20 and December 2, 2002. https://uo.com/wiki/ultima-online-wiki/technical/previous-publishes/2002-2/
- [F1] The Fishin' Statistician, 11,101 casts from August 2001 to April 2002, before Publish 16; skill not stated, most likely GM. https://web.archive.org/web/20020609120859/http://www.thefcb.com:80/novellettes/stats.html
- [F2] The same page, 7,371 casts from July 2002 to February 2003, after Publish 16; skill not stated either. https://web.archive.org/web/20030216074120/http://www.thefcb.com:80/novellettes/stats.html
- [S13] Stratics' fishing guide, captured June 5, 2010. https://web.archive.org/web/20100605065528/http://uo.stratics.com:80/content/skills/fishing.shtml
GetDelay sets min to 6.0 - del * 0.75 and max to 6.0 - del * 1.25, so for any Magery above zero min is the larger value. The draw min + (max - min) * r still covers the right interval, but it runs downward and the names say the opposite.
Swap the two definitions so min is the lower bound. The interval and its uniform distribution do not change; for a given random draw the delay comes out mirrored.
## Summary
`SendKarmaMessage` maps the karma change to a message, and its fallback arm is "You have lost a little karma." (1019063). A change of `0` lands in that arm, so the player is told they lost karma when nothing changed.
How to reproduce on a clean build: kill a creature with 0 karma, such as a grey wolf. The kill awards 0 karma and the player sees "You have lost a little karma."
This change returns early when the change is `0`, which matches how `AwardFame` already handles fame: it sends nothing when fame doesn't change.
## Test plan
- [x] `dotnet build` succeeds
- [x] Kill a 0-karma creature: no karma message
- [x] Kill a negative-karma creature: "gained karma" message is unchanged
Fixes#2700 (with #2704)
A restart (`restart` console command, `AutoRestart`, `CrashGuard`) launches a copy of the server and exits. That copy is detached from the console it was started from: the shell takes the terminal back and console commands stop working. Under systemd or in a container it's killed as soon as the original process exits.
- New setting `server.restartExitCode` (off by default). When it's set, a restart saves and shuts down as usual and then exits with that code, so a shell loop, systemd unit or container restart policy can start the server again on the same console. Values outside 1–255 log a warning and fall back to the current behaviour.
- Fixes `CrashGuard` crash recovery never restarting the server. Since #1809, `Kill()` only records the request, and the unhandled-exception path called `DoKill()` without it.
- Documents systemd, Docker and shell-loop setups in `dev-docs/server-requirements.md`.
`Core.DoKill(true)` built the relaunch command line by putting the assembly path into a raw argument string without quotes, so `dotnet` split it at the first space. That breaks `restart`, `AutoRestart` and `CrashGuard` recovery for any server installed under a path with a space.
- Pass the assembly path through `ProcessStartInfo.ArgumentList`, which quotes it.
- Relaunch through the host that started the process (`Environment.ProcessPath`): the `ModernUO` apphost runs directly, and `dotnet` gets the assembly path. Before this, a server started from the apphost came back under `dotnet`.
- Use one code path on all platforms. `UseShellExecute = true` on Unix made no difference for a program found on PATH.
This doesn't fix the second half of #2700, where the relaunched server loses the console (and gets killed under systemd or Docker). That comes from the server spawning a copy of itself and exiting, and a follow-up PR will add an opt-in restart exit code for a supervisor to act on.
**Testing:** relaunched from a directory with spaces, started both through `dotnet x.dll` and through the apphost, on Windows. The old code fails there with the error from the issue.
## Summary
Stacked on #2676. Review that first; this PR's diff is against it.
Items players sell to NPC vendors go into the vendor's buyback (`ShopBuy`) pack. That pack was purged only when someone opened the buy menu, so a vendor that players sold to but never bought from grew without limit.
- A server operator reported 250k+ items piling up from players crafting and selling on a macro.
- Every one of those items was saved.
- Purging or deleting that vendor then sent one remove packet per item to every nearby client and disconnected them.
The behavior is inherited from RunUO.
- **`VendorBuybackPack`** replaces the plain backpack on `Layer.ShopBuy`.
- It opts in to `SkipsChildSerialization`, so buyback items are never saved.
- Its contents are private like any container's, so removing them reaches only the vendor (no client) and openers. Purges and deletes send nothing to bystanders.
- **Capacity.** The buyback holds at most `250 − buy-list entries` items, which is what the buy gump can show. The oldest items are evicted first. At capacity 0 the sold item is consumed and the seller is still paid.
- **Purge on the restock grid.**
- `Restock()` empties the buyback.
- A purge-only timer, armed on the first sale, covers vendors nobody buys from. It is due at the next restock boundary after now, so a vendor whose restock hasn't run for hours doesn't purge an item the moment it's sold.
- The timer never restocks, so stock doubling and halving is unchanged.
- The old per-item one-hour `LastMoved` check is gone.
- **Existing saves.** A legacy `ShopBuy` pack is replaced on the first tick after load. Thanks to #2676, deleting a pack of 250k+ items takes well under a second.
**Behavior changes for shard operators:**
- A buyback item lasts until the next restock boundary (at most `RestockDelay`), not one hour after its sale.
- A vendor that changes maps drops its buyback.
- A buy window that is open during a purge can show a row whose item is gone. Buying that row does nothing, and reopening shows the current list.
## Test plan
- [x] `VendorBuybackPackTests`: evicts oldest first; capacity 0 consumes the item; purge; a stale front entry can't spin eviction
- [x] `VendorBuybackTests`:
- purge due time stays on the restock grid, including a stale restock and a restock time in the future;
- partial stack sale: the split goes to buyback and the seller keeps the remainder;
- zero capacity consumes the item and still pays;
- restock purges and clears the timer, and the next sale re-arms it;
- deleting the vendor cancels the timer;
- a legacy 20k-item pack is replaced.
- [x] UOContent.Tests and Server.Tests green; schema generator clean
- [x] Manual in-game pass:
- sell and buy back, including a partial stack;
- over-capacity sale;
- purge with no buyer;
- `[restock`;
- vendor facet change;
- restart;
- legacy save with a large `ShopBuy` pack;
- mass selling with bystanders present;
- deleting a vendor with a full buyback.