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
This commit is contained in:
77
link/v8.md
77
link/v8.md
@@ -176,11 +176,10 @@ not discovering it in production.
|
|||||||
|
|
||||||
Three costs come with it, all known and none of them blocking:
|
Three costs come with it, all known and none of them blocking:
|
||||||
|
|
||||||
1. **Six of the twelve stock player-character bodies have no art** — both human ghosts and every
|
1. **Six of the twelve stock player-character bodies have no art on this path** — both human ghosts
|
||||||
gargoyle body (§5.2). `Animations` never reads `AnimationFrame*.uop`, and UOP animation is a
|
and every gargoyle body (§5.2). `Animations` never reads `AnimationFrame*.uop`. **This one is
|
||||||
different container with its own `AnimationSequence.uop`, not a mirror of the mul layout, so
|
not merely accepted: §4.3 adds a decoder for it**, because it is the player character and the
|
||||||
this is not reachable by calling the existing code differently. Those bodies degrade to no
|
scope says player models.
|
||||||
image, which is the same state every creature is in today.
|
|
||||||
2. **`System.Drawing` is a hard dependency, in the decode and not just the encode.** `Frame`
|
2. **`System.Drawing` is a hard dependency, in the decode and not just the encode.** `Frame`
|
||||||
writes ARGB1555 straight through a `LockBits` pointer, so a Linux/Mono shard needs libgdiplus
|
writes ARGB1555 straight through a `LockBits` pointer, so a Linux/Mono shard needs libgdiplus
|
||||||
to read a sprite at all. See §17.
|
to read a sprite at all. See §17.
|
||||||
@@ -191,6 +190,33 @@ Three costs come with it, all known and none of them blocking:
|
|||||||
The residual risk that remains is a patched or custom client tripping an out-of-bounds read on a
|
The residual risk that remains is a patched or custom client tripping an out-of-bounds read on a
|
||||||
path we *do* call. §16's phase 0 is where that gets exercised rather than assumed.
|
path we *do* call. §16's phase 0 is where that gets exercised rather than assumed.
|
||||||
|
|
||||||
|
### 4.3 One decoder we do write: UOP animation, for the player bodies
|
||||||
|
|
||||||
|
The six missing bodies are the player character, and the scope is player models, so they get a
|
||||||
|
decoder rather than a caveat. It is deliberately the **narrowest possible** addition: a reader for
|
||||||
|
`AnimationFrame*.uop`, used *only* for bodies the legacy path cannot resolve. Everything the
|
||||||
|
vendored code can already decode keeps going through the vendored code.
|
||||||
|
|
||||||
|
This client ships `AnimationFrame1/2/3/4/6.uop` (107, 118, 253, 115 and 24 MB) plus
|
||||||
|
`AnimationSequence.uop`. ServUO's `FileIndex` already contains a UOP reader — but `Animations`
|
||||||
|
never constructs one, and the UOP animation *payload* is its own format rather than a repackaged
|
||||||
|
mul record, so wiring the existing `FileIndex` in is not sufficient. Two license-compatible
|
||||||
|
references exist to work from: ClassicUO's animation loader (GPL-3, and we are GPL-3-or-later) and
|
||||||
|
UOFiddler 4.22 (Beerware, already established in §9).
|
||||||
|
|
||||||
|
**The trap this must not fall into, and it is why the phase exists at all.** `Bodyconv.def` maps
|
||||||
|
gargoyle 666 to `anim5`, and `BodyConverter.Convert` faithfully returns fileType 5 — where the
|
||||||
|
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`; fileTypes 3 and 4 return misaligned colour fragments. Rendered and
|
||||||
|
confirmed.
|
||||||
|
|
||||||
|
So the extractor takes `BodyConverter.Convert`'s answer and, if that yields nothing, reports
|
||||||
|
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, and no error is raised anywhere. A "0 rows" outcome is
|
||||||
|
correct behaviour; a plausible wrong picture is the failure this protocol most needs to avoid,
|
||||||
|
because nothing downstream can detect it.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 5. Addressing: one key for every asset
|
## 5. Addressing: one key for every asset
|
||||||
@@ -281,14 +307,15 @@ the **opposite order** to the other two races (695 male, 694 female), and a shar
|
|||||||
| Elf male/female (605, 606) | **Every gargoyle body (666, 667, 694, 695)** |
|
| Elf male/female (605, 606) | **Every gargoyle body (666, 667, 694, 695)** |
|
||||||
| Elf ghosts (607, 608) | |
|
| Elf ghosts (607, 608) | |
|
||||||
|
|
||||||
Six of twelve, including a whole playable race — and §4 accepts that rather than reimplementing
|
Six of twelve, including a whole playable race. These are UOP-only and **§4.3 adds a decoder for
|
||||||
`Animations` to reach `AnimationFrame*.uop`. Those bodies render without an image, exactly as every
|
them** — verified genuinely absent from the legacy files rather than merely mis-addressed, by
|
||||||
creature does today.
|
rendering what the other anim files hold at those indices and finding spiders.
|
||||||
|
|
||||||
Two things follow for the build. The catalogue must **not** treat a missing player body as an
|
Until that phase lands, and on any client that lacks them entirely, the catalogue must **not** treat
|
||||||
error — it is the expected answer for half the set, and a status screen that flags six failures on
|
a missing player body as an error. It is the expected answer for half the set, and a status screen
|
||||||
every import will teach an operator to ignore it. And `shard_spawn_creatures.art` staying NULL has
|
that flags six failures on every import teaches an operator to ignore it.
|
||||||
to remain a first-class state everywhere it is consumed, which it already is.
|
`shard_spawn_creatures.art` staying NULL remains a first-class state everywhere it is consumed,
|
||||||
|
which it already is.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -448,7 +475,7 @@ gets it, once, and it is cached from then on.
|
|||||||
Because §5.1 dropped four of the five directions, a **complete** one-direction animation set for
|
Because §5.1 dropped four of the five directions, a **complete** one-direction animation set for
|
||||||
every body is now ~173,000 frames rather than ~865,000 — around 170 MB. That is still not the
|
every body is now ~173,000 frames rather than ~865,000 — around 170 MB. That is still not the
|
||||||
default and still not something to import before anything asks for it, but it has moved from
|
default and still not something to import before anything asks for it, but it has moved from
|
||||||
"never" to "a thing an operator could reasonably choose", and phase 5 should leave room for a
|
"never" to "a thing an operator could reasonably choose", and phase 6 should leave room for a
|
||||||
bulk-fill-everything switch rather than assuming on-demand is the only mode.
|
bulk-fill-everything switch rather than assuming on-demand is the only mode.
|
||||||
|
|
||||||
**Hued variants are on demand, always.** `static/3922/h33` is generated when something on the wire
|
**Hued variants are on demand, always.** `static/3922/h33` is generated when something on the wire
|
||||||
@@ -544,24 +571,32 @@ disagree, so a split bump means the next bundle silently fails to compose.
|
|||||||
| 1 | The transport: `assets.sources`, flow control, the sidecar line cap, `EXTRACTOR_VERSION`, protocol bump | servuo-plugins, link |
|
| 1 | The transport: `assets.sources`, flow control, the sidecar line cap, `EXTRACTOR_VERSION`, protocol bump | servuo-plugins, link |
|
||||||
| 2 | Clilocs end to end; retire the converter and `UOFIDDLER.md` §Part 1 | all |
|
| 2 | Clilocs end to end; retire the converter and `UOFIDDLER.md` §Part 1 | all |
|
||||||
| 3 | Body resolution (§8) + the 1,144-body catalogue; `shard_spawn_creatures.art` filled | servuo-plugins, module-uo |
|
| 3 | Body resolution (§8) + the 1,144-body catalogue; `shard_spawn_creatures.art` filled | servuo-plugins, module-uo |
|
||||||
| 4 | Item statics and land on demand, hued keys, the cache | servuo-plugins, module-uo |
|
| 4 | **The UOP animation decoder (§4.3)** — the six player bodies the legacy path cannot reach, and the never-sweep-file-types rule enforced in code | servuo-plugins |
|
||||||
| 5 | Deep animation keys (`body/<id>/a<n>/f<n>`) for the future project, plus the bulk-fill switch | servuo-plugins, module-uo |
|
| 5 | Item statics and land on demand, hued keys, the cache | servuo-plugins, module-uo |
|
||||||
| 6 | The atlas over the sidecar (§10); shared-filesystem requirement retired | module-uo |
|
| 6 | Deep animation keys (`body/<id>/a<n>/f<n>`) for the future project, plus the bulk-fill switch | servuo-plugins, module-uo |
|
||||||
| 7 | Admin surface, Import/Update, approve/reject, activity log | module-uo |
|
| 7 | The atlas over the sidecar (§10); shared-filesystem requirement retired | module-uo |
|
||||||
| 8 | Docs pass across five repos; live walk on the real rig | docs |
|
| 8 | Admin surface, Import/Update, approve/reject, activity log | module-uo |
|
||||||
|
| 9 | Docs pass across five repos; live walk on the real rig | docs |
|
||||||
|
|
||||||
Phase 0 exists because §4 chose to call code that can take the shard down if it is wrong, and the
|
Phase 0 exists because §4 chose to call code that can take the shard down if it is wrong, and the
|
||||||
honest way to hold that choice is to try to break it on purpose — in the real host process, against
|
honest way to hold that choice is to try to break it on purpose — in the real host process, against
|
||||||
a client that has been patched — *before* building seven phases on top of it. The probes behind
|
a client that has been patched — *before* building eight phases on top of it. The probes behind
|
||||||
§1.1 were run from PowerShell against a stock client; neither of those is the environment this will
|
§1.1 were run from PowerShell against a stock client; neither of those is the environment this will
|
||||||
actually run in.
|
actually run in.
|
||||||
|
|
||||||
|
Phase 4 sits **after** the catalogue rather than inside it on purpose. The catalogue is useful with
|
||||||
|
1,138 of 1,144 bodies, the UOP reader is the one piece of genuinely new format work in this
|
||||||
|
protocol, and putting it on the critical path would hold up every website-side phase behind it. Its
|
||||||
|
acceptance test is not "it decodes" — it is that a gargoyle looks like a gargoyle, checked by eye,
|
||||||
|
because §4.3's whole point is that this failure mode produces confident, wrong pictures.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 17. Decisions still open
|
## 17. Decisions still open
|
||||||
|
|
||||||
1. **§4: settled 2026-09-10 — call ServUO's vendored `Ultima`.** No decoders reimplemented. The
|
1. **§4: settled 2026-09-10 — call ServUO's vendored `Ultima`,** with one exception added the same
|
||||||
three accepted costs are in §4.2; the crash is confined to the `hasExtra: true` branch that only
|
day: **§4.3's UOP animation decoder**, scoped to the player bodies the legacy path cannot reach
|
||||||
|
and to nothing else (phase 4). The crash is confined to the `hasExtra: true` branch that only
|
||||||
`Gumps` reaches, and nothing here calls `Gumps`.
|
`Gumps` reaches, and nothing here calls `Gumps`.
|
||||||
2. **§17.1's consequence: Linux/Mono shards.** §4.2 item 2 leaves `System.Drawing` in the decode
|
2. **§17.1's consequence: Linux/Mono shards.** §4.2 item 2 leaves `System.Drawing` in the decode
|
||||||
path, so a non-Windows shard needs **libgdiplus** installed or art extraction fails there. Three
|
path, so a non-Windows shard needs **libgdiplus** installed or art extraction fails there. Three
|
||||||
|
|||||||
Reference in New Issue
Block a user