Moving Shot charged its mana in `OnBeforeSwing`, before `BaseRanged.OnSwing` decided whether to fire. A swing that fired nothing (a paralyzed or frozen archer, no ammo, a failed harmful check) still took the mana and left the move armed. Every swing after that charged again, so inside the 3-second window you paid twice, until the archer ran out of mana.
`OnBeforeSwing` now only checks the mana. Moving Shot's own `OnHit`/`OnMiss` charge it, and those only run once a shot has fired. This is how the other weapon abilities pay. A miss still costs mana. A parried shot no longer does: the parry clears the move before `OnHit` runs, the same as for every other ability.
## Summary
With T2A packet crafting menus enabled (`t2aCraftMenus`, default for pre-UOTD eras), cooking did nothing. Double-clicking a Skillet set a target, and `T2ACraftSystem.ShowMenu` had no Cooking branch, so the target was accepted and silently dropped. Nothing else could cook either: the old RunUO "use food on a heat source" code was never hooked up and was removed in #1054.
T2A cooking never used a menu, so this restores the item-on-item / item-on-heat-source flow:
- **Per-item handling:** each ingredient (`CookableFood`, `Dough`, `SweetDough`, `SackFlour`, `BowlFlour`, `JarHoney`) implements `IT2ACombinable.TryCombine` and switches on the target. `T2ACooking` holds the shared target, water, combine and heat-timer helpers.
- **Heat source:** all `CookableFood` (raw meats, fish, eggs, unbaked pies/quiche/pizzas, cookie and cake mix), dough → bread loaf, sweet dough → muffins. One unit is used up at the start, then a 5 s timer; the Cooking check runs from `CookingLevel` to 100 (bread and muffins use `DefCooking`'s range). The food burns on failure or if the cook moves more than 3 tiles away.
- **Combinations:**
- Open flour sack or bowl of flour + water → dough
- Dough + pear / peach / apple / pumpkin / eggs / meat / sausage → the matching unbaked pie, quiche or pizza
- Dough + honey → sweet dough
- Sweet dough + honey → cookie mix
- Flour + sweet dough → cake mix
- The honey and flour + sweet dough recipes work in either direction.
- **Water:** water drinks (pitchers), buckets, barrels, tubs and trough add-ons, in the pack, in hand, or on the ground within 2 tiles and in line of sight.
- **Cooking tools are passive:** with T2A menus on, a tool whose system has no T2A menu (`T2ACraftSystem.HasMenu`) falls through to `ShowCraftMenu`, which does nothing under T2A. No dead target is left.
Behaviour with T2A menus off is unchanged.
## Follow-ups
- Static troughs/barrels and open water aren't water sources here or for pitcher filling (no static water ID list exists); to be fixed separately.
- Whether a bowl of flour returns an empty bowl is unconfirmed (`//TODO`).
- Cooking tools that must merely be in the pack for some recipes are not modelled yet.
## Testing
- `dotnet build`: 0 warnings, 0 errors
- New `T2ACookingTests`: flour + water, closed sack refused, dough + apple, honey in both directions, cookie mix, cake mix, wrong target consumes nothing, water in another player's pack refused
- Not covered: the heat timer (untested); not yet exercised in game
## Summary
The T2A crafting menus offered items the chosen resource couldn't make:
- **Tailoring honours the targeted cloth hue.** The menu was built from all cloth in the pack, but the craft consumes only the targeted hue, so it offered items that then failed on resources. `CanCraftItem` / `FilterEntries` / `AnyCraftableInCategory` now take the hue and count with the same helper the craft uses.
- **Uncut cloth counts in the hued path.** The hue-aware count and consume matched only `Cloth`, so targeting uncut cloth gave an empty menu. The hued path now uses `CraftItem`'s equivalence table like the unhued path. `T2ACraftSystem` reuses that table instead of a narrower copy, which also covers the coloured woods and leathers on later-era shards.
- **T2A tinkering only uses iron ingots.** Coloured ingots are refused with a re-prompt. The menu filter now uses the selected ingot, and Make Last for tinkering always resolves to iron, so a coloured choice left over from the gump flow can't be used.
## Testing
- `dotnet build`: 0 warnings, 0 errors
- New `T2AHuedFilterTests`: only cloth of the targeted hue counts, and uncut cloth of the same hue counts toward the recipe
- No test for the tinkering target and message flow; not yet exercised in game
## Summary
About 114 tests skipped on CI because they needed the official client files (`tiledata.mul`, map and multi files). They gave CI no signal, and locally they ran against whichever client a developer had installed. The test hosts now build their own world instead, so every test runs on CI and runs the same on every machine. CI simulation: Server.Tests 837 passed, UOContent.Tests 1131 passed, **0 skipped**.
- **Generated tile data.** The fixtures write a pre-HS `tiledata.mul` from a small invented tile kit (`Server.Tests/Terrain/TestTiles`) and load it through the real `TileData.Load()`. The test hosts no longer read any client directory (`MODERNUO_CLIENT_PATH`, `MODERNUO_TEST_DATA_DIR` and `C:\Ultima Online Classic` are gone from test code). The file is written to `SyntheticData` in each test project's output, overwritten every run and removed by `dotnet clean`.
- **Injected terrain.** `TestTerrain` stamps land and statics into Felucca and Trammel, which share one terrain. Writes after startup must go in a `TestTerrain.Scope()`, which puts back every block it touched and keeps the step cache fresh.
- **Prefab scenes.** Seven named scenes (town, causeway, sewer walkway, coastline, deep sea, map-edge sea, an upstairs room with an L-shaped counter) replace the Britain coordinates the pathing, AI and fishing tests relied on. Each scene-based test asserts its scene is present first, so a missing scene fails instead of passing on flat ground. Stamping all seven takes 3-5 ms.
- **Multi footprints.** Contrived footprints for the three multi IDs content looks up (0x74, 0x7A, 0x0) are seeded through a new internal `MultiData.SetComponents`.
- **Fingerprint.** `StepCacheFile.HashTileDataFile` is extracted so a test can show that a one-flag change to the file changes the fingerprint, while runtime table mutation does not.
### Engine changes
- `TileMatrix.GetLandBlock`/`GetStaticBlock`: on a map without its files, return the block injected with `SetLandBlock`/`SetStaticBlock`, or the default when nothing was injected. Maps with files take the same path as before.
- `MultiData.SetComponents` (internal): registers a footprint without multi files.
### Test changes worth a look
- Values that were properties of real Britain terrain (probes at Z 10, a pinned plain walk mask of 0xC1) are re-pinned to the synthetic plain (Z 0, 0xFF).
- No tests were deleted. The multi walkability and boat-deck tests assert movement behaviour over a footprint, not what a real multi looks like, so they run on the contrived footprints.
- Maps 0 and 1 keep file indexes 0 and 1, and every stamp writes both, rather than sharing one file index. On a map without files the getters never consult the shared-file cache, and its patch bits would hide one facet's injection from the other.
## Test plan
- [x] `dotnet test -p:SolutionDir=... --filter "Category!=Manual"` with the client env vars pointed at an empty directory: 837 + 1131 passed, 0 skipped, 0 failed
- [x] Mutation check: removing the step-cache clear from `TestTerrain` makes the stale-cache tests fail
- [x] CI green on all platforms
## Summary
`ManagedArchive.ExtractTarFromStream` joined each tar entry name directly onto the output directory, so an entry such as `../x`, `sub/../../x` or an absolute path was written outside it. The restore path (`AutoArchive` → `ExtractTarZstd`/`ExtractTarGz`/`ExtractTar`) feeds archives through this method.
Each entry now resolves to a full path and must stay under the output root (case-insensitive on Windows). An entry that escapes fails the extraction; the existing `catch` in each `Extract*` method logs it and returns `false`.
The input is the server's own backup archives, so this is hardening against a tampered or foreign archive, not something a player can reach.
## Tests
- `ExtractTar_EntryOutsideOutputDirectory_IsRejected` (`../escaped.txt`, `sub/../../escaped.txt`) and `ExtractTar_RootedEntry_IsRejected`. All three fail without the fix.
- All 18 `ManagedArchiveTests` pass.
`BaseRanged.OnCraft` passed the raw resource type to `ApplyWoodBonuses`, which can be null; `CraftResources.GetFromType` threw on the null key and crashed the server. Every other `OnCraft` override already falls back to `craftItem.Resources[0].ItemType` — `BaseRanged` now does the same.
`GetFromType` also returns `CraftResource.None` for a null type, matching its documented contract for unregistered types, so other callers can't hit the same throw.