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
2026-09-10 08:32:58 -05:00
3 changed files with 266 additions and 33 deletions
@@ -57,9 +57,9 @@ The wire protocol is versioned so a mismatch is caught immediately instead of fa
The current version is **6**. It is not released yet — it lives on `edge` and ships with the event system's cutover; the last released pairing is protocol **5**, sidecar **v2.1.0** + overlay **v1.1.0**, resolved as bundle **2026.09.01**, never as "latest of each".
- Every response carries an **`X-UOLink-Version: 6`** header.
- Every response carries an **`X-UOLink-Version: 8`** header.
-`GET /health` and the WebSocket `ws.hello` frame include `"protocol": 6`.
- **Optionally**, send `X-UOLink-Version: 6` on your requests. If it disagrees with the sidecar, the request is rejected **409 Conflict**:
- **Optionally**, send `X-UOLink-Version: 8` on your requests. If it disagrees with the sidecar, the request is rejected **409 Conflict**:
```json
{ "error": "protocol version mismatch", "sidecar_protocol": 6, "client_protocol": "5" }
@@ -1277,7 +1277,7 @@ so a retry loop cannot quietly swallow a mismatched deployment.
A typical character page:
```js
const H = { "Authorization": `Bearer ${TOKEN}`, "X-UOLink-Version": "6" };
const H = { "Authorization": `Bearer ${TOKEN}`, "X-UOLink-Version": "8" };
`DllNotFoundException: zlibwapi64` crashed this shard once (`Crash 6-5-2026-22-38-3.log`) while sending a packed gump. `zlibwapi64.dll` is present in the repo root, so this is a working-directory / native-load-path problem. It will bite the bridge if the bridge ever triggers a gump send. Resolve before load testing.
---
## Host prerequisite: `libgdiplus` on Linux (Protocol 8)
Everything above is a repair to one shard's *scripts*. This one is different in kind: it is a
requirement on the **host**, it applies to every shard, and only to Linux ones.
The Asset Bridge ([`v8.md`](v8.md) §4.4) has the shard read art out of the operator's own UO client
files. ServUO targets `net48`, so on Linux it runs under Mono, and Mono's `System.Drawing` is a thin
layer over **libgdiplus** — which sits in the **decode** path and not merely the encode:
`Ultima.Frame` writes ARGB1555 through a `LockBits` pointer. Without that library a Linux shard
cannot read a sprite at all.
**Windows shard hosts need nothing.**`System.Drawing` ships with .NET Framework.
| Host | Get it with |
|---|---|
| Debian / Ubuntu | `sudo apt-get install libgdiplus` — in Debian since bullseye (6.0.4) and bookworm/trixie (6.1), and in Ubuntu universe |
| Fedora / RHEL | `sudo dnf install libgdiplus` (EPEL or the Mono repository) |
| Docker | `RUN apt-get update && apt-get install -y libgdiplus` in the shard image |
| Alpine, or a distro with no package | Build from source. This is the awkward case, and it is worth avoiding by choosing a Debian-based image |
Upstream is <https://github.com/mono/libgdiplus>. **That repository was archived in March 2025** and
is read-only; distributions still package and patch it, so installing it is a normal supported thing
to do today, but nobody is maintaining it upstream. It is the strongest long-term argument for
eventually moving extraction off `System.Drawing`.
**Its absence is not an error and never a crash.** The shard reports a named status on the source
gate — the first call any import makes — so an operator meets this while setting the shard up rather
| `Animations.GetAnimation(0…2047, 0, 1)` | **1,144** bodies with a decodable first frame, 904 empty, 0 errors |
| `Animations.GetAnimation(0…2047, 0, 1)` | **1,144** bodies with a decodable first frame, 904 empty, 0 errors — of which **357 are wrong pictures**; see §4.8 |
| `Hues.GetHue(33)` | loads |
| `Bitmap.Save(…, Png)` | 852-byte PNG from one creature frame |
a vanished key, and the existing path settings. Every action to the admin activity log, as
@@ -708,10 +888,10 @@ disagree, so a split bump means the next bundle silently fails to compose.
| # | Scope | Repos |
|---|---|---|
| 0 | **DONE 2026-09-10.** Spike: the vendored decoders driven from inside a running ServUO, over a client broken 21 ways. §4 stands; the finding was 22,102 wrong pictures on a *stock* client, and the validator that answers them (§4.5-§4.7) | servuo-plugins |
| 1 | The transport: `assets.sources`, flow control, the sidecar line cap, `EXTRACTOR_VERSION`, protocol bump. **Plus §4.5's validator promoted into the overlay, extended to animations** — phase 0 left that half unbuilt and proved it is needed | servuo-plugins, link |
| 1 | **DONE 2026-09-10.** The transport: `assets.sources`, the single-slot gate (§3.2), the paging envelope (§3.4), the sidecar line cap (§3.3), `EXTRACTOR_VERSION`, `NO_IMAGING`, protocol 7→8. Plus §4.5's validator promoted into the overlay and extended to animations — which found **357 wrong pictures in the body catalogue on a stock client** and cut it from 1,144 to 787 (§4.8) | servuo-plugins, link |
| 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 |
| 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 |
| 3 | Body resolution (§8) + the **787**-body catalogue (§4.8); `shard_spawn_creatures.art` filled | servuo-plugins, module-uo |
| 4 | **The UOP animation decoder (§4.3)** — the **eight** player bodies the legacy path cannot reach (§4.8 moved the elf ghosts across), the never-sweep rule already enforced in code by phase 1's `ResolveAnimation` | servuo-plugins |
| 5 | Item statics and land on demand, hued keys, the cache | servuo-plugins, 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 | The atlas over the sidecar (§10); shared-filesystem requirement retired | module-uo |
@@ -725,7 +905,7 @@ a client that has been patched — *before* building eight phases on top of it.
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
783 of its 787 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.
@@ -766,3 +946,15 @@ in the document.
crash a child process would contain, it is a **wrong picture** that no containment strategy
would have caught, on **22,102 ids of a stock client**. See §4.5 for the checks and the
false-refusal measurement that says the boundary is in the right place.
6. **§3.2/§3.3/§3.4: the transport's three numbers and one shape — settled 2026-09-10, phase 1.**
A **512 KiB** batch budget under a **1 MiB** inbound line cap, with the factor of two load-bearing
(a page always admits its first item, so it may overshoot by one). Flow control is enforced
**on the shard**, as a single slot answering `bridge.busy`, rather than serialised in the
sidecar or left to the website as a convention — the bound belongs where the memory is. And one
**paging envelope** (`more` / `cursor` / `cut`) is defined now, with `assets.sources` as its
first user, rather than left for whichever family pages first to invent.
7.**§4.8: the catalogue is 787 bodies, not 1,144 — measured 2026-09-10, phase 1.** Recorded here
because it changes §11's sizing, phase 3's scope and phase 4's, and because of *how* it was
found: the animation path has §4.5's shared-buffer defect too, and 357 ids with no art were
returning the previously-decoded body's bitmap. The elf ghosts moved from "decodes" to "no art"
in §5.2 for the same reason, taking phase 4's set from six player bodies to eight.
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.