docs(site): the Asset Bridge — protocol 8, a screen for client files, and pictures in the screenshots (Phase 9c)
All checks were successful
PR checks / checks (pull_request) Successful in 1m41s
All checks were successful
PR checks / checks (pull_request) Successful in 1m41s
The last leg of the Asset Bridge (docs/link/v8.md §16, phase 9c). The cutover put protocol 8 on `main` in every declaration site, which turned this repo red by construction: `checkFacts.mjs` reads the protocol from `link`'s `main`. Two checks were failing, and only one of them was expected. **checkFacts (10)** — protocol 7 → 8 in all three reads (sidecar, overlay, bundle), the bundle triple to 2026.09.15 / sidecar v2.3.0 / overlay v1.3.0, and the releases to link v2.3.0, installer v0.2.0, Module-uo v1.3.0. `moduleApi` and the capability list did not move: core's whole share of eight phases was a deletion. **checkReference (9)** — nobody had planned for this one. `Bridge.cfg` grew nine keys across phases 5, 6 and 7 and the reference page had gone on not mentioning them, which is the failure mode that check exists for. They are listed as two new groups, `Client assets` and `Spawn files`, because they are two separate consents. ## The prose the bridge changed - **New page, Administration → Client files.** `Admin → Client files` is a screen an operator has to press a button on and the site had no page for it. It carries the rule nothing else states: **nothing here happens on a restart**, so a patched client keeps serving the old artwork until somebody says so. - **Protocol versions** said the most recent bump touched five repositories and named `website`. Protocol 8 touched four and did not reach core — its absence is the interesting half, and the page now says why. The store-migration paragraph gains 8, which changed no line of the sidecar's store despite moving megabytes. - **The bridge** said "two ways in". There are three: bulk reads go over the request/reply path in pages, one in flight at a time, and the reason they must not ride the event stream is structural rather than about speed. - **Requirements** gains `libgdiplus` for Linux shard hosts, and the note that no game client has to be installed for any of this — a ServUO shard cannot boot without one. - **Troubleshooting** gains the failure mode that did not exist before: a bestiary with no pictures, or items reading as numbers. - **Maintenance** gains the button to press after patching a client, and **Verify the whole stack** the `doctor` check that landed with it. - The spawn atlas and the marketplace both carry artwork now, so both capability descriptions say so, and `canonicalDocs` points at `link/v8.md`. ## Screenshots, retaken against the real thing The atlas and marketplace shots predate the asset plane, so both were retaken on the demo deployment against a real shard and a real sidecar, plus the new admin screen: 1,095 portraits imported in 2.1 s, 67,496 names in 1.4 s, 313 item pictures in 0.8 s. Nothing here is a mock-up. The atlas shot moved 36px down its page because portraits made the rows taller. ## One defect found on the way `seedDemo.mjs` pinned the demo deployment at **protocol 4** with a comment explaining a `module-uo` debt that has since been paid. Its default now comes from `platform.json`, so the rig cannot drift two protocols behind the platform again. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
This commit is contained in:
103
src/content/docs/docs/administration/client-files.mdx
Normal file
103
src/content/docs/docs/administration/client-files.mdx
Normal file
@@ -0,0 +1,103 @@
|
||||
---
|
||||
title: Client files
|
||||
description: Creature portraits, item pictures and the game's own name table — where they come from, the one button that imports them, and why nothing here happens on a restart.
|
||||
---
|
||||
|
||||
import Screenshot from '../../../../components/Screenshot.astro';
|
||||
import { Aside } from '@astrojs/starlight/components';
|
||||
|
||||
Most of what a game shows you is not text. Ultima Online keeps its creature artwork, its
|
||||
item graphics and even its item *names* inside the client files, and a site that cannot read
|
||||
them shows a bestiary of words and a marketplace of numbers.
|
||||
|
||||
With the `uo` module installed, **Client files** appears in the admin sidebar at
|
||||
`/admin/uo/files`. It is where those three things arrive.
|
||||
|
||||
<Screenshot id="admin-client-files" />
|
||||
|
||||
## Where they come from
|
||||
|
||||
A ServUO shard cannot boot without a UO client — it resolves one at startup to read the
|
||||
world's own data. So the files were already on the shard host, and the shard reads and
|
||||
decodes them there, handing the results over the bridge like everything else.
|
||||
|
||||
**Nothing is converted on a desktop and nothing is uploaded.** Earlier versions of this
|
||||
platform asked an operator to install a third-party tool, build a converter against it and
|
||||
copy the output onto the web host. That path is gone.
|
||||
|
||||
## Three things, one page
|
||||
|
||||
| Section | Fills | How it arrives |
|
||||
|---|---|---|
|
||||
| **Creature portraits** | The bestiary and the spawn atlas | One picture per creature body, imported as a **set** |
|
||||
| **Item and land pictures** | Marketplace listings and character sheets | **One at a time**, shortly after a page asks for one |
|
||||
| **Item and title names (clilocs)** | Anywhere an item is named | The whole table at once — tens of thousands of names |
|
||||
|
||||
They are one page because they are one job: they come out of one client install, and they
|
||||
all change at the same moment — when you patch it.
|
||||
|
||||
<Aside type="caution" title="Nothing here happens on a restart">
|
||||
Boot deliberately never asks the shard for client files. A client patch is an event **you**
|
||||
know about and the website does not, and a site that re-read hundreds of megabytes on every
|
||||
restart to discover nothing had changed would pay for the rare case forever.
|
||||
|
||||
So after you patch your client, the site keeps serving the old pictures and the old names
|
||||
until somebody presses a button on this page. That is the whole reason the page exists.
|
||||
</Aside>
|
||||
|
||||
## Update, or re-import everything
|
||||
|
||||
Every section offers the same pair, and the difference is worth knowing:
|
||||
|
||||
- **Update** asks the shard what changed first and transfers only that. When nothing has, it
|
||||
costs one small round trip and answers *"unchanged"*.
|
||||
- **Re-import everything** fetches the lot. It is for the case the first cannot see — you
|
||||
restored a backup, or lost the uploads volume, and the database still remembers pictures
|
||||
that are no longer on disk.
|
||||
|
||||
Item and land pictures work differently, because there are tens of thousands of item
|
||||
graphics times every dye colour and importing them as a set would be absurd. They arrive
|
||||
lazily instead. The two buttons there — *Fetch waiting pictures* and *Refresh the ones I
|
||||
have* — exist for the two moments waiting is the wrong answer: you have just linked a shard,
|
||||
or you have just patched a client.
|
||||
|
||||
## When the page says something is wrong
|
||||
|
||||
Every one of these is a reported state with a reason, not an error. The site keeps serving
|
||||
whatever is already imported in all of them.
|
||||
|
||||
| What you see | What it means |
|
||||
|---|---|
|
||||
| **The shard is busy with another client-file request** | Not a fault. The shard serves one of these at a time, and an import — or the item-picture pass refilling itself — is holding it. It frees itself. |
|
||||
| **The shard is not answering for client files** | The ordinary bridge problem: see [The shard connection](/docs/administration/the-shard-connection/). |
|
||||
| **…set `AssetsEnabled` on the shard** | The asset plane is switched off in [`Bridge.cfg`](/docs/reference/bridge-cfg/). It is a separate switch on purpose — turning it on is consenting to the website reading this host's client files. |
|
||||
| **The shard host cannot render images** | A Linux host with no `libgdiplus`. Names are unaffected, because they have no pixels in them. |
|
||||
| **Waiting for you: *n* pictures … no longer offered** | The shard stopped offering artwork this site holds. A deletion is never silent here; it waits for you to approve or dismiss it. |
|
||||
|
||||
<Aside type="note" title="Linux shard hosts need one package">
|
||||
ServUO runs under Mono on Linux, and the library it decodes sprites with is a thin layer
|
||||
over **`libgdiplus`** — in the *decode* path, not merely the encode. Without it the shard
|
||||
cannot read a single sprite.
|
||||
|
||||
`sudo apt-get install libgdiplus`, or `dnf install libgdiplus`. `runicgateway doctor` checks
|
||||
for it, and Windows shard hosts need nothing. See
|
||||
[Requirements](/docs/getting-started/requirements/).
|
||||
</Aside>
|
||||
|
||||
## What it will not do
|
||||
|
||||
- **It never writes to the game.** Everything on this plane is a read.
|
||||
- **It never overwrites your own artwork.** A portrait you drew and named yourself always
|
||||
wins over an imported one.
|
||||
- **Creatures with no artwork stay as text.** That is normal rather than a failure — a stock
|
||||
client has no animation for most ghost and gargoyle bodies, and the shard reports nothing
|
||||
rather than guessing. A wrong picture is worse than no picture.
|
||||
|
||||
## Canonical documents
|
||||
|
||||
[`link/v8.md`](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/link/v8.md)
|
||||
is the asset plane's design of record;
|
||||
[`link/SHARD_PREREQS.md`](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/link/SHARD_PREREQS.md)
|
||||
covers what a shard host needs first, and
|
||||
[`website/UPGRADE_NOTES.md`](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/website/UPGRADE_NOTES.md)
|
||||
is what to do on a site that was running before this existed.
|
||||
@@ -116,6 +116,13 @@ when ServUO needs restarting — it never restarts your shard itself. Because it
|
||||
**bundle**, the sidecar and the plugin move together and cannot end up disagreeing about the
|
||||
protocol.
|
||||
|
||||
<Aside type="caution" title="After you patch the UO client, press one more button">
|
||||
Creature portraits, item pictures and the name table are read from that client, and the
|
||||
site deliberately never re-reads them on its own — a restart does not, and neither does
|
||||
`update`. They keep serving the old artwork until somebody presses *Update* on
|
||||
**Admin → Client files**. It is one round trip when nothing has changed.
|
||||
</Aside>
|
||||
|
||||
<Aside type="note" title="Update the two sides in either order, but verify after each">
|
||||
They are independent deployments joined by a version-checked contract: a mismatch is
|
||||
rejected with a `409` rather than mis-parsed. So the worst case is a bridge that refuses to
|
||||
|
||||
@@ -81,6 +81,13 @@ lines, and a duration in seconds. Re-posting the same id **replaces** that messa
|
||||
The id is the useful part — give a recurring announcement a stable one and you can update or
|
||||
withdraw it without waiting for it to expire.
|
||||
|
||||
## Client files
|
||||
|
||||
The other half of what the bridge carries has its own screen: creature portraits, item
|
||||
pictures and the game's own name table, read from the UO client on the shard host. It is
|
||||
**Client files**, at `/admin/uo/files`, and it is where an operator goes after patching that
|
||||
client — nothing imports on a restart. See [Client files](/docs/administration/client-files/).
|
||||
|
||||
## What reaches the public
|
||||
|
||||
Events from the shard fan out over two separate streams, and the split is a security
|
||||
|
||||
@@ -88,6 +88,23 @@ the tree and never compiles — and ServUO ignores the script build's exit code,
|
||||
looks clean. `doctor` catches it by comparing file hashes against the install record.
|
||||
</Aside>
|
||||
|
||||
## The bestiary has no pictures, or items show numbers
|
||||
|
||||
Those come out of the UO client on the shard host, and **nothing imports them on a
|
||||
restart** — a button on **Admin → Client files** is the only thing that does. Check that
|
||||
page first: it reports why rather than failing.
|
||||
|
||||
| What it says | What to do |
|
||||
|---|---|
|
||||
| Counts are zero and no import is recorded | Press *Update*. On a shard that was linked before this existed, nobody ever has. |
|
||||
| *…set `AssetsEnabled` on the shard* | The asset plane is off in `Bridge.cfg`. It is a separate switch on purpose. |
|
||||
| *The shard host cannot render images* | A Linux host with no `libgdiplus`. Install it and press *Update* again. Names are unaffected either way. |
|
||||
| *The shard is busy with another client-file request* | Not a fault. Something ordinary holds the slot; it frees itself. |
|
||||
| Pictures were fine and went blank | Check the uploads volume before anything else — the database still remembers pictures that are no longer on disk, and *Re-import everything* is the button for exactly that. |
|
||||
|
||||
Items reading as numbers rather than names is the same page, different section: it means the
|
||||
cliloc table has not been imported. See [Client files](/docs/administration/client-files/).
|
||||
|
||||
## Teams are missing
|
||||
|
||||
Check the sync panel on **Admin → Teams** before anything else: *last success: never* with
|
||||
|
||||
@@ -46,28 +46,35 @@ allowed to be chosen independently.
|
||||
## What a bump obliges
|
||||
|
||||
Changing a message shape means editing every side plus the specification. The most recent
|
||||
bump touched five repositories:
|
||||
bump — **8**, which taught the bridge to carry a game's own client files — touched four
|
||||
repositories:
|
||||
|
||||
| Repository | What had to change |
|
||||
|---|---|
|
||||
| `servuo-plugins` | The handlers, the caps and switches in `Bridge.cfg`, and `overlay.toml` |
|
||||
| `link` | `PROTOCOL_VERSION`, and the endpoints that carry the new commands |
|
||||
| `module-uo` | The verbs it declares, their option sources, and the ingest |
|
||||
| `website` | Core learned a shape it had not had — a lease aimed at one named target |
|
||||
| `servuo-plugins` | The extractors and the decoders they call, the switches and caps in `Bridge.cfg`, and `overlay.toml` |
|
||||
| `link` | `PROTOCOL_VERSION`, a cap on how large a line the shard may send, and the endpoints that carry the new commands |
|
||||
| `module-uo` | The importers, the admin screen, and the pages that render a picture |
|
||||
| `docs` | The protocol document and the integration guide |
|
||||
|
||||
The `website` row is the one worth noticing. Core holds no game connection and names no
|
||||
game noun, so most protocol bumps do not reach it at all — the two before this one did not.
|
||||
This one did, because what changed was not a game *noun* but the shape of a thing core owns
|
||||
the ledger for.
|
||||
**`website` is not on that list, and its absence is the interesting part.** Core holds no
|
||||
game connection and names no game noun, so most protocol bumps do not reach it at all. The
|
||||
one before this did, because what changed then was not a game *noun* but the shape of a
|
||||
thing core owns the ledger for. This one did not reach core because everything it needed —
|
||||
somewhere to put a picture — core already offered every module. Its entire share of eight
|
||||
phases of work was a **deletion**: a developer tool it no longer needed.
|
||||
|
||||
**A protocol bump can also require a store migration**, because the sidecar persists what it
|
||||
forwards. That is not automatic, and it has happened once: version 4 added a column to a
|
||||
table that already existed. Versions 5, 6 and 7 needed none, because every frame is
|
||||
table that already existed. Versions 5, 6, 7 and 8 needed none, because every frame is
|
||||
persisted whole — a bump that only widens a frame, or adds a kind, or adds a guarantee about
|
||||
how a command is executed, asks nothing of a store that defines no schema for a frame's
|
||||
contents. That is the dumb-forwarder property paying for itself.
|
||||
|
||||
Version 8 puts it more sharply still. It is the largest bump this protocol has had, and it
|
||||
moves megabytes of artwork rather than events — and it changed **no line** of the sidecar's
|
||||
store, because the things it carries are answers to requests rather than events to keep. A
|
||||
forwarder that holds no opinion about what it forwards has nothing to migrate.
|
||||
|
||||
## This is not the module API version
|
||||
|
||||
Two different numbers, versioning two different contracts, and confusing them is easy.
|
||||
@@ -102,9 +109,9 @@ What is worth inheriting is the **shape**:
|
||||
|
||||
## Canonical documents
|
||||
|
||||
[`link/v7.md`](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/link/v7.md)
|
||||
[`link/v8.md`](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/link/v8.md)
|
||||
is the current protocol's record, including its cross-repository obligations, and
|
||||
[`link/v6.md`](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/link/v6.md)
|
||||
[`link/v7.md`](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/link/v7.md)
|
||||
the one before it;
|
||||
[`link/PLAN.md`](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/link/PLAN.md)
|
||||
§7 is the wire protocol, and
|
||||
|
||||
@@ -68,7 +68,7 @@ responsibility. The sidecar is a **dumb forwarder** — it makes no access-contr
|
||||
and holds no policy. Access control and the admin-toggleable visibility scope live on the
|
||||
**website**, where an administrator can see and change them.
|
||||
|
||||
## Two ways in
|
||||
## Three ways in
|
||||
|
||||
**Live events** arrive over an outbound **WebSocket** and are routed by the module's ingest
|
||||
dispatcher. Kinds are handled differently by nature: state-changing kinds update tables,
|
||||
@@ -77,6 +77,23 @@ than accumulating history.
|
||||
|
||||
**Point-in-time reads and commands** go over **REST**, through a client that never throws.
|
||||
|
||||
**Bulk reads** — a game's own client artwork, its string table, its spawn files — are the
|
||||
newest and the least obvious. They go over the request/reply path in **pages**, with **one
|
||||
request in flight at a time** and a hard cap on how large a single line may be.
|
||||
|
||||
<Aside type="note" title="Why bulk data must not ride the event stream">
|
||||
It is the tempting shortcut, and it is wrong for a structural reason rather than a
|
||||
performance one: the sidecar **persists every event and broadcasts it to every connected
|
||||
client**. That is exactly what you want for "a house went IDOC" and exactly what you do not
|
||||
want for hundreds of megabytes of artwork, which is an *answer to a question somebody
|
||||
asked* rather than news.
|
||||
|
||||
Sending it as replies instead is what let the same bump move megabytes without the sidecar's
|
||||
store changing by a line. The single slot is the other half: it is what keeps the queue
|
||||
between the game and the writer thread shallow, so rule 2 above still holds while a
|
||||
transfer is running.
|
||||
</Aside>
|
||||
|
||||
Every call carries `Authorization: Bearer <token>` and an `X-UOLink-Version` header. **A
|
||||
protocol mismatch fails fast with `409`** rather than being mis-parsed — see [Protocol
|
||||
versions](/docs/architecture/protocol-versions/).
|
||||
|
||||
@@ -36,11 +36,20 @@ the [`uo` module](/docs/getting-started/install-a-game-module/) and the installe
|
||||
|---|---|
|
||||
| **A working ServUO install** | It must currently boot and compile scripts cleanly. The installer deploys onto a healthy shard; it does not repair a broken one. |
|
||||
| **ServUO {platform.bundle.servuoMin}** *(patch tier only)* | The base install works on any reasonably current ServUO. The optional patch tier is written and tested against stock {platform.bundle.servuoMin}; on any other version it is unsupported, and skipping it still leaves you with a working bridge. |
|
||||
| **`libgdiplus`** *(Linux shard hosts only)* | Only needed for **artwork**. ServUO runs under Mono on Linux, and the library it decodes sprites with is a thin layer over this one — in the decode path, not merely the encode. Without it creature portraits and item pictures stay empty, and names and the spawn atlas are unaffected because neither touches a pixel. `sudo apt-get install libgdiplus`; `doctor` checks for it. **Windows hosts need nothing.** |
|
||||
| **The shard stopped** | `ServUO.exe` locks `Scripts.dll` and rewrites `Saves/` on exit. The installer refuses to deploy under a running shard. |
|
||||
| **Administrator / root** | It writes into system directories and registers a service. |
|
||||
| **Outbound HTTPS** | To fetch the bundle and its two artifacts. No Gitea account and no git client are needed. |
|
||||
| **The sidecar on the same host as the shard** | The shard connects to `127.0.0.1:7788`. Splitting them is not supported — that loopback socket *is* the trust boundary for inbound commands. |
|
||||
|
||||
<Aside type="note" title="You do not need to install a game client for this">
|
||||
You already have one. A ServUO shard cannot boot without a UO client — it resolves one at
|
||||
startup to read the world's own data — so the artwork, the animations and the name table
|
||||
the site shows are already sitting on that host. The shard reads them there and hands the
|
||||
results over the bridge; nothing is converted on a desktop and nothing is uploaded. See
|
||||
[Client files](/docs/administration/client-files/).
|
||||
</Aside>
|
||||
|
||||
<Aside type="caution" title="Back up before the shard install">
|
||||
The overlay overwrites `Scripts/Scripts.csproj`, a stock file, and the optional patch tier
|
||||
edits stock sources. A copy of `Scripts/` and `Config/` costs nothing and is the difference
|
||||
|
||||
@@ -86,11 +86,18 @@ So verify each link in the chain, in order. Each check tells you which one to fi
|
||||
|
||||
<Aside type="note" title="`runicgateway doctor` answers most of this in one command">
|
||||
Run on the shard host, it checks the install record, the ServUO tree, every overlay file
|
||||
hash, the patch tier, the sidecar, its service, `/health`, and that the sidecar and overlay
|
||||
agree on a protocol. Its output is the first thing anyone helping you will ask for. It
|
||||
hash, the patch tier, the sidecar, its service, `/health`, that the sidecar and overlay
|
||||
agree on a protocol, and — on Linux — that the host can decode an image at all. Its output is the first thing anyone helping you will ask for. It
|
||||
exits non-zero when a check failed, so a monitoring system can run it too.
|
||||
</Aside>
|
||||
|
||||
<Aside type="note" title="A bridge can be green and still show no artwork">
|
||||
Creature portraits and item pictures are a separate switch and a separate import, so a
|
||||
perfectly healthy bridge shows a bestiary of text until somebody presses *Update* on
|
||||
**Admin → Client files**. That is the expected first-run state, not a fault — see [Client
|
||||
files](/docs/administration/client-files/).
|
||||
</Aside>
|
||||
|
||||
## What "working" looks like a week later
|
||||
|
||||
- The public shard page shows live status, and the admin dashboard shows events arriving.
|
||||
|
||||
@@ -45,7 +45,8 @@ moved turns this page red rather than leaving a dead link.
|
||||
- **"Why is the module system like this?"** → `MODULE_SYSTEM.md`.
|
||||
- **"What does this API return?"** → your own deployment's `/api/docs`, then
|
||||
`BACKEND_DESIGN.md` §4.
|
||||
- **"What can the shard send?"** → `link/PLAN.md` §5, and `v7.md` for the current protocol.
|
||||
- **"What can the shard send?"** → `link/PLAN.md` §5, and `v8.md` for the current
|
||||
protocol — which is also where the asset plane is specified.
|
||||
- **"What may a scheduled event do to the world?"** → `website/EVENTS.md` for the model,
|
||||
`MODULE_API.md` for the verbs a module may declare, and `link/ADMIN_CONTROLS.md` for what
|
||||
the site may ask a game to do at all.
|
||||
|
||||
Reference in New Issue
Block a user