Lane A R37 (v8 wave 0 §0.1, ruling-side): the poster gate was VACUOUS — corrected to three arms with a permanent control

The audit's claim against my lane, re-measured and CONFIRMED exactly: on the default
boot (synthetic 20261990, gigs default-ON) there are 14 posters, all 14 stand inside
e.width/2 — the bitumen ground.js paints — and 0 stand inside the ruled kerb
roadWidth/2. One refinement it did not make: 12 are spine posters at poleOffset=6m,
8m inside the paint (genuinely mid-road); 2 are frontage posters clipping the corridor
edge by 0.06m (FACADE_PROUD past their venue's facade plane).

WHY the gate was green: my placer seats posters via poleOffset, which is DERIVED FROM
roadWidth, and my selfcheck asserted clearance against roadWidth/2. Gate and subject
were the same function agreeing with itself — the R27 cross-convention trap recurring
inside the very file whose header explains it. It never read a number that came from
Lane B, so no change to where the road is painted could make it fail.

The correction (selfcheck.js, districtInvariants), three arms:
  1. no poster on a carriageway — measured through vergeBand(e)[0], the published law
     itself, the same call B's ground now consumes, so gate and renderer move together
  2. every SPINE poster stands INSIDE some edge's vergeBand — the outer bound the old
     check never had ("not on the road" also passed for a poster in a paddock).
     Frontage exempt BY MEASUREMENT: 18/108 gig plans have a frontage poster past every
     corridor edge (worst 3.98m, launceston_real) via plan_osm's lot setback.
     Spine violations over the same corpus: 0/108.
  3. THE CONTROL — count posters standing on bitumen under the PRE-RULING ground
     (?verge=0, road quad = full e.width) and assert it is NOT ZERO on every gig plan
     (min 3 / max 212 / never 0 across 510 plans), pinned at EXACTLY 14 on the default
     boot. A control allowed to drift toward zero is how a gate goes vacuous.
     The replica is pinned to B's FLAG, not to a line number that will drift.

FALSIFIABILITY PROVEN. Simulated ?verge=0 honestly — vergeBand switched to the
pre-ruling reading, poleOffset pinned to the true band so the PLACER stays
byte-identical (it is a render flag: paint changes, plan does not; fingerprint and
every gig golden green in both runs, so the gate's reading is the only variable):
  corrected (R37) gate  →  FAIL, 1020 failures (2 arms x 510 plans)
  pre-R37 (HEAD) gate   →  ALL GREEN 156,352/156,352  — structurally blind

GOLDENS: NOTHING MOVED, ZERO RE-PINS. 0x5f76e76, GIG_GOLDEN 0xec7a2d39, 25 base town
goldens and 24 real-town gig goldens all untouched — correct and predicted, since the
only generator-adjacent file changed is the selfcheck, which validates output and never
produces it. 156,352 -> 157,407 checks (+1,055), 10.3s.

Corridor law CONFIRMED for Lane B, semantics unchanged, contract published in full
(LANE_A_NOTES §37) with four hazards flagged loudly while B consumes it in parallel:
roadWidth and edge.width are both FULL widths but one is carriageway and one corridor
(that naming IS why this bug existed); lane's band is NOT the literal [2,2] the round
instructions state (2 synthetic edges are, 56 real lane edges are [4,4] — branch on
outer<=inner); arcade is all footpath with coincident kerbs to skip; the inward
extension never inverts (0 of 31,039 edges). Two charter numbers corrected: the "9m
band / 18m of every main" figures are synthetic-only — on every real town a main is
24m => 7m band, and the corpus's dominant edge is side@12 at 22,415/31,039 (72%) with
a 3m footpath.

FILED, NOT FIXED — v8 debt ledger, four entries (LANE_A_NOTES §37): D1 chunkIndex
(two impls, THREE divergences, header comment promises a swap that would break three
ways — owner B) · D2 isOpen (three impls + an ad-hoc fourth site; A's canonical one has
ZERO importers; the live shell copy treats no-hours as CLOSED where A's law says OPEN —
latent until v8 adds an hours-less lot — owner F) · D3 two shop registries (B's missing
all three venue kinds => buildings.js:344 renders op-shop signage on every gig venue
since R13 — owner B) · D4 NEW, mine: the poster gate's inner bound is NEAREST-EDGE and
an arcade (kerb 0) camouflages a real carriageway — 8 real-town plans hide a spine
poster up to 1.78m inside a main, 0 on synthetic. Cost measured decision-grade:
synthetic 0/4 plans move, 11/26 town gig goldens re-pin, poster counts unchanged.
Deferred to R38 on purpose — it moves plan output for a reason that is NOT the kerb
ruling, and the "did the kerb ruling move a golden?" answer must stay a clean no.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
m3ultra 2026-08-03 16:25:04 +10:00
parent 62c0a2185d
commit e4d77629c5
4 changed files with 372 additions and 25 deletions

View File

@ -1,8 +1,73 @@
# LANE A — CITYGEN · progress (PROCITY-A)
*Status: **all deliverables landed; self-check ALL GREEN (156,352/156,352); rounds 230 closed**.
R30 (v7.0-alpha ledger #6): the rotation boundary VERIFIED — F's `day` salt rides runtime streams only,
plan generation structurally cannot receive it; all goldens frozen, zero re-pins.*
*Status: **all deliverables landed; self-check ALL GREEN (157,407/157,407); rounds 230 + 37 closed**.
R37 (v8 wave 0 §0.1): the poster-clearance gate was VACUOUS — it validated my own placer against my own
law and stayed green while all 14 posters on the default boot stood on B's bitumen. Corrected to three
arms with a permanent control; proven to fail on the pre-ruling geometry where the old gate cannot.
All goldens frozen, zero re-pins.*
## Round 37 (2026-08-03) — v8 WAVE 0 §0.1: THE KERB (ruling-side) — the gate I owned was the vacuous one
Full detail in LANE_A_NOTES §37. Four jobs, all measured before they were written.
**1. Verified the audit against my own lane — CONFIRMED, exactly, with one refinement it did not make.**
Default boot (synthetic, seed 20261990, gigs default-ON): **14 posters, all 14 inside `e.width/2` (the
bitumen B paints), 0 inside the ruled kerb `roadWidth/2`.** The refinement: they are two populations —
**12 spine posters at `poleOffset` = 6 m are 8 m inside the paint (genuinely mid-road); 2 frontage posters
clip the corridor edge by 0.06 m** (13.94 of 14, 6.94 of 7 — `FACADE_PROUD` pushes them a fingernail past
their venue's facade plane). Both are inside the paint; "14 in the middle of the road" would over-claim.
Also confirmed: B's `ground.js` road quad is `hQuad(…, e.width, 0)` — the full corridor, kerbs at
`e.width/2 + 0.12`, nine metres outside mine on a main.
**2. Diagnosed WHY the gate was green, and it is our own documented lesson recurring.** My placer seats
posters via `poleOffset`, which is *derived from* `roadWidth`; my selfcheck asserted clearance against
`roadWidth/2`. Gate and subject were the same function agreeing with itself — the R27 cross-convention
trap, inside the very file whose header (`selfcheck.js:33-40`) explains it. It never read a number that
came from B, so no change to the street could make it fail.
**3. Corrected it — three arms, the third a permanent control.** (1) no poster on a carriageway, measured
through `vergeBand(e)[0]`, the published law itself — the same call B's ground now consumes; (2) every
**spine** poster stands *inside* some edge's `vergeBand` (the outer bound the old check never had —
frontage exempt **by measurement**: on 18/108 gig plans a frontage poster legitimately stands past every
corridor edge, worst 3.98 m, launceston_real; spine violations 0/108); (3) **the control** — count the
posters that would stand on bitumen under the pre-ruling `?verge=0` ground and assert it is **> 0** on
every gig plan (measured min 3 / max 212 / never 0 across 510 plans), **pinned at exactly 14** on the
default boot, because a control allowed to drift to zero is how a gate goes vacuous in the first place.
The replica is pinned to B's **flag**, not to a line number that will drift as B edits.
**Proved it fails on the old geometry, and that the old gate cannot.** Simulated `?verge=0` honestly —
`vergeBand` switched to the pre-ruling reading, `poleOffset` pinned to the true band so the **placer is
byte-identical** (it is a render flag: the paint changes, the plan does not; fingerprint and every gig
golden stayed green in both runs, so the gate's reading is the only variable). **Corrected gate: ✗ FAIL,
1020 failures — 2 arms × 510 plans. Pre-R37 gate: ✓ ALL GREEN 156,352/156,352.**
**4. Confirmed the corridor-law exports for B and published the whole contract** (LANE_A_NOTES §37 —
B is consuming them in a parallel session, so it is written as a warning, not a reference). Semantics
unchanged; nothing ambiguous was altered mid-round. Flagged loudly: `roadWidth` returns a **full** width
and so does `edge.width`, but one is carriageway and the other corridor — that naming is *why* this bug
existed; **`lane`'s band is not the literal `[2,2]` the round instructions state** (2 synthetic edges are,
**56 real lane edges are `[4,4]`** — branch on `outer <= inner`); `arcade` is all footpath with coincident
kerbs to skip; and the inward extension **never inverts** (`roadWidth > e.width`: **0 of 31,039 edges**,
though a `side` under 6 m would — guard it). Two charter numbers corrected: the "9 m band / 18 m of every
main" figures are **synthetic-only** (main 28 m); **on all real towns a main is 24 m ⇒ 7 m band**, and the
corpus's dominant edge is `side`@12**22,415 of 31,039 (72%)**, a **3 m** footpath.
**GOLDEN VERDICT: nothing moved, zero re-pins** — correct and predicted, since the only file I changed on
the generator side is the *selfcheck*, which validates output and never produces it. `0x5f76e76`,
`GIG_GOLDEN 0xec7a2d39`, 25 base town goldens and 24 real-town gig goldens all green and untouched.
Checks **156,352 → 157,407** (+1,055), runtime 10.3 s.
**Filed, not fixed — v8 debt ledger, four entries** (LANE_A_NOTES §37): D1 `chunkIndex` (two impls, three
divergences, and a header comment promising a swap that would break three ways — owner B); D2 `isOpen`
(three impls plus an ad-hoc fourth site; A's canonical one has **zero importers**; the live shell copy
treats *no hours* as **closed** where A's law says *open* — latent today, fires the moment v8 adds an
hours-less lot — owner F); D3 two shop registries (B's missing all three venue kinds ⇒ `buildings.js:344`
falls back to **op-shop** signage on every gig venue since R13 — owner B); **D4, new, found this round**:
my own poster gate's inner bound is **nearest-edge**, and an `arcade` (kerb 0) camouflages a real
carriageway — 8 real-town plans hide a spine poster up to **1.78 m inside a main**, zero on synthetic.
Cost measured decision-grade: synthetic **0/4 plans move**, **11/26 town gig goldens re-pin**, poster
counts unchanged. Deliberately deferred to R38 — it moves plan output for a reason that is not the kerb
ruling, and the "did the kerb ruling move a golden?" answer must stay a clean **no**.
## Round 30 (2026-07-18) — the rotation boundary verify (ledger #6, small round as briefed)

View File

@ -394,10 +394,28 @@ barrel: **`roadWidth(edge)`** (full carriageway width — main 10 m, side 68,
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). Poles, verandah posts and **spine** posters belong in the verge
band; the selfcheck asserts no **spine** poster stands within `roadWidth/2` of any edge centreline. A
**frontage** poster is exempt — it is bound to its venue building (the +Z facade B draws), so at a corner
venue whose facade abuts a crossing street it may legitimately sit near that street's kerb (B's building
geometry, not a mid-road defect).
band. A **frontage** poster is exempt from the band — it is bound to its venue building (the +Z facade B
draws), so at a corner venue whose facade abuts a crossing street it may legitimately sit near that
street's kerb (B's building geometry, not a mid-road defect); on a real town it may legitimately stand
**past** every corridor edge, because `plan_osm.js` sets a lot back at `max(KERB, roadWidth/2 + FOOTPATH)`
— measured worst 3.98 m (launceston_real, R37).
**ROUND37 — THE KERB IS FOOTPATH (John's ruling), and the poster gate that was green over a wrong
street.** The band between `roadWidth(e)/2` and `e.width/2` is **footpath**, and Lane B's ground paints
it as footpath rather than as road. The selfcheck's poster-clearance check used to ask one question —
"is the poster outside `roadWidth/2`?" — which A's own placer already guaranteed, because `poleOffset` is
*derived from* `roadWidth`. Gate and subject were the same function agreeing with itself, so it stayed
green for 24 rounds while all **14** gig posters on the default boot stood on painted bitumen. It is now
**three arms**: (1) no poster on a carriageway, measured through `vergeBand(e)[0]` — the published law
itself, the same call B's ground consumes, so gate and renderer cannot drift apart; (2) every **spine**
poster stands *inside* some edge's `vergeBand` (the bound the old check never had — "not on the road"
also passed for a poster in a paddock); (3) **the control** — count the posters that would stand on
bitumen under the pre-ruling ground (`?verge=0`, road quad = full `e.width`) and assert that count is
**not zero**, pinned at exactly 14 on the default boot. Arms 12 prove nothing unless arm 3 shows the two
geometries are distinguishable. Proof of falsifiability, measured: with `vergeBand` switched to the
pre-ruling reading and the placer held byte-identical, the corrected gate goes **red 1020×** (2 arms ×
510 plans) while the pre-R37 gate is **ALL GREEN 156,352/156,352** — structurally blind to where the
road is painted.
**Band names + the drop-in.** `bandName(seed)` generates 90s-Aussie pub-rock names from wordlists.
`web/assets/custom_bands.json` is an **OPTIONAL** editable drop-in (schema `{ "bands": ["The Feral

View File

@ -2,6 +2,194 @@
Durable answers to questions other lanes raised against Lane A. Full status in `A-progress.md`.
## Round 37 (2026-08-03) — v8 WAVE 0 §0.1: THE KERB, and the gate that was green over a wrong street
### → Lane B (URGENT, you are consuming these RIGHT NOW): the corridor law, stated plainly
**The exports are correct and you should consume them as-is. I changed no semantics this round.**
`roadWidth` / `vergeBand` / `poleOffset` in `web/js/core/registry.js:217-240`, re-exported from the
barrel `citygen/index.js:18`. But **four things about them will bite you**, and three of them are things
the round instructions state in a way that is true only for the synthetic town:
**1. `roadWidth` is a TRAP OF A NAME, and it is why this bug existed.** It returns a **full width**, and
`edge.width` is *also* a full width — but `edge.width` is the **corridor** (road + both footpaths) and
`roadWidth(e)` is the **carriageway alone**. Two full widths, similar names, nine metres apart on a main.
Your `ground.js` painted the road quad `e.width` wide for 34 rounds because of exactly this. The kerb is
at **`roadWidth(e)/2`**, i.e. `vergeBand(e)[0]`. Prefer `vergeBand(e)[0]` over `roadWidth(e)/2` in new
code — same number by construction, but it reads as what it is and it survives a change to the split.
**2. `lane`'s verge band is NOT `[2,2]`.** The round instructions say it is; that is the **synthetic**
lane (width 4). Measured over 31,039 edges across synthetic + all 26 town caches: **58 lane edges, 2 at
`[2,2]` (synthetic, width 4) and 56 at `[4,4]` (real, width 8).** The invariant is not a literal — it is
**`outer === inner` ⇒ the edge has no footpath at all** (a lane is all carriageway by law). **Branch on
`outer <= inner`, never on the number 2**, or you emit a zero-width strip on the synthetic town and a
wrong-width one on every real town.
**3. `arcade` is `roadWidth 0` ⇒ band `[0, width/2]` — the WHOLE corridor is footpath.** 1,102 arcade
edges in the corpus (1 synthetic at width 5, 1,101 real at width 6). Skip the road quad *and* both kerb
strips: at `roadWidth 0` your two kerbs are coincident on the centreline and will z-fight.
**4. The inward extension is never negative — measured, not assumed.** `roadWidth(e) > e.width` would
make your inward footpath strip negative-width. **0 occurrences in 31,039 edges.** It is nonetheless
*reachable*: `side` returns a flat 6 for any width < 18, so a `side` edge narrower than 6 m would invert.
No such edge exists in any shipped cache. Guard it anyway (`Math.max(0, …)`) — it costs a call.
**The whole contract, exhaustively — the entire corpus reduces to nine (kind, width) pairs:**
| kind | `edge.width` | `roadWidth` | `vergeBand` | footpath each side | `poleOffset` | count | where |
|---|---|---|---|---|---|---|---|
| `arcade` | 5 | 0 | `[0, 2.5]` | 2.5 | 1 | 1 | synthetic |
| `arcade` | 6 | 0 | `[0, 3]` | 3 | 1 | 1,101 | real |
| `lane` | 4 | 4 | `[2, 2]` | **0** | 2 | 2 | synthetic |
| `lane` | 8 | 8 | `[4, 4]` | **0** | 4 | 56 | real |
| `main` | **24** | 10 | `[5, 12]` | **7** | 6 | 7,446 | **real** |
| `main` | 28 | 10 | `[5, 14]` | 9 | 6 | 6 | synthetic |
| `side` | 12 | 6 | `[3, 6]` | 3 | 4 | **22,415** | both |
| `side` | 14 | 6 | `[3, 7]` | 4 | 4 | 8 | synthetic |
| `side` | 20 | 8 | `[4, 10]` | 6 | 5 | 4 | synthetic |
**Two corrections to the charter's numbers, both measured.** The charter and the audit both describe
"the 9 m band" and "18 m of every main street stops being painted as road". **That is the synthetic town
only** — its main is 28 m wide. **On every real town a main is 24 m**, so the band is **7 m** and 14 m of
each main stops being road. And the dominant edge in the whole corpus is not a main at all: **`side` at
width 12 is 22,415 of 31,039 edges (72%)**, with a **3 m** footpath. Size your tri estimate off that, not
off the 9 m headline. (The +0 draws claim is yours to prove; I have no visibility into your merge.)
**On `?verge=0`:** keep it reverting to *exactly* the pre-ruling geometry — road quad at the full
`e.width`, kerb at `e.width/2 + 0.12`, footpaths outside the corridor. My selfcheck now pins the count of
posters standing on that geometry at **exactly 14** on the default boot, and Lane F's control leg asserts
the same failure at runtime. If `?verge=0` drifts into something that is not the pre-ruling street, three
gates in two lanes stop meaning what they say.
### → Fable / Lane F: the vacuous gate was mine, and it is now falsifiable — with the proof
**The audit's claim against my lane is CONFIRMED, exactly, and I can add a number it did not have.**
Default boot (synthetic, golden seed 20261990, gigs default-ON since R16): **14 posters. All 14 stand
inside `e.width/2` — the bitumen B paints. Zero stand inside the ruled kerb `roadWidth/2`.** The audit's
"all 14 gig posters are standing in the road" is precise, not rhetorical.
*One honest refinement it did not make:* the 14 are not one population. **12 are spine posters seated at
`poleOffset` = 6 m, i.e. 8 m inside painted bitumen — genuinely mid-road.** The other **2 are frontage
posters clipping the corridor edge by 0.06 m** (13.94 m of 14, and 6.94 m of 7) — they sit on their
venue's facade plane, and `FACADE_PROUD = 0.06` pushes them a fingernail past it. Both classes are inside
the paint and the fix lands both, but "14 posters standing in the middle of the road" would be an
over-claim: **12 are, 2 are 6 cm over the kerb line.**
**Why the gate was green.** My placer seats spine posters via `poleOffset(e)`, which is *derived from*
`roadWidth`. My selfcheck asserted clearance against `roadWidth(e)/2`. **The gate and its subject were
the same function agreeing with itself** — the R27 cross-convention lesson recurring inside the very file
that documents it (`selfcheck.js:33-40` already says a self-consistent check over a self-contradicting
spec passes under either convention). It could not see B's geometry because it never read a number that
came from B.
**The correction (`selfcheck.js`, districtInvariants), three arms — arm 3 is the control:**
1. **No poster on a carriageway** — now measured through `vergeBand(e)[0]`, the published law itself, the
same call B's ground consumes. Floors unchanged (frontage ≥ `POSTER_CLEAR`0.02, spine ≥ 0; a `lane`
pole seats exactly *on* its kerb, so 0 must stay legal).
2. **Every SPINE poster stands INSIDE some edge's `vergeBand`** — the bound the old check never had.
"Not on the road" also passed for a poster standing in a paddock; this asserts it stands on paint B
actually lays. Frontage posters exempt **by measurement, not convenience**: on 18 of 108 gig plans a
frontage poster legitimately stands past every corridor edge (worst **3.98 m**, launceston_real),
because `plan_osm.js:459` sets a real-town lot back at `max(KERB, roadWidth/2 + FOOTPATH)`. Spine
violations over the same corpus: **0/108**.
3. **THE CONTROL** — count the posters that would stand on bitumen under the **pre-ruling** ground (B's
road quad at full `e.width`; the `?verge=0` geometry, replicated in `preRulingRoadPosters()` and
pinned to the **flag**, not to a line number that will drift as B edits). Assert it is **> 0** on every
gig plan, and **exactly 14** on the default boot. Arms 12 prove nothing unless the two geometries are
distinguishable; this is what makes them non-vacuous, and pinning 14 rather than `> 0` alone is
deliberate — *a control allowed to drift toward zero is how a gate goes vacuous in the first place.*
Measured range across 510 plans: **min 3, max 212, never 0.**
**THE FALSIFIABILITY PROOF — the new gate fails on the old street, the old gate cannot.** Simulated
`?verge=0` honestly: switched `vergeBand` to the pre-ruling reading (`[width/2, width/2]` — whole
corridor is carriageway) in a scratch copy of the tree, and **pinned `poleOffset` to the true band so the
placer is byte-identical** — `?verge=0` is a *render* flag, the paint changes and A's plan does not.
Fingerprint `0x5f76e76` and every gig golden stayed green in both runs, so the only variable is what the
gate reads. Result:
| gate | under the ruled geometry | under `?verge=0` geometry |
|---|---|---|
| **corrected (R37)** | ALL GREEN 157,407/157,407 | **✗ FAIL — 1020 failures** (2 arms × 510 plans) |
| **pre-R37 (HEAD)** | ALL GREEN 156,352/156,352 | **✓ ALL GREEN 156,352/156,352** |
The old gate is not merely wrong about the street — it is **structurally blind to it**. No change to
where the road is painted can make it fail. That is the vacuous-gate species in one table.
**GOLDEN VERDICT: NOTHING MOVED. Zero re-pins.** Fingerprint `0x5f76e76`, `GIG_GOLDEN 0xec7a2d39`, all
25 base town goldens and all 24 `REAL_TOWN_GIG_GOLDENS` green and untouched. Correct, and predicted: the
only generator-adjacent file I touched is the *selfcheck*, which validates output and never produces it.
Check count **156,352 → 157,407** (+1,055 new assertions), runtime 10.3 s.
### → Fable: v8 DEBT LEDGER — filed, not fixed (four entries, three of them the audit's)
**D1 — `chunkIndex`: two implementations, THREE divergences, and a header comment that is false.**
A's `citygen/plan.js:414` `chunkIndex(plan, chunkSize = CHUNK)` vs B's `world/planutil.js:37`
`chunkIndex(plan)`. The audit named one divergence; there are three:
(a) **bucketing** — A puts a lot in every chunk its rotated-AABB touches, B puts it in the chunk
containing its centre only; (b) **container** — A returns a plain object keyed `"cx,cz"`, B returns a
`Map`; (c) **element type** — A's buckets hold **ids** (`lots:[lot.id]`), B's hold **objects**
(`lots:[lot]`). `planutil.js:4` says *"If Lane A later ships an official chunkIndex(plan) we swap the
import; the shapes match."* — **the shapes do not match, on any of the three axes**, and that sentence is
the trap: it invites exactly the one-line swap that would break the streamer three ways at once.
Consumers are split today: **the runtime streamer uses B's** (`chunks.js:7,16`), **the debug map uses
A's** (`map.html:89,315`), and `sim.js:48-55` re-derives the key math a *third* time, locally, on purpose
(so the standalone sim page needs no Lane B module) — that third copy is defensible and documented.
*Canonical:* **B's for the runtime** (centre-bucketing is what a streamer wants — an AABB-spanning lot
would build in up to four chunks). A's AABB form is right for *coverage* questions, which is what my own
selfcheck asks of it (`selfcheck.js:130-155`). **Cost:** rename, not reconcile — give them distinct names
(`chunkIndexByCentre` / `chunkIndexByCoverage`), delete the false comment, and state which question each
answers. Half a round. **Owner: B** (the file and the runtime consumer are B's); A supplies the rename.
**D2 — `isOpen`: three real implementations plus an ad-hoc fourth site, and A's canonical one has ZERO
importers.** Canonical: `citygen/plan.js:28`, half-open `open ≤ hour < close`, **no hours ⇒ open**,
re-exported at `citygen/index.js:10`. **Nobody imports it** — grep-verified across `world/`, `citizens/`,
`interiors/`. The live one is `web/index.html:454`, published to `window.PROCITY.isOpen` (`:603`) and
consumed by `world/audio.js:270` and **14 call sites across `tools/`** (`flags_check.py`, `shots.py`,
`v2_tour.py`, `qa/interior_scale_check.py`). **It has a real semantic divergence:**
`shop && shop.hours && …` returns **falsy for a shop with no hours**, where A's law says *no hours ⇒
always open*. Third: `citizens/sim.js:363` `_openAt(hours)` — A's semantics, but its own clock
(`(this.timeOfDay % 1) * 24`) instead of `currentHour()`. Fourth site, ad-hoc rather than a rewrite:
`world/dbg.js:241,279,286` inline hour predicates (`hours[1] >= 22`, `hours[0] <= 12 && hours[1] > 13`)
for screenshot bookmarks. **No shipped shop lacks `hours`** (my selfcheck asserts every shop has sane
hours), so the divergence is **latent, not live** — it fires the day a venue, cart, stall or civic lot
carries no hours, which v8's whole slate is about adding. **Cost:** the shell imports A's `isOpen` and
passes `currentHour()`; sim keeps its clock but calls the same predicate; ~5 lines, 1 line of risk.
**Owner: F** (the shell is F's; A's export is already correct and needs no change).
**D3 — two shop registries, and the venue signs are the visible bite.** A's `core/registry.js:32`
`SHOP_TYPES` = 12 types with `sign:{bg,fg,accent,style}`; B's `world/fixture_plan.js:28` `SHOP_REGISTRY`
= **9 types**, missing `pub` / `band_room` / `rsl` entirely, with only `signBg`/`signFg`. Its own header
(`:26-27`) says *"Lane A owns web/js/core/registry.js; it hasn't landed yet, so Lane B carries the slice
it needs"* — **registry.js landed in R13; that comment has been stale for 24 rounds.** The bite is one
line: `buildings.js:344` `const reg = SHOP_REGISTRY[shop.type] || SHOP_REGISTRY.opshop;` — so every gig
venue renders **op-shop** signage (`#2c2820` on `#f0e2c0`) instead of the registry's gilt / stencil / club
palettes, on 24 venues per town across 27 towns, since R13. Confirmed as the audit reported it.
*Also drifted, but harmless:* the facade pools differ on four types (`toy` 3→2, `book` 3→2, `pawn` 4→3,
`stall` 2→1) — **dead config on the live path**, because `buildings.js:405` takes the skin from
`shop.facadeSkin` (A's plan field); B's pool is only reachable on the `fixture_plan` fallback when citygen
is absent (`index.html:138`). **Cost:** `buildings.js` imports A's `SHOP_TYPES` and reads
`.sign.bg`/`.sign.fg`; `SHOP_REGISTRY` shrinks to the fallback-only table it actually is, with an honest
header. Sign atlas colours change on venue lots ⇒ **a screenshot golden may move; not a plan golden.**
Half a round. **Owner: B**, with F re-pinning any shot.
**D4 — NEW, found this round, and it is the same disease one layer down: the poster gate's inner bound
is NEAREST-EDGE, and an `arcade` edge camouflages a carriageway.** Both my selfcheck's arm 1 and my
placer's `gigs.js:304 onCarriageway()` resolve the *nearest* edge and test against that one edge's kerb.
The nearest edge is not always the binding one. Measured over 108 gig plans: the nearest-edge rule reports
**0 violations**; an **all-edges** rule finds **8 plans** (all real towns) where a spine poster stands
inside a *non-nearest* edge's carriageway — **worst 1.78 m inside a main on northbridge_real seed 42**,
where the nearest edge is an `arcade` at 3.22 m whose `roadWidth` is **0**, so it "clears" by 1.45 m and
hides a main whose kerb is 5 m away. `arcade` edges are actively camouflaging: kerb 0 means they win the
nearest-edge test and always pass it. **Zero occurrences on synthetic, including the default boot** — the
kerb ruling is unaffected. **Cost, measured decision-grade, not estimated:** I built the all-edges variant
and diffed the whole corpus — **synthetic 0/4 plans move** (so `0x5f76e76`, `GIG_GOLDEN` and the classic
covenant are untouched), **11 of 26 town gig goldens re-pin**, and **poster counts do not change at all**
(the R20 cap re-subsamples a smaller candidate pool to the same size). So: one placer edit, one gate edit,
11 hash re-pins, no budget change, no visual count change. **Deliberately NOT done this round**: it moves
plan output for a reason that is *not* the kerb ruling, in a shared tree, while F is gating the matrix —
exactly the scope creep a floor round forbids, and it would muddy the "did the kerb ruling move a golden?"
signal, which must stay a clean **no**. **Owner: A. Recommend R38, one small item, self-contained.**
## Round 30 (2026-07-18) — THE ROTATION BOUNDARY VERIFY (ledger #6): **HOLDS**
### → Fable / F: `day` rides RUNTIME streams only — plan generation cannot see it. Measured, not read.

View File

@ -11,7 +11,7 @@ import { generatePlan, chunkIndex, lotCorners, obbOverlap, CHUNK } from './plan.
import { generatePlanOSM, osmTownKeys, validateTownCache, registerTownCache, MIN_TOWN_SHOPS,
medianShopSpacing, MAX_MEDIAN_SPACING_M } from './plan_osm.js';
import { generatePlanFor, gigKeyFor, POSTER_CLEAR } from './index.js';
import { allFacadeSkins, SHOP_TYPES, VENUE_KINDS, genreForVenueKind, roadWidth } from '../core/registry.js';
import { allFacadeSkins, SHOP_TYPES, VENUE_KINDS, genreForVenueKind, vergeBand } from '../core/registry.js';
import { xmur3 } from '../core/prng.js';
const HERE = dirname(fileURLToPath(import.meta.url));
@ -218,41 +218,104 @@ function districtInvariants(plan, label) {
}
ok(clash === null, `${label}: no band plays two venues on one night` + (clash !== null ? ` (night ${clash})` : ''));
// ── posters: town-wide, never on the bitumen — with a real FRONTAGE floor (ROUND16 ledger #6) ──
// ── posters vs THE CORRIDOR LAW — three arms, the third of which is the control (ROUND37 §0.1) ──
const gigIds = new Set(plan.gigs.map(g => g.gigId));
ok(Array.isArray(plan.posters) && plan.posters.every(p => gigIds.has(p.gigId) && isFiniteNum(p.x) && isFiniteNum(p.z) && isFiniteNum(p.ry)), `${label}: posters reference a real gig with finite coords`);
ok(plan.posters.every(p => plan.gigs.find(g => g.gigId === p.gigId).night === 0), `${label}: posters advertise tonight's gigs`);
// Two poster classes, two floors. A FRONTAGE poster sits on its venue's +Z street facade (B's canon:
// buildings.js facade + venue.js door/queue/camera are +Z); ROUND16 pickVenues now PREFERS a band_room/
// rsl lot whose facade clears every carriageway, so — no more R15 blanket exemption — a frontage poster
// must clear its nearest kerb by ≥ POSTER_CLEAR. A SPINE poster is free-standing on the verge and keeps
// the plain no-bitumen floor (≥ 0, the R12 regression guard). 0.02 absorbs r2 poster-coord rounding vs
// pickVenues' un-rounded clearance test (worst observed frontage clearance is ~0.51 m).
//
// ROUND37 CORRECTION — THE VACUOUS GATE, NAMED. Until this round this block asked exactly one
// question: "is the poster outside `roadWidth/2`?". But A's own placer seats spine posters at
// `poleOffset(e)`, which is DERIVED FROM `roadWidth` — so the gate and its subject were the same
// function agreeing with itself. It was green for 24 rounds while all 14 gig posters on the default
// boot stood on painted bitumen, because Lane B's ground painted its road quad the full `e.width`
// (28 m on a main, kerb nine metres inside it) and nothing in this suite could see that number.
// That is precisely the R27 cross-convention lesson recurring (see facadeQuadWorld above): a
// self-consistent check over a contested geometry passes under EITHER reading. So this now does what
// the R27 gate does — replicate the OTHER lane's maths and compare the two against each other.
//
// ARM 1 no poster stands on a CARRIAGEWAY — measured through `vergeBand(e)[0]`, the published law
// itself, i.e. the same call Lane B's ground now consumes. Gate and renderer cannot drift
// apart silently: change the law and both move together.
// ARM 2 every SPINE poster stands ON A FOOTPATH — inside `vergeBand(e)` of SOME edge that has one
// (`outer > inner`; a `lane` is all carriageway, so its band is degenerate and hosts no pole).
// This is the bound the old check never had: "not on the road" also passed for a poster
// standing in a paddock. After the kerb ruling it asserts the poster stands on paint B lays.
// ARM 3 THE CONTROL. If arms 12 pass under BOTH geometries they prove nothing, so count the
// posters that WOULD stand on bitumen under the PRE-RULING ground and assert that count is
// NOT ZERO. That is the falsifiability control living plan-side, permanently.
//
// Two poster classes, two floors (unchanged). A FRONTAGE poster sits on its venue's +Z street facade
// (B's canon: buildings.js facade + venue.js door/queue/camera are +Z); ROUND16 pickVenues PREFERS a
// band_room/rsl lot whose facade clears every carriageway, so a frontage poster must clear its nearest
// kerb by ≥ POSTER_CLEAR. A SPINE poster is free-standing on the verge and keeps the plain no-bitumen
// floor (≥ 0 — a `lane` has vergeBand [w/2,w/2], so its pole seats exactly ON the kerb and 0 is legal).
// 0.02 absorbs r2 poster-coord rounding vs pickVenues' un-rounded clearance test.
// Frontage posters are EXEMPT FROM ARM 2 by measurement, not convenience: a frontage poster rides its
// venue's facade plane, and `plan_osm.js` sets a real-town lot back at `max(KERB, roadWidth/2+FOOTPATH)`,
// so on 18 of 108 gig plans it legitimately stands past every corridor edge — worst 3.98 m
// (launceston_real, R37 measurement). SPINE violations over the same corpus: 0/108.
const lotByIdD = new Map(plan.lots.map(l => [l.id, l]));
const onFacade = p => venues.some(v => {
const lot = lotByIdD.get(v.lot); if (!lot) return false;
const off = lot.d / 2 + 0.06; // gigs.js buildPosters item 1 (FACADE_PROUD)
return Math.hypot(p.x - (lot.x + Math.sin(lot.ry) * off), p.z - (lot.z + Math.cos(lot.ry) * off)) < 0.5;
});
let onRoad = null;
const segs = edgeSegments(plan);
let onRoad = null, offFootpath = null;
for (const p of plan.posters) {
let best = Infinity, bestEdge = null;
for (const e of plan.streets.edges) {
const a = nodeById2(plan, e.a), b = nodeById2(plan, e.b);
if (!a || !b) continue;
const [nx, nz] = nearestOnSeg(p.x, p.z, a.x, a.z, b.x, b.z);
const frontage = onFacade(p);
let best = Infinity, bestEdge = null, onSomeFootpath = false;
for (const [e, ax, az, bx, bz] of segs) {
const [nx, nz] = nearestOnSeg(p.x, p.z, ax, az, bx, bz);
const d = Math.hypot(p.x - nx, p.z - nz);
if (d < best) { best = d; bestEdge = e; }
const [inner, outer] = vergeBand(e);
if (outer > inner && d >= inner - 1e-6 && d <= outer + 1e-6) onSomeFootpath = true;
}
if (!bestEdge) continue;
const frontage = onFacade(p);
const clearance = best - roadWidth(bestEdge) / 2;
const clearance = best - vergeBand(bestEdge)[0]; // ARM 1 — the kerb IS vergeBand's inner edge
const floor = frontage ? POSTER_CLEAR - 0.02 : -1e-6;
if (clearance < floor) { onRoad = [p.id, bestEdge.id, +clearance.toFixed(2), frontage ? 'frontage' : 'spine']; break; }
if (clearance < floor && onRoad === null) onRoad = [p.id, bestEdge.id, +clearance.toFixed(2), frontage ? 'frontage' : 'spine'];
if (!frontage && !onSomeFootpath && offFootpath === null) offFootpath = [p.id, bestEdge.id, +best.toFixed(2)];
}
ok(onRoad === null, `${label}: every poster clears its kerb (frontage ≥ ${POSTER_CLEAR} m, spine ≥ 0)` + (onRoad ? ` (${onRoad[3]} poster ${onRoad[0]} clears edge ${onRoad[1]} by only ${onRoad[2]} m)` : ''));
ok(onRoad === null, `${label}: no poster stands on a carriageway — vergeBand inner edge (frontage ≥ ${POSTER_CLEAR} m, spine ≥ 0)` +
(onRoad ? ` (${onRoad[3]} poster ${onRoad[0]} clears edge ${onRoad[1]}'s kerb by only ${onRoad[2]} m)` : ''));
ok(offFootpath === null, `${label}: every spine poster stands INSIDE some edge's vergeBand (the footpath B paints in to roadWidth/2)` +
(offFootpath ? ` (poster ${offFootpath[0]} is ${offFootpath[2]} m from nearest edge ${offFootpath[1]} and inside no verge band)` : ''));
const preRuling = preRulingRoadPosters(plan);
ok(!plan.posters.length || preRuling > 0,
`${label}: CONTROL — the kerb arms DISCRIMINATE: ${preRuling}/${plan.posters.length} poster(s) stand on bitumen under the PRE-RULING ground (?verge=0)`);
}
const nodeById2 = (plan, id) => plan.streets.nodes.find(n => n.id === id);
// Every edge resolved once to [edge, ax, az, bx, bz] — the poster arms are O(posters × edges).
function edgeSegments(plan) {
const nodeById = new Map(plan.streets.nodes.map(n => [n.id, n]));
const out = [];
for (const e of plan.streets.edges) {
const a = nodeById.get(e.a), b = nodeById.get(e.b);
if (a && b) out.push([e, a.x, a.z, b.x, b.z]);
}
return out;
}
// ROUND37 §0.1 — LANE B'S PRE-RULING GROUND, REPLICATED HERE ON PURPOSE (the R27 cross-lane pattern).
// `ground.js` painted its road quad the FULL corridor — `hQuad(cx, cz, dir, perp, roadLen, e.width, 0)`
// — i.e. bitumen out to `e.width/2`, with A's kerb at `roadWidth(e)/2` nine metres inside it on a main.
// John's R37 ruling moves the paint in to the kerb; Lane B's permanent `?verge=0` flag keeps THIS
// geometry in the codebase forever, so the replica is pinned to the FLAG, not to a line number that
// will drift. It counts a floor, never an over-count: ground.js overruns each segment by OVERLAP=1.5 m
// at both ends and this clamps to the segment. Used as the control arm above and pinned at the default
// boot's exactly-14 in §3c.
function preRulingRoadPosters(plan) {
let n = 0;
const segs = edgeSegments(plan);
for (const p of plan.posters || []) {
for (const [e, ax, az, bx, bz] of segs) {
const [nx, nz] = nearestOnSeg(p.x, p.z, ax, az, bx, bz);
if (Math.hypot(p.x - nx, p.z - nz) < ((e.width || 0) / 2) - 1e-9) { n++; break; }
}
}
return n;
}
// ── 1. determinism: two runs byte-identical ─────────────────────────────────────────
section('determinism (deep-equal across two runs)');
@ -342,6 +405,19 @@ const GIG_GOLDEN = 0xec7a2d39; // ROUND16 re-pin: pickVenues prefers band_room/r
if (GIG_GOLDEN === 0) console.log(` ⚠ gig golden UNPINNED → set GIG_GOLDEN = 0x${hash.toString(16).padStart(8, '0')}`);
ok(GIG_GOLDEN !== 0 && hash === (GIG_GOLDEN >>> 0), `gig layer golden 0x${hash.toString(16)} matches pinned 0x${(GIG_GOLDEN >>> 0).toString(16)}`);
}
// ROUND37 §0.1 — THE PRE-RULING COUNT, PINNED ON THE DEFAULT BOOT. The city-patterns audit reported
// "all 14 gig posters are standing in the road"; Lane A re-measured it and it is exact — 14 posters on
// the default boot (synthetic, golden seed, gigs default-ON since R16), all 14 inside `e.width/2`, and
// ZERO inside the ruled kerb `roadWidth/2`. Pinned here, not just asserted `> 0`, because a control that
// is allowed to drift toward zero is how a gate goes vacuous in the first place — the thing this round
// exists to stop. If a future change moves posters out past `e.width/2`, this fires and the ?verge=0
// falsifiability control (Lane B's flag, Lane F's gate leg) has to be re-argued rather than quietly lost.
const PRE_RULING_ROAD_POSTERS = 14;
{
const n = preRulingRoadPosters(generatePlanFor(GOLDEN.seed, 'synthetic', { gigs: true }));
ok(n === PRE_RULING_ROAD_POSTERS,
`default boot: exactly ${PRE_RULING_ROAD_POSTERS} posters stood on bitumen under the PRE-RULING ground (?verge=0) — measured ${n}`);
}
// ── 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