docs(link): the Asset Bridge (Protocol 8) — client assets without UOFiddler #234

Merged
whitlocktech merged 6 commits from docs/client-assets-v8 into main 2026-09-10 07:03:59 +00:00
Showing only changes of commit b62d0b6307 - Show all commits

View File

@@ -169,10 +169,12 @@ application, and a minimal PNG writer over `System.IO.Compression.DeflateStream`
into a `Bitmap` via `LockBits`, so System.Drawing is in the *decode*, not just the encode — a into a `Bitmap` via `LockBits`, so System.Drawing is in the *decode*, not just the encode — a
Linux/Mono shard needs libgdiplus even to read a sprite. Writing our own pixels and our own PNG Linux/Mono shard needs libgdiplus even to read a sprite. Writing our own pixels and our own PNG
makes the feature portable by construction. makes the feature portable by construction.
3. **It removes the UOP gap.** ServUO's vendored `Animations` reads legacy `anim*.mul` only. This 3. **It removes the UOP gap, which lands squarely on the player bodies.** ServUO's vendored
client has a full 195 MB `anim.mul` so most bodies resolve — but **gargoyle bodies 666/667 `Animations` reads legacy `anim*.mul` only, never `AnimationFrame*.uop`. This client has a full
returned nothing**, because gargoyles live in `AnimationFrame*.uop`. A player race missing from 195 MB `anim.mul` so most creatures resolve — but **six of the twelve stock player-character
an asset store built for "player models and everything" is not a caveat, it is a defect. bodies return nothing**, including every gargoyle and both human ghosts (§5.2). A missing
playable race in an asset store built for "player models and everything" is not a caveat, it is
a defect, and it is in the highest-attention part of the scope.
4. **We are already in this business.** §9 writes a cliloc decompressor from scratch regardless. 4. **We are already in this business.** §9 writes a cliloc decompressor from scratch regardless.
A2 also ends the dependency on whatever version of `Ultima` a given ServUO happens to vendor, A2 also ends the dependency on whatever version of `Ultima` a given ServUO happens to vendor,
@@ -210,13 +212,20 @@ Three properties this shape buys:
### 5.1 There is no direction segment, because only one direction is wanted ### 5.1 There is no direction segment, because only one direction is wanted
Bodies are stored in **five** directions and the client mirrors three of them to reach eight. Only Bodies are stored in **five** directions and the client mirrors three of them to reach eight. Only
one is needed here — the one facing the viewer — so **direction is fixed by the extractor and is one is needed here, so **direction is fixed by the extractor and is not part of the key**. Leaving
not part of the key**. Leaving it in would advertise a choice nobody is going to make and would it in would advertise a choice nobody is going to vary and would five-fold every count in §11 for
five-fold every count in §11 for nothing. nothing.
**The front-facing index is 0.** This was determined by rendering all five for a human, a wolf and **Which one depends on whether the body is a player character:**
a dragon rather than from a table, because the answer is not obvious and the small-thumbnail version
of the same test suggested the opposite: | Body | Direction | Why |
|---|---|---|
| A player character body | **0** — head-on, facing the viewer | A character is a portrait; it should look at you |
| Everything else | **1** — front three-quarter | The view that actually reads as a creature (see the caveat below) |
Which index is which was determined by **rendering all five** for a human, a wolf and a dragon
rather than from a table, because the answer is not obvious and the small-thumbnail version of the
same test suggested the exact opposite:
| Index | View | | Index | View |
|---|---| |---|---|
@@ -226,12 +235,45 @@ of the same test suggested the opposite:
| 3 | Rear three-quarter | | 3 | Rear three-quarter |
| 4 | Directly away — back of the head, and a quadruped's tail toward the camera | | 4 | Directly away — back of the head, and a quadruped's tail toward the camera |
One caveat the render made obvious, and it is the reason this is written down rather than assumed: The caveat the render made obvious is what produced the split: **index 0 is the least legible view
**index 0 is the least legible view for four-legged and long-bodied creatures.** A wolf seen head-on for four-legged and long-bodied creatures.** A wolf seen head-on is a dark blob; the same wolf at
is a dark blob; the same wolf at index 2 is unmistakably a wolf, and at index 1 is the view index 1 is unmistakably a wolf, which is also why UOFiddler's own thumbnail list picks that view. A
UOFiddler's own thumbnail list picks. For a bestiary tile that matters. The extractor therefore humanoid has no such problem — it reads fine head-on, and head-on is what a character portrait
takes the index as a **configuration value defaulting to 0**, so changing the catalogue's mind later wants.
is a setting and a re-import, not a protocol change.
Both indices stay **configuration values** (defaulting to 0 and 1), so changing the catalogue's mind
later is a setting and a re-import, not a protocol change.
### 5.2 "Player character body" is asked of the shard, never hardcoded
`Server.Race.AllRaces` gives every registered race, and each carries `MaleBody`, `FemaleBody`,
`MaleGhostBody` and `FemaleGhostBody`. The plugin enumerates those four ids per race and that set —
nothing else — takes index 0. On stock ServUO 57.4 that is twelve ids:
| Race | Male | Female | Male ghost | Female ghost |
|---|---|---|---|---|
| Human | 400 | 401 | 402 | 403 |
| Elf | 605 | 606 | 607 | 608 |
| Gargoyle | 666 | 667 | **695** | **694** |
This is the §8 argument again in miniature: only code inside ServUO can answer it, and asking is
the only thing that works on a shard with a custom race. Two details make the case that 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.
**Half of that set does not decode with ServUO's vendored library.** Measured:
| Decodes | Does not |
|---|---|
| Human male/female (400, 401) | **Human ghosts (402, 403)** |
| Elf male/female (605, 606) | **Every gargoyle body (666, 667, 694, 695)** |
| Elf ghosts (607, 608) | |
Six of twelve, including a whole playable race. So the one part of the asset scope with the most
attention on it — the player character, head-on — is precisely the part the vendored `Animations`
serves worst, and §4's recommendation to own the decoders is what fixes it. This is the single
strongest piece of evidence for that choice.
--- ---
@@ -505,8 +547,9 @@ prove byte-identical output *before* building six phases on top of it.
libgdiplus dependency, the UOP gap and the vendor-drift risk all going away at once. libgdiplus dependency, the UOP gap and the vendor-drift risk all going away at once.
2. **§11: gump art deferred.** Confirm that paperdoll and equipment gump art is genuinely 2. **§11: gump art deferred.** Confirm that paperdoll and equipment gump art is genuinely
out of scope for 8, given it is the one kind whose existing decoder crashes. out of scope for 8, given it is the one kind whose existing decoder crashes.
3. **§5.1: direction is fixed at index 0** (head-on) and is not in the key — settled 2026-09-10. 3. **§5.1/§5.2: direction — settled 2026-09-10.** Player character bodies use index 0, everything
What is *not* settled is whether the catalogue should default to index 1 or 2 instead, since a else index 1, direction is not in the key, and the player-body set is enumerated from
head-on wolf is a dark blob and a side-on wolf is a wolf. It is a setting either way. `Race.AllRaces` rather than hardcoded. Nothing outstanding; recorded here because it changes
every count in §11.
4. **§13: the default audience** for asset serving — inheriting the using page's audience is 4. **§13: the default audience** for asset serving — inheriting the using page's audience is
proposed; the operator sets the policy either way. proposed; the operator sets the policy either way.