Commit Graph

574 Commits

Author SHA1 Message Date
157e8034b1 Merge pull request 'docs(link): the guide an integrator reads stopped at protocol 6 (Phase 9a)' (#244) from docs/asset-bridge-p9 into main
Reviewed-on: #244
2026-09-14 22:24:40 +00:00
72a379f84c docs(link): 9a touched servuo-plugins too — the plugin README's file table
Its file table stopped at Phase 6, ten rows for a directory of 38. The nine files
this workstream added are documented there now (servuo-plugins#35).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
2026-09-14 13:15:05 -05:00
57ecdc8c29 docs(link): the guide an integrator reads stopped at protocol 6 (Phase 9a)
The Asset Bridge's docs pass, and the acceptance walk that shaped it (v8.md §16
row 9a, §17.13-14). Phase 9 is three legs now: this one, the edge->main cutover,
and the site.

## INTEGRATION.md had stopped at 6 and contradicted itself

Its §2 said "the current version is 6" above examples already carrying
`X-UOLink-Version: 8`, there was no protocol-7 paragraph, and `assets.` appeared
zero times in 1,306 lines. It is the only document an integrator outside this org
has, so it is carried the whole way: the version block corrected, v7 (the event
plane's command half) and v8 (the asset plane) written, a §5 section for the five
routes, 425/422 in the status table, and a caveat that the asset plane is a
working set rather than a stream.

Protocol 7's absence is the Events workstream's debt rather than this one's, but
it cannot be stepped over on the way to 8.

## The operator-facing half

`UPGRADE_NOTES.md` gains the entry an operator reads when this ships: what
changed, the one required action on a Linux host, and the thing that will not
announce itself -- nothing here happens on a restart, so a patched client keeps
serving the old pictures until somebody presses a button.

`installer/INSTALL.md` gains libgdiplus as a prerequisite row and the `doctor`
row that checks it. The index rows for SPAWN_ATLAS and CLILOCS described the
workflows this protocol deleted; v8.md now has an index row of its own, and v7 is
marked as the released protocol.

## The walk

Wiped every asset row and every imported sprite, then walked it as a new
operator: 1,095 portraits in 3.18 s, 67,496 names in 1.42 s, 313 item pictures in
1.38 s, the atlas over the bridge in ~2.0 s, an Update with no drift answered in
0.99 s. Bestiary portraits are the right animals by eye; the marketplace shows
hued item art with cliloc names.

It found two defects (§17.14) and one cutover hazard: module-uo's `edge` is
behind its `main`, missing #35, so the walk measured 0 of 6,455 spawners carrying
a UniqueId. 9b's row says to sync before merging or the cutover ships a
regression.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
2026-09-14 13:06:26 -05:00
1f3c728053 Merge pull request 'docs(link): the panel, and the two deletions it could see and no test could (Phase 8)' (#243) from docs/asset-bridge-p8 into main
Reviewed-on: #243
2026-09-14 16:03:28 +00:00
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
2026-09-14 08:10:49 -05:00
ae0efe26b3 Merge pull request 'docs(link): what a 4 MB spawn file does to a 1 MiB wire (Phase 7)' (#242) from docs/asset-bridge-p7 into main
Reviewed-on: #242
2026-09-14 07:36:29 +00:00
047ecb6771 docs(link): the empty-catalog fix is plane-wide, and the stock-install walk that proves it
10.3 recorded the empty-`catalog` defect as a tree-family bug. It was inline in
three places -- the body catalogue, statics and land, and the tree -- and all
three now share one comparison. Records why BridgeLeases keeps the other shape,
and names the two defects as one kind of thing: a value meaning "I am not
asserting anything" has two spellings on a wire, and a stock install finds
whichever one you did not think about.

Adds the whole-plane check against a stock ServUO install: every family asked
with a real catalog, an absent one and an empty one, plus the cross-family and
stale-catalog refusals and both of stock ServUO's empty decoration files.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
2026-09-14 02:33:25 -05:00
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
2026-09-14 02:00:58 -05:00
48caedd3e0 Merge pull request 'docs(link): the phase the measurement cancelled, and the 452 wrong pictures it found on the way (Phase 6)' (#241) from docs/asset-bridge-p6 into main
Reviewed-on: #241
2026-09-14 06:15:29 +00:00
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
2026-09-14 01:10:01 -05:00
e1cefa5be2 Merge pull request 'docs(link): the hue belongs where the files are, and the cache poisons it (Phase 5)' (#240) from docs/asset-bridge-p5 into main
Reviewed-on: #240
2026-09-11 11:26:22 +00:00
a4b63d87d2 docs(link): the hue belongs where the files are, and the cache poisons it (Phase 5)
§11.1 is new and carries what phase 5 measured: 49,152 addressable static ids
(not the 81,884 `artidx.mul` declares -- `FileIndex` sizes its table from its
length ARGUMENT), 39,189 with art, 4,244 land tiles, 9,963 + 12,140 empty index
slots, and the whole set at 81 MB decoding in 34 s. That last number reopens the
bulk question and the answer is still no: 108 MB of base64 through a 512 KB
single-slot channel to store 43,433 pictures a shard displays a few hundred of.

Two traps, both §4.5's failure mode -- a confident, plausible, wrong picture:

- `Art.GetStatic` hands back the SAME cached Bitmap and `Hue.ApplyTo` repaints in
  place, so hueing edits the library's own copy: the plain key comes back hued
  from then on, and the next hue stacks. `Files.CacheData` off process-wide fixes
  it and also stops a game server retaining 74 MB of Bitmap. Copying instead does
  not solve the retention, and `new Bitmap(src)` throws on ARGB1555 anyway.

- `PartialHue` (13,259 of 65,536 ids) decides whether a hue repaints every pixel
  or only the grey ones, from a file only the shard has. Item 597 is a wooden
  screen with painted flowers; one mode reddens the flowers, the other the whole
  screen, and both decode. Hence land takes no hue segment and `h0` is not a key.

Plus the namespace trap that compiled: unqualified `TileData` binds to ServUO's
own `Server.TileData`, because the enclosing namespace beats `using Ultima;`.

§14 records what the wire gained -- the `static` and `land` families, `families`
on `assets.sources`, and `assets.fetch` becoming shared plumbing whose family is
DERIVED from the keys (§5 made the key the address; a request naming its family
too would have two places to be wrong and one of them silent). Additive, so the
protocol stays 8 and EXTRACTOR_VERSION stays 2. §15 records that `link` needed
nothing in phases 4 or 5: it forwards verbatim in both directions.

§17.10 is the four org-lead decisions. §12 and modules/uo/SCHEMA.md carry the
website side: `uploads/items/`, per-row `catalog` staleness, and why a key with
no art writes no row at all.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
2026-09-11 06:16:15 -05:00
2590d4cc58 Merge pull request 'docs(link): two of the eight player bodies existed, and 233 nobody asked about (Phase 4)' (#239) from docs/asset-bridge-p4 into main
Reviewed-on: #239
2026-09-11 10:09:39 +00:00
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
2026-09-11 04:59:55 -05:00
afd0772289 Merge pull request 'docs(link): the catalogue is real, and UOFiddler's last job is gone (Phase 3)' (#238) from docs/asset-bridge-p3 into main
Reviewed-on: #238
2026-09-10 23:57:18 +00:00
d07772a3d8 docs(modules): the cliloc conversion step IS avoidable now (phase 2 debt)
SCHEMA.md's cliloc section still described the pre-protocol-8 world, two
phases after phase 2 changed it. Three sentences said the same false thing,
so fixing only the flagged one would have left the section arguing with
itself:

- "Sourced from files the operator supplies" -- the BASE comes from the shard
  on any install with uo-link configured; only the overlays are the
  filesystem's, and that asymmetry has a reason worth stating (ServUO has no
  server-side notion of a custom cliloc, so there is nothing to ask for).
- "A base (the converted client table)" -- not converted any more.
- "The conversion step is not avoidable ... so the shard cannot supply names
  on our behalf" -- it does supply them. Phase 2 ported UOFiddler's Mythic
  decompressor into the overlay precisely so nobody converts anything.

Rewritten to say what is true and why the file path still exists (deprecated,
not removed, so an existing install keeps working), plus the two things a
reader of this table actually needs: `shard_cliloc_meta.payload` keeps the
base's fingerprint under `base` SEPARATELY from the overlay hashes -- because
on the bridge the old `clilocs.plain` label is supposed to disappear and one
flat hash map would read that upgrade as a vanished source -- and boot does
not import on the bridge path at all.

Each claim checked against the code rather than from memory:
shardClilocs.model.js:307 (`base: fingerprint`), :438-440 (refreshOnBoot
returns `skipped` on the bridge), clilocSource.js:333 (`missingOverlays`).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
2026-09-10 18:50:45 -05:00
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
2026-09-10 18:41:08 -05:00
4c6b0c566a Merge pull request 'docs(link): clilocs come over the bridge now, and UOFiddler's first job is gone' (#237) from docs/asset-bridge-p2 into main
Reviewed-on: #237
2026-09-10 16:19:35 +00:00
bbd69a8e2e docs(link): clilocs come over the bridge now, and UOFiddler's first job is gone
Phase 2 of the Asset Bridge is built, so the documentation stops telling an
operator to install a GUI tool.

`v8.md` gains §9.1 and §9.2 — what the port cost, what it measured, and where the
base table comes from now. The measurement worth keeping: **67,496 rows in
290 ms**, which is exactly what UOFiddler's own `Ultima.dll` produced from this
same client through the converter this phase deletes. An independent
implementation agreeing to the row is the strongest check available that a format
decoder is correct, and it is not something a subtly-wrong one produces.

§17 records the four shapes the org lead settled before any of it was written.
Two departed from the recommendation: **the bridge always wins** (no source
setting — there is no version of that question an operator benefits from
answering) and **import is admin-triggered** (boot does not call the shard at
all).

`CLILOCS.md` is rewritten around that: where the table comes from, what arrives
and in how many pieces, the refusals — including the two the file pipeline had no
equivalent of (a client patched mid-import, and the base's exemption from the
vanished-source rule, which exists so an upgraded install is not asked to approve
a change the upgrade itself made).

`UOFIDDLER.md` loses Part 1 entirely rather than having it rewritten. What is
left is creature art, which phase 5 takes, after which the page goes away. `v3.md`
§8.6 keeps its reasoning with a note saying what superseded it, because the
argument for why the manual step existed is still the argument for why this was
worth building.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
2026-09-10 11:13:56 -05:00
df9b3fd990 Merge pull request 'docs(link): phase 1 built the transport, and re-measured the catalogue' (#236) from docs/asset-bridge-p1 into main
Reviewed-on: #236
2026-09-10 15:04:39 +00:00
1a048ae1be docs(link): phase 1 built the transport, and re-measured the catalogue
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
f6ea5c0484 Merge pull request 'docs(link): phase 0 ran, and the fault it found is not a crash' (#235) from docs/asset-bridge-p0 into main
Reviewed-on: #235
2026-09-10 08:04:13 +00:00
018f1af5ff docs(link): phase 0 ran, and the fault it found is not a crash
Records the Asset Bridge phase 0 spike (§16) against v8.md, and closes the
last open decision.

§4's choice to call ServUO's vendored `Ultima` STANDS: nothing faulted on a
path this protocol calls, and §9's cliloc reader reproduced UOFiddler's
123,490-entry table byte for byte in 218 ms from inside the shard.

But the spike was looking for the wrong kind of failure. `LoadStatic` and
`LoadLand` decode out of a buffer that is reused, only ever grown, and
filled by a `Read` whose return value is discarded — so a short, absent or
out-of-bounds record does not throw, it renders the PREVIOUS asset. On the
stock, unmodified client on this machine that is 22,102 ids whose index
entry reads `lookup 0, length 0`, all of which the library returns a picture
for. §1.1's "32,766 decoded" was counting these.

New §4.5 states the rule that answers it — validate before calling — with
the six checks phase 0 implemented, the eight deliberate defects they caught
(seven of which the library rendered silently, including a verdata lookup
past verdata.mul's own end, which `Verdata.Seek` bounds-checks nowhere), and
the number that makes the boundary defensible: zero false refusals across
49,151 statics and 16,384 land tiles on a clean client.

New §4.6: `FileIndex`'s UOP constructor ends `MulPath = uopPath`, so
`artLegacyMUL.uop` wins outright and `art.mul` is never opened on a current
client. Bounding an offset against the wrong file is not approximate, it is
meaningless — the spike's first run refused 34,299 good statics that way,
and every refusal read like a real finding.

New §4.7: `Ultima.Gumps.GetGump(2)`, called once from inside a running
shard, made the ServUO process disappear — no catch reached, no console
line, the probe's checkpoint file the only record. §4.1's rule is earned.

§17 now has nothing open:

  * item 4 — the default audience — SETTLED: an asset inherits the audience
    of the page that uses it.
  * item 5 is new: validate-before-calling, chosen ahead of the spike over
    a child-process extractor and over reversing §4, and confirmed by it.
    The dangerous failure turns out to be a wrong picture, which no
    containment strategy would have caught.

§16 marks phase 0 done and adds the half it deliberately left unbuilt to
phase 1: the animation path has no validator, and the patched client's wolf
decoded something else in silence to prove it.

Full measurements and the rig recipe live in servuo-plugins
`tools/scaffolding/README.md`; the code is RunicGateway/servuo-plugins#27.

- [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 02:59:56 -05:00
f2e074c3f7 Merge pull request 'docs(link): the Asset Bridge (Protocol 8) — client assets without UOFiddler' (#234) from docs/client-assets-v8 into main
Reviewed-on: #234
2026-09-10 07:03:55 +00:00
041e1f4069 docs(link): name it the Asset Bridge, and make libgdiplus a stated requirement
The work has a name now — the Asset Bridge — for commits, PR titles, branches
(feat/asset-bridge-p<n>) and conversation. Protocol number stays 8 and the file
stays docs/link/v8.md.

§4.4 closes the last real open question rather than deferring it to phase 0, and
takes all three answers instead of choosing one. ServUO targets net48, so a Linux
host runs it under Mono, and Mono's System.Drawing is a thin layer over
libgdiplus — which §4.2 put in the decode path, not just the encode. So on Linux
it is a hard prerequisite for art. Cliloc and atlas import are unaffected; neither
touches pixels. Windows hosts need nothing at all.

It is now written down as: a SHARD_PREREQS.md entry, a doctor check in the
installer, and a named NO_IMAGING status when it is missing, in the same family
as the cliloc reader's COMPRESSED — never a stack trace, never a 500. Install
routes per distro are in the section, apt-get install libgdiplus being the
normal one.

One fact recorded because depending on something unmaintained should be a
conscious act: github.com/mono/libgdiplus was ARCHIVED in March 2025 and is
read-only. Distributions still package and patch it, so installing it today is
supported and ordinary — but it is the strongest long-term argument for moving
extraction off System.Drawing eventually, and phase 4's UOP reader is written
without it so that door stays open.

§17 restructured: three settled items kept because each changes numbers
elsewhere, and one genuinely open question (the default audience) that does not
block starting.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
2026-09-10 02:01:07 -05:00
d39e4163d1 docs(link): a UOP decoder for the player bodies, and the spider that proves we need one
The six player-character bodies the vendored Animations cannot reach are the
player character, and the scope says player models, so they get a decoder rather
than a caveat. New §4.3, new phase 4, scoped as narrowly as possible: a reader
for AnimationFrame*.uop used ONLY for bodies the legacy path cannot resolve.
Everything the vendored code already decodes keeps going through it.

Verified genuinely absent rather than mis-addressed, and the way that was
established is now the most important warning in the document.

Bodyconv.def maps gargoyle 666 to anim5 and BodyConverter.Convert faithfully
returns fileType 5, where this client has nothing. Asking the OTHER anim files
for index 666 does not fail — it returns 175 decodable action/direction
combinations of a giant spider, because something unrelated occupies that index
in anim2.mul, while fileTypes 3 and 4 return misaligned colour fragments. All of
it rendered and looked at, which is the only reason it was caught: every one of
those reads reports success.

So the extractor takes Convert's answer and reports nothing when that yields
nothing. It must never sweep file types looking for a hit. That does not find
missing art — it silently puts a spider on the gargoyle page, with no error
raised anywhere and nothing downstream able to detect it. A "0 rows" outcome is
correct behaviour; a confident wrong picture is the failure this protocol most
needs to avoid.

Phase 4 sits after the catalogue, not inside it: the catalogue is useful with
1,138 of 1,144 bodies, and the UOP reader is the only genuinely new format work
here, so putting it on the critical path would hold up every website-side phase
behind it. Its acceptance test is that a gargoyle looks like a gargoyle, checked
by eye.

References available and license-compatible: ClassicUO's animation loader
(GPL-3) and UOFiddler 4.22 (Beerware).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
2026-09-10 01:53:42 -05:00
44039e83d5 docs(link): call ServUO's decoders — the crash is on a branch we never take
Decision: no decoders are reimplemented. Extraction goes through ServUO's own
vendored Ultima, which Scripts.csproj already references, so the art half of
this protocol is plumbing and the Mythic cliloc reader (§9) becomes the only
decoder Protocol 8 writes rather than calls.

What makes that safe rather than merely cheap is a distinction §1.1 did not
draw. All three decoders build a FileIndex, but only Gumps passes
hasExtra: true — and FileIndex.cs's own comment says that branch exists FOR
gumpartlegacy.uop, the one UOP layout with an extra field. Art passes
hasExtra: false and probed 49,150 statics plus 16,384 land tiles with zero
faults; Animations touches no UOP at all and probed 1,144 bodies clean. The
access violation is a bug on a branch exactly one decoder reaches, and that
decoder was already out of scope. So "nothing calls Ultima.Gumps" is now a
safety rule, and adding gump art later means fixing that path first.

§4.2 records the three costs this accepts: six of twelve stock player bodies
have no art (UOP-only, not reachable by calling the existing code differently),
System.Drawing stays in the decode path, and we inherit whatever Ultima a shard
vendors — EXTRACTOR_VERSION already covers the last one.

Phase 0 changes shape with it. It was going to prove new decoders byte-identical;
it now tries to BREAK the vendored ones on purpose, from inside a running ServUO
against a deliberately patched client, because the probes behind §1.1 ran in
PowerShell against a stock client and neither is the real environment.

§17 is down to one real question: libgdiplus on Linux/Mono shards.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
2026-09-10 01:11:13 -05:00
b62d0b6307 docs(link): player bodies face you, everything else does not
Player character bodies take direction index 0 (head-on); every other body takes
index 1 (front three-quarter). Direction still does not appear in the key — both
indices are extractor configuration.

The split follows the legibility caveat rather than fighting it: a humanoid
reads fine head-on and a character portrait should look at you, while a wolf
seen head-on is a dark blob and the same wolf at index 1 is obviously a wolf.

§5.2 is new: which bodies count as player characters is asked of the shard, via
Race.AllRaces and each race's MaleBody/FemaleBody/MaleGhostBody/FemaleGhostBody,
never hardcoded. Twelve ids on stock 57.4. Two things say 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.

And the finding that matters most: SIX OF THOSE TWELVE do not decode at all with
ServUO's vendored Animations — both human ghosts and every gargoyle body,
because they live in AnimationFrame*.uop which that library never reads. The one
part of the scope with the most attention on it is the part the vendored library
serves worst, which is now the strongest single argument for §4's recommendation
to own the decoders.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
2026-09-10 01:06:32 -05:00
0c3f0d65cc docs(link): one direction, not five — and it is index 0
Only the viewer-facing direction is wanted for players and monsters, so
direction leaves the key entirely (§5.1) rather than being a segment nobody
varies. Every depth count in §11 falls by exactly 5x.

Which index that is was rendered, not looked up: all five directions for a
human, a wolf and a dragon. Index 0 is head-on — face, chest and front legs —
and index 4 is directly away, with a quadruped's tail toward the camera. The
small-thumbnail version of the same test suggested the opposite, which is why
the finding is in the doc rather than in someone's head.

Measured consequence: body 400 drops 1,050 -> 210 frames, and a complete
one-direction set for all 1,144 bodies is ~173,000 frames (~170 MB) rather than
~865,000. That moves a bulk-fill-everything switch from "never" to something
phase 5 should leave room for.

One caveat kept as an open question: index 0 is the least legible view for
four-legged creatures — a head-on wolf is a dark blob, a side-on wolf is a wolf
— so the extractor takes the index as configuration defaulting to 0, and §17
asks whether the catalogue should default to 1 or 2 instead.

No client-derived image is committed; the render was inspected and discarded.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
2026-09-10 00:47:14 -05:00
556141341f docs(link): Protocol 8 — client assets over the bridge
The shard host already has the UO client — ServUO cannot boot without one, and
Config/DataPath.cfg resolves into Server.Core.DataDirectories at runtime. So the
cliloc table, the creature and item art, and the spawn atlas's own source files
can all reach the website over the bridge that already exists, and UOFiddler,
the desktop conversion step and the website's shared-filesystem view of the
ServUO tree all go away.

Design of record for the work: the shard extracts, the sidecar forwards, the
website decides — which is the only arrangement that keeps the sidecar a dumb
forwarder while still resolving creature slug -> body id, something only code
running inside ServUO can do.

Measured against this machine's ServUO 57.4 tree and client rather than assumed:
Art.GetStatic and GetLand decode ~66,000 ids with no faults, 1,144 bodies have a
decodable first frame, and body 400 alone is 1,050 frames across its actions and
directions — which is what makes the bulk set one thumbnail per body and
everything deeper on demand.

Two findings shape the build. Gumps.GetGump(2) does not fail, it corrupts the
process (AccessViolationException, 0xC0000005) — uncatchable on .NET Framework
4.8 and a shard crash in-process — so §4 recommends owning bounds-checked
decoders rather than calling ServUO's vendored Ultima, which also removes the
System.Drawing/libgdiplus dependency and the UOP gap that leaves gargoyle bodies
666/667 empty. And the shard -> sidecar direction has no line cap today, which
Protocol 8 must close before it starts sending large lines deliberately.

UOFiddler is Beerware, so its Mythic cliloc decompressor can be ported into this
GPL-3.0-or-later tree and the conversion step retired entirely.

Nine phases, four decisions still open in §17.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
2026-09-10 00:25:25 -05:00
132620e8b3 Merge pull request 'docs(events): the site, the landing page, and the cutover step they found (Phase 16c)' (#233) from docs/events-p16c-site into main
Reviewed-on: #233
2026-09-10 04:28:14 +00:00
81b55ac29b docs(events): the site, the landing page, and the cutover step they found (Phase 16c)
The last leg of the events plan, and the record of what it turned up.

**16c began by closing 16b's missing seventh step.** Six repositories were cut
over and every one showed 0 commits on `edge` that are not on `main`. This one
showed 46: step 6 landed the record *on* `edge` rather than cutting `edge` over,
so `docs` `main` opened `EVENTS.md` with "revision 5. No code written. Read
against ... MODULE_API_VERSION 1.9.0 - sidecar protocol 5" while six repositories
shipped the engine on protocol 7 — and `link/v6.md` and `v7.md` existed on no
default branch anywhere.

Nothing in this workstream could have caught that, and the reason is worth
writing down: every check that guards a contract lives in the repository that
DEPENDS on the contract, and a documentation repository has no dependants. The
one check that reads `docs` from outside is `runicgateway.com`'s
`checkReference.mjs`, and 16c is the only phase that would ever have run it.
Closed by #232.

**The site.** Nine `checkFacts` values and twenty-seven `Bridge.cfg` keys, both
red before the phase started — which is the bargain that repository's §12 struck.
Two pages, the treatment Teams has. Two capability entries, the calendar one
deliberately not `needsModule` because a bare core can author and run an event.
A `deploy-events` privacy row, because the participation ledger is personal data
and nothing named it. And `reference/event-catalog` retitled "Shard event
catalog", route unchanged, because two things in the documentation were called an
event catalog.

**`.profile`.** One bullet saying the posture rather than the feature list, and
two stale numbers: protocol 5 → 7 in the four values the installer prints, and
module-uo v1.1.0 → v1.2.2.

**And the plan is marked COMPLETE.** All seventeen phases on `main`, in every
repository they touch. No contract moves in this phase; it makes the ones already
moved legible from outside the organisation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
2026-09-09 22:07:46 -05:00
2db5737f22 Merge pull request 'docs(events): the Event System record — the cutover step 16b missed (edgemain)' (#232) from edge into main
Reviewed-on: #232
2026-09-10 02:54:40 +00:00
e75ba0e6eb Merge pull request 'docs(events): the cutover, the outage it survived, and the defect it exposed (Phase 16b cutover, 6 of 6)' (#231) from docs/events-p16b-cutover into edge
Reviewed-on: #231
2026-09-10 02:14:16 +00:00
bb75910a8f docs(events): the module leg of the re-verify, and the two defects it found
The leg the first commit named as still owed is walked. Module-uo#34 cut v1.2.1,
so the module could arrive the way an operator's does: core fetched the release
MANIFEST over https from the allowlisted host, verified its sha256, and mounted
it; the four values the installer printed then produced `status: connected`,
`pluginConnected: true`, `protocol: 7`. Released core, released module, released
sidecar, released overlay.

That rig confirmed both Phase 16a fixes in the shipped artefacts rather than in a
working tree -- the atlas imports off a stock tree, and a world verb's teardown
leaves the shard answering `owned: [], pruned: 0`, which is the check the no-op
teardown hid behind.

It also found two more defects, both in the released bundle (Module-uo#35). The
aggregator discarded the UniqueId, so `uo.options.spawners` was empty and no
Phase 12b property lease was authorable at all -- while `PARSER_VERSION = 4`'s
own note said a point keeps that field and named Phase 12b as the reason. And a
landmark option value named 23 places at once: 558 landmarks under 320 distinct
`facet/name`, resolved by `.find()`, so an author who picked "Entrance - Destard"
got Blighted Grove with a successful run and no warning.

Both are recorded as one class, because that is the useful part: an option source
that answers empty, or answers with a value that does not identify one thing,
disables a feature silently. Nothing errors; the form simply cannot express the
thing, and a test that checks the parser, or the query, or the column in
isolation passes throughout.

Diff is 45/6, content only -- no CRLF rewrite (checked against --numstat).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
2026-09-09 21:06:34 -05:00
eae8a6f93d Merge pull request 'docs(tree): sync website/PROJECT_TREE.md' (#230) from chore/sync-website-tree into main
Reviewed-on: #230
2026-09-10 01:22:25 +00:00
1a7d2da4dd docs(events): the cutover, the outage it survived, and the defect it exposed (Phase 16b)
`EVENTS_PLAN.md` gains the 16b record: the six steps and why core lands before
the module, the four decisions taken, the releases cut, and the re-verify against
artefacts an operator would actually download.

Three things in it are worth more than the chronology.

A job's log IS readable on this Gitea, through the web route rather than the
API. Every earlier phase diagnosed CI by reproducing jobs locally on the belief
that logs were unreachable; reading one turned four red jobs into four known
causes in about ten minutes. Three were the ten-minute Cloudflare outage in the
middle of the window and a runner that could not resolve sh.rustup.rs -- and
because neither release pushed its tag before dying, the orphaned-tag failure
mode did not occur and a plain workflow_dispatch recovered both.

The seventh defect of this phase: `server-tests` had been red on every events PR
since Phase 10, always the same single test, and the workstream merged over it
eight times. `announce.js` asked for `hour12: true`, which is not the same
request as a 12-hour clock -- for a locale whose default cycle is h23, Node 20
resolves it to h11 and midnight renders "0:00 am", while Node 22+ resolves it to
h12. Same ICU on both sides, so it is V8's ECMA-402 behaviour and not locale
data; the image ships node:20-alpine and a dev machine is newer, so it rendered
correctly for everyone who reviewed it and wrongly for every real recipient. The
rule is now written down: `hour12` is a request about a locale's preference,
`hourCycle` is a request about the clock -- ask for the clock.

And the re-verify itself: the released installer resolved bundle 2026.09.10,
verified both checksums, did a first install into a stock 57.4 tree, the overlay
compiled 0/0 -- which no release had ever been asked to prove -- the shard came
up with the events plane on and dialed the sidecar, the whole protocol-7 event
plane answered, and an event published on released `main` ran to `completed`
with its results published and its finished run visible on /site/events two
minutes later. That last line is 16a's calendar fix holding on `main`.

The module's own install through core's https installer is named as the one leg
still owed: it cannot run until Module-uo#34 cuts the release it fetches.

Diff is 102/1, content only -- no CRLF rewrite (checked against --numstat).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
2026-09-09 20:10:47 -05:00
runic-docs-bot
537c79e5de docs(tree): sync website/PROJECT_TREE.md from RunicGateway/website@655fbf3 [skip ci] 2026-09-10 00:44:20 +00:00
abfdcd4658 Merge pull request 'docs(tree): sync android/PROJECT_TREE.md' (#229) from chore/sync-android-tree into main
Reviewed-on: #229
2026-09-10 00:28:24 +00:00
runic-docs-bot
90f95520a3 docs(tree): sync android/PROJECT_TREE.md from RunicGateway/Android-app@5e61ee2 [skip ci] 2026-09-09 20:10:00 +00:00
08a8f145dc Merge pull request 'docs(events): the acceptance walk, and the three contracts it moved (Phase 16a)' (#228) from docs/events-p16a-walk into edge
Reviewed-on: #228
2026-09-09 13:46:42 +00:00
6647287037 docs(events): the acceptance walk, and the three contracts it moved (Phase 16a)
Phase 16 is split into 16a (the walk), 16b (the cutover) and 16c
(runicgateway.com + .profile), because the phase as written asked for a walk
"against released artefacts" BEFORE the cutover and all three component repos
release on push to `main`. The walk therefore runs against artefacts built from
`edge` the way a release builds them, and 16b re-verifies against the real bundle.

`EVENTS_PLAN.md` gains the 16a record: the rig, all three deliberate failures
passing, the six defects, the one finding withdrawn, and what each fix was
verified against.

Three contracts move, each because the walk proved the built thing did not match
the written one:

**`link/v6.md` — a refusal does not spend its key.** Rule 2 had two cases, throw
and return, and needed a third: a handler that ran to completion and deliberately
refused did nothing, so freezing that refusal as the key's answer made a refusal
that WAITING FIXES impossible to retry past. The section now carries the case
`uo.world.save` found it with, and the rule the release rests on — do not answer
`*.error` after changing the world. `[bridge status` gains `refused=`.

**`website/MODULE_API.md` — `revert`'s `idempotencyKey` identifies a dispatch; it
is not a key to send on the undo.** The paragraph explained what the key is FOR
and never said what it is not, and `module-uo` read it the other way: every
despawn went out under the key its spawn had used, so a store that keys on the key
alone answered the undo with the DO's reply and teardown became a no-op that
reported success.

**`website/EVENTS.md` §I — the public calendar matches a run that OVERLAPS the
window.** The row promised "upcoming, live and recent" and the built route served
only the first, because it read the start instant and a live run has already
started. The default window now reaches back so "recent" has somewhere to live,
and projections are forecast from now rather than into that tail.

Pairs with `website#`, `Module-uo#` and `servuo-plugins#`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
2026-09-09 08:31:06 -05:00
8cb4cb3e01 Merge pull request 'docs(events): detail is a real envelope member now' (#227) from docs/events-module-detail into edge
Reviewed-on: #227
2026-09-09 00:22:08 +00:00
ec55e66720 docs(events): detail is a real envelope member now
Follows the finding recorded a commit ago: §H told a module the revert contract
accepts a `detail`, `classify()` had never read one, and `module-uo` had been
answering one since Phase 12b — so `uo.item.grant`'s report of which recipients
missed out was written into nothing.

Fixed in `website#197` by making the member real rather than by deleting the
reporting, because §H's sentence was right and only its example was wrong.

  * **§1.1, 1.10.0** gains `detail` as a third envelope member beside Phase
    10's two: optional, on both SUCCESS shapes, carried and never interpreted,
    objects only, 4KB, dropped-and-logged rather than failing the step.
  * **§2.4** gains the contract rule — core reads no key out of it, because a
    switch on known keys anywhere in core would be core learning one module's
    vocabulary.
  * **EVENTS.md §F** records the fix, including the half that is easy to miss:
    the run console's `describeLogLine` default returns a kind WORD, so the new
    line would have rendered as the literal string "step.detail" — the channel
    existing and showing nothing.
  * **§H's wipes row** no longer claims `detail` is unread.

MODULE_API stays 1.10.0, amended in place — still on `edge`. The failure
channel is unchanged and is still `error` alone.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
2026-09-08 18:51:32 -05:00
82e22ea365 Merge pull request 'docs(events): what the integration kit's fifth chapter settled (Phase 15)' (#226) from docs/events-p15-kit into edge
Reviewed-on: #226
2026-09-08 23:25:50 +00:00
5d48e7f256 docs(events): what the integration kit's fifth chapter settled (Phase 15)
§F's Integration Kit paragraph gains what building it produced, and §H loses an
envelope member that does not exist.

**It is three chapters, not one.** The book taught a read-only data path end to
end and never told anyone to build a command path, so a chapter 5 teaching a
module to send an idempotency key would have addressed it to a sidecar with
nowhere to put it. Chapters 3 and 4 each gain one section, both skippable.

**Two defects, both found by running the template through core's real registry
and real dispatcher rather than by writing prose:**

  * **An idempotency key belongs on a command, never on a question.** A read
    carrying one is answered by an at-most-once store with the FIRST read's
    reply, forever — the lease applied correctly and the module could no longer
    see it.

  * **§H named a `detail` member on an envelope and `classify()` has never read
    one.** The sentence §H was making is right and its example was wrong: a
    revert of something gone is `{ ok: true }`. Corrected in place, with the
    finding recorded in §F.

That second one has a consequence outside this PR: **`module-uo` took §H at its
word twice.** `uo.item.grant` answers `detail: { granted, missed, why }` and
`uo.world.save` answers `detail: { started: true }`, and neither reaches a
screen or the ledger. The grant is the one that matters — which recipients did
not receive the item is reported nowhere else. Recorded here rather than fixed;
the fix is a Module-uo change and is the org lead's call.

Pairs with Integration-kit#10, which is red on `checkCoreApi` by design and
merges in the P16 cutover with its pin move.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
2026-09-08 18:11:39 -05:00
c7c526a50b Merge pull request 'docs(android): the app's events screens, and the module rows that were never gated (Phase 14b)' (#225) from docs/events-p14b-app into edge
Reviewed-on: #225
2026-09-08 21:58:47 +00:00
0a61915e50 docs(android): the app's events screens, and the module rows that were never gated (Phase 14b)
Events Phase 14b, docs half. `docs/android/PLAN.md` gains **M13** — written before
the code, as the plan requires — and `EVENTS_PLAN.md` and `EVENTS.md` record 14b
as built.

M13 records the six org-lead decisions, the two defects the milestone fixes, and
the three the emulator walk found.

The prerequisite fix is the larger half of the milestone: the app could not tell a
module that is not installed from a lookup that failed, so on a site with no `uo`
module every shard row rendered and every one of them 404'd. **Absence of an
answer is not an answer of absence** — a successful module list that omits the
string hides the rows, a failed read keeps the last answer the host gave, and a
host that has never answered leaves the gate open. Capability and feature are two
gates that compose and answer different questions.

A second, older defect is recorded and fixed: the website path → route table had
been wrong since the module-system cutover on 2026-08-12, because core's `NAV` is
eight rows rather than sixteen and the nine shard rows moved to `/uo/*`. Two tests
in the app's suite had been passing vacuously since that day.

§5 gains the capability gate beside the feature gate, with the two failure
directions spelled out; §6.1 gains the three public event reads.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
2026-09-08 13:09:02 -05:00
b6fefc5466 Merge pull request 'docs(website): the public surface, and the flag that decides what reaches it (Phase 14a)' (#224) from docs/events-p14a-public-surface into edge
Reviewed-on: #224
2026-09-08 17:04:24 +00:00
ed311a2e8d docs(website): the three defects the walk found, and the mount that answered one
EVENTS.md §I records that participation history is mounted twice and why --
the third screen to need the notificationPaths.js mapping, for the reason
the first two did -- and the three defects the live walk found with both
suites green on either side of it. EVENTS_PLAN.md says the same in one
paragraph under Phase 14a's verify line.

Two of the three were one mistake in two files: a split reading a STATUS
where it should read a clock.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
2026-09-08 11:59:23 -05:00