PROCITY/F-progress.md

755 lines
62 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# PROCITY — Lane F progress (INTEGRATION & QA)
**Session:** 2026-07-14 · **Author:** PROCITY-F · **For review by:** fable
---
## Round 31 (v7.0-alpha CLOSE) — THE DIG FLIP + the tag
**Fable's wave-0 playtest found the one alpha-blocker: the dig was default-OFF on a plain boot.**
`index.html` still carried the v2-era opt-in (`params.has('dig')`) six epochs after the README
claimed "dig on" — and every R30 game-loop gate booted `dig=1` explicitly, so the suite was green
while E at a bin did nothing on the boot every player gets. Fixed with the one-line flip
(`DIG_ON = flagOn('dig')` — ?dig=0 opt-out, ?classic=1 off, `dig` on the flags surface) and fenced
with two new STRICT legs: **default_boot_gate asserts dig REACHABILITY on a NAKED boot** (E at an
aimed bin → digActive true → closed clean) and **classic_regression asserts dig INERT at the bin**
(zero `pcdg-*` DOM, zero Storage calls — measured, not assumed). *Harness lesson #4: the
explicit-flag blindfold — a gate that force-feeds its subject's flag can never notice the default
is broken; every claimed-default-on flag needs at least one flag-ABSENT gate.* Splash de-staled
("Every door opens." — the "(soon)" is over; controls line = E riffle/sell · C crate · SLEEP to
save) + the README manual row. `PROCITY_QA_PORT` override so the suite never silently reuses
whatever holds :8130 (this round: Fable's own preview server).
**The re-gate (final tree, :8931): `qa.sh --strict --matrix` 7 passed · 0 failed · 0 warn ·
0 skipped; flags_check 0/0 incl. both new legs; MATRIX 10 towns green.** Money shot:
`docs/shots/v7_alpha/crate_panel.png` — Monster Robot Party's frontage, THE CRATE with 3 real
finds (real cover thumbs, honest paid/where/day), game bar day 1 · $116 · CRATE 3 · SLEEP, driven
through the real loop with the dig param absent. **Tag `v7.0-alpha` — annotated, LOCAL ONLY**
(Fable pushes). THE BETA QUEUE (Fable's design wants, filed NOT fixed) is in LANE_F_NOTES §31.6.
Commits: `50385c0` (flip + gates + splash) · shot + notes commit (tag target). Full record: §31.
---
## Round 30 (v7.0-alpha — THE SAVE AND THE CRATE) — F: the contract first, then the save core + tomorrow
**The contract shipped before a line of implementation** (`53fcd3c`): `window.PROCITY.game =
{ day, cash, collection[], townKey, save(), load(), sleep(), export(), import(json), wallet,
recordFind(), removeFind() }` + the `procity-save/1` schema + the rotation law, published in
LANE_F_NOTES §30 — and it worked exactly as the order intended: **B picked up §30.4 within the
session** (venue.js "tonight" already reads `gigs.weekNight`; hud.js collection/SLEEP surface in
flight), and C's already-published §9 asks (`type` stamped at buy, `title`/`artist`, removal via the
game API, `wallet.sell` mirrored) are honoured in the entry shape.
**Then the layer** (`207bcff`): `web/js/world/save.js` (F-owned, new) — versioned localStorage save,
THE DELTA LAW BY SHAPE (payload keys are exactly `[cash, collection, day, savedAt, schema, town]`
no world field can even be expressed); corrupted/foreign → **loud reject, raw stashed, fresh start,
town boots**; save on sleep + beforeunload; export/import for moving machines. The wallet is a
game-backed facade of C's exact v0 interface, so every proven seam (dig onBuy, shelf buy, cover,
C's onSell) is unchanged — cash just became authoritative from the save. Buy seams record finds;
`?classic=1`/`?game=0` never construct the layer — **ZERO Storage calls, instrumented, not trusted**.
**SLEEP=TOMORROW:** `sleep()` → day+1 → gig `weekNight` re-keys the EXISTING week schedule → wake at
DAWN → save. Stock rotation is a runtime day salt on the EXISTING `stk-*` streams (2 marked seam
lines in C's interiors/layout) + the dig's `binSeed`**plan generation never sees `day`** (A's #6
boundary). **Day 1 IS the pre-v7 town** (measured: `?game=0` stock fp == fresh-game day-1 fp), and
**REAL-sourced crates never rotate** (Monster Robot fp identical across sleeps; the mint crate rotates).
**Two amendments filed loudly, with measurement, not slipped in:** gig night = **(day 1) % 7**, not
the brief's literal `day % 7` (day starts at 1 — the literal form flips a fresh boot's gigs off night 0,
breaking the R13 cover/band gates before the player ever sleeps); and sleep saves AFTER incrementing
(disk always carries the morning you woke into). Fable ratifies or overrules at review.
**Numbers:** 7 verify legs green in fresh no-store contexts — $12 dig buy == pricePaid recorded · sell
halves credit/remove correctly · parody fp `3396372961 → 1818111125` across a sleep, byte-identical
same-day AND across reboots · corrupt blob = 1 loud reject + stash + clean boot · `qa.sh --strict`
**6 passed · 0 failed · 0 warn** with the game layer default-ON. Two F-harness bugs caught and fixed
in the run (the post-sleep CLOSED door; a transform-based fingerprint that was vacuous over batched
parody stock — hash the VERTEX data). Full record: `LANE_F_NOTES` §30.
**Held for the later F session (per the brief):** ledger #5 gates + the sell-card E-key routing.
### Wave 3 — THE GATES SESSION: the routing landed, five STRICT gates, the loop closes end-to-end
**The held join is in.** E inside a shop now routes: **aimed bin → dig · near counter with sellables →
C's sell card · forgiving nearest-bin → dig · shelf-buy** (`interior_mode.js`; 3 marked shell seams in
`index.html``procity:sellOpen` unlock + `sellActive` on the Esc/relock guards, the dig's exact
contract). `onSell` does F's two §9.3 halves in the safe order: `removeFind` first (veto ⇒ nothing
moves), credit second. **The loop, measured through the real DOM:** dig pull **$9 debited** → walk to
the counter → E → card → SELL **+$4** (< $9, the clamp) → collection 1→0 → street HUD **$186 ·
CRATE 0**. One amendment filed with its measurement: the old nearest-bin fallback ignored aim and would
have swallowed E at any counter near a bin — aimed-raycast now outranks the sell trigger, the
forgiving pick runs after it; both branches proven live (Moe Vinyl counter: card opens; Kelpie Sound
bin, wrong aim: dig opens).
**The gates (ledger #5), all landed as STRICT suite smokes in `flags_check.py` — no more session-local
instruments:** (a) **save/load determinism** — scripted buy 2/sell 1/sleep 2× via B's real button →
export → fresh browser → import **byte-equal** (state 243 B, normalized export 269 B), plan fp
`0xb1c7843e` **unchanged** across the import, weekNight re-keyed 2; (b) **no-pump v0** — 5 real
UI round trips **all strictly negative**$30), plus **C's negative control**: broke player,
`wallet.buy` invoked-and-false on the real shelf path ⇒ **recordFind unreachable, zero entry** (dig BUY
disables outright), with a positive arm proving the control discriminates; (c) **classic purity**
Storage-prototype instrument pre-page-script: `?classic=1`/`?game=0` **0 calls, 0 game DOM ids**,
positive control sees the default boot's `procity-save` read + all 7 ids, plan fp game-on == game-off;
(d) **rotation determinism over VERTEX DATA** (the wave-3 binding) — day 1/2/3 fps all distinct,
re-entry byte-identical each day, **cross-boot byte-identical off the persisted save**, `?game=0` ==
day 1 (88,320 hashed bytes / 20 stock meshes printed — the subject provably present). Harness bug #3
caught en route: the HUD is hidden in interiors by design; the gate read it on the street.
**NO TAG** — per the brief, the alpha tags when it plays (Fable's call, likely R31). Full record:
`LANE_F_NOTES` §30.6.
---
## Round 26 (v5.0-beta — EVERY CRATE DIFFERENT) — F: the manifest consumed, the tolerance retired
**Every crate is different, and F measured it rather than taking G's word.** Not by entering one lucky
shop — from the indexes of record: **15 keyed crates, 105 pairs, 0 sharing a title**; **0 crates overlap
the generic v2 pack**; **0 ids reused across crates** (G's sku fix holds at scale — the R24 vacuity dead
at the source, not papered over in F's gate). 4 crates in Red Hill + 11 in Newtown = the manifest's 15
exactly, and the manifest lists nothing that isn't on disk.
**Sourcing, not tier — C was right.** `tier` is the charter's ladder rung, and a mint atlas is *also* rung
1: still a file, still offline, still deterministic. What differs is where the stock came from, which is
`sourcing: real|mint`. E built to C's contract over the brief on the principle that the contract's author
owns the contract's shape. F's gate asserts it: 15/15 declare sourcing, every index agrees with the
manifest, **a mint crate can never report as real**, and the gate PRINTS it per room — provenance a human
never reads isn't doing anything (the R24 licence lesson, one round on).
**The 404 tolerance is deleted, not tuned.** R24 probed every keyed shop and ate a 404 per atlas-less one;
F's smoke could only cope by *naming* them — and a tolerance is a confession. F filed the real fix as a
contract ask instead of inventing it; G built the manifest; **F now consumes it**, and the shell asks for
nothing that isn't listed. Zero 404s because nothing absent gets asked for, not because the gate stopped
looking. **And deleting the tolerance immediately caught a leftover**: F had wired the *boot* preload but
left a second blind probe at the *door*, which 404'd on every keyed-but-unfetchable shop. While the gate
said "8 probes, expected", a ninth would have been invisible too. A named exception protects new bugs the
moment the real fix exists.
**Gate: `flags_check` GREEN** — distinctness · sourcing (real + mint rooms) · soft fall silent · **ZERO
404s** · **0 console errors, no attribution exceptions**. Full record: `LANE_F_NOTES` §26.
---
## Round 24 (v5.0-alpha — THE ALPHA COMPLETION) — **the crate WALKS; F held the tag on A's gate**
**THE CRATE WALKS.** Boot `redhill_godverse`, walk down Musgrave Road into **Monster Robot Party**,
riffle **its own real crate**, buy a real record at its real price. Proven falsifiably: room base =
`assets/stock_godverse/3962749/` · **titles 120/120 in its atlas, 0/120 in the generic pack** · **the
GPU samples `stock_godverse/3962749/stock_record_atlas_0*.webp`, 0 textures from the generic pack** ·
bought *"Abstract Latin Lounge II (Part II)"* **@ $15**, cash **191 → 176**, exact debit, count 0→1 ·
reboot restores 191/0 (gone-this-session, back next boot). Ladder re-proved: `?noassets` → **zero
network requests**; keyed-but-atlas-less shop 2289 → 404 → **parody, live**; `newtown_real` → base
`null`, 350 items — **F's wire is a no-op off the godverse path**. Full record: `LANE_F_NOTES` §24.
**F held `v5.0-alpha` anyway. The tree is red — 156,193/156,201 — and none of it is the product.**
2 reds are goldens G's re-emit legitimately moved (A ran *before* G, so A could not pin them). 6 are
**conflict #2: A's identity sweep contradicts Fable's own standing Ruling 2**, which *mandates* mixed
caches ("merge census heroes **WITH** the donor's texture shops"). A cafe from OSM has no GODVERSE
identity and never will. G measured it, escalated it to "A's call, Fable's ruling", nobody ruled, and
it arrived at F.
**F measured the hazard that gate guards: it does not exist.** 10/10 and 18/18 unique ids, **zero
duplicates**; and partial keying does not mis-stock — F watched 2289 fall soft to parody in the
browser, exactly as charter risk #3 prescribes. **A's own two checks disagree**`validateTownCache`
reasons it correctly ("Missing = tier 0 (soft). Duplicate = mis-stocked (hard). They are not the same
failure") and warns; the sweep hard-fails the same condition.
**Why F didn't just fix it: F is the party that benefits.** Changing a red gate to green, in another
lane's file, to unblock F's own tag, on the one check guarding "never mis-stocked" with Monster Robot's
real crate as the prize — that move would retroactively devalue every green F has ever reported. The
cost of waiting is an hour. Recommended (A's call): the sweep follows A's own validator; A pins the
four goldens in the same commit; F re-gates and tags. One commit.
**Four findings:**
1. **The honest-gate spec was itself vacuous.** The brief specified id equality — but both packs number
items **positionally** (`rec_0000…`), so **all 120 atlas ids are also generic-pack ids**. An
id-equality gate goes green on the generic pack: the exact failure the vacuous-gate law was written
to stop, reproduced inside its own first application. Titles (0 overlap) and the **texture URL**
the file the pixels come from — are what discriminate. F's gate asserts on those.
2. **F's own selector was lying about the epoch's shop.** `DBG.enterShop(3962749)` → "no shop matching":
a GODVERSE shop has two ids and F's R22 selector knew only the plan id. Fixed.
3. **A's uniqueness check is a silent risk-#3 hole** (new — G didn't catch this): on a mixed cache every
texture shop maps to `undefined` and they **all collapse into one Set entry**, so it fails on the
undefineds and **cannot detect a real duplicate**. Fix it *over the defined ids* — that makes the
guard stronger, not weaker.
4. **The licence flag stopped reaching a human.** E's validator now sees the atlas (R23's blind gate is
closed ✓) but prints `provenance.licence` (British) while G now emits `license` (American, per C) —
so `FLAG BEFORE ANY PUBLIC/COMMERCIAL RELEASE`, on real product photos, **silently never prints**
while the gate says OK. E, one line, G already specified it.
---
## Round 23 (v5.0-alpha — THE FIRST REAL CRATE) — **F HELD the tag**
**`v5.0-alpha` is NOT tagged. The crate is real; it has no street.** G's 120 real records off a real
shop's POS are in the tree and byte-identically re-bakeable — genuinely the artifact the charter asked
for. Nothing in the game can reach them. `#7a` isn't a gate that failed, it's a gate that **cannot be
run**. R18's law: *release rounds don't ship known breaches.* Full record: `LANE_F_NOTES` §23.
**F executed the merge that never happened** (charter law #5) — `main` had no `stock_godverse/**` at all,
so the branch F was asked to tag lacked the round's headline. Review had happened, C had verified
pre-merge and passed; only the `git merge` was missing. Disjointness verified before merging, not
assumed. Zero conflicts → `43b035a`.
**What passed — everything except the crate.** `qa.sh --strict --matrix` **7 passed · 0 failed · 0 warn**;
**MATRIX GREEN — 10 towns × 7 gates**; selfcheck **ALL GREEN 152,015/152,015** (was 161,300 — E retired
toowoomba); flags harness green, so the `?classic=1` covenant still holds through the merge. Tier 0
`?noassets`**zero fetches, zero console errors** (the offline law holds); tier 0 default boot on the
godverse town; tier 1 (v2 town-wide) `?stock=real`. **Tier 1 (v5, per-shop) — the round's whole
deliverable — is never fetched.** A, D, E and C's work is sound; only the rung this round was built to
add is unattached.
**Three findings, in order of how much they cost:**
1. **The atlas contract contradicts itself.** `LANE_C_PUB:271` publishes `stock_shop_<id>_index.json`;
C's own loader at `stockpack.js:25` fetches `${base}stock_${type}_index.json`. **Incompatible.** G
built runtime-compatible (so C's base-override check passed); E built contract-compatible (so E's
validator can't see it). **Nobody was wrong — the contract was.** F recommends amending to G's shape;
it's the only one the runtime can load.
2. **The gate built to catch that was blind and green.** `validate_atlas.py` globs a filename that can't
exist at runtime, finds nothing, and `return 0` — so it passes whether the atlas is absent, misnamed,
or perfect. It had never seen one until F pointed it at G's by hand, which then surfaced *deeper* drift
(E's code reads provenance top-level, E's own docstring says nested, G nested it; `licence` vs
`license`; a ceiling stale by one round). *A gate that returns 0 on an empty set is not a gate.*
3. **`#7a` would have passed vacuously.** F walked into The Vintage Record (real shop, real road) and the
crates filled with **real covers — from E's 350-item town-wide v2 pack**, not the crate. Zero requests
to `stock_godverse/`, ever. Eyeballed as written, the gate goes green and proves nothing.
**R24 order — three gaps, none F-only:** **A+G** land a `godverse_id` (verified: *every* godverse shop
reports `godverse_id: null`; the schema has no such field, and C's rule is "C does not invent it") →
**E** builds `redhill_real` (one Overpass fetch — E's file, outward-facing; **not F's to invent**) →
**C** keys the pack cache by `type+base` (today a cache hit returns before `base` is read, so two stocked
shops collide *and fail-soft breaks*) → **F** passes the per-shop base and re-gates. F deliberately did
**not** build its half: untestable with no street and no identity field. Filed, not guessed.
**Also:** A's spacing warn earned on day one — with toowoomba retired, **ballarat now warns at 255 m**.
The thin tail has a second member.
---
## Round 22 (v4.0 — THE EPOCH CLOSE) — F closed it
- **The blocking gate CLOSED.** F's R21 re-measure made the tri diet blocking; B diagnosed, the driver
traced A→**E**, E fixed it, F re-measured: katoomba `venue_night` **221,935 → 108,365 (51%)**, worst town
124,682 — the 200k stress edge is closed. Selfcheck **161,300/161,300** (A's 36 goldens + 23 towns).
- **`DBG.enterShop(selector)`** (Lane G's ask) — id · type · exact name · substring, most-specific-first.
- **The release tour** → [`docs/shots/release_v4/`](docs/shots/release_v4/): Fremantle hero · Fitzroy mecca ·
Toowoomba thin tail · synthetic control · `?classic=1` covenant. All posed through F's R21 cluster
primitive — which earned itself twice: the first cut posed via `street_noon` and **the mecca came out as
bare bitumen**; re-posed through the cluster, Fitzroy reads.
- **⚠ Honest finding (filed v4.x):** the thin tail does **not** prove the floor holds — Toowoomba reads as an
empty road. The floor is a **ratio**, not a count: 0.008 shops/edge vs the heroes' 0.060.08. The frame
ships labelled honestly rather than swapped for a flattering town.
- Docs (README v4 "the towns are the product", notes §22), `qa.sh --strict --matrix` green, tag **`v4.0`**.
---
## Round 2021 (v4.0-beta THE FIVE TOWNS) — F never ran R20; closed R21
**R20: F never ran** (no gate, no tag) — the honest record is in LANE_F_NOTES §20. Unlike R18 (where F ran
and *held* on evidence), the slot simply didn't execute; the round also left C's door-soak, B's selector
index, and D's relocation finding open. R21 closed all four.
**R21: the beta gate.** Waves [A,B,E] → [C,D] → [F]. All five lanes in; F gated:
- **Matrix 10 towns × 7 gates** — boot/determ/district/roads/**tram**/noassets green on all ten; selfcheck
154141/154141; `qa.sh --strict` GREEN.
- **Tram dimension (new)** — B's R20 ruling verified exactly: Newtown runs 16 stops (was 149 — B's phantom
fix), Castlemaine 10; Katoomba/Fremantle/Bendigo **fenced** at 3/2/1 shops fronted. Needed a new seam:
the shell now exposes `window.PROCITY.tram` (B built `routeInfo` for F's smoke; nothing published it).
- **⚠ venue_night re-measure — recorded, premise falsified.** A's cap worked (posters 438→111) but the
density widening ate it: katoomba **200,965 (+965) → 221,935 (+21,935, +11%)**; bendigo 201k. Draws fine
(151/300). Filed as R22's first item (the tri-diet backlog already lives there).
- **Selector ruling** — always in the HUD (the towns *are* the product), + the corollary that F's harness
must hide the HUD for beauty shots (the R19 alpha shot's selector-in-frame was F's own gap).
- **Beta money shot** — [Fremantle's real street at golden hour](docs/shots/v4_beta/fremantle_mainstreet.png):
a corridor of shopfronts, footpaths, a crossing, and a genuinely busy crowd. Tag **`v4.0-beta`**.
---
## Round 1819 (v4.0-alpha REAL ROADS) — F held R18 correctly, then closed R19
**R18: F did NOT run — the gate would have failed by design, so F held the tag** and filed the failure list
(the alpha's charter deliverable). Real roads landed structurally but the selfcheck was RED (katoomba golden
unpinned + poster-kerb breach on all 5 real towns) and the town was fragmented (20 shops over 799 edges = an
empty field). Classic/default stayed frozen — the alpha changed nothing outside the cache path. Fable's
verdict agreed: "F never ran — correctly."
**R19: the close.** A resolved the findings (fragmentation cull/join, poster clamp, goldens pinned), D
re-measured, B shipped the town selector. F ran:
- **B's `BIG_CITY` one-liner** (`e6fcb7b`, early): `shops>120 || edges>200` — real Katoomba's 799-edge graph
now streams like a big city. **280k → ~12k tris.** B proved it; F owns index.html.
- **The alpha gate** — `town_matrix.py` grew a `roads` dimension (a v2 cache must build the REAL graph,
`mode='roads'` via the plan_osm report sink); classic/default proven untouched; budgets in the green.
- **The money shot** — [Katoomba's real main street](docs/shots/v4_alpha/katoomba_mainstreet.png): asphalt +
lane markings, footpaths, a pedestrian crossing, a walking crowd, gig posters, B's town selector in-frame.
**PROCITY's look on real bones.** Tag **`v4.0-alpha`**. (Honest note: 19 real shops sit ~1/edge — a real
*street*, not yet a dense strip; density is a v4.0-beta concern.)
---
## Round 17 (v3.2 THE TAIL + THE SCOUT) — F: the town matrix + the close
The tail drained (drummer lean + bench-sit, wind sway, backline verify, --verify fix) and E's real-map scout
landed (5 real AU town caches + A's cache contract + hardened plan_osm). **F closed it.**
- **Real towns boot in the engine** — the barrel re-exports `registerTownCache`; the shell fetches +
registers `assets/towns/<key>.json` before the plan generates (fail-soft; opt-in; never on default/classic).
`?plansrc=osm&town=bendigo_real`**Bendigo, 9 shops, 3-venue district, 0 errors**.
- **Town-matrix smoke** (`tools/qa/town_matrix.py`, `qa.sh --matrix`) — **10 towns × 5 gates all green**:
2 synthetic + 3 fixtures + 5 real caches, each boot / deterministic / in-budget (69216 draws) / a 3-venue
district / silent under `?noassets`. Castlemaine at the 6-shop floor still lands a district. **The existing
CityPlan contract eats real Australian geography** — the v4 = THE REAL MAP evidence.
- Close: `qa.sh --strict --soak` green, docs (LANE_F_NOTES §17, README), tag **`v3.2`**.
---
## Round 16 (v3.1 THE FLIP) — F bookends; the town shows its best by default
The gig layer soaked through three v3 stages, so v3.1 flips it (and the mature v2 flags) ON by default.
F owns the flip; AE landed their polish against it (seated drummer, town-wide pools, backline, corner-poster).
**Opening commit `a4946b7` (the round's foundation):**
- **The flip** — `index.html` `CLASSIC` + `flagOn()`: gigs/weather/tram default-on, `loadPedFleet({sit:!CLASSIC})`,
`window.PROCITY.flags`; `buildings.js` winmap default-on + classic-aware. **`?classic=1`** forces the exact v2 boot.
- **The covenant moved** — `flags_off_regression → classic_regression` (byte-identical `0x3fa36874`); a new
`default_boot_gate` (all four flags on, gig layer present, gig golden `0xb1d48ea1`, budget); every gig smoke
re-pointed to the DEFAULT boot; qa.sh gate renamed.
- **Seated-stature gate** (ledger #3) — `interior_scale_check.py` + `flags_check.py` accept `procitySeated` figs
at [0.9,1.5] m; the drummer passes at **1.32 m**.
Verified: `?classic` byte-identical (0x3fa36874, gig absent); default = full town (3 venues / 3 genres, gig golden,
112 draws @ venue block); classic/default/district/continuity/cover all green. **Close: re-baseline, re-shot tour
`release_v3_1/` + a `?classic=1` before/after, docs (CITY_SPEC v3.1 flip+classic law, README flags table,
LANE_F_NOTES §16), tag `v3.1`.**
---
## Round 15 (v3.0 RELEASE CLOSE, `?gigs=1`) — F ran last; the release is closed
R14 landed AE's hardening but **F never ran**, so v3.0 stayed untagged. R15 = the close. All deps were in
before F ran (A's poster flip + new golden `0x4f4a549d`, D's amp + R14 continuity seam, B/C verifies). The
six-item brief, each committed atomically:
- **Continuity wiring + smoke** (`687d23f`) — the ped you followed is in the crowd: `crew.spawn({roster})` +
the queue `onAdmit → recordVenueEntry` relay; `smoke_continuity` asserts the seam on identities + settled tris.
- **Alias deletion** (`146b5f3`) — the R12 scalar alias retired; every read keyed by `venueShopId`; tools + spec migrated.
- **Full-week soak** (`edc0c47`, `tools/qa/week_soak.py`) — 7 nights × all venues: cover/roster per night, leak-free (plateau), 28 cycles.
- **Money-shot tour** (`6a35b29`, `tools/qa/tour_shots.py`) — `docs/shots/release_v3/` + contact.html; hero `queue_night` shows A's now-visible poster; settled measurements.
- **Docs** — README v3 section, LANE_F_NOTES §14/§15, this note, five freezes confirmed.
- **Acceptance + tag** — `qa.sh --strict` 6/6 + continuity smoke + week soak green → tag **`v3.0`**.
**Ledger #5 vindicated C:** the gig interior settles to **~182k tris** after the 5 instrument GLBs load (not
the ~34k measured pre-load); the interior gate is **draws** (≤56), so no budget violation — the tour + smoke
now measure settled. Full runbook: [`LANE_F_NOTES.md §15`](docs/LANES/LANE_F_NOTES.md).
---
## Round 13 (v3.0-beta — THE DISTRICT, `?gigs=1`) — WIRED + VERIFIED + TAGGED
AE landed, then D (4-piece + instruments + queue + RY fix). F wired the district together and it fires.
**Seed 20261990 → "Boolarra Heads": 3 venues, 2 playing tonight — The Exchange Hotel (pub, `gig-pubrock`)
+ an RSL (`gig-covers`), one dark (reads true).** All 7 F tasks done, `qa.sh --strict` GREEN (6/6, 0 warn),
tag **`v3.0-beta`**. Shots: `docs/shots/laneF/r13_v3beta_gig_interior.png` + `r13_v3beta_venue_night.png`.
**What F did (ROUND13 §Lane F):**
1. **Per-venue state machine**`gig_state.js` rewritten: one latch per venue off the shared
`procity:segment` spine, keyed by `venueShopId`; all three hard-won R12 laws preserved. New runtime
surface `window.PROCITY.gigs.byVenue` + per-venue getters (`onOf/openOf/gigOf/coverOf/paidOf…`); the
R12 single-venue alias kept one round. B's `venue.js`/`audio.js` + D's `setGig` read it.
2. **Walla + applause** — the crowd bed `crowd-walla` rides the interior tone layer under the live band bed
while `on`; an `applause` sting fires once on the `on→done` edge while inside. Verified: `iMusic=gig-pubrock`,
`iTone=crowd-walla`.
3. **Cover per venue** — the door seam keys off the shop entered, so a night at two venues is two covers.
4. **Debt #1 closed — was a LIVE BUG** — E renamed `pubrock-live→gig-pubrock`, so F's R12 `GIG_BED` bridge
(rewrote `gig-pubrock→pubrock-live`) pointed at a dead key → the band would have mimed in silence.
Deleted; the room's `audio.gigKey` (already the manifest key) feeds the engine directly.
5. **Stature gate**`interior_scale_check.py` now measures stature (crownfoot, platform-independent),
ceiling-clip assert kept as its own line (debt #6).
6. **District smokes**`smoke_gigs`: band→4, a per-venue loop (every gigKey resolves across 3 genres,
crowd ≤ its own watchPoints, no-giants), street budget at the busiest venue block, the queue assert,
plus the R12 determinism/cover/noassets/flags-off arms. All green.
7. **The outdoor queue** — D shipped `VenueQueue` and left the street-side lifecycle to F (its documented
seam): F wires one queue per venue in the shell (spawn at doors off B's `queueZoneFor`, drive each frame,
dispose by `done`), exposed as `queueCountOf(id)`. Verified: 2 in line at doors → 0 by done.
**Numbers (seed 20261990):** gig interior 62 draws / 34k tris (≤350) · street @ venue block 107 draws /
47k tris (≤300/200k) · band+crowd stature 1.611.81 m under a 4 m ceiling · cover $10 debited-once/re-entry-
free/broke-knockback + free night (seed 1234) · flags-off fingerprint 0x3fa36874 unchanged, gig layer wholly
absent. Full runbook + the R13 debt-ledger closeout in [`LANE_F_NOTES.md §13`](docs/LANES/LANE_F_NOTES.md).
---
## Round 12 (v3.0-alpha — the gig layer, `?gigs=1`) — WIRED + VERIFIED
AE all landed before me, so F did what F does: turned five parts into one Friday night. **Seed
20261990 → "Boolarra Heads", venue = The Thornbury Hotel, tonight = SCREAMING UTES, $10 on the door.**
Acceptance shot: `docs/shots/laneF/r12_v3alpha_gig.png`.
| F task (ROUND12 §Lane F) | result |
|---|---|
| **1 — gig state machine** | ✅ new F-owned `web/js/world/gig_state.js`. `quiet → doors (DUSK) → on (NIGHT) → done (DAWN) → quiet`, verified forwards AND backwards on the hand-driven clock. On `window.PROCITY.gigs`. |
| **2 — `?gigs=1` seam + flags-off** | ✅ shell bootstrap fetches `custom_bands.json` (no fetch when flag-off or `?noassets=1`) → `generatePlanFor(seed, src, {gigs:true, customBands})`. Flags-off verified **wholly absent**: no state machine, no `plan.gigs`/`posters`, no pub, no venue geometry, zero band-file fetches, golden `0x3fa36874` unmoved. |
| **3 — cover charge** | ✅ all three halves of John's ruling verified live: **$10 debited once** (191→181, `cover — Screaming Utes` in the wallet), **re-entry the same night free** (181→181), **broke on a new night → polite knockback, no entry**. Free nights (seed 1234) walk straight in, wallet untouched. Zero new UI — rides the R8 buy seam. |
| **4 — audio wiring** | ✅ gig bed inside via C's `gigKey`, B's muffled spill outside reads `PROCITY.gigs.state`. **Found + bridged a real C↔E key mismatch — see below.** |
| **5 — smokes** | ✅ `smoke_gigs` in `flags_check.py`: schedule determinism · band==3 · crowd ≤ watchPoints · dance mix · bed resolves · ≤350 draws · no-giants · `?noassets=1`+`?gigs=1`. Plus the v3 arm of the flags-off regression. |
| **6 — shot + tag** | ✅ shot captured. Tag pending the harness run (below). |
**Numbers.** Gig-night interior **4250 draws** with the rig fleet, **171 draws** on `?noassets=1`
placeholders (worst; budget ≤350) · ~40k tris · street at the venue 69 draws/29.6k tris · **leak-free**
(geo 66→66, tex 63→63 over 4 gig cycles) · band 3 + crowd 8/8 watch points, **3 dancing / 5 standing**
· band stature 1.681.77 m, crowd 1.601.77 m, tallest crown 2.09 m under a 4.0 m ceiling.
**John's drop-in works:** 7 of your 10 hand-written names fill all 7 nights (Screaming Utes, The Dead
Ringers, The Rissoles, The Flannel Shirts, Boganvillea, Chunderbolt, Amyl & The Servos). Covers rolled
`10,0,0,3,2,0,9` — 3 free of 7. Under `?noassets=1` it falls back to the pure generator ("The Wombatts")
with zero fetch, exactly as A specced.
### Three bugs F found in its own work (all caught by verification, fixed, re-verified)
1. **The state machine only advanced when polled.** Driving NIGHT→DAWN→MORNING→NIGHT with nothing
reading in between (player inside the pub, audio muted) **skipped the night roll** — the cover was
never due again. Now it rides `procity:segment`, which `lighting.js` fires on every change.
2. **Rewinding the clock left `done` stuck** on a midday town (the latch only cleared at MORNING).
3. **The night only rolled if the clock passed through DAWN** — so a *jump* from NIGHT to MORNING left
last night's paid stamp valid and **a broke punter walked straight in**. Caught by my own smoke, not
by hand: stepping with `[`/`]` always crosses DAWN, so only jumps (DBG/shots/soak) exposed it. The
roll now keys on landing in the daytime, not on having observed one particular segment.
Also worth recording, because it nearly shipped a wrong gate: **"no giants" must measure stature, not
world crown.** The band stands on C's 0.32 m deck, so a perfectly human 1.73 m guitarist has a 2.05 m
world crown and the naive R10 check **failed a correct band**. The smoke now measures `crown base.y`
and keeps the world-crown assert only for "never clips the ceiling" — the real R10 failure mode.
### Three findings for other lanes (detail + rulings in `LANE_F_NOTES.md` §12)
1. **⚠ C ↔ E: the gig bed key never met.** C emits `gig-pubrock` (and asked E for that name); E shipped
**`pubrock-live`**. Unresolved, the pub plays to a **silent room** and — because the audio law fails
soft — *nothing errors*, you just get a band miming. **F bridged it at the glue** so alpha ships.
This is the R1 facade-skin filename trap again. → **needs a named contract line in CITY_SPEC.**
2. **⚠ D + E: E's 5 instrument GLBs landed; nothing wires them.** D holds primitives and published
`instrumentFor` as the seam "when they land" — they've landed, nobody owns the join. Non-blocking
(asset law; reads fine in the shot). F did **not** silently do it inside D's rig. → R13.
3. **⚠ spec ↔ C: the band has no drummer.** ROUND12 §D.1 says "drummer seated bob" and E built a merged
kit; C's `bandPoses` are guitar/**vocal**/bass and the drum riser is set-dressing. D flagged it and
built to C's poses. A spec/implementation divergence **no gate can catch** → Fable/John to rule.
### Harness note
This MacBook had **no Chromium/Chrome**, so the Playwright gates couldn't run locally; John authorised
the install (done — 93.5 MB, Playwright CDN). `/Volumes/m3ultra/Documents/procity` is a **mount of
m3ultra's `~/Documents/procity`** (same tree), so `ssh m3ultra@100.89.131.57` runs the real gates on the
real files — but **node isn't on the non-interactive ssh PATH**: use
`env PATH=/opt/homebrew/bin:$PATH tools/qa.sh --strict` or every node gate reports a bogus FAIL.
---
## Round 11 (the audio round) — DONE ✅ (v2.1)
John greenlit "generate freely". E generated the pack (23 assets, `web/assets/audio/`, 8.7 MB ≤ 25),
B built the WebAudio engine (`audio.js`, self-unlocking, self-ticking), C added `room.audio =
{musicKey,toneKey}` to buildInterior. **Lane F wired + verified + tagged v2.1:**
| task | result |
|---|---|
| **F1 — interior beds** | ✅ `interior_mode` enter → `PROCITY.audio.playInterior(current.audio)` (fade in), exit → `stopInterior()` (0.7 s fade). Guarded/no-op when muted/noassets/pre-gesture. 2 lines, the seam B/C designed for. |
| **F2 — audio smokes** | ✅ new `smoke_audio` in the flags harness (fresh Chromium): **pre-gesture silence** (ctx not unlocked), **0 boot errors**, **?mute=1** muted, **?noassets=1 zero audio fetches**, **enter/exit leak-free** (12 cycles, 8 shops with a music bed, live AudioNodes bounded at 5, made 42). Caught + fixed my own gesture-via-mouse-click tripping PointerLockControls in headless → gesture via keyboard. |
| **F3 — soak + tag** | ✅ soak with audio on **PASS** (heap 48 MB stable, 0 errors, no drift). `qa.sh --strict` **GREEN 6/6**. **v2.1 tagged + pushed.** |
**Cross-lane (F's own gate, noted to B):** B's `audio.js:143,145` use `Math.random()` for footstep SFX
variant + gain jitter — legitimate transient runtime FX (which shop plays which bed is seeded upstream
in `interiors.js`), but it tripped F's generation-determinism `Math.random` ban. F **scoped the ban
correctly**: `web/js/world/audio.js` is runtime *playback*, not generation → exempted in
`scaffold_check.mjs` (B may instead add a `// cosmetic` marker; either is fine).
---
## Round 10 (fix the R9 giant-rig blocker, re-shoot, re-tag) — verify + ship
R9 shipped a blocking regression: interior rig figures came out ~2× room scale (keeper head 3.83 m in
a 3.4 m room) because `rigs.js buildFigure` normalized off the head bone's **bind-pose** Y. **Lane D
fixed it** (R10, commit `3432fcc`): normalize by the feet→crown **span**, plus a new `qa.sh --strict`
no-giants gate (`interior_scale_check.py`). Lane F verify + re-shoot + soak + re-tag:
| task | result |
|---|---|
| **F1 — verify the fix** | ✅ `qa.sh --strict` **GREEN 6/6** incl. the new scale gate: **24 interior figs / 6 shops all crown ∈ [1.4,2.0] m and under-ceiling** (Second Time Redfern, the giant scene: 1.661.86 m). Live-measured too — the 3.83 m I first saw was a **stale ES-module cache in a day-old browser tab**, not a regression (served + fresh-chromium code both correct). |
| **F2 — re-shoot** | ✅ `browsers.png` re-shot: browser at the rack + keeper at the counter, **human-sized** (*figures human-sized vs door/fittings: yes*). `night_crowd.png` re-framed to a readable "lit VIDEO REGAL amid CLOSED town" — **see the honest note below.** |
| **F3 — soak** | ✅ full-default soak **PASS**: 37 chunks, 20 shops, **heap 54 MB stable (no perf drift** from the keeper fix — it's a build-time scale calc), draws 248, **0 errors**. Tri budget still ~327k > 200k (pre-existing rig-model item, D/E decimation — not an R10 change, not the blocker). |
| **F4/6 — docs** | ✅ tour README updated with stable v2 filenames + captions; this addendum is the R10 release note. |
| **F5 — tag** | ⏸ `v2.0` annotated tag staged on the fixed commit; **holding the push for John** given the night_crowd finding (a public release + his flagged money shot). |
**Honest finding — `night_crowd` has no crowd, and it can't without new work.** John flagged it as
near-black/empty. Re-framed it readable (clean night, tight on the neon). But a visible *street* crowd
does not form: Lane D's night patronage parks ducked-in patrons **hidden inside** the shop (they are
the interior browsers in `browsers.png`), and seed 20261990's video shop is a **low-traffic corner**
(tested default streamed roster + patronage-on at night: **0 patronage, 0 visible peds** at the shop).
This is not a framing bug and not (clearly) a regression — it's that the "night crowd" was always the
*interior* presence. A visible outdoor queue is a **v3 Lane-D mechanic** (loiter-outside-the-openLate-
shop). The ~20 fps in night captures is a **headless software-GL artifact** (128 draws / 65k tris is light).
→ Flagged for D: confirm night patronage at the openLate shop still behaves as the v2.0-rc notes claim.
---
## Round 9 (the v2.0 ship round) — DONE (v2.0 tag pulled, re-tagged in R10)
All lanes landed (C browse points + buy-anywhere, D browser rigs, A CITY_SPEC freeze + closing-time
ruling, E VRAM audit + half-res book/toy atlases, B tram + tour bookmarks). F wired the one cross-lane
seam, graduated the harness, shot the tour, soaked, and tagged the epoch.
| task | status |
|---|---|
| **F1 — interior presence (C→D→F)** | ✅ `interior_mode` reads `occupancyOf(shop.id)` (closure to `citizens`) and spawns browser rigs at C's `room.browsePoints`, one per patronage occupant, **cap 3**; freed by `exit()`'s `keepers.disposeAll`. Door points carry `shopId`. Verified: enter occupied "Second Time Redfern" → 1 browser == `min(occ,3)`, disposed clean, 0 errors. |
| **also wired** | ✅ **Tram** (`?tram=1`, `createTram`+update). **Buy-anywhere** (C2): E at a book-spine/toy-box `buyMesh` buys over the wallet (`collapseBuyItem`+toast); preload book+toy packs. Verified: bought a book spine, cash 191→177. |
| **F2 — smokes to strict** | ✅ buy-v0 / patronage / book-toy / katoomba **graduated warn→STRICT**. New warn smokes: interior-presence, shelf-buy, tram. Fixed the book-stock check to count `isStock OR buyMesh` (C2 tags spines `buyMesh`). `qa.sh --strict` GREEN 5/5, harness 0 fails / 0 warns. |
| **F3 — tour + soaks** | ✅ `tools/v2_tour.py`**11 shots** (`docs/shots/v2_tour/`, un-letterboxed) incl. the **rain-verandah money shot** (rain + winmap-lit open video shop + CLOSED dark neighbours at night) + browsers + dig-real + katoomba + tram_stop + contact sheet. **Both soaks PASS** (full-default + all-on: 37 chunks, 20 shops, heap stable, 0 errors, leak-free). |
| **F4 — docs freeze + tag v2.0** | ✅ Flags table finalized (below); **`v2.0` tagged** on a green tree. |
| **Marshal** | C1 (the round's SPOF) landed early; all lanes committed; no slips. |
### v2.0 flags — FINAL
| flag | owner | status | default |
|---|---|---|---|
| roster | D | **graduated → DEFAULT-ON** (R7 flip); `?roster=v1` escape hatch | on |
| patronage | D | default-on (`?patronage=0` off) | on |
| `?dig=1` | C | opt-in (crate riffle) | off |
| `?winmap=1` | B | opt-in (parallax window glass) | off |
| `?stock=real` | C/E | opt-in (real GODVERSE covers/spines/boxes + buy-anywhere) | off |
| `?weather=1` | B | opt-in (seeded rain/overcast/clear; `?weather=rain` forces) | off |
| `?plansrc=osm[&town=]` | A | opt-in (real-OSM towns: melbourne, katoomba) | off (synthetic) |
| `?tram=1` | B | opt-in (spine tram loop) | off |
| `?noassets=1` | — | opt-in (primitive-only, zero-network) | off |
Determinism goldens frozen: synthetic `0x3fa36874` · osm/melbourne `0x34cfdec0` · osm/katoomba `0x0f652510`.
---
## Round 8 (catch-up + the town comes alive → v2.0-rc) — DONE ✅
All lanes landed (E packs, B weather, A 2nd town, C buy, D patronage). F wired the shell seams,
graduated the harness, tagged.
| task | status |
|---|---|
| **shell wiring (F owns index.html)** | ✅ **A** `&town=<key>``generatePlanFor(seed,'osm',{town})` (Katoomba, 19 shops, verified). **B** weather (3-line contract): `createWeather` on `?weather=1`, `PROCITY.weather={state,intensity}` ({clear,0} when off for D), `weather.update` in loop. **C** session `wallet` (createWallet(seed)) bound to the dig BUY (`getCash`/`onBuy` in interior_mode). **D** patronage: shop door-points per chunk → `citizens.setShops`, `?patronage=0` off-switch, `citizens.setWeather` each frame. All boot clean (0 console errors, settled). |
| **F1 — smoke graduations + additions** | ✅ `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). Katoomba golden `0x0f652510` pinned in the determinism gate (fixed a hex zero-pad bug it caught). |
| **F2 — patronage baseline** | ✅ No re-pin needed — flags-off regression held (patronage nets *fewer* draws, inside tolerance; ≤300 cap + draws-±40% both green). |
| **F3 — tag v2.0-rc** | ✅ **TAGGED.** qa.sh --strict GREEN 5/5 (harness strict). |
| **Marshal** | All lanes committed round-8; no slips this round. |
---
## Round 7 (the flip round → v2.0-beta) — DONE ✅ (v2.0-beta tagged)
Critical path resolved: E1 ped-merge (`447188a`), E2 stock pack (`0c12dac`), **D's GO** (`43edabf`),
C1 `?stock=real` (`3724a65`) all landed. F executes flip → strict harness → tag.
| task | status |
|---|---|
| **F1 — roster flip (prime-law amendment)** | ✅ **DONE + verified.** `index.html`: streamed roster is now the **default** (`chunkStream:{radius:2}` per D's spec); `?roster=v1` = escape hatch (fixed roster); `?roster=stream` still accepted (no-op). Hours-aware `setNightLivelyChunks` on the open-late block. **Same commit** re-pins the flags-off baseline: **old (163 draws/19k tris, roster-off) → new (164 draws/~66k tris, roster-on)** — draws barely move (E1's merge = ~1 draw/rig, the whole point), tris = the crowd (D worst-view 65,899; drift-WARN now draws-only, tris under the hard cap). **Plan goldens UNCHANGED (`0x3fa36874`).** Also fixed the harness's roster probe (was reading a nonexistent prop → blind) + flipped the assertion to require streamMode ON + added an escape-hatch check. Verified: default boot streamMode:true (146150 active), `?roster=v1` streamMode:false (96), both render, 0 errors, ≤300 draws, harness GREEN. |
| **F2 — harness to strict + new-flag smokes** | ✅ **DONE + verified.** `qa.sh` flags gate `warn_gate → run_gate` — the harness (flags-off regression + 4 existing flags + all-on combo) is now **STRICT** (a FAIL fails qa). Wired **`?stock=real`** (F owned the seam): shell preloads the record pack + passes `stockReal`; `interior_mode` builds `makeStockAdapter(getStockPack(shop.type))` and passes it to **both** `buildInterior` (static crate sleeves) and `dig.open` (riffle) — fail-soft to parody if the pack's absent. Added `stock=real` + `weather=1` smokes at **warn-level** (decision #4). Verified: crates show **real GODVERSE covers** (smoke: 7 real atlas sleeve meshes, non-parody batched); weather auto-skips (Lane B not landed); `qa.sh --strict` GREEN (5 gates). |
| **F3 — tag v2.0-beta** | ✅ **TAGGED** (no-hostage rule). Criteria met: roster default-on + escape hatch ✓, harness strict+green ✓, `?stock=real` real sleeves ✓, qa strict green ✓. |
| **⚠ schedule-harness flag** | **B (`?weather=1`) and A (osm parity + 2nd town) did NOT land round-7** — no commits, tree clean. Both are additive (not headline keys); tagged v2.0-beta without them per the no-hostage rule. Weather smoke is in the harness ready to graduate the moment B ships. Flagging for Fable: two lanes slipped this round. |
---
## Round 6 (finish v2 wiring + pay review debts → v2.0-alpha) — DONE ✅ (v2.0-alpha tagged)
Sequencing (Fable): **F1 enforcement harness lands FIRST** (protects the round), then F re-smokes as
lanes land, adds C's ≤350-draw interior assertion when C1 lands, extends the determinism gate if A's
`?plansrc` lands, and tags `v2.0-alpha` when all-green (3 flags acceptable if A no-shows).
| task | status |
|---|---|
| **F1 enforcement harness** | ✅ **DONE + wired.** `tools/flags_check.py` (self-contained server, headless, ~90s): **flags-off regression** (golden hash `0x3fa36874` + subsystems-inactive + ≤300/≤200k budget + ±40% snapshot + 0 errors) · **per-flag smokes** (winmap/dig/roster: cross ≥2 chunks + enter interior; dig opens+closes a riffle) · **all-on combo** (flag-composition law, decision #4). Wired into `qa.sh` as a **warn-level gate** (`--no-flags` skips fast; auto-skips without the Playwright venv). Current: **all smokes GREEN**, regression clean. |
| **F2 plansrc** | ✅ **DONE + verified.** A landed the full seam (`52eb109`). Wired the shell bootstrap (`index.html`, F-owned): `?plansrc=osm``citygen.generatePlanFor(seed,'osm')` → real-data town **"Melbourne", 95 shops** (synthetic default = "Boolarra Heads" unchanged). Pinned **both goldens** in `scaffold_check.mjs`: synthetic `0x3fa36874`, **osm `0x34cfdec0`** (deterministic, 2 runs). Harness auto-detects plansrc + smokes it (enters OSM shop "Mui Karaoke Rooms") + includes it in the all-on combo. Also restored `binUnderAim`'s **nearest-bin fallback** (matches C's test page) so E riffles a nearby bin without perfect aim / before the bin GLB finishes loading. Synthetic golden frozen (selfcheck 1362/1362). |
| **≤350 interior draw assertion** | ✅ **DONE.** C1 batching landed (`35981ad`). Added an interior draw-budget sweep to `flags_check.py` (enter all 9 shop types, assert ≤350 draws/room, dig on so it also confirms batching kept riffling). **Caught a real bug in my own `interior_mode.js`**: it rendered the interior without `renderer.info.reset()` while `autoReset=false`, so draws *accumulated* per frame (inflated 864/644/…). Fixed (reset per interior frame, both dig + walk render). True per-frame result: **all 9 room types ≤350 (worst dept 96)** — C's batching verified. |
| **F3 tag `v2.0-alpha`** | ⏳ all landed flags smoke-green + regression green + qa strict green → tag (3 flags OK if A no-shows). |
Base: v1.1 (`3a831fe`), HEAD `40fefd9`. `qa.sh --strict` GREEN.
---
## Round 5 (v1.1 close-out + v2 foundations) — DONE ✅ (v1.1 tagged; ?dig=1 wired)
Lane F role: **v1.1 watch** + v2 flag-seam wiring + QA-harness-for-v2 + shared-tree marshal.
Full tracking in [`LANE_F_NOTES §10`](docs/LANES/LANE_F_NOTES.md).
| task | status |
|---|---|
| **fal un-park check** | ✅ Verified already done — `039c179` un-parked fal in Lane E's brief (decision #4 + Lane E task 3: glass-case + bus-shelter + optional magazine-rack, ≤2 attempts, costs logged in AUDIT.md). **Nothing for F to change.** |
| **archive R3/R4 instruction claims** (F4) | ✅ Added "SUPERSEDED by R5" banner to `ROUND3_INSTRUCTIONS.md` + `ROUND4_INSTRUCTIONS.md`. |
| **v2 flags table** (F4) | ✅ Scaffolded in LANE_F_NOTES §10 (`?plansrc=osm` A, `?winmap=1` B, `?dig=1` C, `?roster=stream` D) — all pending owning-lane specs. |
| **F1 tag v1.1** | ✅ **TAGGED.** C1 (`aa9c140`: record_crate fixed + counter_till re-map) + E1 (`c522464`: 6 hero props baked 288k→48k, ≤8k each, live on depot) landed. Verified: depot serves baked props (counter_till 7999, drinks_fridge 8000, listening_booth 7986 tris ≤8k), C's `glb.js` maps them, record/opshop interiors render the **counter_till GLB** with keeper stand pose working + no double till, bins upgraded. Re-captured the 2 interior tour shots (fresh chromium → baked props). qa GREEN. `git tag v1.1` (local). |
| **F2 wire `?dig=1`** | ✅ **DONE + verified.** C shipped `dig.js` + the flag but left the in-game input/mode hook to F (LANE_C_NOTES §C2). Wired the consumer pattern into `interior_mode.js` (F-owned): aim at a `userData.kind==='bin'` fitting + press **E**`createDig`/`dig.open` (per-bin seed via `binSeed(shop.seed,pos)`); the dig owns the frame (render `dig.scene`, no interior walk); WALK OUT / Esc closes → back to the room. Shell seam (`index.html`): `?dig=1``dig:` opt; `procity:digOpen` releases pointer-lock (riffle is cursor-driven); unlock→leaveShop guard reads `interiorMode.digActive` so opening a bin ≠ leaving. **Fresh-chromium smoke:** E opens riffle (seeded sleeves + prices render), WALK OUT closes to interior, **leak-free** (steady-state geo/tex flat over 3 cycles), **0 console errors**. Flag off ⇒ no listener/no dig ⇒ byte-identical to v1. |
| **F2 — other v2 seams** | B `?winmap` / D `?roster=stream` are self-contained (no shell input hook needed); smoked clean (boot, 0 errors, budget OK). A `?plansrc` not taken. |
| **F3 v2 QA harness** | ⏳ determinism gate (first-interior-hash per seed) · flags-off regression check (v2-prime-law enforcement) · v2 smoke → qa.sh warn-gate. Build as flags land. |
Base state: v1.0 (`2698b40`), `qa.sh --strict` GREEN 4/4, tree clean.
---
## Round 4 (v1-tag round) — DONE ✅ (all 6 gates green; v1.0 tagged)
Executed Fable's `ROUND4_INSTRUCTIONS.md` Lane F tasks. All lanes committed R4; a 6-auditor
adversarial v1-readiness workflow returned **GO**; `v1.0` tagged locally on the F finalization commit.
| task | status |
|---|---|
| **F1 setExposure** | ✅ Wired (`9416d0a`) **then correctly removed** (`aad9baa`): D3 (`a5e4b64`) made the impostor `toneMapped:true`, so `citizens.setExposure()` became a no-op tone-mapped globally via `renderer.toneMappingExposure`. Removed only *after* D committed (removing earlier would regress night brightness). Final state: no dead call. |
| **F2 storeys-checker** | ✅ `permittedMax = max>=2 ? min(max+1,3) : max` (Lane A's rule, `9416d0a`). Consistency **0 warnings** across 6 seeds; single-storey-type-at-2 still trips. **A confirmed** (`5b8c806`). |
| **F3 gate-3 budget** | ✅ **PASS.** E1 decimated peds (`518678d`, 924k→46k, ≤3k each) + **D2 sign-off** (`a5e4b64`, fleet ≈59.6k at 24-cap). Official measure: busiest node, pop 200, midday, 12-yaw peak → **draws 138 (≤300) · tris ~31k (≤200k)**. Baseline 372k. |
| **F4 gate-6 shots** | ✅ **PASS.** Against committed B2 (`6323d58`, `dbg.js` letterbox+poses): `shots.py v1_tour`**10 full-frame 1280×720, no letterbox**; §3.5 **night closed-facade** (CLOSED plates + lit openLate video "VIDEO REGAL") + real **record/opshop interiors** (keeper + hero props). Fixed `shots.py` so interior shots capture via `enterShop(id)` (were falling back to a street bookmark). |
| **F5 tag v1.0** | ✅ **TAGGED** on the F finalization commit (gate table all-green + shots committed). Local only — John pushes. |
| **audit** | 6-auditor v1-readiness workflow: 5 GREEN (gates 1/3/5/6 + release hygiene) + 1 procedural "land finalization commit first" → resolved. GO. |
---
## Round 3 (lead lane) — DONE, verified live
Executed Fable's `ROUND3_INSTRUCTIONS.md` Lane F tasks. All wiring verified in-browser on seed 20261990.
| task | result |
|---|---|
| **§3.5 hours / closed shops** | `isOpen()` gate on `enterShop`. Midday: open. **Night (22:00): all closed except the openLate video rental** (Video Regal [11,23]) — verified toy shop blocked, video enters. |
| **§3.4 `?noassets`** | **Zero network** (Perf API: 0 GLB/depot/manifest/ped-model requests); 100% primitive, legible. |
| **§3.4 interior GLB** | `useGLB` → fitting GLBs load from the **3GOD depot** (bookshop shelves, +8 meshes, visually confirmed). |
| **§3.4 rig fleet** | 19 ped GLBs → peds **and** keepers upgrade placeholder→**GLB rigs**; **leak-free** (shared-cache proven, cycle2/3 flat). |
| **`--depot` on qa.sh** | `validate_manifest.py --depot` = 0 err (16 GLBs live). **qa.sh GREEN 4/4.** |
| **Gate 2 soak** | **PASS** both modes — assets: 20 interiors leak-free (+GLB-cache warmup), heap 98MB stable; noassets: 37 chunks, heap 15MB. 0 console errors. |
| **Gate 4 asset-free** | **PASS.** |
| **Gate 6 shots** | `docs/shots/v1_tour/` (10 shots) + contact sheet captured; letterbox + 3 fallback bookmarks are Lane B dbg.js. |
**Findings → other lanes** (details in [`LANE_F_NOTES §9`](docs/LANES/LANE_F_NOTES.md)):
- **D/E:** rig ped GLBs are **tri-heavy** (~45k each) → gate 3 tris **~279k > 200k** budget (draws fine ~200/300).
Decimate the ped GLBs; F holds `?pop=140` until then.
- **B:** add 3 shots bookmark poses; fix the headless composer letterbox in `DBG.shot()`; render the §3.5
closed-facade visual (dark windows + CLOSED plate; predicate `window.PROCITY.isOpen`).
- Fixed **5 F-owned harness bugs** so `shots.py`/`soak.py` actually run (DBG async-poll, `DBG.plan`→`PROCITY.plan`,
`enterShop` bare-id, `shop.lot` is an id, midday-pin + GLB-cache warmup + budget→warning).
**Git:** working tree synced to canonical `main` (862bbcd) mid-session — my R2 work is committed as `aa4ed4f`.
R3 edits below are staged by pathspec (see Housekeeping). Nothing pushed.
---
## TL;DR (round 2 — for context)
All five lanes (AE) landed; F stood up the QA harness, **adversarially reviewed and fixed its own
tooling**, then wired the lanes into **a walkable, living town** — interiors, shopkeepers, and street
crowds all integrated and verified live:
1. **Interiors — every door opens (§3.2).** A door opens into a themed, seeded, self-lit 90s-AU
interior; walk it, walk back out (or Esc), room disposes. Verified across **all 9 shop types**,
**leak-free**, worst build **8.4 ms**. F-owned bridge [`web/js/world/interior_mode.js`](web/js/world/interior_mode.js).
2. **Shopkeepers (§3.3a).** A keeper stands at the counter of every shop and **turns to greet** you;
disposed with the room, **leak-free over 5 cycles**. (Asset-free placeholder; rig-fleet upgrade later.)
3. **Street crowds (§3.3b).** Deterministic pedestrians walk the footpaths (`graph = plan.streets`
directly), density follows time-of-day (busy midday, empty at night), LOD-tiered. **Draw budget
holds** — worst main-street view **~191 draws** (budget 300). Shell wiring in [`index.html`](web/index.html).
4. **Plan swap (§3.1)** + **facade skins fix** (prior pass): boots on Lane A's real town, textured.
`tools/qa.sh` is **GREEN (4/4 gates, 0 skipped)**. Remaining: manifest GLB upgrades + `?noassets`
(§3.4 — also upgrades keepers/peds to shared rigs), hours/closed-shops (§3.5), draw-budget shave.
Scoped in [`docs/LANES/LANE_F_NOTES.md`](docs/LANES/LANE_F_NOTES.md) §3.4§3.5,§7.
---
## Lane readiness — ALL COMPLETE ✅ (`node tools/qa/scaffold_check.mjs`)
| Lane | status | notes |
|---|---|---|
| A CITYGEN | **5/5** | plan.js, registry.js, names.js, selfcheck.js, map.html — determinism gate green, verified in-browser |
| B STREETSCAPE | **4/4** | index.html shell, chunks.js streamer, buildings.js, fixture_plan.js — walkable town verified live |
| C INTERIORS | **2/2** | interiors.js (`buildInterior`), interior_test.html |
| D CITIZENS | **3/3** | rigs.js, sim.js (`CitizenSim`), keepers.js (`KeeperManager`), citizens_test.html |
| E ASSETS | **3/3** | manifest.json (validates green), validate_manifest.py, AUDIT.md + pipeline |
---
## What F built / did this session
### QA harness (F-owned, `tools/`) — trustworthy, all green
| file | what | state |
|---|---|---|
| `tools/qa.sh` | gate runner; `--strict` = v1 umbrella | ✅ 4/4 green |
| `tools/qa/scaffold_check.mjs` | scaffold + PRNG determinism (golden-locked) + live readiness matrix + plan-determinism | ✅ |
| `tools/qa/consistency_check.mjs` | cross-lane gate: plan ↔ registry ↔ assets ↔ manifest | ✅ |
| `tools/shots.py` · `tools/soak.py` | Playwright beauty-tour + 10-min soak/budget/`--noassets` (gates 2,3,4,6) | ✅ ready; need Lane B `window.DBG` hook (§4) |
### Adversarial self-review (Workflow, 23 agents)
Ran a 4-auditor + verifier workflow against F's own scripts + runbook. It confirmed **14 real
defects in F's tooling** (false-confidence checks + wrong API assumptions). **All applied:**
- `scaffold_check`: JSON round-trip was a **tautology** (fixed → real deep-compare w/ `-0` norm);
Math.random-ban regex had **false negatives** (fixed → anchored to comments); irange golden check
re-seeded per draw (fixed → single stream).
- `consistency_check`: added a **storeys-vs-registry-range** check, fixed a dead late-night branch,
removed a dead metric.
- `soak.py`: heap heuristic no longer silently passes when heap isn't reported; shop count now
requires **verified interior loads** (not just `enterShop` calls); `--minutes` now honored.
- `shots.py`: fixed an `IndexError` on a trailing `--seed`.
- **Runbook** (`LANE_F_NOTES.md`): corrected against the **real landed APIs**`chunkIndex` returns
`{chunkSize,chunks}` (not callable); chunks is a `createChunkManager` **factory** with no lifecycle
hooks; `CitizenSim` is **graph-driven, not per-chunk**; keepers use `spawn/remove/disposeAll`.
### Integration passes (verified live in-browser)
- **`web/js/world/interior_mode.js`** (new F-owned bridge) + thin marked seam in `index.html`
**§3.2 interiors: every door opens.** `enterShop` looks the shop up in the plan, attaches its lot
dims, calls Lane C `buildInterior`, and switches to a new `interior` mode that renders the room in
its own scene and walks it via the room's occupancy grid. Exit on walk-out / `procity:exitShop` /
Esc → `dispose()` + player restored to the door. **Verified:** door → Toy Shop renders; exit
state-machine correct; **leak-free** (134 geo/37 tex → 217 loaded → 134/37 after dispose); all **9
shop types** `ok+pathOK`, worst build **8.4 ms**; **0 console errors**.
- **Keepers (§3.3a)** — wired into `interior_mode.js`: `KeeperManager` (no fleet → placeholder actor),
spawned at the counter stand pose (`places.find(p=>p.userData.keeperStand)`), ticked in the interior
loop, `disposeAll()` on exit. **Verified:** 11-mesh keeper at the counter, **greets** the player
(turns 0.21 rad, eases back), **leak-free over 5 cycles/5 types** (net growth 0).
- **Citizens (§3.3b)** — `CitizenSim` in `index.html`: `graph = plan.streets` (no adapter needed),
`setPopulation(140)` (`?pop=`), `setTimeOfDay((seg+frac)/6)` + `update(dt)` in the street loop,
`visibilitychange→setPaused`. Fixed a NaN bug (`getClock().hour` is a string). **Verified:** 96
active midday, peds walk footpaths ~1.5 m/s, budget-safe (worst **~191 draws** across all main edges).
- **`web/js/citygen/index.js`** (barrel) → game boots on Lane A's real generated town (was the
fixture). Verified: console "using Lane A generatePlan", HUD "Boolarra Heads · 493 shops".
- **`web/js/world/skins.js`** (1-line chokepoint fix) → `facadeMat` normalizes full-filename skins.
Verified: `facade-*.jpg` requests now **200 OK**, town textured (was all flat-grey).
### Docs (F-owned)
- `docs/LANES/LANE_F_NOTES.md` — integration runbook (real APIs, wiring steps, QA gates, DBG hook request).
- `docs/V2_IDEAS.md` — parked scope (content firehose, override layer, weather, venue, …).
- `docs/shots/` — reference-shot tree (the other lanes are already dropping acceptance shots into it).
---
## Findings for the lanes (F flags; the two blocking-visual ones are already fixed)
| # | lane | finding | status |
|---|---|---|---|
| 1 | B | shell imported non-existent `js/citygen/index.js` → ran on fixture | **fixed by F** (barrel) |
| 2 | B | `skins.js facadeMat` double-wrapped full-filename skins → facades 404 | **fixed by F** (normalize) |
| 3 | B | street measures **~334 draw calls** at spawn (radius 3) — over CITY_SPEC ≤300 | open — shave (radius 2 or more merging) |
| 4 | A | ~10% of shop names have a **doubled possessive** ("Tex's's Toys") — `names.js` | open (cosmetic) |
| 5 | A | some shops have **storeys outside the registry type range** (e.g. video at 2, range [1,1]) | open — confirm corner-anchor intent |
| — | contract | `facadeSkin` is a full filename in the registry but skins.js expected a bare key | **worth a CITY_SPEC line** so ground/awning/interior skins avoid the same trap |
---
## Remaining integration (scoped in LANE_F_NOTES §3; ready to execute)
1.~~**Interiors (§3.2)**~~**DONE**.
2.~~**Citizens + keepers (§3.3)**~~**DONE** (both verified live; see Integration passes above).
3. **Manifest + `?noassets` (§3.4)** — GLB upgrades from `manifest.json`; primitive-fallback flag.
Doubles as the **rig-fleet upgrade** that makes keepers/peds cheap (shared meshes) so `?pop=` can rise.
4. **Hours / closed shops (§3.5)** and the **draw-budget shave** (finding 3).
5. **Full QA gates 14,6** once Lane B exposes the `window.DBG` hook (§4) my harnesses drive.
*Next natural step is §3.4 (manifest + `?noassets`) — it's the asset-free gate AND the fleet upgrade
that upgrades placeholder keepers/peds to shared GLB rigs. §3.5 (hours) is a small data-driven follow-on.*
**Note on verification:** pointer-lock doesn't engage in the automated browser, so a real *walk* is
best playtested by a human (`cd web && python3 -m http.server 8130` → `localhost:8130?seed=20261990`).
F drives/inspects via `window.PROCITY` + JS for programmatic checks.
## Housekeeping
- Nothing committed (all working tree). F's edits are minimal, commented `[Lane F integration]`,
reversible. **New F-owned file:** `web/js/world/interior_mode.js` (interior + keeper bridge). Shell
seam edits in `index.html`: interior wiring (import, bridge create, `enterShop`/`leaveShop`, loop
branch, `setMode` lock, Esc-exit, M/P guards, `window.PROCITY` hooks) + citizens (import, `CitizenSim`
create + `setPopulation`, street-loop `setTimeOfDay`+`update`, `visibilitychange` pause). Also edited:
`skins.js` (facade fix, prior pass). Suggested commit once reviewed — **use a pathspec** (lanes share
one tree, see memory): `git commit -- web/js/world/interior_mode.js web/index.html web/js/world/skins.js
web/js/citygen/index.js tools docs/LANES/LANE_F_NOTES.md docs/V2_IDEAS.md docs/shots F-progress.md`
msg `Lane F: integration — interiors + keepers + street peds + QA harness + barrel + skins fix`.
- A local `python3 -m http.server 8130` is running for the smoke test (harmless; kill when done).
- Lane prompts hard-code `/Users/jing/Documents/PROCITY`; real path is `/Users/m3ultra/Documents/procity`.