Commit Graph

49 Commits

Author SHA1 Message Date
m3ultra
acf734a1e5 Lane A round 12: v3.0-alpha gig layer (?gigs=1) — venue, plan.gigs, band names, closing-time debt
Publishes the gig interface the whole round hangs off (A runs first). Everything is
behind ?gigs=1 as a POST-HOC augmentation (gigs.js withGigs, applied by the selector),
so generatePlan is untouched and all v2 goldens stay frozen (synthetic 0x3fa36874, osm
melbourne 0x34cfdec0, katoomba 0x0f652510). Source-agnostic (synthetic + osm).

- Venue: one 'pub' per town — a spine-end plain shop converted in place (keeps lot
  id/geometry, enterable), shop.venue=true, never the openLate landmark. Registry gains
  a weight-0 'pub' type (interior:'band_room' for Lane C) → never auto-placed → base
  plan byte-identical.
- plan.gigs: 7 nightly {gigId,venueShopId,bandName,genreKey:'pubrock',night,startSeg:5,
  endSeg:5,cover}; cover ~half free / half $2-$10 (F charges at the door).
- Band names: bandName(seed) generator (90s pub-rock) + OPTIONAL web/assets/custom_bands.json
  drop-in (priority into the pool; absent → pure generator, no fetch under ?noassets).
- plan.posters: ~6 seeded {id,gigId,x,z,ry} for Lane B.
- Closing-time debt paid: isOpen(shopOrHours,hour) exported (half-open law as code, D consumes).
- map.html?gigs=1: venue ring + poster pins + gig panel line. Flags-off unchanged.

Self-check ALL GREEN 3074/3074 (gigSuite ×2 sources, flags-off≡base, gig golden
0xa6ae5a5e). qa.sh --strict GREEN 6/6. Verified in-browser both modes. CITY_SPEC v3
section + LANE_A_NOTES handshake for C/D/B/F. Atomic pathspec commit (shared-tree race).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 18:33:53 +10:00
m3ultra
8c275c711b Lane F R11: wire interior audio beds + audio smokes + gate scope
Interior audio (interior_mode): enter -> PROCITY.audio.playInterior(room.audio) with Lane C's
{musicKey,toneKey} (fade in), exit -> stopInterior() (0.7s fade). Guarded/no-op when
muted/noassets/pre-gesture -- silent-and-happy.

New smoke_audio gate (flags harness, fresh Chromium per the R10 standing law): pre-gesture silence
(ctx not unlocked), 0 boot errors, ?mute=1 muted, ?noassets=1 zero audio fetches, enter/exit
AudioNode leak-free (12 cycles, 8 shops with a music bed, live sources bounded at 5). Gesture via
keyboard -- a mouse-click gesture tripped PointerLockControls in headless.

scaffold_check: scoped the Math.random generation-ban to exempt runtime FX PLAYBACK
(web/js/world/audio.js) -- Lane B's footstep SFX variant/gain jitter is transient, not world
generation (which shop plays which bed is seeded upstream in interiors.js). Noted to B.

Soak with audio on PASS (heap 48MB stable, 0 errors, no drift). qa.sh --strict GREEN 6/6.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 17:13:19 +10:00
m3ultra
f7dd44f55a Lane B (Streetscape): round-11 audio — WebAudio engine (street side)
- web/js/world/audio.js: one AudioContext unlocked on first gesture; day/night ambience crossfade, rain (weather.intensity), footsteps, shop-door music spill (nearest open music-shop, distance-gain), tram rumble+bell, enterShop SFX. Consumes Lane E manifest.audio (3x retry). Self-ticks off window.PROCITY.
- House audio law verified live: silent pre-gesture (0 fetches), ?mute=1 silent surface (0 fetches), ?noassets=1 zero audio/manifest fetches, lazy-load (8.7MB<=25MB), no per-frame alloc.
- window.PROCITY.audio for Lane F: playInterior({musicKey,toneKey})/stopInterior (street beds duck while inside), playSfx, setMasterGain, mute, state diag. Confirms Lane C room.audio={musicKey,toneKey} contract.
- Small guarded [Lane B R11 audio] loader in index.html.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 16:00:31 +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
3432fccd1f Lane D round 10: fix interior giant rigs + no-giants QA gate
R9 blocker: interior rig figures were ~2x too tall (keeper crown 3.83m in a 3.4m room,
clipping the ceiling). Root cause in rigs.js buildFigure: it normalised scale by height/headY,
where headY is the head bone's world Y in BIND POSE — but the Mixamo skeleton origin is the hips
(~mid-body), so headY is head-above-hips (~half the standing height), not feet->crown. Every rig
came out ~1.83-2.0x too tall. (The head does NOT move when posed — bind crown 3.209m vs idle
3.205m — so it's a hips-relative measure, not an animation issue.)

Fix: normalise by the feet->crown span (headY - minY, both already computed). buildFigure(rig, h)
now lands the crown at exactly h above planted feet. Fixes keepers, browser rigs, near-tier street
rigs AND the impostor bake (all ride buildFigure). Verified: all 19 fleet rigs crown==1.75m; live
keeper 3.83m -> 1.82m; determinism + dispose-leak soak unchanged.

Regression guard (required): new tools/qa/interior_scale_check.py (Playwright) enters a sample of
shops, injects browser rigs at browse points, asserts every interior fig crown in [1.4,2.0]m AND
< room dims.H AND feet planted. Wired into tools/qa.sh --strict (run_gate => a giant fails qa).
Proven bidirectional: PASS on the fix (24 figs/6 shops), FAIL on a reintroduced giant. Full
qa.sh --strict: 6 passed, 0 failed.

Re-shot laneD/r10_browsers_fixed.jpg. Figures human-sized relative to doors/fittings: yes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 13:01:54 +10:00
m3ultra
5a24f8aa52 Lane F R9 F1+F2: interior presence + tram + buy-anywhere wiring; harness graduated + new smokes
Wiring (F owns index.html + interior_mode.js):
 • Interior presence (C->D->F seam): occupancyOf closure -> interior_mode spawns browser rigs at
   C's room.browsePoints (one per patronage occupant, cap 3); freed by exit()'s keepers.disposeAll.
   Door points now carry shopId for D's occupancy truth.
 • Tram (Lane B, ?tram=1): createTram + update in loop (2 draws, non-blocking).
 • Buy-anywhere (Lane C C2): E aimed at a book-spine/toy-box buyMesh buys over the same wallet
   (collapseBuyItem + toast); preload book+toy packs too. Records keep the dig.

Harness: graduated buy-v0 / patronage / book-toy stock warn->STRICT (katoomba golden already
strict). New warn smokes: interior presence (browsers==min(occ,3), dispose clean), shelf-buy
(book spine bought, cash drops), tram (present). Book-stock check now counts isStock OR buyMesh
(C2 tags book spines buyMesh, not isStock). flags_check GREEN 0/0; qa.sh --strict GREEN 5/5.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 11:42:44 +10:00
m3ultra
16c4450cd9 Lane B round-9: v2 tram (?tram=1) + tour bookmarks + rain-drop polish
- tram.js: 1 small bus, seeded out-and-back loop on the main-street spine,
  door-dwell at each busShelterStops stop, ping-pong at ends. Deterministic,
  exactly 2 draws, night headlights via lighting.isNight(). Flag-off identical.
  Shell wiring (2 lines) documented for F in LANE_B_NOTES.
- dbg.js: 8 v2 tour bookmarks (16 total) incl. rain_verandah hero shot;
  tram_stop now frames the nearest-origin (open) shelter stop.
- weather.js: cap rain gl_PointSize (clamp 2-22px) - near drops are thin
  streaks now, not blobs. Rain smoke still GREEN.
- qa.sh --strict GREEN, golden hash frozen, flags-off byte-identical.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 11:09:41 +10:00
m3ultra
e5d4679471 Lane A round 9: freeze CityPlan v2 contract in CITY_SPEC + closing-time occupant ruling
Ship round. Docs + one selfcheck log-line only; no generator change ⇒ all three goldens
unchanged (synthetic 0x3fa36874, osm/melbourne 0x34cfdec0, osm/katoomba 0x0f652510).

1. CITY_SPEC v2 contract FROZEN — new "CityPlan v2 — frozen producer contract" section: producer
   API, goldens table (keyed seed/plansrc/town) + how to pin a new one, town-key mechanism, osm
   normalization rules, hours/openLate/storeys laws. Matches code as shipped (F diffs claims vs
   code). Refreshed the stale round-6 Plan-sources blockquote to multi-town reality. Layer-1
   changes now require a CITY_SPEC amendment + golden re-pin in the same commit.
2. Recipe sanity — DRY-RAN end-to-end: appended a throwaway Newtown (42 shops), selfcheck auto-ran
   the full parity suite (1931/1932, only golden unpinned) and printed a paste-ready
   `newtown: 0x9045e577,`. Reverted; no 3rd town shipped. Added the selfcheck `⚠ UNPINNED — add to
   OSM_GOLDENS` line so the recipe is mechanical; fixed the recipe wording to match.
3. Closing-time occupant ruling (D's on-call Q) — ratifies + freezes what D's sim.js already does:
   half-open hours, no new entry when closed, occupants DRAIN (finish bounded dwell) never popped,
   player not force-ejected, openLate video is the night crowd. In CITY_SPEC + LANE_A_NOTES. No
   code change asked of D.

qa.sh --strict GREEN; selfcheck 1727/1727.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 10:56:19 +10:00
m3ultra
34145e24a7 Lane D round-9: interior presence — occupancy truth + browser rigs (C->D->F seam)
Follow a ped into a shop and find them browsing. D owns occupancy truth; C owns browse points;
F wires the handoff. Validated end-to-end in-shell (?stock=real). qa --strict GREEN; v1 untouched.

Occupancy (D1): patronage records who's inside which shop by shopId. New
citizens.occupancyOf(shopId) -> {count, occupants:[{seed,enteredAt,pedIndex}]} (pedIndex = the
exact ped who ducked in, for identity continuity). Cleaned on emerge/chunk-drop/stream-toggle.
Deterministic: 17 shops + occupants byte-equal across two runs (fixed a stale-occupancy leak on
stream toggle).

Browsers: keepers.js gained browse:true (faces the shelf via ry, no player-greet) + pedIndex +
seedKey. Each = a merged ped ~1 draw. 12 enter/exit cycles: count == min(occupancy,3) 12/12,
worst 61 draws <=350 (C's headroom holds), leak-free (0 GPU delta after warmup), disposed every
exit via keepers.disposeAll().

Consistency (D2): inside peds hidden on the street (0 rendered), re-emerge at the door.

F handoff verified by running F's exact interior_mode wiring (enter -> occupancyOf ->
browsePoints.slice(0,min(count,3)) -> keepers.spawn). index.html setShops door points need
shopId:s.id added. Closing-time handled by the dwell model (pending A's explicit ruling).

Evidence: docs/shots/laneD/r9_browsers_in_shop.jpg. Changed sim.js + keepers.js + test page.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 10:21:10 +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
5ae635a1e9 Lane C round-9 C1: seeded browse points (room.browsePoints) for Lane D rigs
layout.js placeBrowsePoints(): 1–3 deterministic floor poses in front of
browsable floor fittings, facing the goods. Guarantees (all asserted):
walkable (occ-free, not reserved), reachable from spawn (door→counter
flood-fill), ≥0.6m apart + clear of keeper stand, every archetype (rich
candidate ring — 4 sides×2 gaps + diagonals — plus a nearest-free-cell
fallback so tight/mismatched rooms still yield ≥1). Pure data: no meshes,
no draws; ≤350 law untouched. Exposed as room.browsePoints[]
{x,z,ry,atKind,slotIndex}, same coord frame + ry convention as
counter.stand (contract in LANE_C_NOTES).

Validated headless: 180 type×archetype×seed combos → 0 empties (177×3,
3×1 via fallback), determinism 0 fails, min-sep 0.7m, 0 keeper clashes.
Test page: cyan browse-post markers on the 'path' overlay + HUD count.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 09:36:26 +10:00
m3ultra
9cc00ad29c Lane F R8: wire town/weather/patronage/buy + graduate harness + new smokes
Shell seams (F owns index.html): A &town=<key> -> generatePlanFor(seed,'osm',{town}) (Katoomba);
B weather (createWeather on ?weather=1; PROCITY.weather={state,intensity}, {clear,0} off; update in
loop); C session wallet bound to the dig BUY (getCash/onBuy in interior_mode); D patronage door
points -> citizens.setShops + ?patronage=0 off-switch + setWeather each frame.

Harness: stock=real + weather=rain graduated warn -> STRICT. New warn smokes: buy-v0 (dig BUY ->
wallet, cash 191->179 bag 0->1), patronage (7 peds visit shop doors/~18s), book/toy stock (6/11
real atlas meshes). Determinism gate: pinned osm/katoomba golden 0x0f652510 keyed on (seed,plansrc,
town) + fixed a hex zero-pad bug the katoomba golden exposed (0x0f652510 vs unpadded 0xf652510).

No patronage baseline re-pin needed (nets fewer draws, within tolerance). qa.sh --strict GREEN 5/5.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 09:09:11 +10:00
m3ultra
3daf8df3ec Lane D round-8: shop patronage v0 + weather reaction
The crowd comes alive. Default-on for the streamed roster (behind ?patronage=0); v1 path
(?roster=v1) untouched — no patron fields, golden identity holds. qa --strict GREEN.

Patronage (D1): per-ped state machine walking -> going (steer to a nearby open shop's door,
seeded ~every 10m within 18m) -> inside (hidden, 5-20s seeded dwell) -> emerge (resumes walk).
Hours-aware: noon = all types (up to ~38 inside, 7 types); night = video-only (the openLate shop
draws the crowd, roll ramps up as streets empty). Deterministic (150 chunk-keyed ids), leak-free
(0 GPU delta over the churn), budget-neutral (inside peds hidden -> draws -1).

Weather (D2): reads Lane B's PROCITY.weather contract (no import). setWeather({state,intensity}).
Rain -> density -56% (40-60% target), walk speed +14%, more sheltering (inside 6->25); overcast
-10%; clear = v1.

Also this session: R7 post-flip shell verification (no-flag boot streams, determinism, leak-free,
?roster=v1 restores golden roster) and pushed main + v1.0/v1.1/v2.0-alpha/v2.0-beta to origin.

F wiring (setShops door-point recipe, setWeather, ?patronage=0) documented in LANE_D_NOTES.
Only web/js/citizens/sim.js + web/citizens_test.html changed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 04:42:34 +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
f952f513d0 Lane A round 8: OSM to parity + a second town (Katoomba) — carried R7 brief, full
The carried brief (slipped R7) lands complete — all four items, qa.sh --strict GREEN.

1. Selfcheck PARITY: extracted structuralSuite(plan,label) and run the FULL invariant suite
   on every (source, town) — synthetic + both OSM towns get identical coverage (storeys,
   one-shop-per-lot, facing, within/cross-block overlap, corridor coverage, determinism,
   exactly-one-openLate). ALL GREEN 1727/1727 (was 1362; OSM went subset → full ×2 towns).
2. SECOND TOWN: Katoomba (Blue Mountains, 19 shops, op-shop/book heavy) — regional contrast
   to inner-Melbourne. osm_fixture.js → OSM_TOWNS{melbourne,katoomba}+DEFAULT_TOWN;
   generatePlanFor(seed, source, {town}); ?plansrc=osm&town=katoomba. Golden 0x0f652510.
   Melbourne 0x34cfdec0 + synthetic 0x3fa36874 UNCHANGED (town key kept off the plan).
3. "Add a town" recipe in LANE_A_NOTES (R8): extract → OSM_TOWNS → pin golden → tell F.
4. Normalization + LOG: importer bends real data to the contracts (unknown kind→opshop,
   hours-clamp so only one shop is openLate, video-preferred landmark w/ fallback) and logs
   it via {report}; selfcheck prints per-town (e.g. melbourne hoursClamped:5, openLate:video).

For F: three goldens to pin, gate keyed (seed, plansrc, town); selector gained {town} — see
LANE_A_NOTES R8. Screenshots docs/shots/laneA/osm-{melbourne,katoomba}.png.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 04:15:12 +10:00
m3ultra
900b274a9a Lane B round-8: weather (?weather=1) — seeded rain/overcast/clear, exposes PROCITY.weather for D
New js/world/weather.js + a one-line lighting.setSky(). Seeded per citySeed (weatherFor):
~57/24/19% clear/overcast/rain over 400 seeds, deterministic. Rain = ONE draw (camera-following
Points + procedural streak/fall/wind shader) + wet ground (roughness/darken on shared ground mats,
restored on dispose) + matching rainy sky dome via lighting.setSky. Fully procedural (0 depot fetch).

- Exposes window.PROCITY.weather = { state:'clear'|'overcast'|'rain', intensity:0..1 } for Lane D's
  weather-reactive crowds (decision #1). Documented the contract + the exact 3-line shell wiring for F
  in LANE_B_NOTES (I don't touch index.html). ?weather=1 seeded (default town rolls clear);
  ?weather=rain|overcast|clear forces a state for demos/smokes.
- Worst view 141 draws day / 126 night (<=300); composes with ?winmap + night (rain outside a lit
  interior-mapped window = the money shot, screenshotted); deterministic; 0 console errors; flag-off
  byte-identical (shell never constructs it). Deferred cut line: vertex-shader wind sway on
  trees/awnings (touches other lanes' materials) — noted, not done. Tram stretch not attempted.

qa.sh --strict GREEN (5/5).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 02:34:14 +10:00
m3ultra
b94f86b49f Lane F R7 F2: harness → strict + wire ?stock=real (real GODVERSE covers in the crates)
qa.sh: flags harness warn_gate → run_gate — flags-off regression + the 4 existing flags +
all-on combo are now STRICT (a harness FAIL fails qa). New flags stay warn-level in-harness.

?stock=real wiring (F owns the shell + interior bridge): shell preloads the record stock pack
(preloadStockPack('record'), fail-soft, off under ?noassets) + passes stockReal to the bridge.
interior_mode builds makeStockAdapter(getStockPack(shop.type)) and passes it to BOTH buildInterior
(static crate sleeves) and dig.open (riffle) — null pack → parody canvas (Lane C fail-soft).

Harness: added stock=real smoke (assert non-parody = batched userData.isStock atlas meshes > 0;
verified 7 real sleeves in the record shop) + weather=1 smoke (auto-skips until Lane B lands),
both warn-level (decision #4). qa.sh --strict GREEN (5 gates); crates now show real covers.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 01:56:14 +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
43edabfe56 Lane D round-7: validate ped-merge → GO for roster flip + full-density defaults + escape hatch
Lane E's R7 merge (447188a) made each ped 1 mesh/1 material (~1 draw/near-rig, fleet 116->24 at
the cap) — the gate my R6 memo named. Validated + handed F the GO.

Merge validated: bind+animate/no-T-pose, skinning/skeleton preserved, silhouettes+atlas materials
crisp (hi-vis/suit/kid), impostor bake clean, identity variety + determinism intact. GPU memory
dropped (geom 205->160, tex 141->90).

Flip numbers (full density, perChunk 16, in-shell): worst street view 241 draws <=300 (was 356
unmerged), 65,899 tris <=200k, 17 near-rigs; 30-chunk soak (726 builds/disposes) leak-free, 0 GPU
delta, heap stable. => GO.

D2: enableStream default perChunk 8->16 (full density, the merge affords it); near-cap stays 24.
D3: test page + harness default to stream now; ?roster=v1 escape hatch restores the fixed roster
(verified streamMode false + determinism). No-flag boot -> streamMode true + determinism.
F's web/index.html flip wiring (invert to default-on + ?roster=v1 escape) documented in
LANE_D_NOTES; in-shell no-flag-URL boot completes when F lands F1.

Only web/js/citizens/sim.js + web/citizens_test.html changed (+ NOTES/progress).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 00:51:51 +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
3b86b0c5b3 Lane F R6: ≤350 interior draw gate + fix interior renderer.info accumulation
Added an interior draw-budget sweep to flags_check.py (decision #2): enter all 9 shop types,
assert <=350 draws/room, dig on so it also confirms C1's batching kept ?dig=1 riffling.

The sweep caught a real bug in interior_mode.js (F-owned): the interior/dig render never called
renderer.info.reset() while the shell runs autoReset=false (for the composer), so
renderer.info.render.calls ACCUMULATED every frame — inflating any interior draw read (and the
HUD were it shown). Fixed: reset per interior frame before both the dig and the walk render.

True per-frame result after the fix: all 9 room types <=350 draws (worst dept 96) — C1 batching
(35981ad) verified. flags_check GREEN (regression + interior budget + 4 flag smokes + all-on combo).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 23:10:11 +10:00
m3ultra
24cef5a7bb Lane B round-6: place street_bin + bus_shelter GLBs (on the street graph, collision-verified)
Consumes the two orphaned street GLBs via the use-if-ready furniture path (fail-soft; ?noassets →
primitives, 0 network).

- Bins: genuinely sparse (~1/block, s+=78m, seeded side), decoupled from benches — country town not
  a mall. GLB on its 0.57x0.68 footprint.
- Bus shelters ON THE STREET GRAPH (future tram/bus stops): deterministic rule = midpoint of every
  MAIN edge with hashEdge(id)%3===0, road-side footpath by the kerb, long axis along the edge, open
  front to road -> 1-3/town (2 in seed 20261990). Solid oriented-rect collider (furniture now returns
  colliders; chunks.js merges them into getColliders) -> walk-into-it is blocked. Exported
  busShelterStops(plan) -> [{edgeId,x,z,yaw}] as the stop-list contract for the vehicle loop.
- Perf: worst view WITH rig-fleet citizens 94k tris / 148 draws (<=200k / <=300, big margin);
  deterministic; all-flags-on (winmap+dig+roster) composes clean, 0 errors. street_bin GLB is 3k tris
  -> flagged to Lane E as a decimation candidate (bounded by the sparse cadence for now).

qa.sh --strict GREEN.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 23:04:14 +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
781baea4e2 Lane F R6 F2: wire ?plansrc=osm shell selector + pin osm golden + dig nearest-bin fallback
A landed the plansrc seam (52eb109); F owns the shell bootstrap. index.html: ?plansrc=osm
-> citygen.generatePlanFor(seed,'osm') (real-data 'Melbourne' 95 shops); default synthetic
'Boolarra Heads' unchanged (prime law). scaffold_check.mjs: pin BOTH goldens keyed by
(seed,plansrc) — synthetic 0x3fa36874, osm 0x34cfdec0 (deterministic) — + assert the selector
routes synthetic|osm. flags_check auto-detects + smokes plansrc + the all-on combo (4 flags).

interior_mode.js: restored binUnderAim's nearest-bin fallback (Lane C test-page parity) so E
riffles a nearby bin without perfect aim / before the bin GLB async-loads — fixes the dig
smoke's raycast-miss under fast headless AND improves real UX.

qa.sh --strict GREEN; flags_check GREEN (all 4 flags + all-on combo, 0 console errors).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 22:59:45 +10:00
m3ultra
5db3155991 Lane D round-6: harden streamed roster + default-on readiness memo
Flag ?roster=stream stays default-off; v1 path byte-identical (citizen-0 = R4 golden, passes
F1 flags-off regression). Full evidence memo in LANE_D_NOTES for the R7 default-on decision.

- Soak (in-shell, real town): 242 chunk builds/disposes over 3 walks -> 0 GPU geom/tex delta;
  16 shop enter/exit -> 0 delta; heap oscillates 50->77->54MB (GC, no leak); 188 chunk-keyed
  identities re-derive from seed; 0 console errors.
- Hours-aware density: added setNightLivelyChunks + NIGHT_LIVELY_FLOOR so the open-late video
  block stays lively at night (31->16) while ordinary streets go near-empty (65->6). F call-site
  documented (openLate lot -> chunkKey via sim.chunkKeyAt).
- Composition with ?dig=1: roster is street-tier, inert while interior/dig open (frame loop is
  street-branch-only); enter/exit record shops leak-free with stream on. No shared state with any
  v2 flag -> all-on combo safe.
- Budget gap found + made safe: each decimated ped is 8 sub-meshes / 2 materials = ~7 draws/rig,
  so full-density stream (perChunk 16) hits 356 draws (>300 street budget). Set default perChunk 8
  (291 draws, budget-safe today). The clean unlock for full-density default-on = merge ped
  sub-meshes by material (8->2 -> ~2 draws/rig -> perChunk 16 fits ~260); documented as R7 work,
  F1-safe (v1 spawn view has ~0 near-rigs).

Only web/js/citizens/sim.js + web/citizens_test.html changed (+ NOTES/progress).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 22:56:39 +10:00
m3ultra
52eb1090ef Lane A round 6: second plan source ?plansrc=osm (real-data OSM import) — full seam
The carried-over OSM plan source (no-showed R4–R5) lands complete, invariants green — a
second CityPlan producer behind the same contract, so B–F consume it unchanged.

- osm_fixture.js: 95 REAL inner-Melbourne secondhand/charity/music/book/toy/antique shops,
  extracted deterministically from thriftgod city_source.json (Overpass cache), imported as a
  module ⇒ ZERO network.
- plan_osm.js: generatePlanOSM(seed) — project lat/lon→metres, bin shops into latitude-band
  avenues off a spine (real row + real E–W order preserved), march into a valid non-overlapping
  CityPlan, centre on origin, report true 362×486 bbox, real shop names, one openLate video.
- index.js: generatePlanFor(seed, source) selector — default 'synthetic' BYTE-IDENTICAL
  (golden 0x3fa36874 untouched); 'osm' → the importer. generatePlanOSM exported for F.
- selfcheck.js: parameterized by source — OSM runs the full structural invariant suite
  (frontEdge/facing/no-overlap/chunk-coverage/finite/JSON/determinism/openLate) + its own
  golden 0x34cfdec0; synthetic-only brief checks skipped. ALL GREEN 1362/1362.
- map.html: ?plansrc=osm renders it (reference wiring for F). Screenshot in docs/shots/laneA/.
- CITY_SPEC: plan-source note; LANE_A_NOTES R6: seam + goldens + shape-diffs for F's gate (F2).

Honest cut: street geometry regularized (real row+order drive it, exact metres do not);
name-parody deferred. qa.sh --strict GREEN.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 22:47:38 +10:00
m3ultra
26d68cdb4f Lane F R5 F2: wire ?dig=1 crate-riffle into interior mode
C shipped dig.js + the flag but left the in-game input/mode hook to F (LANE_C_NOTES C2).
interior_mode.js (F-owned): aim at a userData.kind==='bin' fitting + press E -> createDig +
dig.open (per-bin seed binSeed(shop.seed,pos)); the dig owns the frame (renders dig.scene,
suppresses interior walk); WALK OUT/Esc closes back to the room; closed on interior exit.
Shell (index.html): ?dig=1 -> dig: opt; procity:digOpen releases pointer-lock (riffle is
cursor-driven); the unlock->leaveShop guard reads interiorMode.digActive so opening a bin
isn't read as walking out; canvas re-locks for interior walk after close.

Fresh-chromium smoke: E opens the riffle (seeded sleeves + prices render), WALK OUT closes
to the interior, leak-free (steady-state geo/tex flat over 3 open/close cycles), 0 console
errors. Flag off => no listener, dig stays null => byte-identical to v1 (prime law).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 22:03:31 +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
93a3168a99 Lane D round-5: chunk-streamed roster (v2) behind ?roster=stream
Implements the R3 design note (was design-only). Default-off; the v1 whole-graph path is
byte-identical when the flag is off (shell citizen-0 signature matches the R4 golden exactly).

- chunk-local identity keyed (seed, chunkKey, i) — same seed + chunk -> same residents,
  independent of town size / visit order; home edge picked from that chunk's edges.
- windowed spawn/despawn: hook-driven via Lane B's onChunkBuilt/onChunkDisposed (sim auto-
  detects and stops polling) + a camera-poll fallback that needs zero Lane B changes.
- global near-cap + mixer stagger + rig pool + impostor layer unchanged, run across live chunks.
- owner-chunk despawn (no cross-boundary re-keying); per-chunk time-of-day thinning (hours-aware).

Proven on a 9x9 fixture grid: constant far-field density (~58-129 within 70m at spawn AND 150m
out, vs v1's uniform ~12-15); deterministic (507 chunk-keyed identities re-derive from seed);
leak-free (2 full grid walks / ~380 chunk cycles -> 0 geom/tex delta, pool capped 30); budget
unchanged (near-cap 24 + 1 impostor draw, 2.31ms/frame). qa.sh --strict GREEN.

Only web/js/citizens/sim.js + web/citizens_test.html changed. Flag + shell call-site documented
in LANE_D_NOTES for Lane F to wire web/index.html.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 21:07:10 +10:00
m3ultra
2a923b61e6 Lane B round-5 (v2): parallax interior-mapping window glass behind ?winmap=1
The Vuntra window trick — a passing pedestrian sees a plausible room through the shop glass with real
parallax, no geometry. One shared ShaderMaterial does single-cube interior mapping on the existing
window mesh (back wall + seeded shelves + side walls + floor/ceiling); per-shop variation via
aTangent/aTint/aSeed attributes, still 1 window draw/chunk. §3.5 held via the shared uHour/uNight +
aHours: night → open room glows warm, closed room dark.

v2 prime law: WINMAP is read from the URL in buildings.js (self-contained — no index.html seam; F
just needs a flags-table row). Flag-OFF byte-identical to v1 (windowQuad → v1 hoursQuad, v1
windowMaterial). Proven: flag-off windows MeshStandardMaterial w/ no winmap attrs; flag-on interior
ShaderMaterial; worst view 122-168 draws (<=300); 100% procedural (0 fetch delta vs ?noassets
baseline); 0 console errors; GOLDEN.hash untouched. Screenshots in docs/shots/laneB/winmap_*.

qa.sh --strict GREEN (4/4).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 21:00:57 +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
6323d58736 Lane B round-4: closed facades (§3.5) + shot poses/letterbox + furniture GLB + novelty_record
- B1: per-shop closed-facade visuals — dark windows + red CLOSED plate + door tooltip, batched via a
  per-vertex aHours attr + two shared shaders + one uHour/uNight uniform pair on a procity:segment
  event (facade atlas untouched, no per-shop materials, no per-frame poll). 22:00 → only the openLate
  video shop lit, all others CLOSED; ~200 draws worst-view with citizens.
- B2: the shots 'letterbox' was camera-jammed-into-a-wall poses, not a composer bug (composer proven
  correctly sized headless). Reframed street_noon/arcade/warehouse to look down a street
  (streetViewPose), added the 3 missing bookmarks, night_neon now frames the lit video shop amid
  CLOSED neighbours, + a defensive composer resize. shots.py: 10 un-letterboxed shots, 0 errors.
- B3: furniture GLB use-if-ready upgrade (bench + food_cart, sit on their footprints) + novelty_record
  placed at record stores. novelty_record GLB is 26.5k tris (5x glb_law) → stays low-poly primitive
  until Lane E decimates it; ?noassets verified 0 network.

qa.sh --strict GREEN (4/4).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 19:59:27 +10:00
m3ultra
a5e4b64a9d Lane D round-4: in-shell verify (D1) + decimated-ped validation (D2) + impostor tone-map fix (D3)
D1: peds+keepers upgrade placeholder->rig in the shell; 12 enter/exit shops leak-free
(renderer.info.memory back to exact baseline); determinism holds across reload; ?noassets
fully placeholder with zero ped fetches.

D2 (gate-3 critical path): validated Lane E's decimated fleet — all 19 peds <=3k tris
(1564-2805, avg 2450), rigs bind+animate (skinning/skeleton preserved), 0 broken meshes under
eviction churn, silhouettes+identity intact, atlas bakes clean. Near fleet ~59.6k tris at the
24-cap (was ~1.2M) — F clear to re-measure gate 3.

D3: fixed a real bug found only in-shell — the impostor self-tone-mapped (toneMapped:false +
in-shader ACES), correct on the direct-canvas test page but DOUBLE-tone-mapped under the shell's
EffectComposer/OutputPass. Reworked to a standard toneMapped:true material outputting linear via
three's own tonemapping/colorspace chunks, so it matches the near rigs on both render paths.
setExposure is now a no-op (exposure is global). Verified noon+night in-shell.

Only web/js/citizens/impostor.js changed (+ NOTES/progress/R4 shots).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 18:56:28 +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
5b8c806271 Lane A round 4: harden openLate contract (hours>=22 ⟺ openLate == the video), confirm F2
v1-tag round. Two confirmations + one hardening fix; qa.sh --strict GREEN (4/4).

A2 — openLate: F's night gate `hours[1] >= 22` matched 4–15 shops/seed (regular videos
jitter base-21 → 22), not the single landmark the shell assumes. Fixed in plan.js: regular
shops close by LATE_HOUR-1 (21:00); only the deterministically chosen openLate landmark
reaches ≥22, pick is now video-first. So `hours[1] >= 22` and the `openLate` field are the
same singleton, always the video rental (verified 15 seeds). Makes generation match
web/index.html:159-161 + soak.py:68 ("at night only the openLate shop is open"). selfcheck
asserts exactly-one / field⟺threshold / is-video. ALL GREEN 1301/1301.

A1 — Lane F's F2 permittedMax storeys scoping: confirmed 0 warnings across 7 seeds + a
false-negative test (injected video@2 in a scratch checker → caught every seed).

GOLDEN.hash 0x098eec2b → 0x3fa36874: intended A2 output change (a few videos close 21 not
22 for seed 20261990), NOT drift; F2 is tooling-only. Same lots/shops/types/names/skins.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 18:26:30 +10:00
m3ultra
bd40ec7e7a Lane E round 3 (landed by Fable after review): 5 hero props gen'd local + published, local-depot mode, manifest 14 fittings + 8 furniture
- gen_props.py: flux_local -> trellis_mac -> normalize pipeline; 5/8 props kept
  (arcade-cabinet, listening-booth, drinks-fridge, milkshake-mixer, novelty-record)
- magazine-rack -> primitive (thin-wire gotcha); glass-case + bus-shelter -> fal.ai fallback
- cash-register reused from depot; all 22 GLBs live, validate --depot 0/0
- loaders.js ?localdepot=1 + stage_local_depot.py for network-free GLB validation
- qa.sh --strict GREEN 4/4 at commit time

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 18:03:03 +10:00
m3ultra
53f472d0d7 Lane F R3: hours/closed-shops + manifest/rig-fleet/?noassets + --depot + QA harness fixes
§3.5 hours: isOpen() gates enterShop — closed shops locked; at night only the openLate video
rental opens. §3.4: ?noassets (zero-network primitive path, verified via Performance API),
interior fitting GLB upgrades from the 3GOD depot (useGLB), and the rig-fleet upgrade
(models/peds/*.glb) so peds + keepers become shared GLB rigs, leak-free (shared loadGLB cache
proven, not a leak). qa.sh manifest gate now --depot (16 GLBs live) — GREEN 4/4.

Browser QA harness fixes so shots.py/soak.py actually run: DBG async-poll (was racing the async
install), plan via window.PROCITY (DBG.plan unexposed), enterShop bare-id, shop.lot is a lot id,
midday pin (§3.5 else closes shops mid-walk), GLB-cache warmup, budget peaks -> warnings (gate 3
clean pass is authoritative). Gate 2 soak PASS both asset modes; gate 4 asset-free PASS;
docs/shots/v1_tour/ (10 shots) + contact sheet captured.

Findings (LANE_F_NOTES §9): D/E decimate tri-heavy ped GLBs (gate 3 tris ~279k > 200k budget;
draws fine ~200/300). B: add 3 shots bookmark poses, fix headless composer letterbox in
DBG.shot(), render the §3.5 closed-facade visual (predicate window.PROCITY.isOpen).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 17:19:11 +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
fafd5a700a Lane B (Streetscape): round-3 — scene.environment for PBR peds + budget re-measure with citizens
- lighting.js sets scene.environment (neutral PMREM sky->ground gradient, no asset) in attachRenderer, before CitizenSim constructs; Lane D rig-fleet peds bake their impostor atlas from it and render dark if null. Verified set in street/night/interior + after interior-return.
- Re-measured worst continuous-walk view WITH citizens (pop 140, 125 active peds) via window.DBG: 263 scene / 275 total midday, 275 night -- <=300 everywhere. Radius-2 auto already the spawn default.
- Round-3 task 3 (furniture GLB footprints) standby: Fable s3.4 manifest upgrade not yet landed.
- Docs: LANE_B_NOTES.md + B-progress.md round-3 updates.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 16:00:22 +10:00
m3ultra
0985b05790 Lane D (Citizens): round-3 support — setExposure day/night hook + design notes
Fable round-3 §Lane D (all three tasks):
- sim.js: add CitizenSim.setExposure(e) passthrough so mid-tier billboards track the
  shell's per-segment renderer.toneMappingExposure (lighting.js animates it); stored so it
  survives the placeholder->rig atlas re-bake. Verified: uniform tracks 0.55<->2.3 and
  persists across the fleet upgrade.
- Keeper GLB-rig upgrade contract verified leak-free (my side): 15 enter/exit cycles with a
  fleet-backed rig keeper leave renderer.info.memory geo/tex exactly at baseline. Enabling it
  in the shell is one arg (pass `fleet` to KeeperManager) once F's §3.4 lands.
- LANE_D_NOTES: exact Lane F call sites for setExposure + keeper fleet-upgrade, and the
  chunk-streamed roster v1.5 design note (chunk-local identity, windowing, ownership/hand-off,
  budget, opt-in migration) — design only, no implementation.

Code touches only sim.js; no sibling files edited. Determinism still passes, near cap 24,
mixer 0.3ms, no console errors.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 15:55:25 +10:00
m3ultra
b20b3b08f5 Lane A round 3: verify F finding #5 (storeys) stale, harden selfcheck + document corner-anchor rule
Per ROUND3_INSTRUCTIONS 'Lane A — on-call + one verification':
- F finding #5 (shops with storeys outside registry range) verified STALE across 8
  seeds: zero violations. Single-storey types (video/milkbar/stall) never exceed 1;
  the only shops above registry max are the intentional 3-storey corner anchors
  (tall-capable types), exactly per CITY_SPEC. Round-1 cornerBoost gate already
  prevents the reported bug. This is also the source of qa.sh's 5 benign storeys
  warnings (flagged for Lane F to scope to permittedMax).
- Hardened the selfcheck storeys assertion from the too-lax <=max(mx,3) to the exact
  permitted range (max>=2 ? min(max+1,3) : max), so a single-storey type exceeding 1
  now fails loudly. Proven non-vacuous. selfcheck.js only — plan output/fingerprint
  unchanged (golden 0x098eec2b still matches, confirmed per task 2).
- Documented the storeys corner-anchor rule in the CITY_SPEC schema.
- hours/openLate contract unchanged; ready for Lane F's §3.5 consumer.

qa.sh --strict GREEN (4/4). Committed atomically by pathspec (shared-tree race).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 15:52:30 +10:00
m3ultra
aa4ed4fdca Lane F: integration — interiors + keepers + street peds + QA harness
interior_mode.js bridges Lane B's enterShop seam to Lane C buildInterior and Lane D
keepers (spawn at counter keeperStand, greet, leak-free over 5 cycles). CitizenSim
wired into the street loop (pop 140, ~96 active midday, worst ~191 draws). tools/:
qa.sh gate runner (all 4 landed gates GREEN --strict), scaffold/consistency checks,
shots.py + soak.py browser harnesses. docs/shots/ reference tree, LANE_F_NOTES
runbook, V2_IDEAS parking lot.

(F's inline seam edits to index.html/skins.js landed with the Lane B commit.)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 14:29:03 +10:00
m3ultra
5313402e43 Lane B (Streetscape): chunk-streamed walkable town shell
First-person shell: chunk streaming, instanced buildings + facade texture atlas
(22 facade materials -> 1; 5 skin materials city-wide), signs, awnings, furniture,
day/night lighting, minimap, pixel-accurate collision (incl. arbitrary-angle lots),
door raycast -> procity:enterShop, DBG harness, all-fallback mode. Renders Lane A's
full generated town (seed 20261990 'Boolarra Heads', 493 shops); worst continuous-
walk view ~261 draws (<=300 gate). 6 adversarial-review bugs fixed (collision sign,
house doors, awning skin, sign atlas, furniture seed hash, shot-mode restore).

index.html and skins.js include Lane F's inline integration seam edits (marked
'[Lane F integration]'): interior/keeper/citizen wiring + facade filename fix.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 14:28:47 +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
9eb1acc61b Lane D (Citizens): deterministic LOD crowd — rigs/impostors/keepers + test page
Populate the town at city scale, on budget, deterministic per-citizen identity:
- rigs.js — ported 90sDJsim rig stack: mixamorig canonicalisation so one shared
  walk clip drives all 19 peds, head-bone height-normalise, feet-plant, pooled
  near-tier actors with walk<->idle crossfade
- impostor.js — 4-yaw sprite-atlas baker + instanced billboards: whole mid crowd
  in 1 draw call, tone-matched to the ACES near rigs
- sim.js — deterministic roster, footpath lanes off the street graph, near/mid/far
  LOD with hysteresis + hard 24-cap, staggered mixer budget, time-of-day density
- placeholder.js — seeded low-poly box humanoids (frame-one population + ?noassets)
- keepers.js — one keeper per shop at the counter, idle + greet-turn
- 21 ped GLBs (byte-identical from 90sDJsim), citizens_test.html harness,
  LANE_D_NOTES.md, 5 beauty shots

Verified in-browser: 200 citizens at max mixer 0.2ms (budget 2ms), near capped 24,
determinism holds (200 identities match seed), no T-pose, ?noassets crash-free.
Consumes Lane A plan.streets with zero adapter. Six adversarial-review bugs fixed
(non-deterministic fleet order, shared-resource disposal on pool eviction,
impostor colour-management).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 13:43:05 +10:00
m3ultra
42350607a0 Lane C: keeper stand pose on the counter contract (Lane D/F integration)
Answers Lane F §8 (the counter is kind:'counter' in `places`) and closes the
keeper-spawn gap: 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), in room-local
space, rig-front = local -Z per keepers.js. Deterministic; purely additive.

Verified in-browser: 36-build type sweep + 50-room soak — stand in-bounds,
faces customer (dot>0 all), carried on `places` (deep-equal), deterministic;
no change to existing placement, path, leaks, or perf (avg 3.6ms / worst 7.7ms,
geo/tex leak 0).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 13:37:26 +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
m3ultra
8b7ca9aae4 Lane A (CityGen): deterministic town generator, registry, names, 2D map, self-check
Layer-1 CityPlan generator per CITY_SPEC schema v1 — pure data, no THREE, all randomness
through core/prng, deterministic + JSON-serializable + <1ms.

- web/js/citygen/plan.js: generatePlan(citySeed) → CityPlan (spine, cross-streets, market
  square + dept anchor, arcade, warehouse fringe, residential collar w/ corner milkbars,
  laneways) + chunkIndex (Amanatides–Woo edge supercover) + corner overlap-resolution pass.
- web/js/core/registry.js: SHOP_TYPES (all 9) + district weights + enums (shared vocabulary).
- web/js/citygen/names.js + wordlists.js: seeded 90s-AU shop/town names, 50+ patterns.
- web/js/citygen/index.js: canonical barrel entry (adopts Lane F's integration shim).
- web/map.html: standalone Canvas-2D plan debugger (pan/zoom/hover, layers, export JSON).
- web/js/citygen/selfcheck.js: node acceptance harness — determinism, golden fingerprint,
  facing, within- AND cross-block no-overlap, chunk+edge coverage, finiteness, assets. ALL GREEN
  (1271/1271) over 6 seeds. web/package.json ({"type":"module"}) lets node run it.
- docs/shots/laneA/: 5 seeds screenshotted, each reads as a town.

Fixes from an adversarial multi-agent review (15 confirmed defects): market facades faced the
wrong way; cross-block corner overlaps; chunkIndex skipped chunks incl. the origin/spine;
stall frontEdge z-band; doubled-possessive names; unparseable map road colour; +others.

CITY_SPEC amendment (treaty, same commit): shop-types table aligned to the registry lanes
import; documented lot.ry/frontEdge, block.kind/district-id, and web/package.json.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 12:06:41 +10:00
jing
a702a69f1e PROCITY scaffold: spec, research, 6 Opus lane prompts, core modules, vendored three r175, 69 inherited skins
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 10:46:40 +10:00