6ef4b06c76584bd9448488e7c7e4555a4ab50349
11 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
| 6ef4b06c76 |
docs(link): two of the eight player bodies existed, and 233 nobody asked about (Phase 4)
Phase 4 built 4.3's UOP animation reader. What it found first changed what the phase
was worth, so the plan is corrected rather than merely annotated.
- New 4.9: what phase 4 measured. Of the EIGHT player bodies 4.8 assigned this
phase, two are in the client at all -- gargoyles 666/667, in AnimationFrame3.uop.
The six ghost bodies are in no package, and that is established rather than
unfound: the five packages hold 10,724 entries and the
build/animationlegacyframe/%06d/%02d.bin scheme claims every one, leaving no room
for another naming. Also the format as read (AMOU, a per-frame ARGB1555 palette,
direction as a slice of the frame table), the nine bodies whose frame count is not
a multiple of five, the validate-as-we-go bounds and the measurement that says
they refuse nothing real, and the live rig.
- 5.2 rewritten: the player-body set is the LIVING pair per race, six ids not
twelve. Ghost ids left it because no client has art for any of them. Still asked
of the shard, never hardcoded -- only the question changed. And with phase 4 in,
all six have art for the first time.
- 4.3 rewritten against what was measured, including why searching five UOP packages
for one body is NOT the never-sweep rule being broken: a legacy index is addressed
by position, a UOP entry by the hash of a name carrying the body id, which the
payload then declares again.
- 11 sizing: the catalogue is 1,022, not 787. The mix is recorded because "add every
body" sounds like it changes what a catalogue is, and it does not -- the legacy
787 was already 366 equipment bodies.
- 14: manifest and fetch rows carry `source` (legacy/uop). Additive, so protocol
stays 8; EXTRACTOR_VERSION 1 -> 2 is the change consumers actually see.
- 16 phase 4 marked DONE; 17.9 records the four org-lead decisions (fallback applies
to every body; ghost ids leave the set; own PNG encoder; NO_IMAGING stays flat).
- 4.8 and 8.1 keep their numbers as the record of what those phases measured, with a
pointer to where the answer landed.
Two consumer docs repeated the ghost claim as fact and are corrected:
website/SPAWN_ATLAS.md (787 -> 1,022, and "two thirds of the playable ghost and
gargoyle bodies have no art" -> about half the addressable body range) and
modules/uo/API.md (same sentence).
Code: servuo-plugins#31.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
|
|||
| 1a7481e9f4 |
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
|
|||
| bbd69a8e2e |
docs(link): clilocs come over the bridge now, and UOFiddler's first job is gone
Phase 2 of the Asset Bridge is built, so the documentation stops telling an operator to install a GUI tool. `v8.md` gains §9.1 and §9.2 — what the port cost, what it measured, and where the base table comes from now. The measurement worth keeping: **67,496 rows in 290 ms**, which is exactly what UOFiddler's own `Ultima.dll` produced from this same client through the converter this phase deletes. An independent implementation agreeing to the row is the strongest check available that a format decoder is correct, and it is not something a subtly-wrong one produces. §17 records the four shapes the org lead settled before any of it was written. Two departed from the recommendation: **the bridge always wins** (no source setting — there is no version of that question an operator benefits from answering) and **import is admin-triggered** (boot does not call the shard at all). `CLILOCS.md` is rewritten around that: where the table comes from, what arrives and in how many pieces, the refusals — including the two the file pipeline had no equivalent of (a client patched mid-import, and the base's exemption from the vanished-source rule, which exists so an upgraded install is not asked to approve a change the upgrade itself made). `UOFIDDLER.md` loses Part 1 entirely rather than having it rewritten. What is left is creature art, which phase 5 takes, after which the page goes away. `v3.md` §8.6 keeps its reasoning with a note saying what superseded it, because the argument for why the manual step existed is still the argument for why this was worth building. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4 |
|||
| 1a048ae1be |
docs(link): phase 1 built the transport, and re-measured the catalogue
Asset Bridge phase 1, docs half. Code: RunicGateway/servuo-plugins#28, RunicGateway/link#41. ## The correction, which is most of this **New §4.8.** The animation path has §4.5's shared-buffer defect too, and 357 of the 1,144 bodies §1.1 counted are **wrong pictures on a stock client** — ids with `length 0` that return whichever body was decoded before them. Proved by decoding body 320 after a dragon (a dragon), a wolf (a wolf) and a human (a human). So the catalogue is **787 bodies**, and the numbers that were derived from 1,144 move with it: §11's working set, its ~173,000-frame full set (now ~119,000), phase 3's scope. **§5.2's table was wrong in the direction that matters.** The elf ghosts were listed as decoding; their index entry has no record, and what came back was the elf female. Four of twelve player bodies have art, not six — which takes phase 4's UOP decoder from six ids to eight. §1.1 now says outright that every "decoded" count in it is an upper bound. It is not a table to size anything from any more. ## What phase 1 settled - **§3.3** — the two numbers: a 512 KiB batch budget under a 1 MiB inbound line cap, with the factor of two load-bearing rather than cautious. - **§3.2** — flow control is enforced **on the shard**, as a single slot answering `bridge.busy`, not serialised in the sidecar and not left to the website as a convention. Records what it costs: a status poll shares the slot. - **§3.4, new** — one paging envelope (`more`/`cursor`/`cut`) for all five families that will page, defined before the first one needs it. `cut` because "short page" has three meanings and only one of them means finished. - **§6** — hashing had to come off the request path entirely. The gate is unchanged; what changed is that "the normal case must cost nothing" now also means "and the abnormal case must not time out", because the first hash of 1.06 GB does not fit in 10 s. - **§14** — which commands exist now, and which phase brings the rest. - **§16, §17** — phase 1 done; decisions 6 and 7. ## Elsewhere - **`SHARD_PREREQS.md`** gains the libgdiplus requirement (§4.4) — Linux hosts only, with the archived-upstream caveat and the `NO_IMAGING` status the shard now reports on the source gate. - **`INTEGRATION.md`** advertised `X-UOLink-Version: 6`. It was already two versions stale before this change; now 8. - [x] AI-assisted — Claude Code (Opus 5) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4 |
|||
| 018f1af5ff |
docs(link): phase 0 ran, and the fault it found is not a crash
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
|
|||
| 041e1f4069 |
docs(link): name it the Asset Bridge, and make libgdiplus a stated requirement
The work has a name now — the Asset Bridge — for commits, PR titles, branches (feat/asset-bridge-p<n>) and conversation. Protocol number stays 8 and the file stays docs/link/v8.md. §4.4 closes the last real open question rather than deferring it to phase 0, and takes all three answers instead of choosing one. ServUO targets net48, so a Linux host runs it under Mono, and Mono's System.Drawing is a thin layer over libgdiplus — which §4.2 put in the decode path, not just the encode. So on Linux it is a hard prerequisite for art. Cliloc and atlas import are unaffected; neither touches pixels. Windows hosts need nothing at all. It is now written down as: a SHARD_PREREQS.md entry, a doctor check in the installer, and a named NO_IMAGING status when it is missing, in the same family as the cliloc reader's COMPRESSED — never a stack trace, never a 500. Install routes per distro are in the section, apt-get install libgdiplus being the normal one. One fact recorded because depending on something unmaintained should be a conscious act: github.com/mono/libgdiplus was ARCHIVED in March 2025 and is read-only. Distributions still package and patch it, so installing it today is supported and ordinary — but it is the strongest long-term argument for moving extraction off System.Drawing eventually, and phase 4's UOP reader is written without it so that door stays open. §17 restructured: three settled items kept because each changes numbers elsewhere, and one genuinely open question (the default audience) that does not block starting. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4 |
|||
| d39e4163d1 |
docs(link): a UOP decoder for the player bodies, and the spider that proves we need one
The six player-character bodies the vendored Animations cannot reach are the player character, and the scope says player models, so they get a decoder rather than a caveat. New §4.3, new phase 4, scoped as narrowly as possible: a reader for AnimationFrame*.uop used ONLY for bodies the legacy path cannot resolve. Everything the vendored code already decodes keeps going through it. Verified genuinely absent rather than mis-addressed, and the way that was established is now the most important warning in the document. Bodyconv.def maps gargoyle 666 to anim5 and BodyConverter.Convert faithfully returns fileType 5, where this client has nothing. Asking the OTHER anim files for index 666 does not fail — it returns 175 decodable action/direction combinations of a giant spider, because something unrelated occupies that index in anim2.mul, while fileTypes 3 and 4 return misaligned colour fragments. All of it rendered and looked at, which is the only reason it was caught: every one of those reads reports success. So the extractor takes Convert's answer and reports nothing when that yields nothing. It must never sweep file types looking for a hit. That does not find missing art — it silently puts a spider on the gargoyle page, with no error raised anywhere and nothing downstream able to detect it. A "0 rows" outcome is correct behaviour; a confident wrong picture is the failure this protocol most needs to avoid. Phase 4 sits after the catalogue, not inside it: the catalogue is useful with 1,138 of 1,144 bodies, and the UOP reader is the only genuinely new format work here, so putting it on the critical path would hold up every website-side phase behind it. Its acceptance test is that a gargoyle looks like a gargoyle, checked by eye. References available and license-compatible: ClassicUO's animation loader (GPL-3) and UOFiddler 4.22 (Beerware). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4 |
|||
| 44039e83d5 |
docs(link): call ServUO's decoders — the crash is on a branch we never take
Decision: no decoders are reimplemented. Extraction goes through ServUO's own vendored Ultima, which Scripts.csproj already references, so the art half of this protocol is plumbing and the Mythic cliloc reader (§9) becomes the only decoder Protocol 8 writes rather than calls. What makes that safe rather than merely cheap is a distinction §1.1 did not draw. All three decoders build a FileIndex, but only Gumps passes hasExtra: true — and FileIndex.cs's own comment says that branch exists FOR gumpartlegacy.uop, the one UOP layout with an extra field. Art passes hasExtra: false and probed 49,150 statics plus 16,384 land tiles with zero faults; Animations touches no UOP at all and probed 1,144 bodies clean. The access violation is a bug on a branch exactly one decoder reaches, and that decoder was already out of scope. So "nothing calls Ultima.Gumps" is now a safety rule, and adding gump art later means fixing that path first. §4.2 records the three costs this accepts: six of twelve stock player bodies have no art (UOP-only, not reachable by calling the existing code differently), System.Drawing stays in the decode path, and we inherit whatever Ultima a shard vendors — EXTRACTOR_VERSION already covers the last one. Phase 0 changes shape with it. It was going to prove new decoders byte-identical; it now tries to BREAK the vendored ones on purpose, from inside a running ServUO against a deliberately patched client, because the probes behind §1.1 ran in PowerShell against a stock client and neither is the real environment. §17 is down to one real question: libgdiplus on Linux/Mono shards. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4 |
|||
| b62d0b6307 |
docs(link): player bodies face you, everything else does not
Player character bodies take direction index 0 (head-on); every other body takes index 1 (front three-quarter). Direction still does not appear in the key — both indices are extractor configuration. The split follows the legibility caveat rather than fighting it: a humanoid reads fine head-on and a character portrait should look at you, while a wolf seen head-on is a dark blob and the same wolf at index 1 is obviously a wolf. §5.2 is new: which bodies count as player characters is asked of the shard, via Race.AllRaces and each race's MaleBody/FemaleBody/MaleGhostBody/FemaleGhostBody, never hardcoded. Twelve ids on stock 57.4. Two things say a hardcoded list would have been wrong — RaceDefinitions.cs passes the gargoyle's ghost bodies in the opposite order to the other two races (695 male, 694 female), and a shard that calls RegisterRace adds ids no table of ours would contain. And the finding that matters most: SIX OF THOSE TWELVE do not decode at all with ServUO's vendored Animations — both human ghosts and every gargoyle body, because they live in AnimationFrame*.uop which that library never reads. The one part of the scope with the most attention on it is the part the vendored library serves worst, which is now the strongest single argument for §4's recommendation to own the decoders. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4 |
|||
| 0c3f0d65cc |
docs(link): one direction, not five — and it is index 0
Only the viewer-facing direction is wanted for players and monsters, so direction leaves the key entirely (§5.1) rather than being a segment nobody varies. Every depth count in §11 falls by exactly 5x. Which index that is was rendered, not looked up: all five directions for a human, a wolf and a dragon. Index 0 is head-on — face, chest and front legs — and index 4 is directly away, with a quadruped's tail toward the camera. The small-thumbnail version of the same test suggested the opposite, which is why the finding is in the doc rather than in someone's head. Measured consequence: body 400 drops 1,050 -> 210 frames, and a complete one-direction set for all 1,144 bodies is ~173,000 frames (~170 MB) rather than ~865,000. That moves a bulk-fill-everything switch from "never" to something phase 5 should leave room for. One caveat kept as an open question: index 0 is the least legible view for four-legged creatures — a head-on wolf is a dark blob, a side-on wolf is a wolf — so the extractor takes the index as configuration defaulting to 0, and §17 asks whether the catalogue should default to 1 or 2 instead. No client-derived image is committed; the render was inspected and discarded. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4 |
|||
| 556141341f |
docs(link): Protocol 8 — client assets over the bridge
The shard host already has the UO client — ServUO cannot boot without one, and Config/DataPath.cfg resolves into Server.Core.DataDirectories at runtime. So the cliloc table, the creature and item art, and the spawn atlas's own source files can all reach the website over the bridge that already exists, and UOFiddler, the desktop conversion step and the website's shared-filesystem view of the ServUO tree all go away. Design of record for the work: the shard extracts, the sidecar forwards, the website decides — which is the only arrangement that keeps the sidecar a dumb forwarder while still resolving creature slug -> body id, something only code running inside ServUO can do. Measured against this machine's ServUO 57.4 tree and client rather than assumed: Art.GetStatic and GetLand decode ~66,000 ids with no faults, 1,144 bodies have a decodable first frame, and body 400 alone is 1,050 frames across its actions and directions — which is what makes the bulk set one thumbnail per body and everything deeper on demand. Two findings shape the build. Gumps.GetGump(2) does not fail, it corrupts the process (AccessViolationException, 0xC0000005) — uncatchable on .NET Framework 4.8 and a shard crash in-process — so §4 recommends owning bounds-checked decoders rather than calling ServUO's vendored Ultima, which also removes the System.Drawing/libgdiplus dependency and the UOP gap that leaves gargoyle bodies 666/667 empty. And the shard -> sidecar direction has no line cap today, which Protocol 8 must close before it starts sending large lines deliberately. UOFiddler is Beerware, so its Mythic cliloc decompressor can be ported into this GPL-3.0-or-later tree and the conversion step retired entirely. Nine phases, four decisions still open in §17. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4 |