feat(asset-bridge): the body catalogue and slug → body id (Phase 3) #30
Reference in New Issue
Block a user
No description provided.
Delete Branch "feat/asset-bridge-p3"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Phase 3 of the Asset Bridge (
docs/link/v8.md§4.8, §5, §6, §8). Two request families, on opposite threads on purpose.assets.bodies— the Core thread (BridgeBodies.cs)The atlas knows a creature by the class name in
Spawns/*.xml; the client knows it by a body id; nothing in the ServUO tree declares the mapping. Construct the type, readBody.BodyID,Delete(). That is world mutation, so it answers on the Core thread — and it is the one family here that deliberately does not take the asset worker's single slot, because the work is not on the worker and the reply is a couple of kilobytes.The batch is capped at 100 names and an over-long request is refused, never truncated: a silently shortened answer is indistinguishable from a complete one from the website's side, and the names that fell off the end would be recorded as "asked and unanswerable" rather than "never asked".
Every outcome is a row, not a failed request —
ok/unknown/notCreature/failed. A shard is expected to be asked about types it does not have, and a pass that failed over one would teach an operator to stop pressing the button.assets.manifest/assets.fetch— the worker (BridgeCatalog.cs)The manifest carries
{ key, sha256, bytes, width, height, body, direction }and no pixels, so an Update fetches only what moved; the fetch carries base64 PNG. The scan keeps the bytes it hashed rather than decoding all 787 sprites a second time moments later.Three shapes worth flagging for review:
catalogis derived from the client files, not minted per build. The cache is released when idle, and a fresh id per build would force a restart halfway through a slow import although nothing about the client had moved.Nothing trusts the library's success
Every body goes through
CheckEntryandAnimationRecordSanebefore it is decoded. That is what keeps the 357 bodies whose index entry readslength 0— and for which the decoder hands back the previously decoded creature's bitmap — out of the catalogue.Walked on a live shard (43k mobiles, 210k items, real sidecar)
body/320/a0,body/607/a0,body/666/a0family→ 400, stalecatalog→ 422, 101 types → 400 naming the capThe pictures were rendered and looked at, which is the only method that catches this failure mode — a dragon, a giant spider, a wolf at three-quarter, and human male / human female / elf male head-on. 44 of the 787 hashes are shared by two or three bodies; that is the client's own
Body.defaliasing, chased to the source rather than assumed (see the docs PR).dotnet build Scripts.csproj -c Release— 0 warnings, 0 errors.Review notes
AssetBodyBatch,AssetFetchKeys,AssetScanMs,AssetPlayerDirection,AssetCreatureDirection, all documented inBridge.cfg.link(the three REST routes — §16 omitted this repo and was wrong),Module-uo,docs.AI disclosure
🤖 Generated with Claude Code
https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
Two request families, and they run on opposite threads on purpose. `assets.bodies` (BridgeBodies) answers the question only code inside ServUO can: the atlas knows a creature by the class name in Spawns/*.xml, the client knows it by a body id, and nothing in the tree declares the mapping. Construct the type, read Body.BodyID, Delete(). That is world mutation, so it answers on the CORE thread and is the one family here that does not take the asset worker's slot -- and the batch is capped at 100 names, REFUSED rather than truncated, because a truncated answer is indistinguishable from a complete one from the website's side. `assets.manifest` / `assets.fetch` (BridgeCatalog) are the catalogue, on the worker. The manifest carries { key, sha256, bytes, width, height } and no pixels, so an Update fetches only what moved; the fetch carries base64 PNG. The scan keeps the bytes it hashed rather than decoding all 787 sprites twice. Three things worth stating about the shapes: - It pages on the WALL CLOCK as well as on bytes. The rows are ~90 bytes and the whole catalogue is one page by the byte budget, but building it means decoding hundreds of sprites against a 10 s reply timeout. - `catalog` is derived from the client files (sizes, mtimes, both direction settings, EXTRACTOR_VERSION), not minted per build -- the cache is released when idle, and a fresh id per build would force a restart mid-import although nothing about the client moved. - ARGB1555 is expanded to 32bpp here rather than handed to GDI+, because what it does with a one-bit alpha channel varies by platform and a black rectangle behind every sprite would pass any test that only checked the bytes decoded. Nothing trusts the library's success. Every body goes through CheckEntry and AnimationRecordSane before it is decoded, which is what keeps the 357 bodies whose index entry reads `length 0` -- and which the decoder hands back the PREVIOUS creature's bitmap for -- out of the catalogue. Walked on a live shard: 787 rows in one 734 ms page; bodies 320, 607, 666 all absent rather than wrong; 783 at direction 1 and 4 at direction 0; all 455 stock creature classes resolved at ~190 ms per 100 with zero mobiles leaked. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4