Lane A round 14: v3.0 release — 400-seed district invariant sweep + CITY_SPEC §v3 frozen

Release round: harden + freeze, no new systems. No golden moved (synthetic 0x3fa36874, gig
0x1f636349 both re-verified frozen); selfcheck.js is the only code touched, so no seeded generation
stream gained a draw (release-round law).

- selfcheck.js: factored the district checks out of gigSuite into districtInvariants(plan,label)
  (behaviour-preserving — hero-seed gigSuite runs byte-identical), then added a 400-seed synthetic
  sweep + the large edge seeds asserting the full district contract each (2-4 venues, >=1 pub, never
  the openLate shop, no band twice/night town-wide, per-venue 4-6 nights, every poster off the
  carriageway). Asserts the seeded count spans {2,3,4} across 400 seeds (146/121/133 — range is
  exercised, not pinned). osm graceful placement: katoomba (19 shops, no warehouse district) still
  lands 2-4 valid venues incl. a pub for every hero seed. Poster check kept at roadWidth/2 (a literal
  poleOffset compare would false-fail corner posters); added -1e-6 epsilon on the strict boundary.
  ALL GREEN 14264/14264 in ~0.7s.
- CITY_SPEC §v3: final wording pass, marked FROZEN at v3.0 (round 14) with a v2-style marker + an
  explicit carve-out for F's pending alias-line deletion. Fixed the roadWidth prose (full carriageway
  width, not 'half-corridor'). Genres final (pubrock/grunge/covers).

Coordination: the R12 single-venue alias line stays in §v3 for F to delete atomically with the
gig_state.js getters (debt #1) — deleting now would be doc-ahead-of-code (alias still live) and race F.
Flagged to F that the runtime block omits nightOf(id); to B that venue.js:228 is a 2nd bare-string
site. Verified: mutation-tested the sweep (non-vacuous — catches 5-venue/7-night/on-road violations)
+ adversarial pass (goldens frozen, freeze matches code, deterministic).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
m3ultra 2026-07-16 08:36:31 +10:00
parent 55f6698fb2
commit a3911a4590
4 changed files with 125 additions and 8 deletions

View File

@ -1,9 +1,39 @@
# LANE A — CITYGEN · progress (PROCITY-A)
*Status: **all deliverables landed; self-check all-green (3273/3273); rounds 213 closed** (see below).
Node QA gates (selfcheck · scaffold · consistency) GREEN. v3.0-beta DISTRICT interface published (24
venues, week schedule, town-wide posters); gig golden `0x1f636349`. Strict 6/6 is F's end-of-round gate.*
*Date: 2026-07-15 · owner: PROCITY-A (Opus 4.8)*
*Status: **all deliverables landed; self-check all-green (14264/14264); rounds 214 closed** (see below).
v3.0 RELEASE round: 400-seed district invariant sweep landed + CITY_SPEC §v3 frozen at `v3.0`. Goldens
unchanged (synthetic `0x3fa36874`, gig `0x1f636349`) — no golden moved, no rng draw added this round.*
*Date: 2026-07-16 · owner: PROCITY-A (Opus 4.8)*
## Round 14 (2026-07-16) — v3.0 RELEASE: invariant sweep + CITY_SPEC §v3 freeze
Release round, Lane A scope = harden + freeze (no new systems). **No golden moved** (synthetic
`0x3fa36874`, gig `0x1f636349` both re-verified frozen); the only code touched is `selfcheck.js`
(test-only), so no seeded stream gained a draw — the release-round law holds.
Delivered:
1. **400-seed district invariant sweep** (`selfcheck.js`, ALL GREEN **14264/14264** in ~0.7s). Factored
the district checks out of `gigSuite` into `districtInvariants(plan,label)` (behaviour-preserving — the
4 hero-seed gigSuite runs are byte-identical), then swept **seeds 1400 + the large edge seeds**
asserting the whole contract each: 24 venues, ≥1 pub, never the openLate shop, no band twice per night
town-wide, per-venue 46 nights, every poster off the carriageway. Added a distribution assert that the
seeded count **spans {2,3,4}** across 400 seeds (146/121/133) — proves the range is exercised, not pinned.
`structuralSuite` still runs on the 6 hero SEEDS + osm parity (unchanged); the sweep runs the light
district checks only, so 400 gig-on generations stay ~0.5s.
2. **osm graceful placement** in the sweep: `katoomba` (19 shops, **no warehouse district**) still lands
24 valid venues incl. a pub for every hero seed — proving `pickVenues`' off-spine fallback. Honest
scope noted: both osm fixtures keep a main spine, so this proves the no-warehouse branch (not no-spine).
3. **Poster-check hardening**: kept the `roadWidth/2` nearest-edge formulation (a literal `poleOffset`
compare would false-fail corner posters); added a `1e-6` epsilon on the strict `<` to immunise the
exact kerb-line boundary against r2 rounding (only reachable on a no-main-spine fallback; not hit today).
4. **CITY_SPEC §v3 frozen at `v3.0`** — §v2-style FROZEN marker, final wording pass over venues table,
genre map, gigKey contract, corridor law, gig golden. Genres final (pubrock/grunge/covers, Fable's ruling).
Coordination: the R12 single-venue **alias line stays in §v3 for F to delete atomically** with the
`gig_state.js` getters (debt #1) — deleting it now would be doc-ahead-of-code (alias still live) and would
race F on the same lines; the freeze marker carves it out explicitly. Flagged to F that the runtime block
omits `nightOf(id)`, and to B that `venue.js:228` is a second bare-string edit site. **Not touched:**
`web/assets/manifest.json` shows a concurrent Lane E `glb_law` edit in the shared tree — left for E.
## Round 13 (2026-07-15) — v3.0-beta THE DISTRICT: 24 venues + the week + town-wide posters + verge fix

View File

@ -204,7 +204,15 @@ Interiors are **not** street geometry. Walking into a doorway triggers
exactly where you stood. Street windows show a cheap fake (tinted glass + emissive warm glow at
night in v1; interior-mapping shader is a stretch goal).
## CityPlan v3 — gig layer (`?gigs=1`) — v3.0-beta (THE DISTRICT)
## CityPlan v3 — gig layer (`?gigs=1`) — THE DISTRICT
> **FROZEN at `v3.0` (round 14).** The authoritative Layer-1 **gig-layer** contract — venues, the week's
> schedule, posters, the `gigKey` map, the corridor law, and the runtime surface. `web/js/citygen/selfcheck.js`
> enforces every law here, including the round-14 **400-seed district invariant sweep** + an osm
> graceful-placement case. Changes are a **CITY_SPEC amendment in the same commit + a gig-golden re-pin**,
> never silent drift. **One edit is still pending and lands with Lane F's alias-retirement commit** (R13
> debt #1): the R12 single-venue compat alias in the runtime block below retires; everything else here is
> final for the `v3.0` tag.
> **Prime flag law.** The gig layer is a **post-hoc augmentation** applied by the selector only when
> `?gigs=1`. With the flag OFF, `generatePlanFor(seed, src)` returns the byte-identical v2 base plan —
@ -281,7 +289,8 @@ places E's poster skin at `(x, z)` rotated `ry`; F wires "poster → that venue'
the **full corridor** (carriageway + verge/footpath, centreline ± width/2), never the carriageway alone —
so a naive `node.x + 2` seats furniture mid-bitumen. `web/js/core/registry.js` now exports the split as
pure functions over an edge (no plan fields change ⇒ base goldens stay frozen), re-exported from the
barrel: **`roadWidth(edge)`** (carriageway half-corridor: main 10 m, side 68, lane = all, arcade 0),
barrel: **`roadWidth(edge)`** (full carriageway width — main 10 m, side 68, lane = all, arcade 0; the
kerb sits at `roadWidth/2` from the centreline),
**`vergeBand(edge) → [inner,outer]`** (where footpath furniture lives), **`poleOffset(edge)`** (where a
pole/poster seats — just behind the kerb). Posters, poles, verandah posts belong in the verge band; the
selfcheck asserts no poster stands within `roadWidth/2` of any edge centreline.

View File

@ -2,6 +2,47 @@
Durable answers to questions other lanes raised against Lane A. Full status in `A-progress.md`.
## Round 14 (2026-07-16) — v3.0 RELEASE: invariant sweep + CITY_SPEC §v3 frozen
Release round — no new systems, no golden moves. **Synthetic `0x3fa36874` and gig `0x1f636349` are
unchanged**; the only code I touched is `selfcheck.js` (test-only), so no seeded stream gained a draw.
### The 400-seed district sweep (`selfcheck.js`, ALL GREEN 14264/14264 in ~0.7s)
- Factored the district-contract checks out of `gigSuite` into `districtInvariants(plan, label)` so the
sweep asserts them at SCALE without re-running the heavy `structuralSuite` (that still runs on the 6
hero `SEEDS` + osm parity). Behaviour-preserving — the 4 hero-seed `gigSuite` runs are byte-identical.
- Sweeps **seeds 1400** synthetic + the large edge seeds (2e9, 8675309) asserting the full district
contract each: 24 venues, ≥1 pub, never the openLate shop, no band twice in one night town-wide,
per-venue 46 nights, every poster off the carriageway. Also asserts the seeded count **spans {2,3,4}**
across the range (measured 146/121/133) — proves the 24 range is genuinely exercised, not pinned.
- **osm graceful placement**: `katoomba` (smallest fixture, 19 shops, **no warehouse district**) still
lands 24 valid venues incl. a pub for every hero seed — proving `pickVenues`' off-spine fallback.
Honest scope: both osm fixtures keep a main spine, so this proves the *no-warehouse* branch, not
*no-spine* (a genuine no-spine town would need a single-row fixture that doesn't exist yet — v3.1).
- Poster check kept as "≥ `roadWidth/2` from the nearest edge centreline" (not a literal `poleOffset`
compare, which would false-fail legit corner posters seated behind their placement edge's `poleOffset`
but inside a crossing edge's clearance band). Added a `1e-6` epsilon on the strict `<` to immunise the
exact kerb-line boundary against r2 rounding (only reachable on a no-main-spine fallback; not hit today).
### CITY_SPEC §v3 frozen at v3.0
Final wording pass; marked **FROZEN at `v3.0` (round 14)** with a §v2-style marker. Venues table, genre
map, gigKey contract, corridor law, and gig golden `0x1f636349` are all final (genres are pubrock/grunge/
covers — Fable's R14 ruling, John did not re-flavour).
### → Lane F: the alias-retirement handshake (R13 debt #1) — I deliberately did NOT touch it
The R12 single-venue compat alias is **still live** (`gig_state.js:141-151`, read by `audio.js`/`venue.js`),
so I left the §v3 alias sentences (`CITY_SPEC.md` ~lines 271272) **intact** — deleting them at freeze
time would be doc-ahead-of-code and would race you on the same lines. The freeze marker explicitly carves
this out as your pending edit. In your alias-retirement commit (after B drops its readers), delete both
`gig_state.js:141-151` and those two spec sentences **together** — one atomic step, one deleter, no race.
- While you're in that runtime block: it enumerates `stateOf/onOf/openOf/gigOf/coverOf/bandNameOf/paidOf`
+ `markPaidOf` + `.venueShopIds`, but **omits `nightOf(id)`** (`gig_state.js:131`). Add it when you edit.
### → Lane B: second edit site for the bare-string arm
When you remove the `venue.update()` bare-string alias arm, note `venue.js:228` also self-calls
`update('quiet')` at construction through that same arm — fix both or it falls through (harmless today:
still yields `'quiet'`, but it's a live second site).
## Round 13 (2026-07-15) — v3.0-beta THE DISTRICT (published FIRST; C/D/B/F hang off this)
Supersedes the R12 alpha interface below. Same flag (`?gigs=1`), same prime-flag law: flag OFF ⇒ plan

View File

@ -145,7 +145,13 @@ function structuralSuite(plan, label) {
// The v3 GIG layer (?gigs=1). Runs the full structuralSuite (venue-aware) plus gig-specific
// invariants. Validates the DISTRICT interface every downstream lane (C/D/B/F) hangs off this round.
function gigSuite(plan, label) {
structuralSuite(plan, label); // full coverage; venues exempt from late-shop rule
structuralSuite(plan, label); // full structural coverage; venues exempt from late-shop rule
districtInvariants(plan, label); // + the v3 district contract (also run standalone by the ROUND14 sweep)
}
// The v3 DISTRICT contract, factored out of gigSuite (ROUND14) so the 400-seed sweep below can assert it
// at SCALE without re-running the heavy structuralSuite (already covered on the 6 SEEDS + osm parity).
function districtInvariants(plan, label) {
const venues = plan.shops.filter(s => s.venue);
const byId = new Map(venues.map(v => [v.id, v]));
@ -199,7 +205,9 @@ function gigSuite(plan, label) {
const d = Math.hypot(p.x - nx, p.z - nz);
if (d < best) { best = d; bestEdge = e; }
}
if (bestEdge && best < roadWidth(bestEdge) / 2) { onRoad = [p.id, bestEdge.id, +best.toFixed(2)]; break; }
// 1e-6 immunises the exact kerb-line boundary against r2 coord rounding (only reachable on a
// no-main-spine fallback where poleOffset == roadWidth/2; never hit by the current sweep).
if (bestEdge && best < roadWidth(bestEdge) / 2 - 1e-6) { onRoad = [p.id, bestEdge.id, +best.toFixed(2)]; break; }
}
ok(onRoad === null, `${label}: no poster stands on a carriageway` + (onRoad ? ` (poster ${onRoad[0]} is ${onRoad[2]}m from edge ${onRoad[1]}'s centreline)` : ''));
}
@ -294,6 +302,35 @@ const GIG_GOLDEN = 0x1f636349; // ROUND13 re-pin: multi-venue district changed t
ok(GIG_GOLDEN !== 0 && hash === (GIG_GOLDEN >>> 0), `gig layer golden 0x${hash.toString(16)} matches pinned 0x${(GIG_GOLDEN >>> 0).toString(16)}`);
}
// ── 3d. district invariant sweep (ROUND14): 400 synthetic seeds + osm graceful placement ───────────
// Release-round Lane A gate: the district contract must hold at SCALE, not just the 4 hero seeds. Runs
// districtInvariants ONLY (structuralSuite already covers the 6 SEEDS + osm parity) so 400 full gig-on
// generations stay well under a second. Pure validation — NO new rng draws, no golden moves.
section('district invariant sweep (ROUND14: 400 synthetic seeds + osm graceful placement)');
const SWEEP_N = 400;
const sweepVenueCounts = new Set();
for (let s = 1; s <= SWEEP_N; s++) {
const plan = generatePlanFor(s, 'synthetic', { gigs: true });
districtInvariants(plan, `sweep syn ${s}`);
sweepVenueCounts.add(plan.shops.filter(v => v.venue).length);
}
// also cover the large/edge uint32 seeds (2e9, 8675309 …) — where rng edge cases hide — that fall
// outside 1..400 and otherwise only get the flags-off byte-identity check, not the district contract.
for (const s of SEEDS) if (s > SWEEP_N) districtInvariants(generatePlanFor(s, 'synthetic', { gigs: true }), `sweep syn ${s}`);
// the seeded 24 count is genuinely exercised across the range, not pinned to one value
ok([2, 3, 4].every(n => sweepVenueCounts.has(n)),
`sweep: venue count spans {2,3,4} across ${SWEEP_N} seeds (saw {${[...sweepVenueCounts].sort((a, b) => a - b).join(',')}})`);
// osm graceful placement: katoomba is the smallest fixture (19 shops) with NO warehouse district, so
// pickVenues' band_room/rsl rules hit their off-spine fallback — yet still land 24 valid venues incl.
// a pub. (Both osm fixtures keep a main spine, so this proves the no-warehouse branch, not no-spine.)
for (const seed of GIG_SEEDS) {
const plan = generatePlanFor(seed, 'osm', { gigs: true, town: 'katoomba' });
ok(plan.blocks.every(b => b.kind !== 'warehouse'),
`sweep osm/katoomba ${seed}: fixture has no warehouse district (graceful-placement precondition)`);
districtInvariants(plan, `sweep osm/katoomba ${seed}`);
}
// ── 4. every facade skin referenced by the registry exists on disk ──────────────────
section('assets on disk');
for (const f of allFacadeSkins()) ok(existsSync(join(ASSETS, f)), `registry facade exists: ${f}`);