Publishes the gig interface the whole round hangs off (A runs first). Everything is
behind ?gigs=1 as a POST-HOC augmentation (gigs.js withGigs, applied by the selector),
so generatePlan is untouched and all v2 goldens stay frozen (synthetic 0x3fa36874, osm
melbourne 0x34cfdec0, katoomba 0x0f652510). Source-agnostic (synthetic + osm).
- Venue: one 'pub' per town — a spine-end plain shop converted in place (keeps lot
id/geometry, enterable), shop.venue=true, never the openLate landmark. Registry gains
a weight-0 'pub' type (interior:'band_room' for Lane C) → never auto-placed → base
plan byte-identical.
- plan.gigs: 7 nightly {gigId,venueShopId,bandName,genreKey:'pubrock',night,startSeg:5,
endSeg:5,cover}; cover ~half free / half $2-$10 (F charges at the door).
- Band names: bandName(seed) generator (90s pub-rock) + OPTIONAL web/assets/custom_bands.json
drop-in (priority into the pool; absent → pure generator, no fetch under ?noassets).
- plan.posters: ~6 seeded {id,gigId,x,z,ry} for Lane B.
- Closing-time debt paid: isOpen(shopOrHours,hour) exported (half-open law as code, D consumes).
- map.html?gigs=1: venue ring + poster pins + gig panel line. Flags-off unchanged.
Self-check ALL GREEN 3074/3074 (gigSuite ×2 sources, flags-off≡base, gig golden
0xa6ae5a5e). qa.sh --strict GREEN 6/6. Verified in-browser both modes. CITY_SPEC v3
section + LANE_A_NOTES handshake for C/D/B/F. Atomic pathspec commit (shared-tree race).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ship round. Docs + one selfcheck log-line only; no generator change ⇒ all three goldens
unchanged (synthetic 0x3fa36874, osm/melbourne 0x34cfdec0, osm/katoomba 0x0f652510).
1. CITY_SPEC v2 contract FROZEN — new "CityPlan v2 — frozen producer contract" section: producer
API, goldens table (keyed seed/plansrc/town) + how to pin a new one, town-key mechanism, osm
normalization rules, hours/openLate/storeys laws. Matches code as shipped (F diffs claims vs
code). Refreshed the stale round-6 Plan-sources blockquote to multi-town reality. Layer-1
changes now require a CITY_SPEC amendment + golden re-pin in the same commit.
2. Recipe sanity — DRY-RAN end-to-end: appended a throwaway Newtown (42 shops), selfcheck auto-ran
the full parity suite (1931/1932, only golden unpinned) and printed a paste-ready
`newtown: 0x9045e577,`. Reverted; no 3rd town shipped. Added the selfcheck `⚠ UNPINNED — add to
OSM_GOLDENS` line so the recipe is mechanical; fixed the recipe wording to match.
3. Closing-time occupant ruling (D's on-call Q) — ratifies + freezes what D's sim.js already does:
half-open hours, no new entry when closed, occupants DRAIN (finish bounded dwell) never popped,
player not force-ejected, openLate video is the night crowd. In CITY_SPEC + LANE_A_NOTES. No
code change asked of D.
qa.sh --strict GREEN; selfcheck 1727/1727.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The carried brief (slipped R7) lands complete — all four items, qa.sh --strict GREEN.
1. Selfcheck PARITY: extracted structuralSuite(plan,label) and run the FULL invariant suite
on every (source, town) — synthetic + both OSM towns get identical coverage (storeys,
one-shop-per-lot, facing, within/cross-block overlap, corridor coverage, determinism,
exactly-one-openLate). ALL GREEN 1727/1727 (was 1362; OSM went subset → full ×2 towns).
2. SECOND TOWN: Katoomba (Blue Mountains, 19 shops, op-shop/book heavy) — regional contrast
to inner-Melbourne. osm_fixture.js → OSM_TOWNS{melbourne,katoomba}+DEFAULT_TOWN;
generatePlanFor(seed, source, {town}); ?plansrc=osm&town=katoomba. Golden 0x0f652510.
Melbourne 0x34cfdec0 + synthetic 0x3fa36874 UNCHANGED (town key kept off the plan).
3. "Add a town" recipe in LANE_A_NOTES (R8): extract → OSM_TOWNS → pin golden → tell F.
4. Normalization + LOG: importer bends real data to the contracts (unknown kind→opshop,
hours-clamp so only one shop is openLate, video-preferred landmark w/ fallback) and logs
it via {report}; selfcheck prints per-town (e.g. melbourne hoursClamped:5, openLate:video).
For F: three goldens to pin, gate keyed (seed, plansrc, town); selector gained {town} — see
LANE_A_NOTES R8. Screenshots docs/shots/laneA/osm-{melbourne,katoomba}.png.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The carried-over OSM plan source (no-showed R4–R5) lands complete, invariants green — a
second CityPlan producer behind the same contract, so B–F consume it unchanged.
- osm_fixture.js: 95 REAL inner-Melbourne secondhand/charity/music/book/toy/antique shops,
extracted deterministically from thriftgod city_source.json (Overpass cache), imported as a
module ⇒ ZERO network.
- plan_osm.js: generatePlanOSM(seed) — project lat/lon→metres, bin shops into latitude-band
avenues off a spine (real row + real E–W order preserved), march into a valid non-overlapping
CityPlan, centre on origin, report true 362×486 bbox, real shop names, one openLate video.
- index.js: generatePlanFor(seed, source) selector — default 'synthetic' BYTE-IDENTICAL
(golden 0x3fa36874 untouched); 'osm' → the importer. generatePlanOSM exported for F.
- selfcheck.js: parameterized by source — OSM runs the full structural invariant suite
(frontEdge/facing/no-overlap/chunk-coverage/finite/JSON/determinism/openLate) + its own
golden 0x34cfdec0; synthetic-only brief checks skipped. ALL GREEN 1362/1362.
- map.html: ?plansrc=osm renders it (reference wiring for F). Screenshot in docs/shots/laneA/.
- CITY_SPEC: plan-source note; LANE_A_NOTES R6: seam + goldens + shape-diffs for F's gate (F2).
Honest cut: street geometry regularized (real row+order drive it, exact metres do not);
name-parody deferred. qa.sh --strict GREEN.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
v1-tag round. Two confirmations + one hardening fix; qa.sh --strict GREEN (4/4).
A2 — openLate: F's night gate `hours[1] >= 22` matched 4–15 shops/seed (regular videos
jitter base-21 → 22), not the single landmark the shell assumes. Fixed in plan.js: regular
shops close by LATE_HOUR-1 (21:00); only the deterministically chosen openLate landmark
reaches ≥22, pick is now video-first. So `hours[1] >= 22` and the `openLate` field are the
same singleton, always the video rental (verified 15 seeds). Makes generation match
web/index.html:159-161 + soak.py:68 ("at night only the openLate shop is open"). selfcheck
asserts exactly-one / field⟺threshold / is-video. ALL GREEN 1301/1301.
A1 — Lane F's F2 permittedMax storeys scoping: confirmed 0 warnings across 7 seeds + a
false-negative test (injected video@2 in a scratch checker → caught every seed).
GOLDEN.hash 0x098eec2b → 0x3fa36874: intended A2 output change (a few videos close 21 not
22 for seed 20261990), NOT drift; F2 is tooling-only. Same lots/shops/types/names/skins.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cross-lane note (ROUND3 process rule: notes for other lanes go in docs/LANES/):
qa.sh's storeys warnings are benign corner anchors (finding #5 stale), with the
exact permittedMax rule for Lane F to scope its checker; openLate/hours contract
confirmed ready for §3.5.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>