22159ec78fdd3ae51319e22f1451faaf8e0b6001
11 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
| 04ae207fe0 |
docs(link): the cutover, and the counter two branches bumped at once (Phase 9b)
Records the Asset Bridge `edge -> main` cutover: §16's 9b row, §17.15's four org-lead decisions, and the `PARSER_VERSION` correction those forced through §7, §10.4 and `SPAWN_ATLAS.md`. ## The number had to move, and the reason generalises §10.4 wrote phase 7's canonical-read-order bump as `PARSER_VERSION` 4 -> 5. `main` had meanwhile bumped 4 -> 5 for the `UniqueId` fix and RELEASED it as v1.2.2. Same number, different derivation. An install that imported under v1.2.2 stores 5, so a phase-7 build also declaring 5 is read as current by `currentParser` and never re-reads -- the exact failure the constant exists to prevent, reached through a merge instead of through forgetting to bump. Two long-lived branches bumping one counter for different reasons is a defect the counter cannot see; only the merge can. The docs now carry both meanings -- 5 as main's released one, 6 as phase 7's -- rather than collapsing them. ## The cutover is five repos `installer`'s `edge` carried 9a's `libgdiplus` `doctor` check, and `SHARD_PREREQS.md` on `main` had been describing that check as existing since phase 1. §16's 9b row named four repos and would have left an operator-facing doc naming a check in no released binary. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4 |
|||
| fc2d4872f6 |
docs(link): the panel, and the two deletions it could see and no test could (Phase 8)
v8.md section 12.2 records the admin surface as built: one page, Admin -> Client
Files, over the creature catalogue, item and land art, and the cliloc table --
one page because they come off one client install and change on one event. The
four decisions are written down, including the one that kept the phase to a
single repo: section 14's "activity view" is the last import's own summary
rather than a filtered feed, which would have needed a core activity filter, a
`ctx.activity.list` helper and a MODULE_API bump to re-read rows the panel can
keep itself.
The part worth reading twice is what the walk found. Two of the three defects
were DELETIONS, both predate phase 8, and neither was reachable by a test that
did not already suspect them:
* the body import diffing its manifest against every family's rows, so 313
item pictures were staged for deletion under a sentence that was wrong about
what had happened;
* an approved vanish unlinking the sprite and keeping the row, so the key came
back for review forever and the count never moved.
The rule they share is now stated: after phase 5, `shard_assets` is three
collections in one table, and every read of it that feeds a decision has to say
which one it means. Two of the three that did not were deletions.
CLILOCS.md and SPAWN_ATLAS.md now name the button that exists (Admin -> Client
Files) instead of the one they promised, and modules/uo/API.md records that a
`needsReview` reply carries each vanished key's picture.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
|
|||
| dcf7be4975 |
docs(link): what a 4 MB spawn file does to a 1 MiB wire (Phase 7)
10 said the shard would serve `tree/<label>` -> bytes. Measured against a stock 57.4 tree it cannot: Spawns/trammel.xml is 4.03 MB, the sidecar discards any inbound line over 1 MiB, and that file as one base64 row is 5.4 MiB. It would be dropped, time out, and be re-requested forever with no error in it anywhere -- and two files on a STOCK tree are in that state. 10.1 records the three carriages measured before anything was written, and why the winner is not the smallest one: whole-file gzip is 1.21 MB against chunked gzip's 1.26 MB and is bounded by nothing, so it works on every tree anyone would test and fails on the first one nobody did. The chunk is the guarantee; the compression is only the saving. 10.2: it is a `tree` family on assets.fetch, not 14's separate tree.* commands -- phase 5's registry already owns the single slot, the envelope, the ceiling and the mid-import guard, so reusing it left `link` with nothing to do for the third phase running. Its CONSENT is its own, though: Bridge.TreeEnabled, because declining to serve an EA-licensed client is not the same as declining to serve the spawn files an operator wrote, and the atlas would have been the casualty. 10.3 records the two defects and which harness found which. An empty `catalog` is not an absent one. And GZipStream writes nothing at all for zero bytes of input, which stock ServUO's two empty decoration files walk straight into -- an offline probe called that a success, because .NET's own decompressor reads an empty stream as empty data and the declared length and hash both agree with it. Only a live walk through a reader on another runtime disagreed. 10.4: one canonical read order, because the decoration index keeps the first item id it sees and the two readers agreed by coincidence rather than by construction. PARSER_VERSION 4 -> 5. 10.5 has the end-to-end numbers against a live shard. 17 gains the phase's three decisions, including the one that departed from the recommendation: boot never calls the shard, so an install on the bridge has no automatic refresh at all. SPAWN_ATLAS.md is rewritten around the two sources and stops requiring a shared filesystem. Protocol stays 8; EXTRACTOR_VERSION stays 3. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4 |
|||
| b84a73d0e7 |
docs(link): the phase the measurement cancelled, and the 452 wrong pictures it found on the way (Phase 6)
§16 planned deep animation keys and a bulk-fill switch. Measuring first changed the phase: a complete one-direction animation set is 174,453 frames / 281.5 MB (not the ~119,000 / ~117 MB §11 estimated), every frame carries its own centre and 23,818 of 26,274 actions have frames of differing size — and the site displays still pictures. So none of it was built (§11.2, org lead 2026-09-11); `body/<id>/a<n>/f<k>` stays named in §5 and refused in practice. §4.10 is what the walk found instead. A body's actions are a contiguous band and the next body's begin immediately after, so one action past the ceiling is a real record of another creature: 643 of 795 legacy bodies pass every check there and 452 are byte-identical to body+1's action 0. `Animations.GetAnimLength` is not the ceiling — it disagrees with the index arithmetic on one body of this client, by nine actions. What shipped: the 73 bodies with no art at action 0 (a horse at `body/820/a23`), the catalogue key carrying its action, the atlas join that reads it instead of hardcoding `a0`, and the ceiling that makes the fallback walk safe. Catalogue 1,022 -> 1,095; EXTRACTOR_VERSION 2 -> 3; protocol stays 8. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4 |
|||
| 6ef4b06c76 |
docs(link): two of the eight player bodies existed, and 233 nobody asked about (Phase 4)
Phase 4 built 4.3's UOP animation reader. What it found first changed what the phase
was worth, so the plan is corrected rather than merely annotated.
- New 4.9: what phase 4 measured. Of the EIGHT player bodies 4.8 assigned this
phase, two are in the client at all -- gargoyles 666/667, in AnimationFrame3.uop.
The six ghost bodies are in no package, and that is established rather than
unfound: the five packages hold 10,724 entries and the
build/animationlegacyframe/%06d/%02d.bin scheme claims every one, leaving no room
for another naming. Also the format as read (AMOU, a per-frame ARGB1555 palette,
direction as a slice of the frame table), the nine bodies whose frame count is not
a multiple of five, the validate-as-we-go bounds and the measurement that says
they refuse nothing real, and the live rig.
- 5.2 rewritten: the player-body set is the LIVING pair per race, six ids not
twelve. Ghost ids left it because no client has art for any of them. Still asked
of the shard, never hardcoded -- only the question changed. And with phase 4 in,
all six have art for the first time.
- 4.3 rewritten against what was measured, including why searching five UOP packages
for one body is NOT the never-sweep rule being broken: a legacy index is addressed
by position, a UOP entry by the hash of a name carrying the body id, which the
payload then declares again.
- 11 sizing: the catalogue is 1,022, not 787. The mix is recorded because "add every
body" sounds like it changes what a catalogue is, and it does not -- the legacy
787 was already 366 equipment bodies.
- 14: manifest and fetch rows carry `source` (legacy/uop). Additive, so protocol
stays 8; EXTRACTOR_VERSION 1 -> 2 is the change consumers actually see.
- 16 phase 4 marked DONE; 17.9 records the four org-lead decisions (fallback applies
to every body; ghost ids leave the set; own PNG encoder; NO_IMAGING stays flat).
- 4.8 and 8.1 keep their numbers as the record of what those phases measured, with a
pointer to where the answer landed.
Two consumer docs repeated the ghost claim as fact and are corrected:
website/SPAWN_ATLAS.md (787 -> 1,022, and "two thirds of the playable ghost and
gargoyle bodies have no art" -> about half the addressable body range) and
modules/uo/API.md (same sentence).
Code: servuo-plugins#31.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
|
|||
| 1a7481e9f4 |
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
|
|||
| 42867d7ef8 |
docs(modules): close Phase 4 — de-UO the backend reference, record the acceptance run
Phase 4 slice 4. BACKEND_DESIGN.md had never been de-UO'd: Phase 3 rewrote core's code and README, but §5.2's identifier check reads code, not prose, so nothing ever looked at the design document. It still described 27 shard_* tables, 20 UO route rows and the shard visibility ladder as core's, a phase after core stopped being able to serve any of them. Moved, text unchanged: BACKEND_DESIGN §3 six shard_* schema sections (226 lines) → modules/uo/SCHEMA.md BACKEND_DESIGN §4 13 public + 7 admin UO route rows → modules/uo/API.md BACKEND_DESIGN §6.5 the audience ladder (70 lines) → modules/uo/API.md §4 Core keeps the seam and gains the eight /admin/modules routes it had never documented. §6.5 becomes "Module-owned audience boundaries": core's security boundary ends at authentication, roles and the session, and a module that serves game data brings its own. Also fixed on the way: users.router.js was still listed as 15 routes (it is 9 — six went to the extension slot), and the push section still promised config/shardStreams.js "moves out with it" four slices after it left. The acceptance table now carries results. Criterion 2 was proved for real against module-uo v0.3.0 on an empty database, which turned up the uninstall ordering defect fixed in website#146. AI disclosure: this contribution was AI-assisted (Claude Code). Co-Authored-By: Claude <noreply@anthropic.com> |
|||
| afcdb373ec |
docs(website): an operator runbook for extracting from your own UO client
CLILOCS.md and SPAWN_ATLAS.md each explain WHY the operator has to supply something out of their own client, but neither says how. UOFIDDLER.md is the missing procedure: where to get UOFiddler, which two files in the zip matter, which runtime it needs, where Cliloc.enu actually lives, the conversion, how to point the site at the result, and how to confirm it took. Verified end to end on a stock Windows box: UOFiddler 4.22.2 (Ultima.dll is net10.0), .NET SDK 9.0.312 building the net8.0 converter, RollForward carrying it onto runtime 10.0.8, and the site's own parser reading the output back. Corrects one claim while doing it. CLILOCS.md said a UOFiddler GUI export "works equally well"; it does not. Its Cliloc tab writes `Number;Text;Flag` -- three columns, flag LAST -- and parseClilocText splits on the first separator only, so the flag is absorbed into the name and every item renders as `quarter staff;0`. The parser already handles `number,flag,text` with the flag in the middle, but a trailing `;0` is indistinguishable from a name that genuinely ends that way, so this stays a documented `sed` on the operator's side rather than a heuristic that would corrupt real names. Co-Authored-By: Claude <noreply@anthropic.com> |
|||
| be7e1a69ce |
docs(website): the spawn atlas API, the admin panel, and the delay-unit trap
Docs half of website #113 (Protocol 3.0 Part C, second website PR). Carries the atlas rewrite that missed #67: that PR merged before the "derive from the tree on every boot" commit was pushed, so `edge` currently describes the build/import-artifact design that was rejected in review, not what shipped in website #112. It lands here. New in SPAWN_ATLAS.md: the six public routes and five admin ones, and three behaviours that read as bugs unless they are written down — an unreadable tree answers 200 with status "unavailable" rather than 500 (refresh reports outcomes so boot is never blocked by a bad tree, and the contract is preserved at the API), setting the ServUO path deliberately does not import, and `points` is a count while `spawners` is the list. Also the delay-unit trap: XmlSpawner stores MinDelay/MaxDelay in minutes OR seconds per record, decided by that record's own DelayInSec flag, so a `5` is five minutes on one spawner and five seconds on the next. Both are plausible respawn times, which is what makes it silent. 170 of 6,455 stock spawners are second-flagged. And PARSER_VERSION, which exists because hashing the tree alone would strand an install whose maps never change on whatever an older parser derived. BACKEND_DESIGN.md gains the routes, the router-map entry, and the parser-version rule. v3.md marks Part C done and records in 6.3 what the API half found. api-route-inventory.json refreshed from the live manifest — it had drifted to 200 routes before this PR (real count was 204) and is now 215. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U7CBg11prhLimL9iHSX1bP |
|||
| ff1c2064a5 |
docs(website): the atlas reads the shard's tree on every boot, not a snapshot
Follows the redesign in website #112. Two decisions from the original §6 were rejected in review and replaced; the docs now describe what was actually built. **The committed artifact is gone.** A shard's maps change over its life, so a snapshot in the repo silently drifts from the world players actually see. The ServUO tree is the single source of truth and the atlas is re-derived on every server boot, hash-gated so an unchanged tree costs one read pass and no write. **Nothing may name a facet.** The first implementation carried a lookup table of the six stock UO facets. A shard may add facets, replace them outright, or rename them when its maps are updated, and a built-in list mishandles all three silently. Reconciliation is now by matching against the facet set discovered from the shard's own data, with an unmatched name keeping its own rather than being forced into a wrong bucket. ## Changes - **`website/SPAWN_ATLAS.md`** rewritten: the two ideas that shape the design, how to configure the tree path, the boot flow as a decision tree, the approve/reject flow, and the code layout. The artwork policy is unchanged and still explicit — no art ever ships, operators extract their own from their own client files. - **`link/v3.md` §6.1 (new)** records the two rejected decisions plus the two boot-path contracts. The old "what real data changed" list becomes §6.2. §6's now-superseded passages — the artifact bullet, the payload budget, the operator re-run story — are marked rather than deleted, so the reasoning stays legible. - **`website/BACKEND_DESIGN.md`** documents `shard_atlas_pending` and the two contracts that make it safe: a facet removal is staged for a human, and the boot refresh can never block startup. The two contracts are the part worth reviewing. Losing a facet is indistinguishable at boot from a half-copied or mid-update tree, so it is staged rather than applied; and no failure mode of the atlas — missing path, unreadable mount, malformed file, database error — is allowed to stop the site coming up. --- - [x] AI-assisted: written with **Claude Code** (Claude Opus 5), reviewed before opening. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U7CBg11prhLimL9iHSX1bP |
|||
| 3fb3f63f25 |
docs(website): record the spawn atlas pipeline and what real data changed
Protocol 3.0 order 3 (Part C), docs half of website #112. Part C is website-only — no plugin, no sidecar, no new kinds, no wire change. ## New: website/SPAWN_ATLAS.md The operator-facing reference: the build/import split and why it exists (build needs a ServUO tree, import does not, and the container has the artifact but not the tree), the re-run story, the artifact format, the placement transform, and the three quirks in the source data that are silent when unhandled. Also documents the artwork policy explicitly: **the project ships no creature art and no extraction tooling.** Sprites live in the operator's own client .mul/.uop files and are theirs, not ours to redistribute. `art` is nullable and NULL on every fresh import; an operator who wants art extracts it themselves into the gitignored uploads/atlas/ and maps slugs in a gitignored art map. Text-only is the normal, supported state — not a degraded one. ## New: v3.md §6.1 — what the build against real data changed Six corrections, kept as a diff rather than edited into §6 in place, because each is a trap the next person would otherwise re-enter: 1. **Six facets, not thirteen.** Eodon.xml and the other named-area files carry TerMur/Trammel points; the facet comes from each record's `<Map>`. 2. **The XML dependency call resolved: hand-rolled, zero deps.** §6 left fast-xml-parser vs a tokenizer open. 3. **Facet names disagree between sources** — Locations says `Ter Mur`, `<Map>` says `TerMur`. Unreconciled the landmark fallback never fires there and every unregioned Ter Mur/Tokuno spawn silently reads "Wilderness". 4. **Spawn type tokens carry XmlSpawner directives** (`Fairy,{RND,4,8}`, `alchemist/z/-50`). Taken literally they invent creatures that do not exist and split real ones in two. 71 of 845 affected; 800 remain after stripping. 5. **The artifact is 1.41 MB, not "well under 1 MB"** — down from 4.40 MB via three encodings. Getting under 1 MB would mean dropping the spawner name. 6. **DELETE, not TRUNCATE** — TRUNCATE is DDL in MariaDB and implicitly commits, which would defeat the all-or-nothing reload the design asked for. §6 also now records that Part C ships as two website PRs: the parsing half is where the correctness risk lives and should not be reviewed inside a 10k-line diff alongside routes and React. ## BACKEND_DESIGN.md The seven atlas tables, the import-owned contract, the four column choices that are traps (`spawn_range`/`grp` reserved words, DELETE vs TRUNCATE, explicit point ids, plain INDEX not FULLTEXT), and the distinction between the configured champion roster and the live champ.update feed. PROJECT_TREE.md is left alone — it is auto-generated by the sync-project-tree workflow. --- - [x] AI-assisted: written with **Claude Code** (Claude Opus 5), reviewed before opening. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U7CBg11prhLimL9iHSX1bP |