feat(asset-bridge): phase 0 spike — the decoders, from inside a live shard #27
Reference in New Issue
Block a user
No description provided.
Delete Branch "feat/asset-bridge-p0"
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?
Asset Bridge phase 0 (
docs/link/v8.md§16). Docs half: RunicGateway/docs#235.§4 chose to call ServUO's vendored
Ultimarather than reimplement it, and the evidence was a PowerShell probe against a stock client — neither the process nor the client the extractor will run in. This runs the same decoders from inside a running ServUO 57.4, against a client broken in 21 catalogued ways.§4's decision stands. Nothing faulted on a path this protocol calls. But the spike was looking for the wrong kind of failure.
The finding: 22,102 wrong pictures on a stock, unmodified client
Those ids have an index entry of
lookup 0, length 0— no record at all.FileIndex.Seekrejectslookup < 0andlength < 0, and zero is neither, so it treats an empty slot as a hit.LoadStaticthen decodes 0 bytes intom_StreamBuffer— reused, only ever grown, filled by astream.Readwhose return value is discarded — so the id renders whatever the previously-decoded asset left behind.It is specific to the UOP path:
artidx.mulstores-1for an absent record, while an unmapped UOP slot is a zeroed struct. That is why the earlier probe counted 32,766 of these as "ok" — they decode, they raise nothing, and no success count can tell them from art.A bulk import that trusted the library would have written 22,102 duplicate images into the site under ids that have no art.
The validator, measured both ways
BridgeAssetValidatorprototypes the validate-before-calling response chosen for §4.2's residual risk. Against the patched client it refused all eight record-level defects — seven of which the library rendered without raising anything:static/4104Seekdoes check the record's startstatic/4105Seeknever checks the endstatic/4109static/4111LoadStatic's guards bound the write, nothing bounds the readstatic/4131Verdata.Seekis bounds-checked nowhereland/256LoadLandreads a fixed 2,024 B regardlessAgainst the stock client it refused nothing across 49,151 statics and 16,384 land tiles. That second number is what makes the boundary defensible — a checker that refuses real art is worse than no checker.
The UOP wins outright, and it cost a run to learn
FileIndex's UOP constructor ends with a bareMulPath = uopPath.artLegacyMUL.uopwins andart.mul/artidx.mulare never opened on a current client. The first run of this probe refused 34,299 perfectly good statics for "declaring 10533x2085" because it bounded UOP offsets againstart.mul— and every one of those refusals looked like a real finding. It also means a custom-art shard adding graphics toart.mulwhile the UOP is present gets nothing, silently.The gump crash, reproduced where it counts
One
Ultima.Gumps.GetGump(2)from inside the shard and the ServUO process disappeared — no exception line, nocatchreached, no console output. The report ends mid-section andcheckpoint.txtreadinggump 2is the entire record, which is why the checkpoint is written before the call. §4.1's "nothing callsUltima.Gumps" is now earned rather than argued.§9 is proven
No UOFiddler installed, no
dotnet build, no 5 MB file copied to a server. The reference is what makes it a test: a subtly wrong inverse-BWT coder still yields a plausible table, and a row count would sail past it.What's here
All under
tools/, so nothing is deployed —deploy.ps1copies onlyoverlay/.tools/scaffolding/BridgeAssetProbe.cs— the sweep andBridgeAssetValidator. Runs off the Core thread (§8's split, rehearsed), snapshotsRace.AllRaceson it, and checkpoints the id it is about to touch.tools/scaffolding/BridgeMythicCliloc.cs— the §9 reader in net48 C#, ported from UOFiddler (Beerware) with every file-derived index bounds-checked, which upstream's blanketcatchdoes not do. Phase 2 promotes it intooverlay/.tools/patch_client.ps1— builds the patched client in five tiers (nouop,verdata,customart,corrupt,bodyconv). Hashes every file it touches in the source before and after and aborts on a change; catalogues all 21 defects to a manifest beside the copy.assetprobeverb onBridgeRigDriver, so stock and patched run against one boot.tools/scaffolding/README.md.Left for phase 1, deliberately
The animation path has no validator. The patched client's verdata entry for body 34 points past verdata.mul's end and the wolf still "decoded" — counted among the 1,144 successes, silently rendering something else.
GetAnimationalso allocatesnew int[frameCount]from a file-supplied int. v8.md §16 now names this in phase 1.Also reproduced exactly, from a different process against the same files: 1,144 decodable bodies, 904 empty, 0 faults, and 6 of the 12 player-character bodies absent. Note the gargoyle ghost bodies resolve to file type 1, not 5.
No protocol version change here — phase 0 adds no wire surface.
🤖 Generated with Claude Code
https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
docs/link/v8.md §16 phase 0. §4 chose to CALL ServUO's vendored `Ultima` rather than reimplement it, on the evidence of a PowerShell probe against a stock client — neither the process nor the client the extractor will run in. This runs the same decoders from inside a running ServUO 57.4 against a client broken in 21 catalogued ways, and it found more than a crash. Adds, all under tools/ and therefore never deployed: * BridgeAssetProbe.cs — the sweep, plus BridgeAssetValidator, a prototype of the validate-before-calling response chosen for §4.2's residual risk. Runs off the Core thread, snapshots Race.AllRaces on it, and writes the id it is ABOUT to touch to a checkpoint file before every call. * BridgeMythicCliloc.cs — the §9 Mythic cliloc reader in net48 C#, ported from UOFiddler (Beerware) with every file-derived index bounds-checked. Phase 2 promotes this into overlay/. * patch_client.ps1 — builds the patched client in five tiers. Hashes every file it touches in the SOURCE before and after and aborts on a change. * an `assetprobe` verb on BridgeRigDriver, so stock and patched can be run against one boot rather than two shard processes. The findings are written up in tools/scaffolding/README.md. The four that change what phase 1 has to build: * FileIndex's UOP constructor ends `MulPath = uopPath`, so artLegacyMUL.uop wins outright and art.mul/artidx.mul are never opened on a current client. A validator bounding offsets against art.mul is not approximate, it is nonsense — the first run refused 34,299 good statics on that mistake, and every refusal looked like a real finding. * 22,102 WRONG PICTURES on a stock, unmodified client. Empty UOP index slots read `lookup 0, length 0`; Seek treats that as a hit, and LoadStatic decodes zero bytes into a shared buffer it reuses, only ever grows, and fills from a Read whose return value is discarded — so the id renders the previously-decoded asset. The mul path does not do this (artidx stores -1), which is why the earlier probe counted 32,766 of them as "ok". A bulk import that trusted the library would have written 22,102 duplicate images under ids that have no art. * The validator caught all 8 record-level defects — 7 of which the library rendered without raising anything, including a verdata lookup past verdata.mul's own end (Verdata.Seek is bounds-checked nowhere) and an 8000x8000 bitmap allocated from two bytes in a file. It refused NOTHING across 49,151 statics and 16,384 land tiles on the stock client, which is the number that makes the boundary defensible. * §4.1's crash reproduces in-process: one Ultima.Gumps.GetGump(2) and the ServUO process disappeared — no catch reached, no console line, the checkpoint file the only record. "Nothing calls Ultima.Gumps" is now an earned safety rule. §9 is proven: 123,490 entries in 218 ms, byte-identical to UOFiddler's own output, with no UOFiddler installed and nothing copied to a server. Not covered, and named as phase 1 work: the animation path has no validator at all, and the patched wolf decoded something else in silence. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4