§16 planned deep animation keys and a bulk-fill switch. Measuring first changed
the phase: a complete one-direction animation set is 174,453 frames / 281.5 MB
(not the ~119,000 / ~117 MB §11 estimated), every frame carries its own centre
and 23,818 of 26,274 actions have frames of differing size — and the site
displays still pictures. So none of it was built (§11.2, org lead 2026-09-11);
`body/<id>/a<n>/f<k>` stays named in §5 and refused in practice.
§4.10 is what the walk found instead. A body's actions are a contiguous band and
the next body's begin immediately after, so one action past the ceiling is a real
record of another creature: 643 of 795 legacy bodies pass every check there and
452 are byte-identical to body+1's action 0. `Animations.GetAnimLength` is not
the ceiling — it disagrees with the index arithmetic on one body of this client,
by nine actions.
What shipped: the 73 bodies with no art at action 0 (a horse at `body/820/a23`),
the catalogue key carrying its action, the atlas join that reads it instead of
hardcoding `a0`, and the ceiling that makes the fallback walk safe. Catalogue
1,022 -> 1,095; EXTRACTOR_VERSION 2 -> 3; protocol stays 8.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
§11.1 is new and carries what phase 5 measured: 49,152 addressable static ids
(not the 81,884 `artidx.mul` declares -- `FileIndex` sizes its table from its
length ARGUMENT), 39,189 with art, 4,244 land tiles, 9,963 + 12,140 empty index
slots, and the whole set at 81 MB decoding in 34 s. That last number reopens the
bulk question and the answer is still no: 108 MB of base64 through a 512 KB
single-slot channel to store 43,433 pictures a shard displays a few hundred of.
Two traps, both §4.5's failure mode -- a confident, plausible, wrong picture:
- `Art.GetStatic` hands back the SAME cached Bitmap and `Hue.ApplyTo` repaints in
place, so hueing edits the library's own copy: the plain key comes back hued
from then on, and the next hue stacks. `Files.CacheData` off process-wide fixes
it and also stops a game server retaining 74 MB of Bitmap. Copying instead does
not solve the retention, and `new Bitmap(src)` throws on ARGB1555 anyway.
- `PartialHue` (13,259 of 65,536 ids) decides whether a hue repaints every pixel
or only the grey ones, from a file only the shard has. Item 597 is a wooden
screen with painted flowers; one mode reddens the flowers, the other the whole
screen, and both decode. Hence land takes no hue segment and `h0` is not a key.
Plus the namespace trap that compiled: unqualified `TileData` binds to ServUO's
own `Server.TileData`, because the enclosing namespace beats `using Ultima;`.
§14 records what the wire gained -- the `static` and `land` families, `families`
on `assets.sources`, and `assets.fetch` becoming shared plumbing whose family is
DERIVED from the keys (§5 made the key the address; a request naming its family
too would have two places to be wrong and one of them silent). Additive, so the
protocol stays 8 and EXTRACTOR_VERSION stays 2. §15 records that `link` needed
nothing in phases 4 or 5: it forwards verbatim in both directions.
§17.10 is the four org-lead decisions. §12 and modules/uo/SCHEMA.md carry the
website side: `uploads/items/`, per-row `catalog` staleness, and why a key with
no art writes no row at all.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
SCHEMA.md's cliloc section still described the pre-protocol-8 world, two
phases after phase 2 changed it. Three sentences said the same false thing,
so fixing only the flagged one would have left the section arguing with
itself:
- "Sourced from files the operator supplies" -- the BASE comes from the shard
on any install with uo-link configured; only the overlays are the
filesystem's, and that asymmetry has a reason worth stating (ServUO has no
server-side notion of a custom cliloc, so there is nothing to ask for).
- "A base (the converted client table)" -- not converted any more.
- "The conversion step is not avoidable ... so the shard cannot supply names
on our behalf" -- it does supply them. Phase 2 ported UOFiddler's Mythic
decompressor into the overlay precisely so nobody converts anything.
Rewritten to say what is true and why the file path still exists (deprecated,
not removed, so an existing install keeps working), plus the two things a
reader of this table actually needs: `shard_cliloc_meta.payload` keeps the
base's fingerprint under `base` SEPARATELY from the overlay hashes -- because
on the bridge the old `clilocs.plain` label is supposed to disappear and one
flat hash map would read that upgrade as a vanished source -- and boot does
not import on the bridge path at all.
Each claim checked against the code rather than from memory:
shardClilocs.model.js:307 (`base: fingerprint`), :438-440 (refreshOnBoot
returns `skipped` on the bridge), clilocSource.js:333 (`missingOverlays`).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
Phase 3 is built and walked on a live shard. What the walk measured, and the
two places the design of record needed correcting:
§8.1, new: the catalogue is 787 exactly as §4.8 predicted, and the whole scan
of bodies 1-2047 takes 734 ms cold -- so the wall-clock paging §11 designed
never fires on this client. Every §4.8/§5.2 prediction held when the bytes were
rendered and LOOKED at: 320, 607, 608 and 666 come back absent rather than as
another creature's picture, and the direction split is 783 at index 1 against 4
at index 0 -- four player bodies, not six.
44 of the 787 hashes are shared by two or three bodies, which is the exact
signature of the wrong-picture bug, so it was chased rather than assumed. It is
the client's own Body.def aliasing (83 {1}, 84 {1}, 106 {12, 59}), and the check
that settles it is at the source: Translate(ref body, ref hue) rewrites `body`
only when bit 31 is set, unlike the one-argument overload -- and ResolveAnimation
calls that same two-argument overload, so validator and decoder resolve the
identical record.
§12.1, new: **§12 is right about the outcome and wrong about the mechanism.**
`shard_spawn_creatures` is emptied and refilled by every atlas refresh, and a
refresh runs on every boot -- so an imported filename written to that row is
destroyed by an ordinary re-parse of the ServUO tree, and the next Update finds
the client files unchanged and never restores it. Three tables outside that
blast radius, and the atlas import re-derives `art` on the way past.
§14: **§16 listed phase 3 as servuo-plugins + module-uo and that was wrong.**
web.rs routes every command explicitly, so `link` is in the phase. Corrected in
both places.
UOFIDDLER.md is DELETED, two phases earlier than §9.1 predicted -- creature art
was the only thing still on it. SPAWN_ATLAS.md §Artwork is rewritten around the
import, keeping the operator's own map as the thing that wins; the module's
SCHEMA.md gains the three tables and API.md the two admin routes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
Phase 4 slice 4. BACKEND_DESIGN.md had never been de-UO'd: Phase 3 rewrote core's
code and README, but §5.2's identifier check reads code, not prose, so nothing
ever looked at the design document. It still described 27 shard_* tables, 20 UO
route rows and the shard visibility ladder as core's, a phase after core stopped
being able to serve any of them.
Moved, text unchanged:
BACKEND_DESIGN §3 six shard_* schema sections (226 lines) → modules/uo/SCHEMA.md
BACKEND_DESIGN §4 13 public + 7 admin UO route rows → modules/uo/API.md
BACKEND_DESIGN §6.5 the audience ladder (70 lines) → modules/uo/API.md §4
Core keeps the seam and gains the eight /admin/modules routes it had never
documented. §6.5 becomes "Module-owned audience boundaries": core's security
boundary ends at authentication, roles and the session, and a module that serves
game data brings its own.
Also fixed on the way: users.router.js was still listed as 15 routes (it is 9 —
six went to the extension slot), and the push section still promised
config/shardStreams.js "moves out with it" four slices after it left.
The acceptance table now carries results. Criterion 2 was proved for real against
module-uo v0.3.0 on an empty database, which turned up the uninstall ordering
defect fixed in website#146.
AI disclosure: this contribution was AI-assisted (Claude Code).
Co-Authored-By: Claude <noreply@anthropic.com>