feat(assets): the website half of the Asset Bridge (Asset Bridge cutover, 3 of 5) #44

Merged
whitlocktech merged 17 commits from edge into main 2026-09-14 23:11:22 +00:00
Member

Step 3 of 5 of the Asset Bridge edge → main cutover (docs/link/v8.md §16 row 9b). Merge
after website#202; link#44 + servuo-plugins#36 are the pair that has to land together.

#43 is inmain is now fully contained in edge (0 commits ahead), so this no longer
reverts the released UniqueId fix.

What comes over

Seven phases, 46 files, +10,169 / −191, and everything the website does with a client file:

Phase PR What it added
2 #36 Cliloc ingest from the shard instead of a file someone converted — 67,496 names
3 #37 Creature artwork from the shard's own client; the atlas's art column filled and rendered
5 #38 Item pictures on the marketplace and the character sheet, hued where the files are
6 #39 A creature's picture is whichever action has one — the per-body action ceiling
7 #40 The spawn atlas reads the shard, not the shard's filesystem
8 #41 Admin → Client Files — the panel that operates all three imports
9a #42 A busy shard is not a broken one; the column that reached no existing install

New tables shard_assets, shard_creature_bodies, shard_asset_meta; uo_link_config.protocol
defaults to 8.

Two things an operator gets from this release that are not Asset Bridge features

  • shard_spawn_points.unique_id finally reaches existing installs. Events phase 12b added it
    to the CREATE TABLE and nowhere else, so it landed on fresh installs and no upgraded one
    CREATE TABLE IF NOT EXISTS never adds a column. Every spawn-atlas import on an upgraded install
    has failed outright since v1.2.0 with Unknown column 'unique_id': no bestiary refresh, no
    spawn map, no champion altars. The ADD COLUMN IF NOT EXISTS line ships here (org lead's call at
    9a, weighing it against a hotfix to main).
  • The spawn atlas stops needing a shared filesystem. SPAWN_ATLAS.md used to require the
    website — the one internet-facing component — to read the ServUO tree over a bind mount. That
    was the single place "only the sidecar bridges the shard" was broken, and it is closed.

PARSER_VERSION is 6, not 5

Both branches bumped 4 → 5 for different derivations and main's 5 is released (v1.2.2, the
UniqueId fix). Had the merge kept 5, every install that imported under v1.2.2 would already store
5, currentParser would call a phase-7 build current, and phase 7's canonical read order would have
reached nobody already running. Resolved in #43 with both meanings kept in the version log.

The pin stays put

ci/core-ref.json names website main 655fbf3f. website#202 moves core's main but not
MODULE_API_VERSION, and the pin asserts an API version rather than a tip — so the frozen-manifest
job keeps answering, and the Integration kit stays green through the window. Org lead's call,
2026-09-14.

Verification

  • npm test --prefix server723 pass, 0 fail
  • npm run build --prefix client then npm test --prefix client45 pass, 0 fail
  • CI on #43's head: server-tests, client-build and frozen-manifest all green
  • Phase 9a's acceptance walk, clean-state, against a real shard + sidecar: 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 s, an update with no drift in 0.99 s

AI disclosure

Authored with Claude Code (Claude Opus 5). Commits carry the Co-Authored-By trailer.

🤖 Generated with Claude Code

https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4

Step **3 of 5** of the Asset Bridge `edge → main` cutover (`docs/link/v8.md` §16 row 9b). Merge after `website#202`; `link#44` + `servuo-plugins#36` are the pair that has to land together. **#43 is in** — `main` is now fully contained in `edge` (0 commits ahead), so this no longer reverts the released `UniqueId` fix. ## What comes over Seven phases, **46 files, +10,169 / −191**, and everything the website does with a client file: | Phase | PR | What it added | |---|---|---| | 2 | #36 | Cliloc ingest from the shard instead of a file someone converted — 67,496 names | | 3 | #37 | Creature artwork from the shard's own client; the atlas's `art` column filled and rendered | | 5 | #38 | Item pictures on the marketplace and the character sheet, hued where the files are | | 6 | #39 | A creature's picture is whichever action has one — the per-body action ceiling | | 7 | #40 | The spawn atlas reads the shard, **not the shard's filesystem** | | 8 | #41 | `Admin → Client Files` — the panel that operates all three imports | | 9a | #42 | A busy shard is not a broken one; the column that reached no existing install | New tables `shard_assets`, `shard_creature_bodies`, `shard_asset_meta`; `uo_link_config.protocol` defaults to **8**. ## Two things an operator gets from this release that are not Asset Bridge features - **`shard_spawn_points.unique_id` finally reaches existing installs.** Events phase 12b added it to the `CREATE TABLE` and nowhere else, so it landed on fresh installs and **no upgraded one** — `CREATE TABLE IF NOT EXISTS` never adds a column. Every spawn-atlas import on an upgraded install has failed outright since **v1.2.0** with `Unknown column 'unique_id'`: no bestiary refresh, no spawn map, no champion altars. The `ADD COLUMN IF NOT EXISTS` line ships here (org lead's call at 9a, weighing it against a hotfix to `main`). - **The spawn atlas stops needing a shared filesystem.** `SPAWN_ATLAS.md` used to require the *website* — the one internet-facing component — to read the ServUO tree over a bind mount. That was the single place "only the sidecar bridges the shard" was broken, and it is closed. ## `PARSER_VERSION` is 6, not 5 Both branches bumped 4 → 5 for different derivations and main's 5 is **released** (v1.2.2, the `UniqueId` fix). Had the merge kept 5, every install that imported under v1.2.2 would already store 5, `currentParser` would call a phase-7 build current, and phase 7's canonical read order would have reached nobody already running. Resolved in #43 with both meanings kept in the version log. ## The pin stays put `ci/core-ref.json` names website `main` `655fbf3f`. `website#202` moves core's `main` but not `MODULE_API_VERSION`, and the pin asserts an API version rather than a tip — so the frozen-manifest job keeps answering, and the Integration kit stays green through the window. Org lead's call, 2026-09-14. ## Verification - `npm test --prefix server` — **723 pass, 0 fail** - `npm run build --prefix client` then `npm test --prefix client` — **45 pass, 0 fail** - CI on #43's head: `server-tests`, `client-build` and **`frozen-manifest` all green** - Phase 9a's acceptance walk, clean-state, against a real shard + sidecar: **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 s, an update with no drift in 0.99 s ## AI disclosure Authored with Claude Code (Claude Opus 5). Commits carry the `Co-Authored-By` trailer. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
wtclaude added 17 commits 2026-09-14 23:09:39 +00:00
feat(cliloc): import the table from the shard, not from a file someone converted (Phase 2)
All checks were successful
PR Checks / client-build (pull_request) Successful in 34s
PR Checks / frozen-manifest (pull_request) Successful in 53s
PR Checks / server-tests (pull_request) Successful in 8m18s
893a36618b
The base cliloc table now comes over the bridge. `clilocBridge.js` walks
`GET /cliloc` page by page and the model merges the `custom/` overlays over it —
overlays stay on disk because ServUO has no server-side notion of a custom
cliloc, so there is nothing on the shard to ask for.

**The shard wins whenever uo-link is configured and enabled**, with no mode
setting: there is no version of "which source?" an operator benefits from
answering. A file on disk remains the source only where there is no shard link,
plus a one-off explicit `path` — deprecated, not removed, and unchanged.

**Boot no longer imports on the bridge.** The file path could hash 5 MB locally
and skip in 14 ms; a shard round trip in the boot sequence would be spent
answering "no" on every restart but the one after a client patch — and patching a
client is an operator action, so importing became one. Admin → Shard → Import.
Whatever table is loaded keeps serving until then.

Three checks in the walk, each for a way a shard can hand back a table that looks
complete:

  * only `cut: 'end'` finishes it — a short page can equally be a spent budget,
    and a truncated table renders some items named and some not, which is exactly
    what NO table looks like;
  * the cursor must advance, or the walk stops rather than spinning;
  * every page echoes the source's size and mtime, so a client patched mid-import
    is refused outright rather than stitched from two files.

**The base is exempt from the vanished-source rule**, which is an upgrade detail
rather than a preference: an install that used the file pipeline carries its base
file's label in the stored fingerprint, and on the bridge that label is *supposed*
to disappear. Counting it as vanished would demand an approval for a change the
upgrade itself made. Overlays keep the rule in full.

**The protocol pin moves 7 → 8** — the third declaration site, and the one
nothing enforces. Phase 1 moved the sidecar and the overlay together because the
installer refuses a mismatched bundle; this one has to be moved by hand, in the
phase that first calls a protocol-8 route. The schema block above it is the
record of what forgetting costs: two phases of every REST call answered 409.

Verified against a live shard, sidecar and site: 12 pages, 67,496 rows imported
in 1.68 s, the operator's three-row overlay overriding stock strings on top of
it, and the next import correctly `unchanged`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
Reviewed-on: #36
feat(assets): creature artwork from the shard's own client (Phase 3)
All checks were successful
PR Checks / client-build (pull_request) Successful in 18s
PR Checks / server-tests (pull_request) Successful in 24s
PR Checks / frozen-manifest (pull_request) Successful in 49s
a194ec68e0
Until now the only way a creature got a picture on this site was for an
operator to open UOFiddler on a desktop, export sprites by hand, copy them to
the web host and write a spawnAtlas.art.json naming each one. Almost nobody
did, so shard_spawn_creatures.art was NULL on every install.

The shard has had those files the whole time. Admin -> Shard -> Import now
walks its asset manifest, fetches only the sprites whose hash changed, writes
them under uploads/atlas/, asks the shard for a body id per atlas creature
(§8: it CONSTRUCTS the creature and reads Body.BodyID, which is the only thing
that is right for a shard's own custom creatures) and points each creature at
its picture. On a stock client that is 787 portraits, about a megabyte.

**The one thing v8.md §12 got wrong, and it is not cosmetic.** It says
`shard_spawn_creatures.art` "starts being filled by the import". That table is
emptied and refilled by replaceAtlas on EVERY atlas refresh, and a refresh runs
on every boot -- so a filename stored there would be destroyed by an ordinary
re-parse of the ServUO tree, with the next Update finding the client files
unchanged, reporting "nothing to do", and never restoring it. Nothing would
report a fault; the pictures would just be gone.

So the assets and the body map live in their own tables outside that blast
radius, and applyAtlas re-derives `art` on the way past as
`{ ...derived, ...operatorMap }` -- which is also the one place "the operator's
own artwork wins" is enforced, on every rebuild rather than only at import.

Smaller decisions worth not rediscovering:

- The derivation joins on the catalogue KEY, not on the body id. The simpler
  join is correct today and stops being correct the moment phase 6 adds
  body/400/a2/f0, at which point one slug matches dozens of rows.
- Filenames are content-addressed. A stable name overwritten in place leaves
  every browser and CDN serving the previous client's sprite, with the database
  row perfectly correct.
- An unchanged key whose FILE is missing is fetched again. The row and the disk
  can disagree (a wiped uploads volume, a restore from a dump), and a broken
  image on a creature page is worse than one re-fetched sprite.
- A key the shard cannot render is not a failure. Two thirds of the playable
  ghost and gargoyle bodies have no art on a stock client, and an import that
  reported eight failures every time would teach an operator to ignore the panel.
- A key that VANISHED from the manifest needs review before anything changes:
  an unmounted client volume and a deliberate downgrade look identical here.

23 new tests; 674 server and 42 client tests pass. The SQL was also run against
a real MariaDB, which is what proved the CONCAT join and the singleton CHECK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
Reviewed-on: #37
feat(assets): item pictures on the marketplace and the character sheet (Phase 5)
All checks were successful
PR Checks / client-build (pull_request) Successful in 17s
PR Checks / frozen-manifest (pull_request) Successful in 41s
PR Checks / server-tests (pull_request) Successful in 8m23s
f335531538
Both places this site already knew an item's (ItemID, hue) and could only print
it as text now show the picture, hued the way the client would draw it. The
shard does the hueing: whether a hue repaints every pixel or only the grey ones
is a flag in `tiledata.mul`, which a browser has no way to read.

**Ingest warms; the route only serves** (org lead, 2026-09-11). A page never
waits on the shard and never causes a fetch -- it renders what is stored and
leaves out what is not, which is the state every install was in before this
phase. Fetching happens behind that, on a timer, from the keys the site's own
rows name. The alternative, fetching on first request, was rejected on one
number: the shard's asset plane serves ONE request at a time, so a URL that
fetched would let any visitor walk 49,152 ids times 3,000 hues through that slot
and park an operator's own import behind it.

The wanted set is DERIVED (`SELECT DISTINCT item_id, hue`) rather than queued, so
it is self-healing: a restart loses nothing, and a key stops being wanted the
moment the vendor row naming it is deleted. The in-memory hint set on top is only
for the character sheet, which is fetched live from the shard and stored nowhere
-- nothing on disk would ever name those keys.

Staleness without a manifest (§7): every row records the shard's `catalog` id, a
hash of the files that decide its bytes. A client patch changes it and a restart
does not, so "is this out of date?" is a per-row question -- and pictures nobody
looks at any more are simply never re-fetched, which is why this is lazy rather
than a sweep. `shard_asset_meta` is deliberately NOT written here: it is the body
catalogue's singleton, and a warm pass touching it would tell the body import
that a client it never looked at is unchanged.

A key the shard has no art for writes no row at all. An empty row would make the
key held and it would never be asked again -- including after the operator
patches in the graphic that was missing.

`assets.sources` now reports which families an overlay serves, so an overlay
older than phase 5 is one reported state with a sentence naming the fix, instead
of a refusal per pass forever with no picture ever appearing.

688 server tests pass (14 new); client builds; the frozen manifest regenerates
with one added route, all documented, no core URL moved.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
Reviewed-on: #38
feat(assets): a creature's picture is whichever action has one (Phase 6)
Some checks failed
PR Checks / frozen-manifest (pull_request) Successful in 51s
PR Checks / client-build (pull_request) Successful in 8m9s
PR Checks / server-tests (pull_request) Failing after 14m27s
c6c51b190d
The shard's catalogue can now answer for 73 bodies it used to report absent —
they have no art at action 0 and real art at a later one, and their key says
which (`body/820/a23` is a horse). This side stores that action and stops
assuming `a0` anywhere.

The atlas join is the part that mattered. It read

  a.asset_key = CONCAT('body/', b.body, '/a0')

which would have silently dropped exactly the creatures this phase adds. It now
reads the row's own action, with COALESCE for rows written before the column
existed — a NULL inside CONCAT makes the whole comparison NULL, which would have
taken every portrait off the site on upgrade with the database perfectly correct
and nothing in any log. It still matches at most one row per slug: a deeper key
(`body/820/a23/f4`) does not equal the catalogue key.

Verified against a real MariaDB with the live shard's own 1,095-row manifest: the
ALTER applies to an installed-shape table and is idempotent, the horse joins to
its a23 picture, a pre-phase-6 NULL-action row keeps its portrait, and a stored
frame key does not become a second candidate.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
Reviewed-on: #39
feat(atlas): the spawn atlas reads the shard, not the shard's filesystem (Phase 7)
All checks were successful
PR Checks / server-tests (pull_request) Successful in 23s
PR Checks / frozen-manifest (pull_request) Successful in 1m8s
PR Checks / client-build (pull_request) Successful in 7m59s
d4d5989926
`spawnAtlasSource.js` gains a second backend behind its existing interface
(docs/link/v8.md 10). Where a shard is linked and enabled the tree arrives over
the sidecar; where there is none, a local ServUO tree is read exactly as before.
An explicit --servuo path is an instruction and overrules both.

The parsers do not move. spawnAtlasParse.js is still pure, still fs-free and
still CI-covered without a ServUO tree anywhere near it; `buildFromFiles` is now
where the parse starts, and both readers feed it the same shape.

treeBridge.js walks the manifest and then the chunks. Three of its checks are
not decoration -- each is a way this ends in a tree that LOOKS imported, and XML
is forgiving enough that a mis-assembled spawn file parses cleanly and simply
has fewer spawns in it:

  - every chunk re-declares its address and carries the hash of its own
    uncompressed bytes, and chunks are placed by declared index rather than
    arrival order
  - the whole file is hashed after reassembly against its manifest row
  - the catalog must not move mid-walk, or the import is refused rather than
    stitched out of two trees

Boot does not call the shard. The same answer 17.7 gave the cliloc table, and
the same reasoning: a local tree hashes in ~120 ms and skips, while a round trip
in the boot sequence would answer "no" on every restart that did not follow a
map edit. Editing spawn files is an operator action, so importing is one --
Admin -> Spawn Atlas -> Import. What that costs is real and is said out loud in
the panel, the CLI and the log: an install on the bridge has NO automatic
refresh at all.

Two things the live walk found that the unit tests could not:

  - PARSER_VERSION 4 -> 5. The parse is order-sensitive in one place -- the
    decoration index keeps the FIRST item id it sees for a type -- and the two
    readers agreed on a stock tree by coincidence, since the filesystem reader
    walks each directory with localeCompare while the shard sorts whole relative
    paths. buildFromFiles now sorts by label, ordinally, once, whatever order
    the files arrived in. Identical input, a different answer for a handful of
    types: exactly what the version number exists to push through the hash gate.
    The parity test asserted deepEqual, which ignores key order; it now asserts
    serialised equality too.
  - The source fingerprint is taken over RAW BYTES at both ends. Hashing decoded
    text hashes a UTF-8 re-encoding -- identical for valid UTF-8, different for a
    file that is not, because an undecodable byte becomes U+FFFD and never comes
    back. One Latin-1 character in a creature name would have made the drift gate
    report a change on every import, forever, with the tree untouched.

A 200 from assets.sources also stopped meaning "the client files are on offer":
a shard may now serve its configuration tree while declining to serve its UO
client. Both client-file readers check `assetsEnabled` and say DISABLED, instead
of reading an empty file list as "your client has no cliloc.enu" and sending an
operator to their client install for a setting that lives on their shard.

Measured end to end against a live shard and the real sidecar: 141 files,
11.9 MB, 158 chunks, 3 pages, 1.33 MB on the wire, 512 ms; every file
byte-identical to disk; and the atlas built over the bridge identical to the one
built off it -- 6,455 points, 800 creatures, 387 regions, 558 landmarks,
25 champions, 309 decoration types.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
Reviewed-on: #40
feat(assets): the panel that operates the client-file imports (Phase 8)
Some checks failed
PR Checks / frozen-manifest (pull_request) Successful in 1m3s
PR Checks / server-tests (pull_request) Successful in 8m4s
PR Checks / client-build (pull_request) Failing after 14m21s
675e879b48
Admin -> Client Files: one page over the three things that come out of the
operator's UO client -- creature portraits, item and land pictures, and the
cliloc table. One page rather than three because they are one job: same client
install, same bridge, and all of them change at the same moment, when the
operator patches that client. Boot never asks the shard for any of it, so these
buttons are the only thing that imports.

The cliloc pair had had no UI at all since phase 2. On a bridged install, where
boot deliberately stopped calling the shard, that meant `curl` was the only way
to load 67,496 names.

Update and Re-import everything are section 6's two stages as two buttons rather
than one button and a checkbox, because they cost wildly different things. A
vanished key is reviewed in the page and not in a table -- an asset import only
happens because someone pressed a button here, so the review is already in front
of the person who caused it -- and it shows each key's PICTURE, since
`body/820/a23` names nothing a human recognises. `shard_asset_meta` gained a
`last` block (what the import did, who ran it) so the panel can answer "did last
week's import do anything" without scrolling core's whole activity log.

The live walk against a real shard imported 1,095 portraits in 3.5 s, warmed 313
item pictures in 0.6 s and reloaded 67,496 cliloc rows in 1.7 s -- and found two
DELETIONS that predate this phase and that no test could see, because only a
screen showing the numbers together makes them visible:

  * The body import diffed its manifest against every family's rows. Phase 5 put
    item and land art in the same table, and a body manifest never mentions
    them, so all 313 item pictures were staged for deletion with a sentence
    saying the shard had stopped offering them.
  * An approved vanish unlinked the sprite and kept the row. The catalogue went
    on counting a picture that was gone, the atlas could point a creature page at
    a missing file, and the next forced import offered the same key for review
    again -- reporting "nothing was changed" about a file it had deleted.

Both fixed here, with the removals now inside `saveAssets`'s own transaction.
The same whole-table read made the panel announce a 1,408-row creature catalogue
on an install holding 1,095 portraits and 313 item pictures.

Protocol stays 8 and EXTRACTOR_VERSION stays 3: nothing on the wire changed.

Refs: docs/link/v8.md sections 12.2, 14, 16 (phase 8)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
ci: re-run the client checks
All checks were successful
PR Checks / server-tests (pull_request) Successful in 27s
PR Checks / frozen-manifest (pull_request) Successful in 1m1s
PR Checks / client-build (pull_request) Successful in 7m59s
5705aa9c23
Run 68's client-build spent 14m21s in "Set up job" and then failed every step
at 0s with no log uploaded — the runner died during container setup. The same
commit's server-tests and frozen-manifest jobs passed, and frozen-manifest built
this very chunk on the same runner. Nothing in the tree changed; this is the
push the workflow needs to run again (pr-checks has no workflow_dispatch).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
Reviewed-on: #41
fix(assets): a busy shard is not a broken one, and a column that reached no existing install (Phase 9a)
All checks were successful
PR Checks / client-build (pull_request) Successful in 21s
PR Checks / frozen-manifest (pull_request) Successful in 1m20s
PR Checks / server-tests (pull_request) Successful in 8m4s
3c087a43cd
Two defects the Phase 9 acceptance walk found on a real rig, one of them ours and
one of them released (docs/link/v8.md §17.14).

## "The shard is not answering for client files" about a shard that was fine

The status call exhausts its 425 backoff whenever something else holds the
shard's single asset slot -- an import the operator started, or the item-art warm
pass refilling itself after a client patch. Phase 8's panel rendered that with
the same banner as a shard that is down or has the plane switched off, and left
it standing, because the page only re-reads after an action. On the rig it was up
for a quarter of an hour while the warm pass refilled 313 pictures and every
direct call to the same route answered normally.

BUSY now says what it is, and one automatic re-read four seconds later clears the
ordinary case. One per mount, guarded by a ref: a page that retried forever would
be holding the slot it is waiting for. DOWN, DISABLED and NO_IMAGING read exactly
as they did.

## Every spawn-atlas import on an upgraded install has failed since v1.2.0

`shard_spawn_points.unique_id` (Events Phase 12b) was added to the CREATE TABLE
and nowhere else. `CREATE TABLE IF NOT EXISTS` does not add a column to a table
that already exists -- which is what the twenty-odd `ADD COLUMN IF NOT EXISTS`
lines in this same file are for -- so it reached fresh installs and no existing
one, and `replaceAtlas` inserts the column unconditionally:

    Unknown column 'unique_id' in 'INSERT INTO'

No bestiary refresh, no spawn map, no champion altars, on every install whose
tables predate 12b. A fresh install cannot reproduce it and neither can a test
whose schema is this file applied to an empty database; it took a rig with old
tables. Org lead, weighing that it is already released: it ships here on edge
rather than as a hotfix to main.

Verified by dropping the column, rebooting, watching the replay put it back, and
importing 6,455 spawners over the bridge.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
Reviewed-on: #42
chore(cutover): sync main into edge before the Asset Bridge cutover
All checks were successful
PR Checks / client-build (pull_request) Successful in 21s
PR Checks / server-tests (pull_request) Successful in 27s
PR Checks / frozen-manifest (pull_request) Successful in -31s
f9bbc7a90d
`edge` was BEHIND `main` by two commits — Module-uo#35 (the atlas keeps its
`UniqueId`, and a landmark option value names one landmark) and the Event
System's core re-pin — so merging `edge` into `main` as the Asset Bridge
cutover would have REVERTED a released fix. Phase 9a's walk measured it:
0 of 6,455 spawners carried a `UniqueId` on an `edge` rig even after the
column existed.

## The one conflict, and why the number had to move

Both sides bumped `PARSER_VERSION` 4 -> 5, for different reasons, and main's
5 is RELEASED in v1.2.2: "a point keeps its `UniqueId`". `edge`'s 5 was
phase 7's canonical label order.

Keeping 5 would have made phase 7's change unreachable. `sameSources` gates
on the tree hash and `currentParser` on the stored number; an install that
imported under v1.2.2 already stores 5, so a phase-7 build declaring 5 would
be called current and would never re-read. That is precisely the trap this
constant exists to defeat, so the merged file carries BOTH notes: 5 is main's
released meaning, 6 is phase 7's, with the renumbering explained in place.

Everything else merged clean and keeps #35's files verbatim.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
Merge pull request 'chore(cutover): sync main into edge before the Asset Bridge cutover (Phase 9b, 0 of 5)' (#43) from chore/cutover-sync-main into edge
All checks were successful
PR Checks / client-build (pull_request) Successful in 19s
PR Checks / server-tests (pull_request) Successful in 30s
PR Checks / frozen-manifest (pull_request) Successful in 1m30s
76c224fff1
Reviewed-on: #43
whitlocktech approved these changes 2026-09-14 23:11:03 +00:00
whitlocktech merged commit 6246383962 into main 2026-09-14 23:11:22 +00:00
Sign in to join this conversation.
No Reviewers
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: RunicGateway/Module-uo#44
No description provided.