Commit Graph

35 Commits

Author SHA1 Message Date
m3ultra
293a82c5e8 R42 THE LOOK: five lane-runs — the pub is dressed, the decks read, and the town is cast
John looked at R41's proof shots and named three faults. All three are fixed, at zero draw
cost, and every lane corrected somebody's numbers on the way — including its own.

E-LOOK (§42.1+§42.2a) — THE PREMISE WAS WRONG. "The pub props have no texture" (my brief,
quoting E's own R41 note) is false: all 37 carry COLOR_0 and three.js renders it. The colour
was BLEACHED — jukebox at saturation 0.02, 15 assets under 0.05; what the source library
called colour was baked AO. So the fix was a palette imposed on colour already present, in
place: 46 assets, draw delta +0 and tri delta +0 on every one, proved the honest way — the
concatenated POSITION/NORMAL/TEXCOORD/index bytes are sha1-identical to R41, so footprints
and origins provably did not move. (A first-pass bbox check ignored node rotations and
"found" 3 phantom violations; thrown out for the byte comparison.) The banquette was 1990s
office teal and went burgundy; the beer umbrella was white and went brewery green.
THE DECK, diagnosed before being touched: 7 greys, chassis 0.05 luminance on a 0.033 bench =
1.21:1, and the one high-contrast feature was INSIDE-OUT (white ring around a dark disc; a
turntable is a dark record on a light platter). Now Technics silver at 6.74:1, black vinyl,
red start/stop. Node contract re-read from the shipped GLB: 4/4 + 1/1 PASS. Filed to R43,
not fixed: deck_1200_rigged has NO PLINTH (bottom 43mm of 119mm lost to R41 join damage).

E-CAST (§42.3) — 19 of 24 ship. Rejected: comical_luchador_01 (Mixamo Ch43, purple mask
reading EL CHUPACABRA, identified from the file's own material name), a 3-heads-tall cartoon
child, a robed wuxia elder, a WW2 infantryman — and dj_phrtt_01, AN UNCLOTHED BODY whose only
texture is a bare-skin atlas, which was in the DEFAULT crowd and live on the depot. It is the
R41 deny-list's failure class through a different door: an innocent name from another
pipeline. Only rendering all 24 and LOOKING found it.
Ruling 2 (prefer CC0) was REFUSED ON MEASUREMENT: Quaternius is 25 nodes / 23 joints and ZERO
of 64 canonical tracks bind — it would have loaded silently and never animated. Replacements
came from Mixamo's Ch## pool, filtered to the contract-clean 65-bone family (candidates ran
65..99 bones; named characters carry facial/hair bones).
THE LICENCE GAP WAS UNREAD, NOT MISSING: every source FBX carries ApplicationVendor "Mixamo,
Inc." internally. Adobe royalty-free, no attribution owed. B's amber rows are now real.

B (§42.5) — MY ASSIGNED FIX WAS USELESS AND B PROVED IT. document.fonts.ready shipped, then
measured: the atlas canvases are already byte-identical across boots, the game ships NO
webfonts (document.fonts.size === 0), and ready fires 50ms BEFORE the first fillText. The
real cause of unreproducible bookmarks is the wall-clock CROWD — 983 changed px of 921,600,
ALL in the pedestrian band; 0 px with the crowd out. New DBG.freeze() takes stable bookmarks
2/5 -> 5/5. Also found: ?pop=0 boots 140 citizens (0 is falsy against the default).
THE BENCH: D's filing correct on both counts. Front-vs-road 90.0deg -> 0.0deg, min=med=max
over 60/60 instances; the streetlight arm now reaches 1.05m over the road where it reached
0.00. Both used a base yaw that pointed +Z down the street while their own comments claimed
otherwise — the side term was already right, which is why it survived 41 rounds of reading.
Also: on any box with CDN access 9 of 60 benches are the depot GLB, 90deg out from the house
law — normalised at the geometry, once, at load. benchStops(plan) exported as one truth.

C (§42.2b) — 122 vs 123 WAS THE CAMERA ASPECT, and nobody was wrong. Draw counts are
post-frustum-cull and the sweep camera inherits the window aspect: 1.25->116, 1.60->122 (C's
harness), 1.778->123 (F's), 2.37->124. Pinned MEASURE_ASPECT=16/9 with a 1.25 control.
E's "keep the bench dark" advice was measured and declined with reason (deck-vs-bench was
already 4.73:1, over threshold). "Reads as a black box" is a claim about pixels, so C measured
pixels: near-black in the booth crop 57.5% -> 26.7%, distinct colours 228 -> 389. Bench
rebuilt 3->7 boxes with an open record bay; headphones on the mixer; faceDoor aims booths at
the player's entry — alignment within 45deg went 49% -> 92%. Two bugs found by looking: a
fitting could be planted INSIDE A WALL (0.77m of a 0.86m half-depth in plaster), and every
booth part sat 25mm sunk into its own worktop.

D (§42.4) — five swaps IN PLACE at the same index (pickRig indexes these arrays and pedIndex
is in the identity signature and the impostor atlas; removal would renumber the town).
Identity signature pre- vs post-swap: 0 differing lines of 150 — who each citizen is did not
move, only which body they wear. dj_phrtt_01 verified gone AT THE WIRE, not from source: a
fresh default boot fetches 32 ped GLBs, none retired.
THE CONTROL ARM FOUND A 41-ROUND-OLD BUG: the ped being RETIRED, comical_boy_01, binds only
3,132 of 3,460 tracks — six thumb bones lack the mixamorig prefix. It had been silently
dropping six tracks per clip since it shipped. Testing the thing being thrown away found it.
Imported B's benchStops mid-round: sitters now 0.0deg to the road, signatures byte-identical
across the rewire. Corrected F's filing: the browse smoke was flaky-by-construction (a 16-25s
race against a live sim), not deterministically red.

F (§42.6) — qa.sh --strict 24 passed / 0 failed / 0 warn / 0 skipped. New Gate 11: period law
(0/5 banned; CONTROL: R41's roster trips all five) and roster licence (24/24, 0 unverified;
CONTROL: an amber row turns 20 red). r41_shots.py promoted warn -> strict.
R41'S BYTE-HASH BAN IS REVERSED AND ITS STATED REASON WITHDRAWN AS WRONG. A gate may pin a
hash under five in-run assertions (classic+flags boot, freeze().stillLive all-false, a second
freeze removing 0 draws, settle until DBG.info() stops changing, hash with the dbg panel
hidden): 5/5 bookmarks now pin to one sha256 over 3 fresh boots. Two findings got it there —
the chunk streamer (a fixed settle is a bet on load) and the fps readout, which with
everything else frozen was the last thing moving at 53-62 px.
?pop=0 fixed (Number.isFinite) — a no-op for 35 rounds. Three red flags_check gates fixed.

Street: noon 282 · NIGHT 291/300 — the nine-draw margin INTACT, every R41 row reproducing to
the digit. Interior at pinned 16:9: 123 @ dept/auto, margin 227. selfcheck 157,647/157,647,
0x5f76e76 unmoved. No tag — John's playtest rules the epoch.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-07 23:49:49 +10:00
m3ultra
422f17263e Lane C R41 §41.4: the shops get their stock — and the measuring instrument was wrong
110 fittings placed across 12 shop types via new kit41.js (96-row catalogue, 67 ids in
pools) and ONE generic kitProp builder; recipes name pools, never asset ids. glb.js now
honours seeded per-fitting variants, multi-GLB booths, and a MEASURED autoYaw instead of a
110-row facing table.

THE INSTRUMENT WAS WRONG. drawSweep cleared every interior EXCEPT current, so every
historical interior number carried a phantom second room — 'toy', untouched this round,
'gained' 4 draws. R39/R40's celebrated 'worst 188, margin 162' was inflated. Corrected, and
with 110 new fittings placed: worst room in the game is 122 @ dept/auto, MARGIN 228.
Per type: record 61->78, pub 45->59, band_room 39->50, stall 71->82, book 51->61,
pawn 60->70, video 54->63, rsl 52->60, dept 116->122, opshop 102->105, milkbar 68->69,
toy 86->86 (+0, the control).

The pub finally has a bar (plywood_bar), banquettes, pokies, a stage flank. Milk bars get a
bain-marie and pie warmer. The pawn shop gets an 808 and an SP-1200 on the bench. Record
shops get a rigged DJ booth. Market stalls get 2-4 diggable bins each, closing R39's measured
44%-no-crate hole to 0/40.

WARDROBE, route R5: 257.5 KB total (246 KB atlas + 17 KB index), ONE fetch, one material,
one draw per fitting — 52 layers picked BY NAME, alpha-cropped, shelf-packed, 3.7% of source
bytes, lazy behind ?stock=real. LICENCE CALL MADE EXPLICITLY: AUDIT.md says no real brands
ship and the source library is prompted FROM real labels — the adidas jacket renders a
legible trefoil. Screened on a contact sheet, then relabelled in parody voice (Paddy's
Bootleg, Kaymart, Coogee Knits, Mombo, Stoosh).

FIVE DEFECTS FOUND AND FIXED, one long-standing: buyMesh has been INVISIBLE on every GLB
fitting since R9 (hiding every buyable item under ?stock=real) · DJ booths were
nondeterministic (parts attached in network order) · booth gear floated at 0.92 m · batch.js
dropped userData sub-tags on merge, so any post-batch flag read was already a no-op · the
path guarantee could empty a room (pub/wide/4242 returned a bar, a stage and nothing else) —
new restore pass, 129 pulled -> 101 restored over 480 rooms, path re-proved on every restore.

Gates: 288-room sweep x 4 arms all pass; soak 60 rooms avg 3.83 ms worst 10.3 ms, leaks 0;
960-room corpus 0 path-fails 0 carves; determinism byte-equal across fresh contexts
(sha256 c26b4848); ?noassets=1 60 rooms with 0 GLB / 0 manifest / 0 wardrobe / 0 stock
fetches. DJ control nodes recorded on fitting.controlNodes for a future driver (glTF drops
the limit constraints — a driver must clamp travel itself).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-07 18:11:07 +10:00
m3ultra
eeba920d68 Lane C R39 §39.3: the op shop gets a bin — the verb table, and the margin was never six
THE TABLE (LANE_C_PUB §10, the deliverable): what a player can actually do in each of
the 12 shop types, counted in 48 rooms per type. `record` digs (4.6 bins/room) and is the
only room where anything can be sold. `stall` digs — in 27 of 48 rooms; the other 21 (44%)
have NO crate, a measured correction to the round's binding fact #5. The other ten types
offer looking. `?stock=real` is opt-in, so the default boot has zero `buyMesh` anywhere;
garments/snacks/magazines/treasures are unbuyable in any mode; `interactable:true` on
cases/fridges/returns/arcade is read by nothing.

THE MECHANISM: `rummageBin` — a castored galvanised dump tub, rim 0.76 m, heaped with
stickered bric-a-brac and a hand-lettered "EVERYTHING $2" card. 72 rooms: placed 72/72,
reachable 72/72 (0.77–1.15 m), 0 carves, pathOK 72/72, deterministic 72/72, and the rest
of the room byte-identical to pre-R39 72/72 (the spec is appended last). Armed end-to-end:
aim → dig → 16 offers → BUY → bin depletes. Default: 0 bins, dig never opens.

THE CONTENTS ARE NOT SHIPPED. `o.dig` defaults false, so the tub is furniture and the
economy is untouched; `opts.fittingOpts.rummageBin.dig` (or `?opshopdig=1`) arms it. Held
for John with the numbers: 44 record + 36 stall vs 147 op shops across 23 towns.

THE BUDGET: `C-progress.md:841`'s GLB-on 344 is R9-era and stale — today's tree measured
319 before I touched it, so the margin was 31, not six. The bin costs +5 GLB-off / +10
GLB-on at opshop/hall; every other type byte-identical. Margin now 21. Also: `slot.pool`
stops the scatter fill minting a material per item, so 12 goods cost less than 9 did.

FILED, NOT FIXED: Lane E's bookshelf GLB ships transmission:1, which triggers three.js's
transmission pre-pass and issues every draw in the room twice — opshop/hall 191→104,
book/hall 101→51. Roughly half the interior budget. E's asset, so E/F rule.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 20:59:42 +10:00
m3ultra
369a2be23e Lane C R30 (v7.0-alpha ledger #2, implementation): sell.js — the sell counter lands. E at the bench → the mirrored price sticker; offer strictly below pricePaid by construction (8/8 scripted round trips lose money); type=type fail-closed (untyped item sells nowhere); wallet.sell = the credit mirror; game layer absent → no card, zero errors. Verified on :8144 no-store, stubbed game shape (F's core not landed); soak green avg 3.4ms · leak 0/0 · draws card-open ≡ closed 2026-07-18 14:14:14 +10:00
m3ultra
488067004c Lane C R28: audioEmitter — the till rings from the counter; the panner was inert
R28 ledger #3. My own idea, parked since R7. Brief stated acceptance only, so I scoped it.

THE TRAP MY OWN v3.0 NOTE SET: it said "userData.audioEmitter = musicKey so Lane B can
position/spatialise the bed". Both halves wrong. (1) Shape: the engine holds no reference to
the room group and would traverse a scene it cannot reach — emitters belong on room.audio as
data, riding the EXISTING playInterior() call (no lifecycle, no traverse, no dispose hook).
(2) Method: "spatialise" invites a PannerNode. NOTHING in this tree sets ctx.listener (zero
hits), so a panner resolves against a listener frozen at (0,0,0) — which, because rooms are
built room-local at the origin, is the room's own CENTRE, forever. Measured, OfflineAudioContext,
real pub stage bed:
    panner, listener never set (the engine as it SHIPS): RMS 0.106905 door / 0.106905 stage
                                                          -> ratio 1.0000, bit-identical
    panner + listener tracking the player:                RMS 0.055665 / 0.500000 -> 8.98x
It plays, throws nothing, warns nothing, and never changes by one sample as you cross the room
— while failing at the only thing it exists to do. "Routed through a panner?" yes. "Audible in
a venue?" yes. "In a shop?" yes. All green, all worthless.
-> ASSERT THE DELTA (gain at door != gain at stage), NEVER THE PRESENCE. Vacuous-gate law, 4th
application — and this one had no author to blame but me.
The house idiom was already right: tram/spill/gig-spill are all hand-rolled distance-gains vs
PROCITY.camera.position. No listener, no panner.

SHIPPED (C's files only):
- room.audio.emitters {stage?, counter?} — room-local, 3dp, 18/18 same-seed deep-equal. `r`
  (reach) is the ROOM DIAGONAL, not a constant: measured diagonals 9.81-15.63m, and scaling to
  the room holds door->stage proximity in a 0.42-0.59 band from a pokey band_room to a hall pub.
  `floor` is the room-filling residue, never 0 — a PA fills a pub; what changes as you walk in
  is the BALANCE, not the presence. Swing 2.65-4.52 dB (John R20: subtle changes are great).
- THE TILL RINGS FROM THE COUNTER — audible positioning in a normal shop with NO ENGINE CHANGE,
  via B's already-public playSfx(key,{gain}): C does its own distance math, hands B a scalar.
  Measured through the real BUY button, real record shop: 0.9489 @0.60m vs 0.6436 @4.19m =
  3.37 dB; negative control (no emitters) fires at exactly 1.0. sfx-till.ogg has been on disk
  since 15 Jul and had NEVER ONCE PLAYED. Routing, not content — as the brief said.
- ?mute=1 now actually silences the crate riffle (below).

WHY THE VENUE HALF IS AN ASK: a one-shot resolves its gain once, at fire time, from a position C
already knows. A bed must be re-gained every frame inside the engine's update() loop — B's file.
Structural, not political. B has a ~12-line paste-ready diff in the house idiom; audio.js
untouched. The letter of "consulted not modified" could have been met by reaching through B's
exposed state.layers diag to rewire its graph from outside — that backdoor is strictly worse
than the edit it avoids. Refused.

TWO BUGS FOUND:
- F DROPS EMITTERS ON THE ONE NIGHT THEY MATTER. interior_mode.js:289 rebuilds the spec on a gig
  night ({musicKey: ra.gigKey, toneKey: WALLA_KEY}) and every unnamed field dies. Measured on a
  real gig-night pub: quietNight_emittersSurvive TRUE, gigNight_emittersSurvive FALSE. Silent,
  fail-soft, no error. -> F: don't just spread `ra`; pass emitters DELIBERATELY, gig-night only —
  on a quiet night the radio + room-tone genuinely ARE room-filling.
- ?mute=1 NEVER SILENCED THE RIFFLE (R5->R28). dig.js opened a SECOND, private AudioContext
  straight to destination, breaking both stated house laws (ONE AudioContext; mute forces
  silence). It survived three epochs because NO GATE ASSERTS MUTE => SILENCE: the engine returns
  its silent surface and looks honest from its own side, while the sound came from somewhere it
  never knew existed. A gate that asks the engine whether it is muted can only ever answer yes.
  Fixed via B's PUBLIC .muted getter (consulted, not modified), URL fallback when standalone.
  Measured with a positive control so the probe provably CAN see sound: live 1 ctx / 1 source;
  muted 0 / 0. Blips stay procedural on purpose — zero-fetch, deterministic, still work under
  ?noassets=1 where a sampled riffle cannot. Only mute was broken; only mute was fixed.
  -> F: assert SILENCE, not muted-state.

HARNESS HONESTY: the first till rig reported the same clean 3.37 dB — from a CONTAMINATED rig
(createDig appends a panel per call, close() doesn't remove it, so querySelector clicked trial
1's stale button every time). Tell: panelUp false on all three, and the NEGATIVE CONTROL
returning 0.6436 where it must return 1.0. The gains matched the formula exactly; I refused them
anyway and re-ran isolated (panelCount 1, panelUp true, control 1.0). Never report a number from
a rig you have watched lie — even when you like the number.

QA GREEN 6/0/0/0. Docs: LANE_C_AUDIO.md -> v3.1 (contract + both asks + every measurement).
2026-07-17 17:39:54 +10:00
m3ultra
6960bdd279 Lane C R27 (v5.0): the tier-2 seam ruling + LANE_C_PUB v5-FROZEN (ledgers #2, #6)
SEAM RULING (§8): bless the direct wire — NO lifecycle hook. F already holds currentAdapter
(interior_mode.js:67) and passes it to dig.open() PER DIG (:111), so the live read lands, F
updates the pack, the next riffle picks it up; not-landed/slow/dead => the adapter is simply the
tier-1 pack => the dig never blocks (charter risk #1). No C lifecycle change.

BUT THE QUESTION UNDERNEATH IT IS C'S, AND THE OBVIOUS WIRING IS WRONG. pickRealOffers indexes
pack.items[(rr()*len)|0], so FILTERING THE ARRAY RESHUFFLES THE CRATE. Measured, 5 crates: one
record selling changed 21 OTHER records (6/2/2/6/5 collateral). Pick-then-omit (draw from the
stable full list, drop gone after) = 0 collateral: a crate with 3 sold shows 13, not a different
16. Selling one record must remove one record, not re-stock the shop.

THE TRAP: both forms hide the sold record — so F's planned assertion ("a real gone item never
renders") GOES GREEN ON THE WRONG WIRING. It cannot see the 21. -> F: assert on COLLATERAL (the
survivors are unchanged across a gone delta), not absence. Vacuous-gate law, third application,
and the cheapest to miss because it passes.

LANDED (dig.js): pack.gone (Set|array) omitted at PICK time. F's invariant published: update item
FIELDS in place (live price) freely; NEVER add/remove/reorder items[] — that identity+order IS the
seeded pick's stability (R25's positional-id lesson, one layer up).

DETERMINISM BOUNDARY sits exactly here: server down => byte-identical tier 1 (gate-able); server
up => the crate legitimately changes (tier 2 is non-deterministic BY DESIGN), so no gate may
assert dig determinism with the server up, and pack.gone never enters a seeded stream or golden.

SCOPED: the bin fan is decorative (a different seeded pick from the dig's offers) — a sold cover
may linger there until rebuild. That's when I'd publish a post-build hook; the epoch's claim
doesn't need it, because the crate you RIFFLE is true.

VERIFIED: pure-function measurement for the collateral claim, then end-to-end through the real
dig's public surface (pull -> Esc -> scroll -> pull): sold record gone, survivors 4/4, collateral
0, items[] untouched at 120, live in-place price shows on the next pull. (My first harness
returned the same title 4x — I never un-pulled — so its number was an artifact; re-ran it properly
rather than report a metric I'd just watched lie.)

DOCS FREEZE (§6): LANE_C_PUB is v5.0-FROZEN, whole document, with the case law at the top: three
amendments here exist because a rule of mine met a case written AFTER it, and both times the lane
building to my rule took the red. Header now states it: when a line meets a case it didn't
foresee, the line is the stale thing — amend it, don't bend the case.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 13:53:02 +10:00
m3ultra
53e83308a0 Lane C R26 w4: the mint-id fence — my rule was the stale thing (wave 4, C's one line)
F's fourth hold traced to MY §7.2a. I wrote "sku_-prefixed for real-stock packs" in R25 —
before mint existed — so it names one space where there are two. E implemented it faithfully
and its validator did exactly what I told it to: demand sku_ on 14 mint crates. The check
wasn't wrong; my rule was. (Second time a contract line of mine met a case written after it
and another lane took the red.)

AMENDED §7.2a — the id form is SOURCING-SCOPED and the prefix IS the fence:
  sourcing "real" -> sku_<POS id>            the shop's POS inventory (a physical copy)
  sourcing "mint" -> mint_<dealgod listing>  a dealgod listing (a plausible stand-in)

WHY G WAS RIGHT TO REFUSE THE EASY GREEN: filing dealgod listing ids under sku_ would have
gone green in one line and recreated the R24 collision INSIDE THE ECONOMY LAYER — where
tier-2's sold-means-gone looks up POS skus. R24's collision entered the wrong shop; the same
collision in the money layer sells the wrong record — a real copy, out of a real shop, to
satisfy a minted stand-in. A prefix that lies costs money, not a gate. Blessed as design:
disjoint namespaces; no gate, lookup or economy path may treat them as one (same standing
note as plan-ids vs godverse-ids).

VERIFIED against every crate before handing E the line: 15 crates (1 real + 14 mint), 0 fails
under the amended rule (prefix matches sourcing, ids unique within pack), and 0 ids appear in
both spaces across 344 distinct ids. The fence already holds in the data; only my wording didn't.

-> E: assert the form against `sourcing` (already read at line 178) — sku_ for real, mint_ for
mint; a crate wearing the other's prefix FAILS. Stronger than the check it replaces, not looser.
-> F: 6/1/0 should flip to 7/0/0. Doc-only: no code, no atlas bytes, no goldens.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 12:32:45 +10:00
m3ultra
4f0adf12d8 Lane C R26 (v5.0-beta): three contract lines + the dig price sticker (ledgers #1, #4, #6)
Wave 1 — contract lines published first; G and E build to them.

#1 THE BRIEF SAID `tier`. IT CAN'T BE (§7.2b). G's atlases already emit tier: 1 = the CHARTER'S
LADDER RUNG (0 parody / 1 static-real / 2 live), and `kind` is already the stock type. A mint atlas
is ALSO rung 1 — same mechanism (a statically baked per-shop atlas); only the data's origin differs.
Real-vs-mint is an orthogonal axis, not a rung — overloading `tier` puts two meanings in one name,
the same species as the plan-id/godverse-id collision just standing-noted, and as R25's positional
ids. RULING: top-level `sourcing: "real"|"mint"`, REQUIRED; `tier` stays the numeric rung. A mint
index MUST NOT carry POS-snapshot fields (the separation is the honesty). Gates read `sourcing`;
absent => FAIL.

#4 `condition` ISN'T IN THE INDEX (§7.2c) — the ledger assumed it was. The index carries
artist/title/price/price_band; G's SNAPSHOT has condition + sleeve_cond and drops them at emit, so
pickRealOffers mapped cond:'' and the card rendered dangling separators around a hole. That WAS the
"database row". Ruled optional index fields, emitted when the source has them; no invented grades on
mint items (a plausible-but-fictional grade is what `sourcing` exists to prevent).

#6 THE ATLAS MANIFEST (§7.2d): stock_godverse/index.json is the authority for which shops carry
atlases — no entry => no fetch => zero 404s, so the zero-console-errors law holds with no
attribution exception. Shape + both-ways consistency specified. No manifest + no atlases = a
legitimate silent zero-shop state, not an error.

THE PRICE STICKER (code): the pull panel is now the shop's price sticker — manila card, rotation +
shadow, typewriter caps, italic title, a stamped condition grade, and the round yellow $ sticker the
sleeve art already uses (the panel echoes dig.js's own canvas language instead of fighting it).
Fields compose only when present: a missing field never leaves a stray separator.

VERIFIED (fresh context, 5 card shapes, both star branches): real grail VG+/VG + ★GRAIL; real
standard VG+ + STANDARD; real ungraded COLLECTOR (grade omitted cleanly); parody NM + 1973 PUNK;
parody rare (seed 4) VG + 1973 POP + ★RARE. No dangling separators anywhere.

The verification caught a defect in MY OWN card: a real grail read "GRAIL ★ GRAIL" — `s` carried the
price_band AND `rare` derived from that same band (parody is immune: its `s` is a genre). Fixed at
the mapping. And I didn't leave the parody-rare branch on assumption — scanned seeds until a rare
pull appeared and asserted it says RARE, not GRAIL. An unexercised branch is an unproven one.

Budgets: the card is DOM — zero draws, zero tris, no 3D change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 09:38:42 +10:00
m3ultra
0d8d59a22a Lane C R25: sku-derived slot ids — F's id-collision finding made structural (ledger #3)
One contract line, published first so G re-emits against it. §7.2a added.

THE COLLISION, MEASURED: both packs number items positionally from zero, so all 120 godverse
ids are also generic-pack ids — 120/120 overlap. An id-equality gate cannot discriminate which
pack a rendered item came from; that is exactly why the brief's own #7a spec was vacuous. (The
vacuous-gate law's first application broke the law — and my own R23 metric had the same disease.)

THE RULE (§7.2a): items[].id MUST derive from the source's stable key, never from position, and
MUST be unique across ALL packs. Real-stock packs: id = "sku_" + <POS sku> (G's snapshot already
carries `sku` per item, e.g. 20260314070442 — it's just dropped at emit, so this is index-only).
Generic packs keep rec_NNNN as their namespace; real-stock must not emit into it.

TWO REASONS, and the second outlives the gate:
1. positional ids collide by construction (the 120/120 above) -> id-equality gates can't
   discriminate. Sku ids fix that permanently, now, at two atlases instead of fifty.
2. POSITION IS NOT IDENTITY: a positional id renumbers on every re-bake — sell one record out of
   crate 550 and rec_0050 silently becomes a different record. Anything holding an id across
   snapshots (a wallet purchase, a saved dig, a gate assertion, and above all tier-2's
   sold-means-gone) would point at the wrong item. The POS sku is stable because it is the shop's
   own identity for that copy (release_id identifies the pressing, not the copy). So this is also
   the identity tier-2 will need, bought now for free.

SCOPE: index-only — no atlas bytes, no town cache, no goldens, no C code (id isn't read by the
loader, §7.3; the dig/buy consumers and gates read it).
-> G: re-emit with sku_ ids. -> E: assert the form (present, unique within pack, sku_-prefixed).

Committed by explicit pathspec — F's six staged files sit untouched, as its session left them.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 02:18:37 +10:00
m3ultra
ba901d4a75 Lane C R24: the contract fix — the runtime is the authority (ledger #1, the root cause)
F held the tag because my published atlas contract and my own loader were mutually
incompatible. That was mine. Closed, and published FIRST so G and E re-emit against it.

WHAT WAS WRONG: my R22 §7 invented `stock_shop_<godverseShopId>_index.json`; the loader
(stockpack.js:25) fetches `${base}stock_${type}_index.json`. An index named per the contract
can never be fetched by the code meant to load it. G built runtime-compatible (correct), E
built contract-compatible (also correct — the contract said so), and E's validator globbed
stock_shop_*_index.json -> zero files -> vacuous pass. Nobody was wrong; the contract was.

§7 REWRITTEN AGAINST THE CODE (LANE_C_PUB):
- Authority rule: stockpack.js IS the contract; doc vs loader disagreement = doc bug.
- Layout ratified as G shipped it: assets/stock_godverse/<godverseShopId>/stock_<type>_index.json
  (`type` is load-bearing — SLOT_FOR picks the slot). stock_shop_* RETIRED.
  -> E: the glob becomes stock_godverse/**/stock_*_index.json (the vacuous gate's actual cause).
- Runtime-read vs ignored stated exactly: reads atlases[] + items[{atlas,uv}] (uv top-left, the
  loader flips V); ignores version/atlas_px/cell*/count/kind/tier/shop/crate/provenance.
- Provenance drift dies: runtime reads none of it, so the authority is E's validator + the 23
  shipped town caches -> TOP-LEVEL `license` (US), attribution, generator, snapshot. British
  `licence` retired; G lifts+renames those four, keeps its rich nested provenance{} as colour.
  atlas_px stays REQUIRED (checked against real dims). cell* informational — C reads uv, never
  cell; do NOT shrink the cell for VRAM (the dig renders covers close-up).

CODE (F's filed item): stockpack cache identity is now `${base}|${type}`;
getStockPack(type, base = DEFAULT_STOCK_BASE); new opts.stockBase on buildInterior = the seam F
wires. Omitted => town-wide packs, byte-identical to today. Pre-v5 callers untouched.

VERIFIED FRESH — and I fixed my own metric first: R23's "8/8 atlas-backed" only checked that a
map exists, but a parody canvas has a map too (same species as the vacuous gate). Re-asserted
falsifiably: every sleeve's texture source IS stock_godverse/3962749/stock_record_atlas_00/01.webp,
pack items == index items by id; negative control — the parody room's textures are (canvas, no src)
and touch the atlas zero times, so the metric discriminates. Collision FIXED (shop 120 + town 350
coexist). Fail-soft FIXED with a populated cache (missing base -> null -> parody). 98 draws with
real covers, pathOK, 0 carves, 0 console errors.

SCOPED NOT SLID: the LRU+dispose (§7.6) lands at v5.0-beta — one stocked shop can't accrete, and
safe eviction needs a room-exit hook (a live room holds the pack's shared material) that arrives
with F's per-shop preload. Written into the contract so it can't slide silently.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 00:43:50 +10:00
m3ultra
d2eb9df03f Lane C R23 (v5.0-alpha): the first real crate verified in the room (ledger #3)
Verified G's stock_godverse/3962749/ (120 real records, Monster Robot Party crate 550,
2 x 2048^2, cell 256) in a fresh context — deliberately BEFORE the merge, so a contract
failure would block it. It didn't.

PASS — the contract holds; G re-emits nothing:
- Resolves through the existing stockAdapter path via a base override (no C code change for
  the single-shop alpha). 120 items / 2 atlases / 8-of-8 stock meshes atlas-backed (real
  covers: peperecordings, DEEPCHILD, "39", mff FREAKS...).
- Ceilings hold: 118 draws with real covers (<=350 law), pathOK, 0 carves.
- Fail-soft holds (clean cache): missing atlas -> null -> parody (20 meshes, 110 draws);
  no adapter (?noassets shape) -> parody, zero fetch. 0 console errors.
- G's index compliant: items[] sorted by id (my determinism ask honoured), provenance, uv in
  [0,1], 2 atlases = exactly the cap, cell_w/h harmless (C reads uv, never cell).

CORRECTED MY OWN CEILING (LANE_C_PUB §7.2): I wrote "<=8 MB decoded" from an assumed ~16-item
"typical shop". A real record shop is 120 records => 2 x 2048^2 ~= 32 MB, 4x my number. The
structural rules held (<=2 atlases; 2048^2 for 60+ items — G landed on the cap); only the MB
figure was wrong, because it described a shop that doesn't exist. New: <=2 atlases @<=2048^2,
LRU = 1 shop's atlas set resident => <=32 MB peak (bounded; vs E's 181 MB unbounded today).
Do NOT shrink the cell below 256 to save VRAM — the dig renders covers close up.

G's ask #1 CONFIRMED and worse than reported (LANE_C_PUB §8 — C's spec, C's fix): stockpack
keys _packs/_resolved by type alone, so base is ignored on a cache hit (measured: the 2nd
preload returned the identical object). (1) two stocked shops collide; (2) it BREAKS FAIL-SOFT
— an unstocked shop of a cached type inherits that crate instead of parody. Seam: key by
type+base, per-shop preload on entry, LRU=1 + dispose; C consumes whatever identity field
lands, C does not invent it.

Two gaps block F's #7a, neither C's: the crate has no street (redhill_real absent; monsterrobot
not in any shops[] — G's ask #2 to E/Fable), and interior_mode.js:192 pins base to
assets/models/ (needs §8 + the identity field).

Committed from a temp git worktree on main — the shared checkout stayed on lane-G-godverse
(Fable's review) and G's branch keeps its disjoint namespace. Docs-only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 23:42:30 +10:00
m3ultra
b0e4ce103e Lane C R22 (v4.0): 23-town door-soak (#5) + the G2a atlas contract (#3)
Wave 3 — ran after A pinned the 36 goldens (3face97) and E's tri-diet fix (d81e499).

#5 — 23-TOWN DOOR-SOAK: five-town harness scaled (~10 doors/town; sample takes one of every
type present then stride-fills). 238 doors / 1,133 in-plan shops / 23 towns.
  23/23 PASS — 0 throws / 0 carves / 0 path-fails / 0 over-budget; worst 159 draws (fitzroy)
  <=350. All 12 archetypes covered town-set-wide (the whole registry). 23/23 towns landed 3
  venues — A's cluster bias + district placement hold from the 12-shop thin tail (darwin,
  toowoomba) to the 139-shop mecca (fitzroy, worst 159 = same band as everywhere: interiors
  don't care about town scale). Lane G's newtown_godverse passes (18 shops, worst 134).

#3 — G2a PER-SHOP ATLAS CONTRACT (C's half), published in LANE_C_PUB §7. All three Fable priors
hold; atlas-per-shop is strictly BETTER for draws than today's type-packs. The binding constraint
is VRAM, not draws — E's dispose ask GRANTED, it's my seam:
  - Draws: 1 atlas/shop = 1 draw (shared material + baked UV -> batch merges). Cap 2; 60+ item
    shops spill to the type-pack. Headroom huge (stock rooms 41-58 vs the <=350 law).
  - VRAM: E's 181MB audit is right, and the cause is my own design note (stockpack.js:11 caches
    atlases "NOT per-room, like depot GLBs") — correct for 3 bounded type-packs, wrong for
    unbounded per-shop. Ruling: <=2 resident atlases, <=8MB decoded, <=1024" typical; C lands the
    LRU cap + dispose in stockpack.js. Pre-existing + ?stock=real is opt-in (index.html:196) so
    it can't touch the default boot -> NOT a v4.0 blocker; C's first v4.x item, before G2a ships.
  - Schema: E's index confirmed verbatim, keyed per shop; UV top-left (C flips V); C reads uv not
    cell. Fail-soft/parody/?noassets unchanged. Ask to G: emit items[] in deterministic id order —
    C's seeded per-bin pick indexes into it.

C->F: 23-town interior verdict GREEN. Docs-only; both C items closed for v4.0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 22:12:00 +10:00
m3ultra
c134584b88 Lane C R21 (v4.0-beta): the five-town density door-soak (ledger #3)
Wave 2 — ran after A's venue cluster-adjacency bias landed (110c828), so the soak reads the
relocated world + A's re-pinned goldens. The carried R20 #6.

Five towns at their new densities, ~25 deterministically sampled doors/town (sample takes one
of EVERY type present, then stride-fills, so the texture classes' first real appearance is
always covered):

  town              shops  sampled  venues  types  worst draws   verdict
  katoomba_real      72      25       3      10       157         PASS
  newtown_real       67      25       3      11       155         PASS
  fremantle_real     79      25       3      11       153         PASS
  bendigo_real       35      25       3      10       154         PASS
  castlemaine_real   23      23(all)  3       8       156         PASS

123 doors: 0 throws / 0 carves / 0 path-fails / 0 over-budget, worst 157 draws (<=350).
typesInTown == typesSampled in every town (all archetypes entered, incl. all 3 venue kinds).
All five towns landed 3 venues -> A's cluster bias + district placement hold at density.

§6 mapping verified live: katoomba cache = 20 heroes + 60 texture (exactly E's 3x cap, heroes
untouched). Texture's first real appearance renders right -- "Blue Mountains Chocolate Company"
(real OSM shop=chocolate) wears milkbar as a believable confectionery counter shop. Texture,
not takeover. Cache->plan deltas are A's counted drops, not an interior concern.

C->F: density verdict GREEN. Docs-only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 21:05:17 +10:00
m3ultra
c7ca39b4f7 Lane C R20 (v4.0-beta): publish OSM-class -> archetype mapping (ledger #2, C's half)
The density widening's C half: real towns fetch general retail, not just secondhand.
Published the OSM shop-class -> registry-archetype mapping in LANE_C_PUB.md §6 (new
v4.0-beta amendment; frozen venue contract §0-§5 untouched). C->E handshake: E builds to it.

- No new archetypes. General retail wears existing recipes: food/grocer/newsagent/chemist
  -> milkbar; clothes/hardware/big-retail -> dept; produce/florist -> stall.
- Secondhand stays the hero (charity->opshop, books->book, music->record, video->video,
  pawn->pawn, toys->toy); never subsampled. Unknown/unlisted -> opshop (R18-proven).
- Subtle per John: texture not takeover. Copy-paste JSON map + tasteful fetch list (skip
  vehicle/funeral/industrial/pure-services) + guardrail for E to subsample if retail drowns
  the secondhand strip (count in norm.dropped).
- Mechanic: plan_osm keeps registry types, so E bakes a registry type per the map into each
  cache's shop.type -> no C code change, budgets unchanged.

Pre-validated before E builds to it: milkbar/dept/stall/opshop all build valid at a real
katoomba lot (8.5x14, 0 carves, <=143 draws); dept reads as a clean dressed-retail floor
(shelves+racks+counter, escalator does not dominate the 7.9x11.7 hall) -> clothes/hardware
-> dept confirmed. Full five-town door-soak (ledger #6) follows E's re-emitted caches.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 18:32:05 +10:00
m3ultra
f17babd8d7 Lane C R19 (v4.0-alpha close): re-soak katoomba interiors on real roads (ledger #5, on-call)
E's roads landed (katoomba_real now schema v2, 484 real roads), so re-ran the door soak
against the real street graph (closes the R18 "re-run when roads land" caveat).

Honest caveat: the dev server also served Lane A's UNCOMMITTED R19 fragmentation WIP
(plan_osm.js: JOIN_TOL/BRIDGE_MAX, junction-protected DP, island cull/bridge), so this
exercised A's in-progress finalization, not a frozen state.

Robust result (holds whatever A's WIP does): every shop the plan produced builds a valid
interior -- 0 throws/carves/path-fails/over-budget, worst 147 draws; 3 gig venues (district
placement holds on the real graph); plan deterministic; lot dims unchanged (7.5-8.5 x 14 ->
interiors dim-identical to R18, road-agnostic). Shop count came out 19 here, but that reflects
A's in-flight cull/bridge logic (expected per Fable's fragmentation ruling), NOT a defect --
not filed as a finding; interiors build clean whichever count A pins.

Final C spot-confirm pending A's committed finalization + golden pin (C confirms after A).
Docs-only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 17:32:49 +10:00
m3ultra
c7525a682a Lane C R18 (v4.0-alpha): interior spot-soak of katoomba_real (ledger #6)
Entered every katoomba_real shop the way the shell does (registerTownCache ->
generatePlanFor osm, gigs on = default boot -> buildInterior per shop, lot dims
from plan.lots). All 20 doors PASS: 0 throws / 0 carves / 0 path-fails / 0
over-budget, worst 153 draws (<=350); 7 types incl. the 3 gig venues (pub/rsl/
band_room) all built valid.

Degenerate-lot stress (preempts A's real-road re-seating): 32 extreme-lot x type
combos (incl. 0x0, 1x1, thin slivers) -> 0 throws/carves/path-fails; every room
floors at >=4x4 m. The interior contract absorbs any real frontage.

Honest caveat: current katoomba_real.json is schema v1 (roads:none) -> marched
fallback (E's roads cache not committed yet). Interiors are road-agnostic (lot dims
+ type + seed only), so the conclusion holds once roads seat shops on real edges;
one-line re-run if lot dims shift. Interior lane: 0 failures for the alpha list.
Docs-only (verification of shipped behaviour).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 16:18:46 +10:00
m3ultra
abcadcba13 Lane C R17 (v3.2): close the backline render handshake (ledger #3)
D's R16 wiring (ab7a8d0) reads stage.backline[0] with an immediate primitive ampPrim()
fallback + GLB upgrade. Verified fresh (seed 20261990), 3 archetypes x 2 modes:
- amp plants at backline[0] exactly (offset 0.00); 1 amp per stage (double-amp gone).
- no interpenetration: amp-to-nearest-band AABB edge gaps all positive — pub 0.40/0.44,
  band_room 0.27/0.29, rsl 0.54/0.57 m (GLB/primitive). Rendered band_room confirms the read.

On-call (for A/E real-map scout): C interior type fallback eats real OSM shop types — 18 real
shop= values (+ ''/null/undefined/aliases) all build 0 throws, all pathOK, resolve to opshop.
No C code change (docs-only; verification of shipped behaviour).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 14:18:36 +10:00
m3ultra
42b11e717d Lane C R16 (v3.1): stage.backline[] amp unification, drop primitive ampStacks (ledger #5, C half)
C exposes room.stage.backline[] — 2 seeded up-stage amp slots {x,z,ry,y} (pure data) — and
drops its primitive ampStacks from placeStage. Lane D plants E's guitar_amp GLB at backline[0]
(D's primitive fallback under ?noassets): one unified backline, GLB when present.

- backline[0] = (stage.x + stage.w*0.36, deckY, stage.z - stage.d*0.22) == D's R15-verified amp
  pose exactly, so plumbing D's plant to backline[0] moves nothing on screen. backline[1] = spare
  stage-left mirror. ry=pi (audience-facing), y=deckY.
- Verified fresh (seed 20261990): slots up-stage, on-deck, clear of all 4 bandPoses (nearest-bass
  0.87 pub / 0.70 band_room / 0.92 rsl) and all watchPoints (>=1.95 m); determinism byte-identical;
  0 leftover ampStack groups; drawSweep venues 122/120/131 (was 126/124/135, -~4 draws each) <=350.
- Determinism/golden-safe: dropping the amp loop only drops draws on the 'stage' sub-stream (nothing
  downstream reads it); placement snapshot excludes ampStacks; interiors aren't hashed; ?classic has
  no venues. Disjoint from F's flip. LANE_C_PUB §3 amended (v3.1 marker).

C->D handshake: backline shape live. Verify with D once the GLB is wired at backline[0].

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 12:16:38 +10:00
m3ultra
2446ae3a5d Lane C R15 (v3.0): verify D's guitar_amp reposition (ledger #4)
D moved the backline plant (66aec0f) to C's §3 corrected pose exactly
(band.js:236 = stage.x + stage.w*0.36, deckY, stage.z - stage.d*0.22).

Verified fresh (port-isolated context, seed 20261990, D's real GigCrew + GLB backline):
- matches §3 both venues; bass clearance 0.87 m (pub) / 0.92 m (rsl) — the R14
  interpenetration (0.29 m) is resolved; on-deck; flanks C's primitive ampStack
  (4.4 m pub / 3.5 m rsl) as a clean up-stage backline; no band/crowd clash.
- rendered pub gig confirms the read (amp up-stage behind the bassist).

LANE_C_PUB §3 updated RESOLVED; freeze otherwise stands. No code change (docs only).
Nothing else blocks on C — F runs the release (continuity wiring, alias delete, week
soak, tour, tag v3.0).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 10:49:20 +10:00
m3ultra
62e38c5df8 Lane C R14 (v3.0 release): venue audit + docs freeze + guitar_amp ruling
Light release round — C interior code unchanged since R13 (venue geometry stable by
construction); this is the audit, the amp ruling, and the v3.0-FROZEN docs.

- Re-verified vs the current tree (D's continuity + RY-fix + real instrument GLBs landed):
  contract byte-identical (caps 8/8/12, deckY/riserY, gigKeys, determinism), drawSweep <=350
  (worst 161), gig-night draws pub 60 / band_room 52 / rsl 71 <=350. Release-law confirmed:
  D's rng namespace is disjoint from C's ctx.stream; no golden can move.
- guitar_amp ruling (LANE_C_PUB §3): NOT blessed as-is. D's pose (stage.z+0.15, down-stage)
  interpenetrates the bass rig by 0.29 m; move it up-stage-right ~ (stage.w*0.36, deckY,
  stage.z - stage.d*0.22), clear of the bass + C's primitive ampStacks. Full amp unification
  = v3.1. Cosmetic + ?gigs opt-in -> not a tag blocker. C->D handshake.
- Docs freeze: LANE_C_PUB.md + LANE_C_AUDIO.md marked v3.0-FROZEN; RY §0 reframed past-tense
  (the +Z fleet was fixed at source in D's buildFigure, R13 - no per-consumer flips); budgets
  updated to R14; in-flight handoffs removed.
- Comment-only code (zero runtime/golden/rng change): fixed stale R12 inline return-shape
  comments (watchPoints 6..8 -> 8/8/12; stage bandPoses[3] -> [4] + riserY) in interiors.js +
  layout.js; documented the standalone gigKey hand-build. theme.js untouched.

Note for E/F: settled gig-night interior is ~182k tris (E instruments 14k each x5); F's smoke
reports ~34k measuring before the async GLBs load. Interior draw gate passes; v3.1 LOD candidate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 08:40:42 +10:00
m3ultra
82b711f962 Lane C R13 (v3.0-beta, the district): band_room + rsl archetypes, 4-piece band, RY audit (?gigs=1)
- theme.js: band_room (small/scuzzy tin shed) + rsl (carpet-and-flock club) venue recipes,
  reusing the pub machinery via a new per-kind venueSpec (deck size/height + crowd cap).
- layout.js: parametrized placeStage/placeWatchPoints; 4th `drums` pose seated on the riser
  (debt #3 — the band is now a 4-piece across all venues), stage.riserY + per-pose y.
- interiors.js: robust gig-<genreKey> resolution (gig.genreKey -> shop.genreKey -> kind
  default) matching E's renamed manifest beds (debt #1); API doc for the 4-piece + riserY.
- LANE_C_PUB.md: 3-kind venue contract + pinned ry convention and the empirically-verified
  keeper/browser back-to-front finding + source-normalise fix for Lane D (debt #4).

Verified fresh (seed 20261990 + 90-build soak): watch caps 8/8/12, gigKeys
gig-pubrock/gig-grunge/gig-covers, determinism 0, path/carve 0, gig-night worst RSL 237 draws
asset-free (16 rigs) / 77 GLB <=350, leak geo0/tex0. Shot: docs/shots/laneC/rsl_gig_r13.jpg.
Lane F: bump flags_check band==3 -> ==4 (tools/flags_check.py:674,794) — RED by design.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 23:05:03 +10:00
m3ultra
05e020e3b8 Lane C: round-12 pub venue — archetype + watchPoints + audio.gigKey (?gigs=1)
v3.0-alpha band room. New pub recipe (venue:true): stage + PA + amps at the back wall (new stage/paSpeaker/ampStack fittings, GLB slots stage/pa_speaker/amp for E), bar counter (keeper), sticky carpet, blocked back door = green room.

buildInterior returns for venues: room.watchPoints[] (6-8 seeded audience poses {x,z,ry,dance} facing the stage, dance ~1/3, counter.stand ry convention); room.stage {x,z,w,d,deckY,frontZ,bandPoses[3]} for Lane D's band; room.audio.gigKey ('gig-'+genreKey) set when Lane F passes opts.gig.

Flags-off byte-identical: venue code runs only for recipe.venue on its own seed sub-streams; non-pub rooms have watchPoints:[], stage:null, unchanged placement/audio.

Verified: 40-seed pub sweep (0 throws/carves/path-fails, 8 watchPoints, dance 0.38) + 60-room mixed soak (0 det/path fails, leak geo0/tex0, worst 19ms). Non-pub unchanged. Interface for D/F: docs/LANES/LANE_C_PUB.md. scaffold + manifest green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 18:59:48 +10:00
m3ultra
89f278b603 Lane C: round-11 audio contract — room.audio on buildInterior
buildInterior returns room.audio = { musicKey, toneKey }, seeded per shop: keys into Lane E's manifest.audio (interior music bed + room-tone). Lane F's interior_mode plays it, B's audio.js resolves keys->files. Keys not files: buildInterior stays synchronous + asset-free; silence on missing/mute/noassets (house audio law).

Maps mirror the manifest per-type types arrays: record/milkbar/video/dept/arcade always play their bed; other types room-tone-only except a seeded ~1-in-3 general radio (the 'general'-tagged milk-bar bed).

Verified 9 types x 8 seeds: 0 bad keys, 0 nondeterminism, all keys resolve + match manifest type membership, deterministic per shop. Contract + F wiring: docs/LANES/LANE_C_AUDIO.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 15:46:58 +10:00
m3ultra
c68bba8f32 Lane C round-9 C2: buy-anywhere — pull-and-buy on book spines + toy boxes
Book/toy shelves get pull-and-buy over the SAME wallet; records keep the
deep dig. New stockpack.buildBuyableShelf(): a real shelf is merged,
per-item-ADDRESSABLE meshes — one per atlas (all covers drawn from one
seeded atlas → one draw/shelf), tagged noBatch so batch.js leaves them
intact. Aim a spine/box → pull card (title/author-or-maker/$price/band
from the pack index) → BUY debits the wallet, adds to inventory, and
collapseBuyItem() zero-areas the item's quad in place (removed from the
shelf, no geometry churn). Parody stock → no buy mesh → no card. Fail-
soft per pack; atlasPool() keeps each shelf single-atlas (one draw).

Test page: E interacts (bin→dig, shelf→card) + a #shelfBuy pull card;
shelfBuySoak() buys one item per book/toy seed. Validated: raycast→card
→BUY end-to-end (quad collapses to centre, cash/inventory update),
buy-soak 6/6 collapseOk leak geo0/tex0, ?stock=real draws record 41/
book 58/toy 57 ≤350, drawSweep GLB-off 168/on 344 unchanged, placement
determinism 0 fails, qa --strict GREEN 5/5. Shot: browse_buy_r9.jpg.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 09:56:21 +10:00
m3ultra
fbc8ffa1c8 Lane C round-8: buy loop v0 + book/toy real stock (?stock=real)
C1 — buy loop v0 (new wallet.js): runtime-only economy — seeded cash
(~$60–200), buy()/broke-gate/in-memory bag, onChange for a cash chip.
World gen untouched (purchases never re-enter the plan/seeded build).
Test page: #cashChip HUD + I-toggle inventory + dig BUY IT button
(pull → bag, cash down, bin depletes); digSoak buys ≤2/visit.

C2 — book spines + toy boxes wired to ?stock=real. stockpack.js keys
each pack to a slot (record→sleeve, book→spine, toy→box); stock.js
shelfLine builds real atlas-UV cover planes for book/toy shelves
(static, no dig), fail-soft per pack. E's packs live: record 350/6,
book 311/4, toy 273/5 (items/atlases).

C3 — validated: draws ≤350 (record 41, book 42, toy 51), determinism
0 fails, leak-free (0 geo/tex delta ×20; buy-soak 12 visits/5 bought,
leakGeo0/tex0), reload resets wallet, flag-off + ?noassets untouched,
qa.sh --strict GREEN 5/5. Shot: docs/shots/laneC/buyloop_r8.jpg.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 04:33:00 +10:00
m3ultra
3724a65505 Lane C R7 C1: ?stock=real — real GODVERSE sleeves in the crates
The stockAdapter seam built in v1 finally eats. Lane E's stock pack (parody-
transformed Discogs metadata + cover atlas) feeds record-shop bins + the dig
riffle behind ?stock=real. Economy stays a stub (buying = toast).

- Contract handshake (task 1, unblocked E): confirmed E's index schema as-is
  in LANE_C_NOTES (items[{id,title,artist,price,price_band,atlas,uv}]; uv
  origin top-left, C flips V for WebGL; fail-soft; per-type packs).
- stockpack.js (new): preloadStockPack/getStockPack (cached) + makeStockAdapter.
  Atlas texture + material are shared/cached (like depot GLBs); each sleeve is
  a cover PLANE sampling the atlas at its baked UV, so all real sleeves in a
  room batch to ONE draw (record: 59 draws).
- stock.js binFan: real cover planes (else parody canvas). dig.js: riffles real
  covers, pull card shows title/artist/$price/band. interiors.js: opts.stock=
  'real' wires the adapter (warn-once + parody fallback if pack absent). Test
  page reads ?stock=real, preloads record/book/toy, passes the adapter to the dig.

Validated (E's staged 24-sample; real covers drop in unchanged when E gets the
DB): determinism 0/30, leak-free rooms + real dig (0/0 over 25 cycles), draws
<=139 all rooms (record 59), fail-soft on no-pack types, ?dig=1&stock=real
leak-free. Flag-off + ?noassets untouched (procedural path byte-identical).
qa.sh --strict GREEN. F smoke hook + E contract in LANE_C_NOTES. Shot
docs/shots/laneC/stock_real_r7.jpg.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 01:23:23 +10:00
m3ultra
c88d8906a2 Lane C R6 C2: map the 3 orphaned fittings (glass_case/magazine_rack/crate_stack)
Published R5, unconsumed until now:
- glb.js KIND_TO_GLB: glassCase->glass_case, magazineRack->magazine_rack,
  and a NEW crateStack primitive (fittings.js — a stack of crates) ->
  crate_stack. yaw 0 (validated on-screen).
- theme.js: crateStack in record + opshop (back-room dressing); glassCase
  added to milkbar; magazineRack added to pawn; milkbar magazineRack moved
  window->wall (dodges the door-counter congestion).
- layout.js: fixed placeNearCounter — it picked one random side and gave up,
  so counter-zone fittings (the glass case) rarely landed. Now it tries both
  sides + offsets, then falls back to a free aisle spot, so a min>=1 fitting
  reliably lands.

Validated (GLB on, baked depot): all 3 load + render planted/scaled/facing
right. Appearance glassCase 12/12 (toy/pawn/milkbar), magazineRack 12/12
(milkbar/pawn), crateStack ~11/12 (record/opshop). drawSweep still <=350
with them on (worst 177 GLB-on / 157 off). Determinism 0 fails, leak-free,
dig (?dig=1) still riffles batched bins (C3 re-verify). Contact sheet
docs/shots/laneC/glb_orphans_r6.jpg. qa.sh --strict GREEN.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 23:19:48 +10:00
m3ultra
35981ade5f Lane C R6 C1: interior draw batching (<=350 draws/room law)
Review debt: book room was 1,245 draws GLB-on. Diagnosed two drivers —
multi-material stock boxes (one draw per face, 6x) and one mesh per stock
item — then batched:

- stock.js: stock boxes are single-material now; every stock mesh tagged
  userData.isStock.
- batch.js (new): batchRoom() two-tier merge, called from interiors.js before
  the GLB upgrade. Room-level merge of all isStock across every fitting
  (grouped by material, world transforms baked) + per-fitting merge of fixture
  frames (kept in-group so record bins stay raycast targets for ?dig=1 and the
  GLB upgrade can hide the fixture). Uses vendored BufferGeometryUtils
  mergeGeometries.
- glb.js: frame-hide is now by isStock tag (not frame-count), so batched
  fixtures hide while the room-level stock rides on the GLB.
- interior_test.html: drawSweep() asserts <=350 draws across all type x
  archetype (GLB off and on), folded into the soak; exposed as
  window.PROCITY_C.drawSweep for F's harness (hook in LANE_C_NOTES).

Result (drawSweep, seed 1990): GLB-off worst 152, GLB-on worst 313
(opshop/hall) — book 1,245 -> 189. Determinism 0 fails / 810, leak-free, dig
still riffles batched bins, worst warm build 38ms (<50), visually identical.
qa.sh --strict GREEN (5 gates).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 23:01:04 +10:00
m3ultra
5671a06aef Lane C R5 C3: re-measure interiors after Lane E's hero-prop bake
E baked the 6 heavy hero props to <=8k tris (c522464). Re-measured every
interior GLB-on (cache-busted to bypass the dev browser's stale HTTP cache;
production clients fetch baked directly):
- record 43.6k tris (C3 target, ~273k pre-bake), draws 322
- milkbar 38.9k tris (was 468k — the R4 over-budget flag is now CLOSED)
- all rooms <=107k tris; book is the draw-call outlier (1245 draws from ~9
  bookshelf GLBs + ~600 primitive spines) — tris fine, interiors render
  one-at-a-time so gate 3 unaffected; instancing spines is a future lever.
Full table in docs/LANES/LANE_C_GLB_VALIDATION.md. Round 5 complete for Lane C.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 21:16:15 +10:00
m3ultra
7131f530a4 Lane C R5 C2: crate-riffle dig behind ?dig=1 (v2)
Ported 90sDJsim/web/world/dig.js → web/js/interiors/dig.js. Walk to a record
bin, press E, riffle its sleeves (scroll/drag), click the front sleeve to pull
it, BUY IT. Faithful to the source: hinge-forward riffle, pull-and-inspect
panel, procedural fwip/thunk audio.

- Riffle items = seeded procedural sleeves per bin via the stockAdapter(shop,
  'sleeve') seam (parody 90s-AU records now; real GODVERSE records feed the
  same seam in round 6+). Same bin ⇒ same order.
- Buy is STUBBED (toast + remove-from-bin; getCash returns a constant) —
  economy is round 6+ per integrator decision #1.
- Own scene + camera; leak-free: per-open GPU resources (sleeve textures/
  materials, crate parts) freed on every close(), persistent set freed on
  dispose(). Dig soak (20× enter/leave) → leakGeo 0 / leakTex 0.
- Flag-gated (?dig=1): buildInterior is UNTOUCHED, so ?dig=0 boot is
  byte-identical to v1 (prime law). The dig is wired only in the test page via
  an E-key raycast; F owns the shell input/mode seam — hook documented in
  docs/LANES/LANE_C_NOTES.md.

Verified: riffle + pull + buy-stub work; dig soak leak-free; placement
determinism unaffected; close returns to the interior. qa.sh --strict GREEN.
Shot: docs/shots/laneC/dig_riffle_r5.jpg.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 21:11:31 +10:00
m3ultra
aa9c140eca Lane C R5 C1: v1.1 re-map — record_crate (fixed) + counter_till
Round-5 §Lane C task 1 (the v1.1 gate). Map Lane E's R4-late re-exports in
glb.js:

- record_crate FIXED (E stripped sourceless AO/roughness slots) → crate/
  recordBin upgrade to plastic crates with the procedural sleeves inside;
  removed the stale "fails to load" comment.
- counter → 'counter_till' (the real ~1.6m timber counter WITH a modelled
  till; NOT the 4m balcao). Footprint-fit + floor-plant + keeper-stand pose
  unchanged.
- Double-till resolved (documented): counter_till carries its own till, so
  the separate cash_register attach is dropped whenever the counter GLB is
  live (falls back to cash_register on the primitive counter otherwise);
  milkshake_mixer stays on the milk-bar bench, lifted +0.10m onto the taller
  GLB counter.

Validated (GLB on, via depot): sweep+soak {throws 0, path 0, det 0, worst
10.4ms}, 0 rooms with a visible primitive till, leak-free vs a saturated
baseline (the transient 2geo/4tex first-sight is record_crate + counter_till
cache-fill). Primitive path byte-unchanged (glb.js-only edit). qa.sh --strict
GREEN. Contact sheet docs/shots/laneC/glb_v1_1_r5.jpg. → F can tag v1.1
(LANE_C_NOTES.md).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 20:56:00 +10:00
m3ultra
34926324bd Lane C R4: hero props into rooms (arcade/booth/fridge/mixer/register)
Round-4 §Lane C — map the 4 new hero props + cash_register (manifest now
14 fittings), validate, measure, clean up.

- glb.js: map listening_booth→listeningCorner (record), drinks_fridge→fridge
  (milkbar), arcade_cabinet→new arcadeCabinet primitive (video focal prop).
  New attachCounterTop() places cash_register on every counter's till slot
  (hides the primitive till) + milkshake_mixer on the milk-bar benchtop;
  counter primitive + keeper stand untouched. All yaw 0 (no correction).
- fittings.js: new arcadeCabinet primitive (footprint/height matched to the
  GLB); counter exposes counterTop attach points + tags the primitive till.
- theme.js: arcadeCabinet in the video recipe; the 3 focal props (arcade,
  listeningCorner already, +) raised to priority 6 + placed first so the
  door-counter path loop can't pull them → 24/24 appearance.
- interiors.js: thread recipe.key to upgradeRoom (gates milkshake_mixer to
  milk bars).
- interior_test.html: load GLBs via the live depot: path (drop the deleted
  _local_glb localBase shim; ?localdepot=1 still works offline).

Validated (all via live depot): primitive sweep 810 {throws 0, pathFail 0,
detFail 0}; GLB-on soak {throws 0, path 0, det 0, leakGeo 0, leakTex 0,
worst 7.3ms}; all 5 new props render planted/scaled/facing right (contact
sheet docs/shots/laneC/glb_heroprops_r4.jpg). Room tris measured: milk-bar
GLB-on ~468k (drinks_fridge 77k + milkshake_mixer 73k) — reported to Lane E
as the #1 bake-pass target (not fixed, per integrator decision #2; interiors
render one-at-a-time so gate 3 is unaffected). record_crate still broken +
counter still oversized — both await Lane E re-export (C task 5). Deleted the
stale web/assets/_local_glb/ validation copy. qa.sh --strict GREEN.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 18:44:09 +10:00
m3ultra
fe40ea7d0a Lane C: round-3 GLB hero-prop validation (useGLB path)
Validated Lane E's 9 fitting GLBs in real rooms. 5 good, now live behind useGLB (wire_shelf, clothes_rack, bookshelf, cube_shelf_wide, work_table); record_crate broken (won't load) + counter too long/no-till fall back to primitive. Report: docs/LANES/LANE_C_GLB_VALIDATION.md.

glb.js: real-metre scale clamped to footprint, per-kind yaw for Z-major assets (+90deg) per manifest facing convention, manifest.localBase for offline validation, awaitable upgrades. interiors.js: room.glbReady. interior_test.html: GLB toggle.

Soak useGLB on (50 rooms): throws 0, pathFail 0, detFail 0, leakGeo 0, leakTex 0, worst 10.5ms, 256 upgrades. qa.sh --strict green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 16:18:12 +10:00
m3ultra
8577c48594 Lane C: keeper stand pose on the counter contract (re-land on main)
buildInterior now returns room.counter = { mesh, pose, stand } and tags the counter
interactable with userData.keeperStand. stand = {x,z,ry} where a shopkeeper stands
(service side, facing the customer, rig-front = local -Z per keepers.js), clamped
in-bounds. Deterministic; purely additive.

Note: commit 4235060 carries this message but actually contains Lane A's round-2
content (shared-index race, flagged in 3e98f05). This commit lands the real Lane C
change — identical content to stray branch lane-c/keeper-stand (85438b5), which is
now redundant. Verified: 36-build sweep + 50-room soak green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 14:27:58 +10:00
m3ultra
0e9d3fb0f2 Lane C: on-demand seeded shop interiors (library + test page)
Every shop door opens into a unique, believable, seeded interior — generated
in ~4ms, byte-identical every revisit (shop.seed), themed by shop.type.

- interiors.js: buildInterior(shop, THREE, opts) public API — pure fn of shop,
  returns {group, spawn, exits, places, dims, placement, dispose()}.
- theme.js: 9 CITY_SPEC type recipes (archetype/wallpaper/floor bias, clutter,
  counter pos, fittings mix, stock kind, signage) + type aliasing + one-time
  mergeRegistry() override seam for Lane F. Standalone (no hard registry dep).
- shell.js: room shell from lot x archetype (cosy/gallery/wide/hall/pokey),
  glazed shopfront + street backdrop, blocked back doorway, interior lighting.
- fittings.js: parametric kit ported from 90sDJsim + extended (bins, crates,
  4 shelf types, VHS aisle, glass case, counter+till, fridge, magazine/spinner
  racks, armchair, escalator, pegboard, barred window, returns slot, art).
- layout.js: per-archetype zones, thriftgod shuffled wall-slot system,
  occupancy grid, guaranteed door->counter flood-fill path (pull/carve).
- stock.js: v1 visual stock (pooled canvas sleeves/spines/boxes/garments/snacks
  with price stickers) + stockAdapter hook for BaseGod content later.
- context.js: seed sub-streams + shared-geometry cache + leak-free disposeAll().
- glb.js: optional GLB hero-prop upgrade via Lane E manifest (off by default,
  primitive fallback).
- interior_test.html: standalone page — seed/type/archetype, first-person walk,
  wireframe/occupancy/path debug, 50-room soak (perf + leak + determinism).

Acceptance (verified): same seed -> identical placement (0/810 mismatches);
9 types x 5 archetypes render sensibly (docs/shots/laneC grid); build <50ms
(steady ~4ms, soak worst 8ms); leak-free dispose (geo/tex delta 0); door->counter
path always exists (0 fails, 0 carves); runs with zero assets and zero network.
Adversarial multi-agent review: 5 findings, all fixed and re-verified.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 12:08:12 +10:00