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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
- 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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>