docs(link): phase 0 ran, and the fault it found is not a crash #235

Merged
whitlocktech merged 1 commits from docs/asset-bridge-p0 into main 2026-09-10 08:04:15 +00:00
Member

Records the Asset Bridge phase 0 spike (§16) against v8.md, and closes the last open decision. Code half: RunicGateway/servuo-plugins#27.

§4's decision stands. Nothing faulted on a path this protocol calls, and §9's cliloc reader reproduced UOFiddler's 123,490-entry table byte for byte in 218 ms, from inside the shard, with no UOFiddler installed.

But the spike was looking for the wrong kind of failure.

The finding

LoadStatic and LoadLand decode out of a buffer that is reused, only ever grown, and filled by a Read whose return value is discarded. So a short, absent or out-of-bounds record does not throw — it renders the previous asset. On this machine's stock, unmodified client that is 22,102 ids whose index entry reads lookup 0, length 0; FileIndex.Seek rejects lookup < 0 and length < 0, and zero is neither, so it treats an empty slot as a hit. §1.1's "32,766 decoded" was counting these.

A bulk import that trusted the library would have written 22,102 duplicate images into the site under ids that have no art.

What changed here

New §4.5 — validate before calling. The rule, the six checks phase 0 implemented, the eight deliberate defects they caught (seven of which the library rendered silently, including a verdata lookup past verdata.mul's own end — Verdata.Seek is bounds-checked nowhere at all), and the number that makes the boundary defensible: zero false refusals across 49,151 statics and 16,384 land tiles on a clean client. Also the two other ways an id with no art yields a picture: GetStatic throws above the index ceiling, and with the default checkmaxid: true returns item 0's picture instead.

New §4.6 — the UOP wins outright. FileIndex's UOP constructor ends with a bare MulPath = uopPath, so artLegacyMUL.uop wins and art.mul is never opened on a current client. Bounding an offset against the wrong file is not approximate, it is meaningless: the spike's first run refused 34,299 good statics that way, and every refusal read like a real finding. Second consequence, for operators: a shard adding custom art to art.mul while the UOP is present gets nothing, silently.

New §4.7 — the gump crash, reproduced in the host process. One Ultima.Gumps.GetGump(2) and the ServUO process disappeared. No catch reached, no console line; the probe's checkpoint file was the only record. §4.1's rule is earned rather than argued.

§17 has nothing open

  • Item 4 — the default audience — SETTLED: an asset inherits the audience of the page that uses it. The flat alternative necessarily disagrees with some page that uses it, in one direction or the other, and silently.
  • Item 5 is new: validate-before-calling. Chosen ahead of the spike over a child-process extractor and over reversing §4 — and the spike then found the shape that settles it. The dangerous failure is a wrong picture, which no containment strategy would have caught.

§16

Phase 0 marked done. The half it deliberately left unbuilt moves into phase 1: the animation path has no validator, and the patched client's verdata-broken wolf decoded something else in silence to prove it needs one. GetAnimation also allocates new int[frameCount] straight from a file-supplied int.

Full measurements and the rig recipe live in servuo-plugins/tools/scaffolding/README.md.

  • AI-assisted — Claude Code (Opus 5)

🤖 Generated with Claude Code

https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4

Records the Asset Bridge **phase 0** spike (§16) against `v8.md`, and closes the last open decision. Code half: RunicGateway/servuo-plugins#27. **§4's decision stands.** Nothing faulted on a path this protocol calls, and §9's cliloc reader reproduced UOFiddler's 123,490-entry table byte for byte in 218 ms, from inside the shard, with no UOFiddler installed. But the spike was looking for the wrong kind of failure. ## The finding `LoadStatic` and `LoadLand` decode out of a buffer that is **reused, only ever grown, and filled by a `Read` whose return value is discarded**. So a short, absent or out-of-bounds record does not throw — it renders the *previous* asset. On this machine's **stock, unmodified** client that is **22,102 ids** whose index entry reads `lookup 0, length 0`; `FileIndex.Seek` rejects `lookup < 0` and `length < 0`, and zero is neither, so it treats an empty slot as a hit. §1.1's "32,766 decoded" was counting these. A bulk import that trusted the library would have written 22,102 duplicate images into the site under ids that have no art. ## What changed here **New §4.5 — validate before calling.** The rule, the six checks phase 0 implemented, the eight deliberate defects they caught (seven of which the library rendered silently, including a verdata lookup past `verdata.mul`'s own end — `Verdata.Seek` is bounds-checked nowhere at all), and the number that makes the boundary defensible: **zero false refusals** across 49,151 statics and 16,384 land tiles on a clean client. Also the two other ways an id with no art yields a picture: `GetStatic` throws above the index ceiling, and with the default `checkmaxid: true` returns **item 0's picture** instead. **New §4.6 — the UOP wins outright.** `FileIndex`'s UOP constructor ends with a bare `MulPath = uopPath`, so `artLegacyMUL.uop` wins and `art.mul` is never opened on a current client. Bounding an offset against the wrong file is not approximate, it is meaningless: the spike's first run refused **34,299 good statics** that way, and every refusal read like a real finding. Second consequence, for operators: a shard adding custom art to `art.mul` while the UOP is present gets nothing, silently. **New §4.7 — the gump crash, reproduced in the host process.** One `Ultima.Gumps.GetGump(2)` and the ServUO process disappeared. No `catch` reached, no console line; the probe's checkpoint file was the only record. §4.1's rule is earned rather than argued. ## §17 has nothing open - **Item 4 — the default audience — SETTLED:** an asset inherits the audience of the page that uses it. The flat alternative necessarily disagrees with some page that uses it, in one direction or the other, and silently. - **Item 5 is new:** validate-before-calling. Chosen ahead of the spike over a child-process extractor and over reversing §4 — and the spike then found the shape that settles it. The dangerous failure is a **wrong picture**, which no containment strategy would have caught. ## §16 Phase 0 marked done. The half it deliberately left unbuilt moves into **phase 1**: the animation path has no validator, and the patched client's verdata-broken wolf decoded something else in silence to prove it needs one. `GetAnimation` also allocates `new int[frameCount]` straight from a file-supplied int. Full measurements and the rig recipe live in `servuo-plugins/tools/scaffolding/README.md`. - [x] AI-assisted — Claude Code (Opus 5) 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
wtclaude added 1 commit 2026-09-10 08:00:24 +00:00
Records the Asset Bridge phase 0 spike (§16) against v8.md, and closes the
last open decision.

§4's choice to call ServUO's vendored `Ultima` STANDS: nothing faulted on a
path this protocol calls, and §9's cliloc reader reproduced UOFiddler's
123,490-entry table byte for byte in 218 ms from inside the shard.

But the spike was looking for the wrong kind of failure. `LoadStatic` and
`LoadLand` decode out of a buffer that is reused, only ever grown, and
filled by a `Read` whose return value is discarded — so a short, absent or
out-of-bounds record does not throw, it renders the PREVIOUS asset. On the
stock, unmodified client on this machine that is 22,102 ids whose index
entry reads `lookup 0, length 0`, all of which the library returns a picture
for. §1.1's "32,766 decoded" was counting these.

New §4.5 states the rule that answers it — validate before calling — with
the six checks phase 0 implemented, the eight deliberate defects they caught
(seven of which the library rendered silently, including a verdata lookup
past verdata.mul's own end, which `Verdata.Seek` bounds-checks nowhere), and
the number that makes the boundary defensible: zero false refusals across
49,151 statics and 16,384 land tiles on a clean client.

New §4.6: `FileIndex`'s UOP constructor ends `MulPath = uopPath`, so
`artLegacyMUL.uop` wins outright and `art.mul` is never opened on a current
client. Bounding an offset against the wrong file is not approximate, it is
meaningless — the spike's first run refused 34,299 good statics that way,
and every refusal read like a real finding.

New §4.7: `Ultima.Gumps.GetGump(2)`, called once from inside a running
shard, made the ServUO process disappear — no catch reached, no console
line, the probe's checkpoint file the only record. §4.1's rule is earned.

§17 now has nothing open:

  * item 4 — the default audience — SETTLED: an asset inherits the audience
    of the page that uses it.
  * item 5 is new: validate-before-calling, chosen ahead of the spike over
    a child-process extractor and over reversing §4, and confirmed by it.
    The dangerous failure turns out to be a wrong picture, which no
    containment strategy would have caught.

§16 marks phase 0 done and adds the half it deliberately left unbuilt to
phase 1: the animation path has no validator, and the patched client's wolf
decoded something else in silence to prove it.

Full measurements and the rig recipe live in servuo-plugins
`tools/scaffolding/README.md`; the code is RunicGateway/servuo-plugins#27.

- [x] AI-assisted — Claude Code (Opus 5)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
whitlocktech merged commit f6ea5c0484 into main 2026-09-10 08:04:15 +00:00
whitlocktech deleted branch docs/asset-bridge-p0 2026-09-10 08:04:16 +00:00
Sign in to join this conversation.
No description provided.