docs(link): the catalogue is real, and UOFiddler's last job is gone (Phase 3)

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
This commit is contained in:
2026-09-10 18:41:08 -05:00
parent 4c6b0c566a
commit 1a7481e9f4
7 changed files with 195 additions and 149 deletions

View File

@@ -188,10 +188,52 @@ Four column choices worth stating, because each one is a trap:
Deceit"). The live `champ.update` feed in `shard_champs` is the separate answer to "it is on level 3
right now". Both exist; they are not the same data.
**`shard_spawn_creatures.art` is always NULL on a fresh import.** The project ships no creature
artwork: sprites live in the operator's own client `.mul`/`.uop` files and are theirs, not ours to
redistribute. An operator supplies art via a gitignored map plus images under the (already
gitignored) `server/uploads/atlas/`. Text-only is the normal, supported state.
**`shard_spawn_creatures.art` is DERIVED, never written by the atlas import itself** — see
`shard_assets` below. The project still ships no creature artwork: sprites live in the operator's own
client `.mul`/`.uop` files and are theirs, not ours to redistribute. What changed in Protocol 8 is
who extracts them: the shard does, from its own client, over the bridge. An operator's hand-drawn map
plus images under the (already gitignored) `server/uploads/atlas/` still wins over anything imported.
Text-only is still the normal, supported state — an install with no shard link never imports one, and
even a complete import leaves two thirds of the playable ghost and gargoyle bodies without art.
## shard_assets / shard_creature_bodies / shard_asset_meta — the Asset Bridge (Protocol 8)
Creature artwork read from the shard's own UO client ([`link/v8.md`](../../link/v8.md) §6, §8, §12).
| Table | Shape |
|---|---|
| `shard_assets` | `asset_key` VARCHAR PK (§5's key, e.g. `body/34/a0`), `family`, `sha256`, `bytes`, `width`, `height`, `body`, `direction`, `file`, `imported_at` |
| `shard_creature_bodies` | `slug` PK, `type_name` (the ServUO class name asked), `body` nullable, `status`, `resolved_at` |
| `shard_asset_meta` | Singleton (`id = 1`), `payload` JSON (catalogue id, extractor version, source fingerprint, counts), `imported_at` |
**These are the one part of this schema that is deliberately NOT import-owned**, and the reason is
the atlas tables sitting directly above them. `replaceAtlas` empties and refills
`shard_spawn_creatures` on every refresh, and a refresh runs on every boot; an imported filename
stored on that row would be destroyed by an ordinary re-parse of the ServUO tree, with the next asset
Update finding the client files unchanged, reporting "nothing to do", and never restoring it. So the
assets live out here, upserted per key, and `replaceAtlas` re-derives `art` from them on the way
past — `{ ...derived, ...operatorMap }`, which is the one place "the operator's map wins" is
enforced.
Four details that are load-bearing rather than incidental:
- **`file` is a filename under the uploads directory, never a path**, and it is content-addressed
(`uo-body-34-a0-<sha8>.png`). A stable name overwritten in place would leave every browser and CDN
serving the previous client's sprite from cache, with the row perfectly correct.
- **The `art` derivation joins on the catalogue key**, `a.asset_key = CONCAT('body/', b.body, '/a0')`,
not `a.body = b.body`. The simpler join is correct today and stops being correct the moment deeper
animation keys (`body/400/a2/f0`) arrive, at which point one slug matches dozens of rows.
- **`shard_creature_bodies` IS replaced whole**, unlike `shard_assets`: it is derived from the atlas's
creature list, so a slug that has left the atlas has no meaning, and the pass that rebuilds it is a
shard round trip rather than a file transfer.
- **`status` keeps the negative answers** — `unknown` (the spawn files name a type this shard's
scripts do not define, which is real drift), `notCreature` (a spawn entry for an item or
decoration, a permanent answer), `failed`. Without them the next pass asks again, and each name
costs a real constructor on the shard's Core thread.
`shard_spawn_creatures.name` already holds the ServUO **class name** — the atlas build picks the
winning spelling of the spawn type token rather than inventing a display label — which is why the
body pass needs no extra column to ask its question.
## shard_clilocs / shard_cliloc_meta — UO's localization table (Protocol 3.0)