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
This commit is contained in:
@@ -23,7 +23,7 @@ knowing a module answers now.
|
||||
|---|---|---|
|
||||
| `/api/v1/public/shard` | 19 | Anonymous. **Never site-mode gated** — the shard surface stays readable during maintenance, per feature audience. |
|
||||
| `/api/v1/public/atlas` | 6 | Anonymous, and unlike `/shard` it **is** site-mode gated: nothing here touches the sidecar, it is parsed shard content. |
|
||||
| `/api/v1/admin/shard` | 26 | Behind core's `isLoggedIn + noindex + staffOnly` group gate, then **mixed per route** — see below. |
|
||||
| `/api/v1/admin/shard` | 28 | Behind core's `isLoggedIn + noindex + staffOnly` group gate, then **mixed per route** — see below. |
|
||||
| `/api/v1/admin/uo-link` | 7 | `adminOnly`. The sidecar connection config, its live status, the admin SSE stream and the town crier. |
|
||||
| `/api/v1/player/shard` | 8 | `requireAuth`, **any role** — staff are a superset of players — and every handler is self-scoped to `req.user.id`. |
|
||||
| `/api/v1/admin/users/:id/shard/*` | 6 | `adminOnly`. The module's routes hanging off a **core** resource, through core's `admin.users.detail` extension slot: core owns the user, the module owns what it knows about their game accounts. |
|
||||
@@ -81,6 +81,8 @@ account-linking routes and the sidecar config under `/admin/uo-link` are in the
|
||||
| PUT | `/shard/atlas/path` | point the atlas at a different tree (persisted as `spawn_atlas_servuo_path`, which wins over `SERVUO_PATH`). Blank clears it. Deliberately **does not import** — moving the mount and reloading the world are separate decisions — and returns fresh status so the panel can offer the import next. |
|
||||
| GET | `/shard/clilocs` | cliloc-table status (`adminOnly`): every source found now (base first, then `custom/` overlays in merge order), what each contributed at the last import, readability, drift across the set, the entry count, and `missingSources`. `configured:false` is a supported state — item names then render as ids. No public counterpart: the table is never served *as* a table. |
|
||||
| POST | `/shard/clilocs/import` | reload after a client patch or an overlay edit; `{force}` ignores the hash gate, `{approve}` accepts a **vanished** source (refused by default — see the table notes above). **A missing path — or the likely mistake of pointing at the client's own COMPRESSED `Cliloc.enu` — answers 200 with `status:"unavailable"` and a `code`, not 500.** `COMPRESSED` is called out by name: a 500 would say only "something broke", and the operator needs to be told which file to convert. |
|
||||
| GET | `/shard/assets` | client-asset import status (`adminOnly`, Protocol 8): the imported body catalogue, how many sprites are on disk, how many atlas creatures resolved to a body id, and the shard's own client files beside them. `drift:true` means the client was patched. `shard.imaging.ok:false` is the named `NO_IMAGING` state — a Linux shard host with no `libgdiplus` cannot decode a sprite at all, and the reason names the package. No public counterpart: the pictures are served as ordinary files under `/uploads`. |
|
||||
| POST | `/shard/assets/import` | import creature artwork from the shard's UO client; `{force}` ignores the hash gate, `{approve}` accepts a catalogue that no longer offers assets this site holds (refused by default — an unmounted client volume and a deliberate downgrade are indistinguishable, and the wrong guess deletes artwork). **This is the ONLY thing that imports** — boot deliberately never calls the shard. An operator's `spawnAtlas.art.json` always wins over an imported sprite. A body this client has no art for is **not** a failure: two thirds of the playable ghost and gargoyle bodies are in that state on a stock client. |
|
||||
| PUT | `/shard/clilocs/path` | point the site at a different cliloc base file or directory (persisted as `cliloc_client_path`, which wins over `UO_CLIENT_PATH`). Overlays are read from `custom/` beside it either way. Blank clears it. Deliberately **does not import**, same reasoning as the atlas path. |
|
||||
|
||||
## 4. Shard visibility — the audience boundary (Protocol 3.0)
|
||||
|
||||
@@ -21,7 +21,6 @@ these routes *mean* are the ones that already existed and did not move:
|
||||
| [`SPAWN_ATLAS.md`](../../website/SPAWN_ATLAS.md) | The bestiary / spawn atlas, parsed from the shard's own ServUO tree |
|
||||
| [`MARKETPLACE.md`](../../website/MARKETPLACE.md) | The player-vendor index |
|
||||
| [`CLILOCS.md`](../../website/CLILOCS.md) | UO's id → name table |
|
||||
| [`UOFIDDLER.md`](../../website/UOFIDDLER.md) | Operator runbook for extracting creature art. The cliloc table now arrives over the bridge ([`link/v8.md`](../../link/v8.md) §9) |
|
||||
| [`../../link/PLAN.md`](../../link/PLAN.md), [`../../link/INTEGRATION.md`](../../link/INTEGRATION.md) | The wire protocol this module speaks to the sidecar |
|
||||
|
||||
---
|
||||
|
||||
@@ -188,10 +188,52 @@ Four column choices worth stating, because each one is a trap:
|
||||
Deceit"). The live `champ.update` feed in `shard_champs` is the separate answer to "it is on level 3
|
||||
right now". Both exist; they are not the same data.
|
||||
|
||||
**`shard_spawn_creatures.art` is always NULL on a fresh import.** The project ships no creature
|
||||
artwork: sprites live in the operator's own client `.mul`/`.uop` files and are theirs, not ours to
|
||||
redistribute. An operator supplies art via a gitignored map plus images under the (already
|
||||
gitignored) `server/uploads/atlas/`. Text-only is the normal, supported state.
|
||||
**`shard_spawn_creatures.art` is DERIVED, never written by the atlas import itself** — see
|
||||
`shard_assets` below. The project still ships no creature artwork: sprites live in the operator's own
|
||||
client `.mul`/`.uop` files and are theirs, not ours to redistribute. What changed in Protocol 8 is
|
||||
who extracts them: the shard does, from its own client, over the bridge. An operator's hand-drawn map
|
||||
plus images under the (already gitignored) `server/uploads/atlas/` still wins over anything imported.
|
||||
Text-only is still the normal, supported state — an install with no shard link never imports one, and
|
||||
even a complete import leaves two thirds of the playable ghost and gargoyle bodies without art.
|
||||
|
||||
## shard_assets / shard_creature_bodies / shard_asset_meta — the Asset Bridge (Protocol 8)
|
||||
|
||||
Creature artwork read from the shard's own UO client ([`link/v8.md`](../../link/v8.md) §6, §8, §12).
|
||||
|
||||
| Table | Shape |
|
||||
|---|---|
|
||||
| `shard_assets` | `asset_key` VARCHAR PK (§5's key, e.g. `body/34/a0`), `family`, `sha256`, `bytes`, `width`, `height`, `body`, `direction`, `file`, `imported_at` |
|
||||
| `shard_creature_bodies` | `slug` PK, `type_name` (the ServUO class name asked), `body` nullable, `status`, `resolved_at` |
|
||||
| `shard_asset_meta` | Singleton (`id = 1`), `payload` JSON (catalogue id, extractor version, source fingerprint, counts), `imported_at` |
|
||||
|
||||
**These are the one part of this schema that is deliberately NOT import-owned**, and the reason is
|
||||
the atlas tables sitting directly above them. `replaceAtlas` empties and refills
|
||||
`shard_spawn_creatures` on every refresh, and a refresh runs on every boot; an imported filename
|
||||
stored on that row would be destroyed by an ordinary re-parse of the ServUO tree, with the next asset
|
||||
Update finding the client files unchanged, reporting "nothing to do", and never restoring it. So the
|
||||
assets live out here, upserted per key, and `replaceAtlas` re-derives `art` from them on the way
|
||||
past — `{ ...derived, ...operatorMap }`, which is the one place "the operator's map wins" is
|
||||
enforced.
|
||||
|
||||
Four details that are load-bearing rather than incidental:
|
||||
|
||||
- **`file` is a filename under the uploads directory, never a path**, and it is content-addressed
|
||||
(`uo-body-34-a0-<sha8>.png`). A stable name overwritten in place would leave every browser and CDN
|
||||
serving the previous client's sprite from cache, with the row perfectly correct.
|
||||
- **The `art` derivation joins on the catalogue key**, `a.asset_key = CONCAT('body/', b.body, '/a0')`,
|
||||
not `a.body = b.body`. The simpler join is correct today and stops being correct the moment deeper
|
||||
animation keys (`body/400/a2/f0`) arrive, at which point one slug matches dozens of rows.
|
||||
- **`shard_creature_bodies` IS replaced whole**, unlike `shard_assets`: it is derived from the atlas's
|
||||
creature list, so a slug that has left the atlas has no meaning, and the pass that rebuilds it is a
|
||||
shard round trip rather than a file transfer.
|
||||
- **`status` keeps the negative answers** — `unknown` (the spawn files name a type this shard's
|
||||
scripts do not define, which is real drift), `notCreature` (a spawn entry for an item or
|
||||
decoration, a permanent answer), `failed`. Without them the next pass asks again, and each name
|
||||
costs a real constructor on the shard's Core thread.
|
||||
|
||||
`shard_spawn_creatures.name` already holds the ServUO **class name** — the atlas build picks the
|
||||
winning spelling of the spawn type token rather than inventing a display label — which is why the
|
||||
body pass needs no extra column to ask its question.
|
||||
|
||||
## shard_clilocs / shard_cliloc_meta — UO's localization table (Protocol 3.0)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user