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

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

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

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

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

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

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

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

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-07 23:49:49 +10:00

139 KiB
Raw Blame History

PROCITY — Lane F progress (INTEGRATION & QA)

Session: 2026-07-14 · Author: PROCITY-F · For review by: fable


Round 42 (§42.6) — PROVE THE LOOK: three paired frames, two new laws about the cast, and the byte-hash ruling reversed against its own author

Full runbook detail (gate arms, the control transcripts, the pixel diffs): LANE_F_NOTES §42. Shots + their index: docs/shots/laneF_r42/ (README in the folder).

Every number below is from a command run today on the integrated tree — five lanes' uncommitted work in one working copy — in fresh headless contexts on port-isolated no-store servers, seed 20261990, 1280×720. No git command that touches the repository was run — no status, add, commit, push, stash or tag. The one exception, declared rather than buried: git ls-tree -r v2.1 -- web/, read-only and against a tag, run once to confirm the four retired ped bodies really are in the v2.1 baseline before re-pinning the classic covenant around them. It is the identical command flags_check.py itself runs on every invocation. No tag (John's playtest session rules the epoch).

The four asks, each with its outcome

① [B] ?pop=0 HAS BEEN A NO-OP FOR 35 ROUNDS. Fixed, and gated. index.html (B filed it at :454) read (parseInt(params.get('pop'), 10) || 140) >>> 00 is falsy, so every "town frozen" measurement any lane has quoted since the flag landed booted 140 citizens. Now Number.isFinite(_pop) ? _pop : 140: a parsed 0 is finite and wins, an absent or unparseable value is NaN and falls to the default, negatives still clamp through the >>> 0. Measured both ways in r42_repro.py arm 0?roster=v1&pop=00 active; CONTROL, the same boot with no pop flag → 97 active (target 140), so the arm is a gate and not a tautology. B's second ask (a gate pinning a hash must assert freeze().stillLive) is ask ② and is enforced inside the gate that does it.

② [B] THE BYTE-HASH RULING — REVERSED. See the ruling below; it is the round's other headline.

③ [C] interior_mode.js's WARDROBE_BASE LITERAL IS GONE. C moved the declaration into interiors/stockpack.js and re-exported it from interiors.js; this module now imports and re-exports C's constant instead of keeping a second copy that agrees with it today. index.html:58 is unchanged — a re-export, not a rename. The comment above that line says the two sides must spell the base identically or the (type, base) cache key misses silently; the only way to guarantee that is to have one side. assets/wardrobe/ is now spelled once in the repo.

④ [D/E] dj_phrtt_01 — WHAT SHIPS: NOTHING. And the four the note lumped in with it were never published at all. The filing said "still on disk and still on the depot"; I checked the depot rather than the note, read-only:

body web/models/peds/ pipeline/_normalized/ _published.json GET digalot.fyi/3god/a/<f>.glb
dj_phrtt_01 (unclothed) present present present 200 · 712,792 B — LIVE
comical_luchador_01 · comical_boy_01 · man_elder_01 · man_soldier_ww2_01 present 404 · never published

So the depot job is one file, not five. And web/models/ is not gitignored (web/assets/models/ is the one that is) and tools/deploy_digalot_procity.sh rsyncs the whole of web/ — so today those bytes would go out on a deploy even though nothing loads them. Ruling: they do not ship. The exact commands are in the box below; F does not run a destructive publish and web/models/** is Lane E's file set. Meanwhile "impossible to return silently" is now four assertions, not a promise:

  • the name cannot come back into the fleet — r42_roster.mjs arm 1 reads PED_NAMES;
  • it cannot come back through a module or a page — arm 4 scans every loadable surface;
  • it cannot come back on the wire — D's r42_cast.py asserts 0 retired bodies fetched on default and on ?classic=1, now wired into qa.sh Gate 11, and flags_check.py's classic arm now asserts the same thing from the covenant side (see the red gates below);
  • a sixth un-vetted body cannot appear at all — arm 5 requires every ped GLB on disk to be shipped, a base clip, or explicitly recorded as withdrawn in credits.json. That arm exists because a name deny-list could never have caught this one: its name is innocent, and what found it was Lane E rendering the roster and looking.
# ── THE REMOVAL, for the integrator (Fable). Run at close, then re-run tools/qa.sh --strict. ──
# 1. the repo copies — all five. (web/models/ IS tracked; git will see the deletions.)
rm web/models/peds/{comical_boy_01,comical_luchador_01,man_elder_01,man_soldier_ww2_01,dj_phrtt_01}.glb
# 2. the publish staging copy — only dj_phrtt_01 was ever staged (pipeline/_normalized/ is gitignored)
rm pipeline/_normalized/dj_phrtt_01.glb
# 3. the depot ledger row, or validate_manifest.py --depot's drift check goes red on a file that has gone
python3 - <<'PY'
import json, pathlib
p = pathlib.Path('pipeline/_published.json')
rows = json.loads(p.read_text())
p.write_text(json.dumps([r for r in rows if 'dj_phrtt_01' not in json.dumps(r)], indent=1) + '\n')
print(f'{len(rows)} -> {len(json.loads(p.read_text()))} rows')
PY
# 4. the depot copy itself — LANE E / the depot host. There is NO delete verb: CITY_SPEC §depot
#    documents POST /api/upload, GET /a/<f>, GET /api/list and nothing else, and publish.py only
#    uploads. digalot.fyi/3god is a shared CDN (1,223 assets today) that sibling games read, so this
#    is a deliberate act on the host, not a script.
# 5. THEN the deny-list can take the names permanently — r41_denylist.mjs is byte-scoped over web/,
#    so adding them to BANNED is RED today by construction and GREEN the moment step 1 runs.
# 6. verify. Nothing in the R42 gates breaks: the roster gates read PED_NAMES, not the directory,
#    and the quarantine arm's "declared withdrawn" bucket simply empties.
node tools/qa/r42_roster.mjs        # expect: 32 GLBs = 24 shipped · 8 base clips · 0 withdrawn · 0 undeclared
python3 pipeline/validate_manifest.py --depot
bash tools/qa.sh --strict

THE RULING: R41's byte-hash ban is REVERSED, and its stated reason is withdrawn as wrong

R41 ruled no gate may pin a screenshot byte-hash *"while the sign atlas paints bold Npx Arial before the font resolves." The symptom was real and I reproduced it independently. The cause was not the font. B proved it in §42.5-C — the atlas canvases are byte-identical across boots, document.fonts.size === 0 because PROCITY ships no font faces, and fonts.ready resolves 4960 ms before the first fillText on 6 of 6 boots. What moves the frame is the wall-clock crowd.

I verified B's claim myself and did not reproduce 5/5 first time. Two things had to be found, and both are now load-bearing lines in tools/qa/r42_repro.py:

  1. A fixed settle is a bet on how fast the box streams today. crossroads_busy came back 2-of-3 in the gate and 3-of-3 byte-identical standalone; the difference was six browser launches of load in front of it. The chunk streamer is the wall-clock process nobody had named — the crowd is only the one you can see. The harness now polls DBG.info() and requires the whole tuple (draws · tris · chunks · geometries · textures) to repeat before capturing, and prints it, so a mismatch arrives with its diagnosis instead of as a bare inequality.
  2. Pin the render, not the instrument. With the crowd frozen, the clock paused, the settled tuple identical on every boot and the camera transform equal to nine decimal places, two bookmarks still split. The entire difference was 5362 pixels in a 7×10 box at (1252, 82)#pc-fps in the ?dbg=1 readout printing fps 20 on one boot and fps 21 on the next. (And an element screenshot does not exclude an overlay: the canvas is full-viewport and the HUD composites on top of it, so locator('#canvas').screenshot() captured the same pixels. #pc-dbg is hidden for the capture and restored after.)

With both, on this tree:

?classic=1&dbg=1&weather=0&tram=0&magpie=0&washing=0, 3 fresh contexts each frozen, render frozen, whole page unfrozen (control)
street_noon 1 of 3 1 of 3 3 of 3
shopfront_detail 1 of 3 1 of 3 2 of 3
crossroads_busy 1 of 3 1 of 3 3 of 3
market_square 1 of 3 2 of 3 ← the fps digit 1 of 3
night_neon 1 of 3 2 of 3 ← the fps digit 1 of 3

5 of 5. And the strongest form available: the hashes were written in one run and re-measured green in a second, independent run (--goldens docs/shots/laneF_r42/shot_goldens.json).

THE RULING (R42 §42.6), replacing R41's. A gate may pin a screenshot byte-hash. Five conditions, every one an assertion in the same run and none of them a hope: (1) the boot carries ?classic=1&dbg=1&weather=0&tram=0&magpie=0&washing=0; (2) DBG.freeze() is called after the pose and freeze().stillLive comes back all-falsethis is B's ask, and a gate that pins a hash without it is forbidden; (3) a second freeze() after the settle removes zero draws, proving nothing came back; (4) the harness settles until DBG.info() stops changing, never for a fixed number; (5) the hash is of the render with #pc-dbg hidden, never of the whole page. ?pop=0 is not a substitute for (2) and never was — it covers the fixed roster only, and until today it covered nothing at all.

One clause survives from R41, for a different and now-measured reason: committed cross-round golden VALUES stay out of qa.sh. R42 alone moved 46 prop colours, five bodies, a bench yaw and a booth composition; a committed pixel value would have gone red on all four while telling us nothing about correctness. What the gate pins is the stability — N fresh boots, one sha256 — which is the property the ruling was ever about, and it cannot rot when Lane E repaints a couch. The values are written to docs/shots/laneF_r42/shot_goldens.json for anyone who wants them, and r42_repro.py --goldens PATH re-checks them in one command.

The new gates F owns, each with its control demonstrated

tools/qa/r42_roster.mjs — RULINGS 1 AND 3 BECOME REPO LAW (node, zero deps, ~0.1 s, two gates). It reads PED_NAMES out of rigs.js and credits.json, and not the directory listing — Lane D's filing, and it is right: the five retired files are still on disk by design (deleting a name mid-wave shortens a pool and renumbers every pickRig index), so a directory gate would go red on files that no longer ship while staying green on a body that quietly reappeared in the source.

--gate period (ruling 1) result
1. no banned body in PED_NAMES 0 of 5, 24 shipped
2. the five replacements at their own slots man_casual_04=normal[6] · person_casual_01=normal[9] · man_smart_01=djs[4] · woman_smart_02=comical[0] · person_youth_01=comical[1]
3. pool shape 17 / 5 / 2 = 24, unmoved
4. no banned name on a loadable surface (web/**/*.{js,mjs,html} + assets/manifest.json) 69 surfaces · 0 hits
5. quarantine — every ped GLB on disk is shipped, a base clip, or declared withdrawn in credits 37 GLBs = 24 shipped · 8 base clips · 5 declared withdrawn · 0 undeclared
CONTROL R41's own roster replayed through the same predicates trips all 5 by pool and index, and reports all 5 replacements missing; a deleted name shortens normal to 16, which arm 3 catches; arm 4's matcher fires on a synthetic reference
--gate licence (ruling 3) result
every shipped ped resolves to a credits row with a real licence 24 / 24, 0 unverified — 20 on ped-bodies-base (Mixamo RF), 4 on ped-bodies-dj
every required:true row carries an attribution pass
CONTROL restoring R41's amber unverified on the roster row turns 20 peds RED; deleting the ped-bodies-dj row turns dj_techno_01 RED

Comments are stripped before parsing, and that is not decoration: every pool in PED_NAMES carries a trailing // was <retired name> note, so the first cut of this gate matched inside the comments and reported a false RED on all five.

tools/qa/r42_budget.py — THE INTERIOR LAW, WITH THE INSTRUMENT PINNED (C's §42 ask, in a file I own rather than an edit to r41_integration.py). C settled the two-round 122-vs-123 argument in §42.2b and neither lane's bookkeeping was at fault: it is the camera aspect — a fixed vertical fov inheriting the window's aspect, so a wider window puts more room in the frustum. C asked me to set camera.aspect explicitly inside the sweep rather than inherit it. Done, with two controls:

  • the identical sweep at aspect 1.25 reads 116 @ dept/auto — C's published control value, to the digit, reproduced on demand instead of relitigated;
  • the pinned sweep run in a 1280×1024 window (which would have inherited 1.25) returns 123 @ dept/auto, identical to the 1280×720 run. That is the actual claim: the number is now a property of the tree, and the gate cannot drift when someone edits an unrelated new_page.

tools/qa/r42_repro.py — THE BYTE-HASH GATE. Arms and controls in the ruling above.

tools/qa/r42_tour.py — the proof tour (opt-in --tour, warn-level: ~8 min, and pointed at its real outdir it rewrites the round's committed shots).

Also wired into qa.sh Gate 11: D's r42_cast.py and r42_shots.py (scratch outdir, like the R41 shot gate). And r41_shots.py is PROMOTED from warn-level to strict — D fixed the browse arm by removing the window rather than widening it (the frame is taken inside the winning room the moment a candidate beats the incumbent, and the harness never leaves), and D was right to measure my filing first: I called it deterministically red and it was flaky by construction, which is a different defect with a different fix.

THE PROOF SHOTS — the deliverable (docs/shots/laneF_r42/)

Every AFTER is framed from the committed R41 original's own camera derivation — the two interiors re-run R41's exact standoff recipe, and the street reads R41's published pose straight out of its sidecar — because a pair whose halves were framed by two different algorithms is not a pair. Every humanoid frame carries the R10 human-sized line and — adopting Lane D's §42.4 idea, which is a good one — a CAST line resolving every figure back to fleet.all[pedIndex].pedName with an explicit assertion that none of the five retired bodies is in the picture.

pair before → after draws tris
pub_furnished_ab.jpg white plastic props, two purple EL CHUPACABRA luchadors, an unclothed body at the stage edge → brown vinyl and beer-carpet red, 13 figures wearing 11 bodies, none retired 71 → 72 222,526 → 222,250
record_dj_booth_ab.jpg a black box → Technics silver in a composed booth 40 → 43 36,252 → 41,719
street_postures_ab.jpg the R41 roster, benches side-on → recast crowd, benches turned to the road with a sitter on one 173 → 174 43,562 → 42,970

The street AFTER stands on the BEFORE's own camera, parsed out of the committed R41 sidecar. The first cut re-ran R41's live-crowd cluster search: one run landed within 40 cm of R41's pose and the next landed 30 m away behind a wall, because the search scores a crowd that is walking while it scores. Re-rolling until it looks right is not a method. (8.58, 16.69) → (12.08, 10.63) is read out of docs/shots/laneF_r41/street_postures.txt at run time — a number I typed would be a different claim — so the AFTER stands exactly where the BEFORE stood and the only variable in the pair is the tree. +1 draw at that pose is the ±1 a single bookmark moves run to run (D §42.4 measured the same on an unchanged tree); the 802-station pin below reads 0 / 0.

Plus three single frames: record_dj_booth_door.jpg — the frame R41 never took, the booth from Lane C's own room.spawn at eye 1.62 m, 7.76 m out, because "it reads as a plain black box" is a claim about the entry view and R41 answered it with a product photograph from 2.5 m; street_bench.jpg; credits_panel.jpg (the roster row with its per-asset Mixamo evidence, the withdrawn row, and no ped reading unverified).

The bench, measured in the frame and NOT read back off B's rule. Instances are identified by position only — a benchStops(plan) station within 2 cm — and the angle is then taken between the instance's own world +Z and the foot of the perpendicular onto Lane A's road centreline. Neither input is the placement rule.

bench yaw, live scene R42
stations matched in the streamed window 61 of 254
CONTROL: the same stations offset 3 m 0 of 61 — "coincides" is a measurement
bench FRONT vs the direction to the road min = median = max = 0.0° (R41: 90.0°)
bench front vs the street's own direction 90.0° (R41: 0.0°)

One instrument note, recorded rather than tuned away: 12 of 61 benches stand nearer a cross street's centreline than their own (a station 14 m from a node is close to the corner), and attributing those to the geometrically nearest edge reads up to 174.4° — the bench is correctly facing the street it belongs to and the instrument is wrong. That is the same trap B named in §42.5-A. The first cut of this gate failed on exactly that and the fix was to use the station's own edgeId, which is position-derived metadata, not the yaw rule.

The budgets, restated on the final integrated tree

STREET (ruling 4) — the pin method of record, budget_walk.py --method stepwalk --stepm 2 --fixed-yaw 0 --laps 2 = 802 stations down the main spine at yaw 0, the worst bearing:

boot R41 pin R42 measured law
default, noon (seg 2) 282 / 100,346 282 / 100,346 ≤300 byte-exact
default, NIGHT (seg 5) 291 / 120,093 291 / 122,183 ≤300 margin 9, INTACT
?classic=1, NIGHT 269 / 90,580 269 / 90,580 ≤300 byte-exact to the R38/R40/R41 pin
?classic=1, noon 269 / 90,592 ≤300
default, NIGHT, ?clips=0 291 / 82,013 291 / 84,103 Δ draws 0

NO LANE SPENT A STREET DRAW. Every row that has an R41 value reproduces its draw count to the digit — 282, 291, 269, 291 — on a tree carrying five lanes' work, including a five-body recast, a 46-asset repaint, a bench and streetlight yaw fix and a recomposed DJ booth.

On the +2,090 night tris (present on ?clips=0 too, so not the motion library; present on B's control tree too, so not B's yaw work — it arrives through models//assets/): decomposed at the tri-worst station by rendering, hiding citizens.group and re-rendering, the crowd there is worth 2,5092,716 tris for one near rig plus the whole billboard layer, at 2 draws. A one-for-one body swap that trades a 1,564-tri cartoon child for a 2,738-tri teenager moves triangles by more than the delta observed and cannot move draws. Ruling 4 governs draws; draws did not move.

INTERIOR — the true numbers, on the pinned instrument:

12 types × 6 archetypes, aspect 16:9 worst law margin
GLB off 93 @ opshop/hall ≤350 257
GLB on 123 @ dept/auto ≤350 227
GLB on + ?stock=real 123 @ dept/auto ≤350 227
control: the same sweep at aspect 1.25 116 @ dept/auto
control: pinned 16:9 in a 1280×1024 window 123 @ dept/auto

Worst interior 123, MARGIN 227 — unchanged by R42. The pub spent nothing at the worst room (dept has no booth); the booth composition costs +3 draws where it lands, measured in the record_dj_booth pair (40 → 43), inside C's declared "+4 at most, anywhere". Two rooms move under ?stock=real (toy 86 → 120, book 64 → 98) and neither reaches dept.

Three red gates in F's own file — two caused by other lanes' R42 work landing correctly, one a race

tools/flags_check.py went RED three times across this round's --strict runs. None was a game defect and none was gettable by loosening a threshold.

① THE CLASSIC FETCH SURFACE WIDENED — and the covenant's own escape clause is the answer. The classic allow-list is derived from the v2.1 tag's file tree at gate time (git ls-tree), and four of D's five swaps sit in PED_NAMES.normal/comical, which ?classic=1 loads. So classic now fetches man_casual_04 · person_casual_01 · woman_smart_02 · person_youth_01, none of which existed at v2.1. The gate's own message names the two exits: "either the asset belongs in classic and the covenant needs a ruling + a re-pin, or it must be gated out of ?classic."

It is not gettable by gating. Gating the replacements out of classic means classic keeps shipping the luchador, and ruling 1 is a repo-wide law, not a default-boot law. So: a ruling, and a re-pin — of four named files, recorded as SUBSTITUTIONS rather than admitted as a class, and self-verifying: a substitution classifies OK only if the body it replaces is itself in the v2.1 tree, so the entry cannot smuggle in an asset that was never baseline. (man_smart_01 replaces dj_phrtt_01 in the djs pool, which is gated off classic — deliberately absent from the map, and red if it ever appears.)

And a second assertion, because the allow-list alone would have passed the dangerous version of this silently: classic's wire must contain 0 of the 4 retired bodies. A substitution that became an addition grows the pool, renumbers every pickRig index and moves the seeded crowd — and an allow-list that only asks "is this file permitted" cannot see that. Now measured: 4 substitutions present, 0 retired fetched.

gig night: dancer NOT driven by a dance clip — the R41 smoke_djdance bug, second edition. The dancer was on venue_northern_soul, 16.1 s, advancing 12.77 → 14.18, position.y flat: a perfectly healthy dancer. The predicate demanded the live clip's duration be one of the four R13 Rokoko dance clips — but D's §41.3 gig widening deliberately installs Lane E's venue-library clips into some crowd slots. The assertion stopped being true in R41 and only stayed green while the sampled slot happened to hold a Rokoko clip; R42's swap changed which slot got sampled and surfaced it. Fixed by asking the ClipBank whether it holds the clip (fleet.bank.has(name)) instead of pinning a clip identity the game no longer promises. Nothing is weakened — that the four dance clips load and bind is asserted by the census arm above it and the four-fetch positive control below it, and that a clip rather than the procedural fallback moves the body is stalled + bouncing, both untouched. Same species as R41's filing 2: a gate that pinned an identity instead of the behaviour.

no-pump: trip 1 buy failed — {cash0: 191, disabled: True} — a race, and the third instance of the same anti-pattern in one file. The R30b gate drives the REAL dig UI: press E, click the sleeve, wait 350 ms, then click whatever BUY button is there. On the third heavy --strict run of the day it read the button still disabled on trip 1 with $191 in the wallet — so not affordability, just a pull panel that had not finished resolving. The same gate was green on the two runs before it with byte-identical deltas [-28, -16, -13, -13, -16], which is the signature of a race, not a regression. Widening the sleep moves the race; polling for the precondition removes itwait_for_function until the control exists AND is enabled, cap 6 s, and the panel's own text is now returned so a genuine disabled button arrives with its reason instead of a bare disabled: True (which is what made this cost a whole run to diagnose). The broke-player negative control passes expect_enabled=False, because there a disabled button is the assertion and waiting six seconds for it to enable would be waiting for the gate to fail.

This is exactly what F filed against D in R41 and D corrected back in §42.4 — a harness that bets a fixed number on a live process. It was in my own file three times over. The rule, now written down in LANE_F_NOTES §42-A/§42-F: never wait_for_timeout a live thing into readiness; poll for the precondition and assert it. The same fix pattern closed the byte-hash gate's chunk-streamer race in the same round.

QA

bash tools/qa.sh --strict

SUMMARY  24 passed · 0 failed · 0 warn · 0 skipped
● QA GREEN — every landed gate passed.                                   (exit 0)

24, up from R41's 18: six new gates (the two node roster gates, D's r42_cast.py and r42_shots.py, F's r42_budget.py and r42_repro.py) and zero warnr41_shots.py moved out of the warn column into the strict one, so the suite has no non-blocking gate left in it.

node web/js/citygen/selfcheck.jsALL GREEN 157,647/157,647, fingerprint 0x5f76e76 — unmoved. Nothing this round writes a plan field.

Gate 11 is new this round (--no-r42 skips its browser half, like every other class): the two node roster gates, D's r42_cast.py and r42_shots.py, F's r42_budget.py and r42_repro.py. --tour adds the proof tour opt-in and warn-level: it costs ~8 minutes (the street arm HOLDS until the live crowd offers its frame — that dwell is the method, not slack) and pointed at its real outdir it rewrites the round's committed shots, so a routine gate run sends it to a scratch directory.

Filed back

  1. → LANE E / the integrator: dj_phrtt_01.glb is live on the shared depot (200, 712,792 B) and there is no delete verb in publish.py or in CITY_SPEC's depot contract — only POST /api/upload, GET /a/<f>, GET /api/list. An append-only shared CDN cannot un-publish a mistake, which is worth a spec line of its own. The other four rejects are 404 — they were never published, so the note that lumped all five together was wrong in a way that would have sent someone hunting for four files that do not exist.
  2. → LANE E / LANE B: two credits.json globs claim the same three files and disagree about their licence. models/peds/woman_*.glb (ped-bodies-base, Mixamo RF) and models/peds/woman_dj_*.glb (ped-bodies-dj, on-device TRELLIS) both match woman_dj_01/02/03. The gate resolves it by longest-literal-prefix and reports the ambiguity, and the resolution is right today — but delete the narrower row and those three silently inherit "Adobe Mixamo", which is the wrong provenance recorded confidently. Name the DJ five explicitly. (credits.json is not in F's editable set.)
  3. → LANE E: two shipped peds are below the tri band E published. man_elder_01 is 1,602 tris and comical_boy_01 1,564, against §42.3's stated shipped band of 2,0673,999. Both are retired, so it costs nothing now — but the band was quoted as a contract the replacements were measured against, and it did not describe the roster it came from.
  4. → R43, conditional on the removal above: once step 1 of the removal box runs, add the five names to r41_denylist.mjs's BANNED list. It is byte-scoped over web/, so it goes green the moment the files leave and RED forever after if one comes back — the strongest available form of "impossible to return silently", and it is unavailable until the bytes are gone.
  5. → LANE C, for information: ?stock=real raises toy to 120 draws and book to 98 (from 86 and 64). Still 3 draws under dept's 123. If the wardrobe pattern spreads to more types, the worst room may stop being dept — the law is fine either way, but the sentence everyone quotes would change owner.
  6. → EVERY LANE: ?pop=0 now means zero, and DBG.freeze() is the real knob. Any measurement in any lane doc that says "town frozen, ?pop=0" was taken with 140 citizens in it.

Files touched (only these)

web/index.html ([Lane F R42] mark) · web/js/world/interior_mode.js ([Lane F R42] mark) · tools/qa.sh (Gate 11 + the R41 shot-gate promotion) · tools/flags_check.py (the three red gates above) · tools/qa/r42_roster.mjs (new) · tools/qa/r42_repro.py (new) · tools/qa/r42_budget.py (new) · tools/qa/r42_tour.py (new) · docs/shots/laneF_r42/** (new) · F-progress.md · docs/LANES/LANE_F_NOTES.md · README.md. No repository-touching git command was run (the one read-only git ls-tree v2.1 is declared at the top of this section). No tag. I did not touch web/assets/credits.json, web/models/**, pipeline/**, tools/qa/r41_integration.py, tools/qa/r41_denylist.mjs or any other lane's file.


Round 41 (§41.6) — THE LIBRARY LANDS: five seams wired, six gates added, and the interior margin was never 162

Full runbook detail (gate arms, the control transcripts, the A/B method): LANE_F_NOTES §41. Shots + their index: docs/shots/laneF_r41/ (README in the folder).

Every number below is from a command run today on the integrated tree — five lanes' uncommitted work in one working copy — in fresh headless contexts on port-isolated no-store servers, seed 20261990, 1280×720. No git command was run. No tag (John's playtest session rules the epoch).

The five asks, each with its outcome

① [C] THE WARDROBE IS ON — and the arm that ships by default is still the canvas. Two marked edits: interior_mode.js gains WARDROBE_BASE = 'assets/wardrobe/' and stockBaseFor(shop) (godverse per-shop base first — a real shop's real stock outranks the town art; else the wardrobe base for opshop; else undefined → the town-wide packs), consumed at enter() and in stockInfo. index.html preloads preloadStockPack('opshop', { base: WARDROBE_BASE }) beside the three town-wide preloads, inside the same STOCK_REAL gate. One constant, one resolver, both sides — the R24 cache-key lesson, because (type, base) is the cache identity and a base spelled two ways fails silently to parody.

?stock=real (ON) default boot (OFF)
room's resolved base assets/wardrobe/ null
pack items resolved at build time 52 0
real garment ids on rendered meshes 69 (Puppy Jumper · Splatter Smiley Tee · Squiggle Knit Dress · Trackie Dacks…) 0
atlas the GPU is sampling wardrobe_atlas_00.webp
wardrobe requests exactly 2 (index + one atlas) 0
console errors 0 0

Which ships default: the canvas. STOCK_REAL is params.get('stock') === 'real' && !NOASSETS — opt-in, unchanged. Both arms A/B'd against a control tree with F's two edits reverted (web/ copied, assets/models/vendor symlinked to the live tree so another lane's wave is identical on both arms; town frozen pop=0&weather=0&tram=0&magpie=0&washing=0):

frozen town, control vs treatment default arm ?stock=real arm
fetch surface 160 URLs, identical hash, added [] removed [] +2 URLs exactly (stock_opshop_index.json, wardrobe_atlas_00.webp), removed []
street (shopfront_detail) 131 draws / 18,018 tris — identical 131 / 18,018 — identical
op-shop interior 59 draws / 28,505 tris — identical 59 → 55 draws (4), 28,505 → 28,065 tris
geometries · textures 199 · 111 — identical 199 → 194 · 111 → 100 (11)

So: nothing breaks without it, byte-for-byte, and turning it on is a draw and texture WIN — 52 garments come off ONE shared atlas material where the procedural path built a canvas per garment.

② [D] THREE GATES WIRED into qa.sh Gate 10: node tools/qa/r41_postures.mjs, tools/.venv/bin/python tools/qa/r41_citizens.py, …/r41_shots.py. The shot gate is wired with --outdir "${PROCITY_SHOT_OUT:-$TMPDIR/procity-r41-shots}" so a routine gate run never rewrites the round's committed shots; PROCITY_SHOT_OUT=docs/shots/laneD regenerates them deliberately. Two run strict; r41_shots.py runs warn-level with its reason recorded at the call site — see filing 5 below, its browse arm loses its browser to the sim between the scan and the frame.

③ [E] pipeline/clips_verify.py WIRED as Gate 10's first gate (no venv, no browser, ~1 s): GREEN, 0 errors / 0 warnings, 46 clips / 6 groups / 3,498,124 B.

④ [B] THE NON-REPRODUCIBLE BOOKMARKS — confirmed, and the set is BIGGER than B measured. Independently, on today's tree: ?classic=1, town frozen, 2.5 s settle, sha256 of the PNG, 3 boots per bookmark:

bookmark distinct hashes over 3 boots
street_noon · shopfront_detail · crossroads_busy 3 of 3 — not reproducible (B's finding, reproduced)
market_square 3 of 3 — not reproducible (B measured this one as rock-solid; it is not, here)
night_neon 1 of 3 — STABLE

THE RULING: no R41 gate pins a screenshot byte-hash on any bookmark, and none should while the sign atlas paints bold Npx Arial before the font resolves. Only night_neon could carry one and one stable bookmark is not a golden suite. Everything R41 asserts is a counter — draw calls, triangles, fetch surface, item ids, texture URLs, stature — all of which reproduce exactly. The tour shots are evidence, captioned with the counters that are gated, and their README says so. Filed to B for R42: one await document.fonts.ready before the first sign-atlas paint in buildings.js, then re-measure — cheaper than a perceptual-compare harness, and it fixes the cause.

⑤ [C] THE CORRECTED drawSweep — confirmed on an independent instrument, and the true numbers. F wrote its own sweep (r41_integration.py arm 2): F's own scene bookkeeping, F's own strip-and- measure loop, PROCITY_C.drawSweep never called — because the thing C found broken was exactly the sweep's scene bookkeeping.

12 types × 6 archetypes, seed 1990 F's own sweep C's instrument, today margin to ≤350
GLB off 93 @ opshop/hall 93 @ opshop/hall 257
GLB on 123 @ dept/auto 123 @ dept/auto (twice) 227
GLB on + ?stock=real 123 @ dept/auto 123 @ dept/auto 227
GLB on + noKit (pre-R41 control) 116 @ dept/auto 234

THE PHANTOM CONTROL: the same loop with one stale room deliberately left in the scene — the pre-R41 behaviour, reproduced on purpose — reads 197 @ dept/auto, an inflation of +74 draws on every reading. That is C's §41.4-0 finding confirmed independently, and it decomposes R39/R40's "worst 188" exactly: 116 of dept plus ~72 of a record shop nobody was looking at.

THE TRUE NUMBERS, used everywhere in this close: worst interior 123 draws, law ≤350, MARGIN 227. Not 188/162. §41.4 spent +7 draws at the worst room (116 → 123). Two honest notes: C published 122, and today's tree reads 123 on both instruments — C's table also reads 14 draws light on record/book/video/pawn/milkbar/band_room in the noKit arm too, so the difference is C's measurement environment, not the R41 kit. And even the inflated historical reading (197) was never a breach of the law.

The new gates F owns, each with its control demonstrated

tools/qa/r41_denylist.mjs — RULING 3 BECOMES REPO LAW (node, zero deps, 0.8 s). Seven banned names (character_kit_modular, exports/bodies/, elsa_coronation_hair_wig_kh3, daphne_sexy, mocaponline, SK_Mannequin, anatomy/) checked two ways: banned path components, and banned bytes in every file under web/, binaries included — a GLB carries its source node names in the JSON chunk and a manifest carries the path it was published from, so "nothing references it" is a claim about text and "nothing contains it" is the claim a licence audit asks. Result: 738 files · 197.3 MiB · 0 symlinks · 0 hits. CONTROL, run every time: the gate writes a plausible plant (a manifest row naming ~/Documents/character_kit_modular/exports/bodies/body_07.glb) into web/assets/, asserts the scan goes RED on 2 names, removes it in a finally, and asserts the scan returns to 0 hits. A deny-list that has never fired is indistinguishable from one with a typo in its regex.

tools/qa/r41_integration.py — the seams F wired and the budgets F answers for (Playwright, five arms, own port):

  1. the wardrobe, both arms — the table above; the OFF arm is the control.
  2. the interior budget on F's own sweep + the phantom control — the table above.
  3. ?noassets=1 still clean over a walk that enters op-shop, record and pub: 0 GLB · 0 clip GLB · 0 motion manifest · 0 wardrobe · 0 stock pack · 0 asset manifest, 0 console errors, 106 requests swept. CONTROL: the same walk on ?stock=real fetches 5 of 6 cargo classes, so the classifier can see what it claims is absent.
  4. ?clips=0 / ?classic=1 carry no R41 cargo?clips=0: 142 distinct URLs, 0 clip cargo, 0 wardrobe, clipStats {0 groups, 0 clips, 0 B}. ?classic=1: 125 URLs, same three zeros. CONTROL: the default boot fetches the library (2 requests, bank 2 groups / 16 clips / 1,242,272 B), so the zero-arms are a gate and not a tautology.
  5. the deny-list from the network side190 distinct URLs across street + gig night + 3 interiors, 0 banned names; the matcher demonstrated firing on a synthetic banned URL.

The budgets, restated on the final integrated tree

STREET (ruling 4) — the pin method of record, budget_walk.py --method stepwalk --stepm 2 --fixed-yaw 0 --laps 2 = 802 stations down the main spine at yaw 0, the worst bearing:

boot R40 pin R41 measured law
default, noon (seg 2) 282 282 / 100,346 tris ≤300
default, NIGHT (seg 5) 291 291 / 120,093 tris ≤300 margin 9, intact
?classic=1, NIGHT 269 / 90,580 269 / 90,580 ≤300 byte-exact
default, NIGHT, ?clips=0 291 / 82,013 Δ draws 0

NO LANE SPENT A STREET DRAW. The pin reproduces R40 to the digit on every class, and the motion library's own A/B (?clips=0 vs default at the same station set) is 0 draws: clips are skeleton-only, 0 tris, and a seated ped is a ped that was already drawn.

INTERIOR — before/after, and the instrument correction that dwarfs both:

draws law margin
R39/R40 published worst (phantom-inflated) 188 ≤350 162
pre-R41 worst, corrected instrument (noKit) 116 ≤350 234
R41 worst, corrected instrument (12 types × 6 archetypes) 123 @ dept/auto ≤350 227
the same reading with the phantom re-introduced 197 ≤350 153

And the game-side number, which is the one a player can actually stand in — 33 real open shops entered through the shell with GLB and ?stock=real, draws read off the interior scene's own render: worst 110 @ dept (Golden Department Store), margin 240. Per type: dept 110 · toy 110 · book 78 · opshop 67 · video 54 · record 51 · pawn 47 · milkbar 46 · stall 17. Venues are shut at midday, so the pub was measured on its own clock: 49 quiet (18:30) and 77 on gig night with the band and the crowd in the room — the round's most-furnished interior, at 22% of the law.

QA

bash tools/qa.sh --strict

SUMMARY  18 passed · 0 failed · 0 warn · 0 skipped
● QA GREEN — every landed gate passed.                                   (exit 0)

All six R41 gates included and green, D's warn-level shot gate passing on this run too. R39's transmission gate independently corroborates F's sweep from the other side: GLB-on worst room 123 (dept/auto) with E's fix vs 193 (opshop/hall) with transmission restored — 70 draws saved, and its GLB-off control pair is equal at 93 on both roots, as it must be.

node web/js/citygen/selfcheck.jsALL GREEN 157,647/157,647, fingerprint 0x5f76e76 — unmoved. Nothing this round writes a plan field.

Filed back (two of them were red gates F fixed in its own file)

  1. tools/flags_check.py _enter_record_shop — R41 closed its search window, 8 smokes went RED. It took the first OPEN record shop of six whose counter sits ≥2.6 m from every bin (so counter-E can never fall into binUnderAim's 2.5 m fallback). §41.4's 24 kit bins per record shop brought that distance down across the board: the first six now read 2.36 / 2.02 / 2.26 / 2.21 / 2.21 / 2.10 m. Measured over twelve, 3 still clear the bar (Colonial 2.80 · Bluey's 2.79 · Gazza's 2.84) — so the property survives and the window was the problem. Widened to the whole open set, and it now reports the shop and its margin so the next narrowing shows up in the log instead of arriving as eight failures. Verified first that this is not a game defect: the shell orders aimed bin → trySell → nearest bin, so the sell card wins at a counter regardless of the fallback radius. → LANE C, for information: record-shop counters now sit 2.02.8 m from the nearest bin. Nothing is broken; if you ever want the counter to keep a clear 2.6 m, that is the number.
  2. tools/flags_check.py smoke_djdance — R41 made the mixer multi-action and the stall test lied. It read mixer._actions[0] before and after a 700 ms window and failed when tAfter <= t. D's §41.3 gives every actor per-instance clip swapping and the gig widening installs a venue clip into some crowd slots, so index 0 is no longer the live action nor the same one 700 ms later: a healthy dancer read t 24.19 → "tAfter" 14.21 and the gate called it a stalled page. Fixed to identify the running action by clip name, follow that clip across the window, measure advance modulo the clip duration (a 30.58 s loop sampled across its wrap is advancing, not stalled), and treat a legitimate mid-window swap as advancing. Now green, and it reports both durations in play (30.58 s and 52.42 s — the R13 dance pick and D's venue widening, side by side).
  3. → LANE B: the bookmark instability is wider than filed (market_square too — 3 distinct hashes over 3 boots). The R42 fix is one await document.fonts.ready in buildings.js.
  4. → LANE C: worst interior reads 123 on this tree, not 122 — on C's own instrument as well as F's. The noKit arm carries the same 14 draw spread, so it is not the kit.
  5. → LANE D: r41_shots.py's BROWSE arm goes red on this tree, and it is the harness, not the game. Its street arm is excellent and reproducible (8 near-tier rigs, states {walk 7, bench-sit 1}, 3 distinct clips @walk/sit_hands_thighs/walk_shopping_bag, 126 draws, every stature clean). The browse arm scans up to 8 shops for browsers, keeps the best, then RE-ENTERS the winner — and a patronage occupant is free to walk out during the ~3 s per candidate that scan costs, so the re-entry spawns 0 browsers and the arm asserts on an empty room. Measured: 3 consecutive standalone runs all land on T & R Books with figures: 1 (keeper only), byte-identical at 34 draws / 36,751 tris — while the same gate passed with figures: 2 earlier the same day under different machine load, with nothing in R41's shipped code moving in between. Fix: re-check k.browse after the re-entry and re-pick, or don't re-enter — keep the winner entered. Until then it is wired warn-level in qa.sh with that reason recorded at the call site, so it runs and reports without failing a round for a browser who went home. (F's own tour hits the same seam and gets a browser every run by dwelling 25 s on the street first so several shops hold occupants — docs/shots/laneF_r41/browse_interior.jpg, a browser on browse_close_cabinet at a bookshelf.)
  6. F's own r39_transmission.py had an unmeasured 30 s navigation timeout, and it cost a round a RED. Its pg.goto(interior_test.html?localdepot=1) waits for load — every subresource — on Playwright's 30 s default. Inside a full --strict run (four browser gates back to back on a loaded box) it timed out once and printed a stack trace where an assertion belongs; the same gate went green twice standalone, immediately after, and the page reaches load in 0.4 s over 27 requests, 3 runs of 3. Pinned to an explicit 90 s — ~200× the measured cost, so it cannot mask a regression, and one class of false red is out of --strict for good. An unstated default is a number nobody measured.
  7. → LANE C: assets/wardrobe/ is now spelled in two files — F's exported WARDROBE_BASE in interior_mode.js (the shipping path) and a local const at interior_test.html:93 (yours). They match today. Two spellings of one cache key is the exact shape of the R23 bug, so if the wardrobe ever moves, import the constant instead of editing both.

Files touched (only these)

web/index.html ([Lane F R41] marks — Lane B's [Lane B R41] marks untouched) · web/js/world/interior_mode.js · tools/qa.sh · tools/flags_check.py · tools/qa/r41_denylist.mjs (new) · tools/qa/r41_integration.py (new) · tools/qa/r41_tour.py (new) · tools/qa/r39_transmission.py (F's own gate, one timeout — filing 6) · docs/shots/laneF_r41/** (new) · F-progress.md · docs/LANES/LANE_F_NOTES.md · README.md. No git command was run. No tag.


Round 40 (§40.6 — THE PLAYTEST HARNESS) — F8 turns John's session into tickets; the wave-1 asks settled

Full spec (note schema · tour stops · gate arms · ask detail): LANE_F_NOTES §40.

THE KEYSTONE LANDED. web/js/world/playtest.js (NEW, F-owned) + marked shell seams:

  • F8 anywhere drops a note: {pos, mode (street/interior/dig/map), shopId+shopName, street/locality via A's address layer (own createStreetLocator, zero draws), citySeed, town, seg+clock, boolean flags, fps, draws, tris} + a screenshot download of the CURRENT view (interiors/open crates render via the new interiorMode.renderFrame()) + an overlay one-liner — Enter saves, Esc cancels, typing never reaches the game, and F8 inside a shop does not eject you (noteActive joins the digActive/sellActive unlock contract).
  • Delta-law persistence (the fog's pattern): key procity-playtest, caps 200 notes/240 chars FIFO at write AND validated at read, corrupt-reject-WHOLE and loud, import = adopt (reject leaves current notes untouched). Export = one tickets-JSON (procity-playtest/1, 18 typed fields per note — schema documented §40.2 of the notes) via the note-card button, DBG.exportNotes(), or PROCITY.playtest.exportJSON(). The store is LAZY, and a gate earned that: the first cut's constructor getItem turned R30c classic purity RED (?game=0: storage calls 1) on the full strict run — fixed to read-on-first-use, R30c re-run green, and r40_playtest.py grew its own instrumented ?game=0 zero-calls arm so the class stays caught in F's gate too.
  • Classic-gated by construction: dynamic import only when flagOn('playtest') (default-on, ?playtest=0 opt-out) — an off boot never fetches the module (185/207 requests swept clean), never grows the DOM, never touches storage (classic: localStorage 0 keys). Control demonstrated: the default boot fetches + publishes it.
  • ?bugtour=1: dbg.js now loads under the flag alone, and N walks 11 stops — spawn → street day → shop/buy (book) → dig (record) → op-shop bin, caption says UNARMED → gig night → tram → rain (caption: re-run &weather=rain if the seed rolled dry) → night → fog map → second town via the selector — every stop a caption of what to look at, DBG bookmarks reused verbatim, F8 live throughout.
  • Gate tools/qa/r40_playtest.py GREEN, all arms + controls: off-byte-identical (+ its fetched-on-default control) · the REAL F8 key path round-trips (download → overlay → Enter → export schema-validates 18/18 → reload persists same id → Esc arm → both reject arms) · the tour advances through all 11 stops with 0 console errors, no ?dbg=1 needed.

THE FIVE ASKS: ① D's door_footpath_check.mjs wired (qa.sh Gate 9, plain node, ~2 s) · ② B's r40_lane_b.py wired (Gate 9, Playwright class) · ③ D's doorRect derivation measured and FILED BACK: through D's own classifier + shipped snap, the d/2+0.05 doorRect step reads synthetic strict 447→446, walkable 473→472, and breaks 1 previously-passing point (violates D's own FIX-ONLY arm) — real towns 100% either way; the 20 plaza misses are stall-packing geometry, not nudge length; shell stays +0.6, numbers handed to D · ④ A's debt D2 CLOSED: the shell's isOpen is A's canonical (no-hours ⇒ OPEN) via the barrel, every PROCITY.isOpen consumer (audio + 19 tools call sites) inherits it; remaining third impls listed in the notes (sim.js _openAt — already A's semantics; dbg.js openLate predicates) · ⑤ README ?r=N row now states the R40 law (391 measured, ≤420 declared, gated; 307 gone) + flags_check's selector label now reads RULED R40.

QA: bash tools/qa.sh --strictGREEN (summary line in the round-close message; Gate 9's three new gates all pass) · selfcheck 157,647/157,647 ALL GREEN, fingerprint 0x5f76e76 — zero goldens moved (the harness is DOM + storage; it renders nothing and writes no plan). Shots: docs/shots/laneF_r40/r40_f8_note_overlay.png · r40_bugtour_caption.png.

Parked (unchanged, per the round brief): op-shop bin contents · fog default-on · v9 third layer · the audio listen pass — all become F8 tickets the moment John plays. No tag.


Round 39 (v9 WAVE 1 — THE CITY IS THE GAME) — five gates, five controls, and the interior margin is 162

Full detail: LANE_F_NOTES §39. (R38's close lives in LANE_F_NOTES §38 — it never got a row here.)

tools/qa.sh --strict --matrix GREEN — 10 passed · 0 failed · 0 warn · 0 skipped, on the true final tree, with three new gates wired in as Gate 8. Lane A's selfcheck reproduces exactly: 157,647/157,647 ALL GREEN, fingerprint 0x5f76e76 — zero goldens moved, in a round that added an address layer, a fog ledger, a spawn move and a new interior fitting.

THE ROUND'S BIGGEST NUMBER IS NOT THE ONE THE LEDGER ASKED FOR. The interior margin is 162 draws, not 21 and never 6. Lane E's transmission fix takes the worst room from 329 → 188 draws. Measured with tools/qa/r39_transmission.py: two port-isolated no-store roots, identical inode for inode except one file — the shipped bookshelf GLB vs a copy with transmissionFactor: 1 re-inserted (BIN sha1 identical, only the JSON chunk moved) — both driven through interior_test.html's own drawSweep.

drawSweep worst glass bookshelf (= the pre-E tree) E's fix (shipped) Δ
GLB-on 329 opshop/hall 188 dept/auto 141
GLB-off 162 opshop/hall 162 opshop/hall 0 — the control's own control
margin vs the ≤350 law 21 162 +141

opshop 329→174 (47%) · book 247→125 (49%) · every other type 0, which is the shape Lane C predicted from an isolated room, reproduced at sweep scale by a harness C did not write. The worst interior room in the game is now dept/auto — a room with no bookshelf in it. The default boot fetches these GLBs from the public depot, so I fetched all four R39 GLBs off digalot.fyi and scanned them: sha1-identical to the local tree, 0 transmissive materials, 4/4.

The five gates, each with its control demonstrated in the same run:

gate verdict the arm that could have failed
39.1 address (r39_address.mjs) GREEN 1,192/1,219 shops (97.8%), 23/23 ways supplier, 857/857 membership agree / 0 wrong. RED ARM: a 3 m shift error raises adelaide 89 → 98 resolved shops (Lane A's number to the digit) while inventing 9 names — stats().shiftCheck reads DISAGREE on all 72 perturbations, and it is the ONLY alarm that catches the 3 m case
39.2 fog (r39_runtime.py) GREEN HIDES 4/72 at spawn, 0 of 61 distant shops known · REVEALS 2,125 m → 58 shops · FRONT TEST 0 of 59 shopfronts learned when probed from 5 m behind · +0 draws AND +0 tris, four fresh browsers, both orders, drained, 3 reads each · 8/8 corrupt saves rejected · null store on classic/game=0/fog=0
39.3 THE SIGN (r39_runtime.py) GREEN shops in front of the player at spawn 4/4 · 4/4 · 4/4 · 1/1; with the pre-R39 sign re-derived on the same tree, 0 on all four
39.4 rummage bin (r39_runtime.py) GREEN 120 rooms: tub 60/60 both arms, kind:'bin' 0/60 default vs 60/60 armed, 0 carves, 60/60 deterministic, reachable 0.801.03 m. THE CONTENTS DID NOT SHIP — on the default street boot the tub is kind:'prop', 0 dig targets, E opens nothing
39.5 transmission (r39_transmission.py --validator) GREEN the validator HARD FAILS rc 1 on a fabricated glass asset and PASSES on the shipped tree — both halves, or "it failed" only proves the gate is always red

Re-baselined, without falsifying a release record (tools/qa/r39_shots.pydocs/shots/laneF_r39/): the R32 cluster-pose spawn frame and the R35 first-five splash were both shot at the old pose. v7_tour/02_the_strip.png and v7_beta/first_five_splash.png are kept as history; the re-baseline ships its own BEFORE, camera parked at the old -sin pose on the same tree. Katoomba's BEFORE is a paddock and a blank back wall; the AFTER is the awnings and the shopfront run. The R35 gate needed no re-baselining — smoke_first_five is pose-independent and was green either side.

Three corrections, all mine to make and all measured: LANE_A_NOTES §39's "a 30 m shift returns 0 street names" — it returns 40/12/47 wrong ones, because on a dense grid a big shift lands you on the next street · LANE_B_NOTES §39's "a fresh export has no known key" — true of ?fog=0, no longer of a default boot, because B's own +sin fix means you spawn in front of four shopfronts · and my own harness bug, recorded: I first pointed the transmission control at web/assets/models/ when validate_manifest.py reads pipeline/_normalized/, and nearly filed a defect against Lane E on the strength of it. Aim the control at the code path, not the plausible path.

Held → Lane E: check_transmission() skips any GLB with no local _normalized copy and never scans web/assets/models/. Demonstrated with a live control: remove the local copy and the same glass asset passes rc 0. A depot-only transmissive GLB — the exact shape of longbench and streetlight — would ship unseen.

No tag.


Round 37 (v8 WAVE 0 — THE FLOOR) — the budget of record settled · two vacuous gates repaired

0.3 — THE BUDGET OF RECORD: 292 draws / 124,793 tris. The audit was right and the spike was right; they were measuring different BEARINGS. Two sessions disagreed by 90 draws (280 walked vs 191 teleport-walk). Measured on one tree, one box: worst draw per bearing, 12 bearings × 102 stations down the synthetic spine — 0° 284 · 30° 248 · 60° 208 · 90° 177 · 120° 196 · 150° 214 · 180° 211 · 210° 207 · 240° 178 · 270° 182 · 300° 223 · 330° 267. 110 draws of spread from the camera direction alone, and the peak sits at yaw 0 — the default argument of DBG.teleport(x, z, ry = 0). The audit took the default and landed on the worst bearing; the spike supplied a yaw and landed mid-range. Both numbers were correct; they were not the same quantity. Lane B's independent R37 walk (276 draws / 99,880 tris) and mine (282 / 99,878) agree on triangles to 2 — same frame, two harnesses. Viewport is irrelevant (1280×720 and 2560×1440 identical to the byte). The R+1 dispose window is worth +19 draws (31 vs 27 live chunks), not the 6080 folklore claimed — the other ~100 draws between bookmark and walked-worst are place and bearing. THE PIN (default boot, 7bd96e5): synthetic 292 draws (night, 31 live chunks) / 120,145 tris; ?classic=1 269 / 90,580; corpus tri worst 124,793 on fitzroy_real — the audit's "99124k" range confirmed at its top. Margin: 8 draws, ~75k tris. Only 8 frames of 810 exceed 280; classic never does. Also found: ?r=3 measures 307 draws and breaches the ≤300 law (ungated override, filed). Independently confirmed B's kerb change at +0 draws / 8 tris. Headless SwiftShader — but draw/tri counts are counted in JS, not by the GPU, and the viewport control proves it; frame TIME is still unmeasured on any real GPU and no proposal should claim it.

0.3b — THE SHARED INSTANCE CAP: 256. Measured: an InstancedMesh costs +1 draw at every N from 32 to 2048, and N × tris/instance always submitted (the instance bounding sphere spans the town — at N=2048 all 368,640 prop tris go down the pipe from anywhere). So draws are not the constraint; triangles are. 256 × a 24 tri billboard cell = 5121,024 tris = 0.5% of budget; CPU to rewrite all 256 matrices, 0.01 ms/frame. Binding companions: ≤24 tris/instance at the cap, and spend it proximity-first — measured anchor supply runs synthetic 246 poles / 6.8 km to bendigo_real 7,325 poles / 188 km, so 256 sprayed town-wide on bendigo is one instance per 730 m and statistically unencounterable.

0.2 — smoke_tram REBUILT. It asserted !!getObjectByName('tram'), printed a SKIP and returned on absence, and passed on the fenced path because tram.js:110-114 adds a named EMPTY group. Now STRICT with four arms: RUNS (route/stops, and the body moves 19.8 m over 45 frames under the shell's own loop) · BEHAVES (an isolated tram, fixed dt, 400 s: dwell time accounts to exactly 9 × 3.5 s, max cruising tick 0.4665 ≤ SPEED·dt 0.45, every discontinuity bounded by 2·LANE with ≥1 end reversal, riding 3.2 m off Lane A's own main-edge centreline) · GENUINELY ABSENT on the fenced katoomba_real (0 stops, 0 group children, 0 m moved) · OFF under ?tram=0/?classic=1. Both controls demonstrated every run — the run-predicate pointed at the fenced town and the absence-predicate pointed at the running tram must each go red.

0.1 (F half) — THE KERB GATE: the ruling is in the paint, and the control fires at EXACTLY 14. New STRICT smoke_kerb. A's corrected selfcheck asks the plan-side question; F's asks the runtime one, of the paint — raycast straight down at every rendered poster and classify the topmost ground hit by MATERIAL IDENTITY (skins.groundMat() is cached, so the road mesh's material is the object the factory returns — no mesh names, no strings, nothing that drifts when B edits). Topmost, not first, because B put the carriageway on top at junctions. Six arms, all green: 14 posters rendered == 14 in plan.posters, every one within 0 m (nobody had ever asserted the poster you see is the poster A placed) · detector control A — a main centreline classifies ROAD at y 0.04 · detector control B — 13 m off it classifies FOOTPATH at y 0.02 · THE RULING: 0 of 14 posters on road paint, tally {footpath: 14} · THE CONTROL: the same leg fails under ?verge=0 at EXACTLY 14, tally {road: 14} · CLASSIC KEEPS ITS PAINT — 13 m off the centreline is still ROAD under ?classic=1, 0 posters. Two lanes, two wholly independent methods, the same number 14: A counted plan-side by offset in node; F counted paint-side by raycast in the browser off B's geometry.

0.5 — classic_regression GETS A NETWORK ALLOW-LIST, derived from the v2 tag at gate time (git ls-tree + v2.0's own manifest), never a deny-list. The brief's shape would have missed the breach it was written for: the R36 widening was models/peds/woman_dj_01.glbsame-origin — so "four depot GLBs + same-origin" is green straight through it. /models/peds is enumerated by the v2.0 tree (exactly the covenanted 21 files; today's dir holds 32). The nine depot fetches are baseline, not a breach — proven by booting the actual v2.0 tree and capturing its wire. Control every run: injecting the R36 breach itself plus a never-existed asset turns the clean verdict into exactly 2 named violations. F measured classic's surface as +6 files vs the real v2.0 tree and held rather than ruling; Fable ruled (4a7c0f1): the 5 audio files are RATIFIED BASELINE (v2.1 was v2's final tagged state, so audio shipped inside v2 as the furniture GLBs did) and assets/towns/index.json is NOT baseline but a named, printed exception. Landed in the mechanism by moving the authority tag v2.0 → v2.1 — surgical, not generous: git diff v2.0 v2.1 -- web/ is the audio pack + audio.js + four wiring edits, zero other assets, zero deletions, so the tag change admits precisely the ratified audio. Gate output confirms: same-origin baseline 47 → 52, exceptions 2 → 1, control still discriminating. "Should ?classic=1 show the town selector at all?" → v8 debt ledger, R38+.

THE CLOSE — qa.sh --strict --matrix on the true final tree (A + B + Fable + F all landed, port-isolated :8975): 7 passed · 0 failed · 0 warn · 0 skipped — QA GREEN. Selfcheck 157,407/157,407 (A's +1,055 poster assertions, green on the ruled street) · manifest 0 err/0 warn · flags harness 0 fails / 0 warns (classic fp 0x5f76e76, 162 draws/16,642 tris, fetch surface clean vs v2.1 with 1 named exception and the injection control firing · default boot gig golden 0xec7a2d39, 131 draws/23,382 tris, dig reachable naked · the new kerb gate and the rebuilt tram gate both green with every control) · figure-scale 29 figs/6 shops · MATRIX GREEN 10 towns × 7 gates, tram verdicts unchanged (fenced on bendigo/fremantle/katoomba_real — the same three the rebuilt smoke uses for its absence arm). No tag: wave 0 levels the floor, it does not ship a version. Caveat kept honest in the notes: the matrix's own budget column is clean-load bookmark numbers (69161 draws) — a regression signal, not the budget of record, which this very round proved under-reports a walked-and-turned frame by ~120 draws. Making the matrix walk-and-turn is filed to the debt ledger, not smuggled into a floor round.

Full record: LANE_F_NOTES §37.


Round 36 (THE v7.0 CLOSE, wave 2) — the gate, the freeze, the tag

Zero alpha-blockers stood (Fable's wave-0 naked-boot playtest walked the whole loop by hand), so wave 2 was pure close: E's three flags measured shut, the full gate over the final tree, docs frozen, tag v7.0 (annotated, LOCAL — Fable pushes).

E's 2 a.m.-asset flags, closed by measurement: (a) the 5 trellis DJ bodies' bind skeletons measure 0.9840.989 units vs the legacy roster's 173.46 (cm) — the figure-scale gate grew a DJ-body leg that spawns all five BY NAME through the real browse path: statures 1.641.89 m, all in [1.4,2.0], feet planted, rig-kind verified (the old sample's (i*7)%19 could never reach indices 1721). (b) New STRICT smoke_djdance: the 4 Rokoko clips bind 64/65 tracks, sole unbound mixamorigReference (gate on binds + NaN, never node counts — no template pin); a gig-night dancer's live action IS a dance clip (identity by duration, mixer advancing, position.y flat = the clip moves the body); ?classic fetches zero dance clips (positive control: default fetched 4); ?noassets procedural sway ALIVE (placeholder dancers bounce 0.07 m — the inverse pair proves the branch). (c) Rokoko zone = E's w1 provenance, nothing runtime.

THE HOLD → RESOLVED (the five-holds law, end-to-end in one round): wave 2 found ?classic=1 fetching the 5 DJ body GLBs (unconditional PED_NAMES.normal members, rigs.js, b339402 — classic's crowd pool widened 17→22 ungated). F WARNED it in the suite and held; Fable ruled "classic keeps its people"; D fixed it in rigs.js alone (2fd4c1c: base-17 restored, the five append behind djs = dance riding the shell's existing !CLASSIC signal; classic's 129-citizen census byte-identical to a b339402^ shadow tree). F's wave-2.5 re-gate on the true final tree: the warn flipped to "✓ zero DJ-body fetches" — flags harness 0 fails / 0 warns, default boot keeps all 22 bodies and the organic gig-crowd proof. Held, ruled, fixed by the owning lane, re-proven — nothing improvised.

The gate (final tree, :8946 port-isolated): qa.sh --strict --matrix 7 passed · 0 failed · 0 warn · 0 skipped; flags harness 0 fails / 1 warn wave 2 (the held finding, deliberate) → 0 fails / 0 warns on the wave-2.5 re-gate (true final tree, run 3 on the healed box — the first two re-gate runs died to a box-level fork famine, ~10,600 leaked m3panel processes, fixed at source; starvation artifacts, not gates); selfcheck 156,352/156,352; figure-scale 29 figs / 6 shops; MATRIX GREEN 10 towns × 7 gates. The R3235 smokes all re-green on the final tree: bible (ledger Σask $333 → Σoffer $135, scarcity across reload, ?game=0 control) · hunt (rumor + auto-clear + travel day 1→2→3, same-town free) · godbay (one-hammer byte-identical, SOLD net $19 bounded, the paper on the wake toast) · first-five (HILLS HOIST $191 splash, beats on the real path, seeded across browsers). Classic covenant fp 0x5f76e76, gig golden 0xec7a2d39, world fp 0xb1c7843e unmoved.

The freeze: README v7.0 section (the loop + the morning-paper manual row) · V7 charter risks retired-or-carried v4-style (economy RETIRED-structural, bot verifier carried to the live tier · schema RETIRED · re-emit identity CARRIED, never fired · scope RETIRED · v6 collision RETIRED) · LANE_F_NOTES §36. Full record: LANE_F_NOTES §36.


Round 35 (v7.0 close, wave 2) — THE FIRST FIVE MINUTES (Fable, solo, JING5)

A fresh game arrives HUNTING: one seeded artist want (rng(citySeed,'firstwant',0) × the parody roster) named on the splash with the pocket money ("You're hunting HILLS HOIST — $191 in your pocket"), pointing the rumors and the tour at the same target. Three toast-beats walk the hustle off existing events (walk-in → digOpen → first find → first sale-or-listing, then the listener removes itself). Returning saves get no tutorial; ?game=0/classic never reach it. #pc-toast body-mounted so beats and morning papers surface inside shops too. STRICT smoke_first_five green (roster want + splash · real-path beats · returning/?game=0 controls · cross-browser seed determinism). Full record: LANE_F_NOTES §35.


Round 34 (v7.0 close, wave 1) — GODBAY: the offline auction (Fable, solo, JING5)

⚖ LIST from the crate (mail-in consignment) → listings on procity-save/1 → resolves the day after, wherever the day moves (sleep, travel, imports — adopt() and sleep() share the resolver). THE ONE-HAMMER LAW: identity-seeded (never day-seeded) — a passed-in item re-lists to the same result forever, no retry farm. 15% pass · uniform-across-band hammer (grail 2×low) · 12% war ×1.52 · 12% fees, net floor $1; EV ≈ 0.8×mid so random flips bleed and only finds profit; keeper = floor, auction = bounded ceiling. The morning paper rides the wake/arrival toast ("⚖ SOLD 'Cane Toad Blues' — hammer $22, $19 after fees"). Band table imported from C's bible.js (one authority), PRNG from core. STRICT smoke_godbay green (real-DOM consign · byte-identical double-sleep · bounded honest resolution · travel hammers · game=0 control). Full record: LANE_F_NOTES §34.


Round 33 (v7.0-beta wave 2) — THE HUNT: wantlist · rumor · travel (Fable, solo, JING5)

☆ WANT on the pull card → wants on procity-save/1 (optional, capped, deduped); buying a matching item clears the hunt; THE WANTLIST section in the crate panel. The keeper gossips: seeded 45% banner mutter naming a hunted want + a real cached town. TRAVEL COSTS A DAY: adopting a save from another townKey (town OR seed) charges +1 day in save.js adopt() — load and import both pay, same-town stays free (byte-equality gates untouched); arrival toast names the fare. New STRICT smoke_wantlist_travel (real-card want · rumor scan · auto-clear · +1-day with intact cash/collection · same-town control · return fare · ?game=0 zero-DOM). Full record: LANE_F_NOTES §33.


Round 32 (v7.0-beta wave 1) — THE BIBLE + scarcity + the §31.6 queue (Fable, solo, JING5)

Guide bands derived in js/interiors/bible.js (E's thresholds mirrored, no pack re-emit) → the pull/sell cards carry the guide stamp; parody bins roll seeded bands + 10% collector/ grail gems (asking under the keeper's bandLow offer — margin by construction); §9.1 sell basis swapped to bandLow exactly as pre-ratified (alpha entries keep the pricePaid basis). Scarcity: optional pulls on procity-save/1 — a bought dig slot stays gone all day (pick-then-omit, zero collateral), pruned on sleep; real-sourced pulls never expire. §31.6 queue: cluster-pose spawn (main-street doors, classic keeps the frozen pose) · THE CRATE + game bar live in interiors (C anywhere, yields to the cards) · DAWN dome swaps to high-cirrus (weather override wins). Gates: new STRICT smoke_bible (band law over every bin · ledger closes to the cent on the GEM's bin, Σask $333 → Σoffer $135, gems +$21 · emptied bin 0 across reload · ?game=0 control re-opens 16/16) + sell_routing amended to the exact formula (the fence moved from the receipt to the band). Full record: LANE_F_NOTES §32.


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 binSeedplan 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.htmlprocity: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: ?noassetszero 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 disagreevalidateTownCache 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 ?noassetszero 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 tourdocs/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 shotFremantle's real street at golden hour: 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 gatetown_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 shotKatoomba's real main street: 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_realBendigo, 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 flipindex.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 movedflags_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 + tagqa.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.


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 machinegig_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 gateinterior_scale_check.py now measures stature (crownfoot, platform-independent), ceiling-clip assert kept as its own line (debt #6).
  6. District smokessmoke_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.


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.py11 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=osmcitygen.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.

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 EcreateDig/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=1dig: 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_tour10 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):

  • 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.planPROCITY.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.
  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.
  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 §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 APIschunkIndex 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 8130localhost: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.

Round 38 (v8 WAVE 1 — INHABITATION) — the budget reconciliation · four new gates, four controls · the round close

THE BUDGET OF RECORD DOES NOT MOVE: 292 / 300 draws, margin still EIGHT. The brief's naive sum (292 + washing 1 + magpie 1 = 294) is wrong in our favour. The pinned worst-draw frame is a NIGHT frame and both draw-spending layers are structurally zero at night — the washing comes in (count = 0), the magpie roosts (count = 0). Wave 1's one draw is spent in daylight, where the walked peak sits ten draws lower. Two pinned trees (6bc7926 control, byte-identical under web/ to R37's 7bd96e5; 4514cb9 treatment, byte-identical under web/js to the final 74d6257), assets symlinked to the live tree on both sides, 802 stations @ 2 m at yaw 0, sim + tram LIVE: night 292 → 292 (+0) / 120,145 → 122,879 tris; noon 282 → 283 (+1) / 99,872 → 103,118; ?classic=1 269 / 90,580 → unchanged; fitzroy_real 172 / 130,727 → unchanged; bendigo_real 204 / 131,492 → unchanged even with ?magpie=1 forced. The control reproduced R37's pin to the byte, twice. Every revert is exact: ?yards=0 at night gives the control's three numbers; ?yards=0 at noon gives 282 / 99,872, isolating the noon +1 to the washing alone.

Lane B's "control 346" — answered rather than dismissed. As a delta it is the wall-clock ped spawn artifact R37 named (114 draws of swing between two browser instances measures the sim, not the build), and B was right to hide the sim. But 346 > 300 is a claim about the world, and neither stepwalk (walks the chunks in, photographs a half-dressed street) nor dwell (fills the street, arrives clean at 27 chunks) can adjudicate it. tools/qa/budget_walk.py gains --method stepsettle: the audit's fixed-yaw stations, each drained then held N real rAF frames — R+1 dispose residue AND a fully-acquired crowd in the same frame. **Like for like — the same 101 stations at 8 m, yaw 0 — the settle is worth +3 draws at noon (277→280 control, 278→281 R38) and 9 at night (287→278 both trees), and the sign flip is the finding: at night the settled street RETIRES near rigs to impostors (tris 120,145→100,939), so the crowd goes home and a sampler that never waits does not see it. The highest draw count anywhere in the reconciliation — 4 methods × 2 trees × 2 segments × 5 towns — is 292, the pin. Nothing read over 300. The mechanism that could legitimately have breached it (full crowd inside a full R+1 dispose window) now reads 280/281. B's 346 was the artifact, and the refusal is now backed by the measurement that could have falsified it.

A correction to my own R37 pin: the corpus TRI worst is 130,727 on fitzroy_real, not 124,793 — and it reads 130,727 on the control tree, so it is my R37 sampling density (12-bearing 8 m sweep) versus this round's 2 m fixed-yaw stations, not a wave-1 cost. Re-pinned. A pin is only as good as its station spacing, and mine had not said so.

FOUR NEW GATES, FOUR CONTROLS DEMONSTRATED (smoke_ambient · smoke_character · smoke_magpie · smoke_yards, all wired into flags_check.main()):

  • §1.1 audio — six sources anchored (63/8/81/32/39/57, B's numbers exactly); dog audible at 5.9 m with 1 play scheduled (for a one-shot audible is only published once plays > 0, so one word covers fetch→decode→gain→schedule→counter); hours enforced (sprinkler audible 06:30 → idle 12:30, same pose); day-of-week enforced (mower idle Mon → audible Sat, same pose); pan is a bearing (yaw 0/90/180/270 → +0.00 +0.85 0.00 0.85); zero draws byte-identical to ?ambient=0. THE CONTROL: a second server, root symlinked file-for-file except one patched manifest.json with sfx['dog-bark-fence'] renamed → that source reports no-entry, 0 plays, never audible, other five unchanged, 0 console errors. Stated on every run: audible OUTPUT is unverifiable headlessly — nobody in this loop has ears.
  • §1.2 character — classic is the frozen v1 literals with all tints null and 5 ground meshes; all 23 shipped keys matched per-town including the two carrying state: ''; the control fires (an unknown key returns matched:false); 21 distinct palettes across 23 towns (a single shared palette FAILS by name); and the two orphans are on the GPUground-gravel.jpg bound on Castlemaine, ground-reddust.jpg on Darwin, read off the live material.
  • §1.3 magpie — the latch arm verbatim with its positive control first (TRUE before the roll); day-roll clears it; localStorage gained NO new key (baseline taken after a game.save() — the first cut blamed the magpie for v7's own write); the season is 23 distinct positions across 24 towns, run UNFORCED, against B's first cut's 24-of-24-at-214, with newtown_real in season on day 1 and 129/1000 seeds hitting the 11.5% design ratio; territory 6 → 157 synthetic → bendigo; +1 draw / +182 tris at one pose reused across both boots.
  • §1.4 yards — 145 house + 36 yard = 181 lots, 181 hoists → 394 items, 256 active, 0 at night; the cost decomposition at one pose with the sim quiet: box props +0 draws (and 2, because the 36 yard lots used to fall through to buildShopfront()) with +1,936 tris, washing exactly +1 draw / +512 tris; and on real towns it SKIPS LOUDLY — a ⊘ SCOPE line printing 0 house/yard lots / 0 hoists / mesh never created, next to the synthetic 181 that makes the zero attributable.

Also caught, in my own gates: the pan arm's first draft used two bearings and read +0.00/0.00 because that anchor sat on the forward axis — it would have called a working panner inconclusive. Four bearings. The gate's first draft failed the standard the gate exists to enforce.

SUITE: tools/qa.sh --strict --matrix GREEN on the true final tree (74d6257 — E's session-2 publish landed mid-round, web/ diff vs B's 4514cb9 is manifest + one thumb, no code, and both were already in the tree both budget servers read): 7 passed · 0 failed · 0 warn · 0 skipped; flags_check GREEN 0/0 with all four new gates inside it; MATRIX GREEN 10 towns × 7 gates, 0 fetches under ?noassets on all ten, tram verdicts unchanged. Wave 1 moved nothing in the matrix.

Held: nothing. Recorded, not fixed (other people's files): magpie.js says 154 triangles at :9/:123 and 182 at :42 — measured 182, so two stale comments in B's file; E's three published GLBs are manifest-and-depot but not under web/assets/models/ (staged in pipeline/_normalized/, nothing loads them, validator's contract covers it — not a defect). Carried debt unchanged: A's D1D4 · the matrix's budget column is bookmark numbers · ?r=3 breaches at 307 · the ?classic=1 town-selector question.

No tag — wave 1 is content; the epoch tags when it is whole.