From b20b3b08f5520def8d3d1fb1ae8b8b378218444d Mon Sep 17 00:00:00 2001 From: m3ultra Date: Tue, 14 Jul 2026 15:52:30 +1000 Subject: [PATCH] Lane A round 3: verify F finding #5 (storeys) stale, harden selfcheck + document corner-anchor rule MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per ROUND3_INSTRUCTIONS 'Lane A — on-call + one verification': - F finding #5 (shops with storeys outside registry range) verified STALE across 8 seeds: zero violations. Single-storey types (video/milkbar/stall) never exceed 1; the only shops above registry max are the intentional 3-storey corner anchors (tall-capable types), exactly per CITY_SPEC. Round-1 cornerBoost gate already prevents the reported bug. This is also the source of qa.sh's 5 benign storeys warnings (flagged for Lane F to scope to permittedMax). - Hardened the selfcheck storeys assertion from the too-lax <=max(mx,3) to the exact permitted range (max>=2 ? min(max+1,3) : max), so a single-storey type exceeding 1 now fails loudly. Proven non-vacuous. selfcheck.js only — plan output/fingerprint unchanged (golden 0x098eec2b still matches, confirmed per task 2). - Documented the storeys corner-anchor rule in the CITY_SPEC schema. - hours/openLate contract unchanged; ready for Lane F's §3.5 consumer. qa.sh --strict GREEN (4/4). Committed atomically by pathspec (shared-tree race). Co-Authored-By: Claude Opus 4.8 --- docs/A-progress.md | 36 ++++++++++++++++++++++++++++++++++-- docs/CITY_SPEC.md | 4 ++++ web/js/citygen/selfcheck.js | 10 +++++++++- 3 files changed, 47 insertions(+), 3 deletions(-) diff --git a/docs/A-progress.md b/docs/A-progress.md index cfcec38..1db1cd5 100644 --- a/docs/A-progress.md +++ b/docs/A-progress.md @@ -1,7 +1,8 @@ # LANE A — CITYGEN · progress (PROCITY-A) -*Status: **all deliverables landed; self-check all-green (1283/1283); round-2 cross-lane requests from -B/E/F closed** (see bottom). Awaiting Fable review.* +*Status: **all deliverables landed; self-check all-green (1283/1283); round-2 (B/E/F requests) and +round-3 (F finding #5 verified stale + golden hash confirmed) closed** (see bottom). `qa.sh --strict` +GREEN. Awaiting Fable review.* *Date: 2026-07-14 · owner: PROCITY-A (Opus 4.8)* > **⚠ Fable — commit-label mixup (no action needed on the code):** my round-2 changes are committed @@ -167,3 +168,34 @@ stall facades now appear in the market square. single reference for Lane C/E when modelling facades. No code change; it works. - **Still-open design questions from round 1** (retail density ~493/core vs Lane B's draw budget; true closed residential ring) remain parked for your call — both are one-constant tunes, not rework. + +## Round 3 — verification round (2026-07-14, per ROUND3_INSTRUCTIONS "Lane A — on-call + one verification") + +Small round as briefed. All three tasks done; `bash tools/qa.sh --strict` **GREEN** (4/4 gates, +selfcheck 1283/1283, manifest 0 errors). + +**1. F finding #5 — "shops with storeys outside the registry type range" → verified STALE / CLOSED.** +Scanned every shop across 8 seeds against the *correct* permitted range. Result: **zero violations.** +`video`/`milkbar`/`stall` (registry `[1,1]`) are observed at 1 storey only (e.g. 412 video shops, none +at 2 — so F's specific "video at 2" case does not occur). The only shops above their registry `max` +are the tall-capable types (`record`/`opshop`/`toy`/`book`/`pawn` `[1,2]`, `dept` `[2,3]`) reaching 3 +via the intentional **corner anchor** — exactly the CITY_SPEC "occasional 3-storey corner anchor". My +round-1 `cornerBoost` gate (only boosts registry max ≥ 2) already prevents the reported bug. + - **This is the source of `qa.sh`'s 5 consistency warnings** ("N shops have storeys outside their + registry range … ask Lane A"). **They are benign** — all intended corner anchors, not plan↔registry + drift. *Lane F: you can silence them by scoping the check to `permittedMax = max≥2 ? min(max+1,3) : max` + instead of the strict registry `max`.* + - **Hardened anyway** (the finding's real lesson): the selfcheck's storeys assertion was too lax + (`<= Math.max(mx,3)` would have passed a video at 3). Tightened to the exact permitted range, so a + single-storey type exceeding 1 (or any type past `min(max+1,3)`) now fails loudly. Proved non-vacuous + (rejects `video@2`, `record@4`, `dept@1`; allows `record@3`). Documented the `storeys` rule in the + CITY_SPEC schema so the intended-vs-drift question can't recur. + +**2. Golden fingerprint confirmed.** Committed `GOLDEN.hash = 0x098eec2b` matches the current selfcheck +fingerprint on main. No output drift. (selfcheck.js assertion tightening doesn't touch the plan, so the +hash is unchanged.) + +**3. Hours / `openLate` contract — ready for its first real consumer (Lane F §3.5).** No changes needed: +`hours=[open,close]` (24h ints, `0≤open registryMax` on a corner anchor is EXPECTED, not plan↔registry drift. // openLate: present and `true` on EXACTLY ONE shop per town — the designated late-night landmark // (closes ≥ 22:00; a naturally-late type, video rental or milk bar, never a market stall). Absent // on every other shop. Lane B lights it after dark; Lane F's night gate keys off this field diff --git a/web/js/citygen/selfcheck.js b/web/js/citygen/selfcheck.js index a19dc42..8e076bf 100644 --- a/web/js/citygen/selfcheck.js +++ b/web/js/citygen/selfcheck.js @@ -84,7 +84,15 @@ for (const s of SEEDS) { ok(plan.shops.every(sh => sh.hours[0] >= 0 && sh.hours[1] <= 23 && sh.hours[1] > sh.hours[0]), `seed ${s}: every shop has sane hours`); ok(plan.shops.every(sh => sh.name && sh.sign), `seed ${s}: every shop is named`); ok(plan.shops.every(sh => !/[{}]|undefined/.test(sh.name + sh.sign)), `seed ${s}: no unresolved tokens / undefined in names`); - ok(plan.shops.every(sh => { const [mn, mx] = SHOP_TYPES[sh.type].storeys; return sh.storeys >= mn && sh.storeys <= Math.max(mx, 3); }), `seed ${s}: storeys within range (corner ≤3)`); + // STOREYS strictly within the registry range, corner-boost included (Lane F finding #5 class): the + // occasional 3-storey corner anchor adds ≤1 storey capped at 3, and ONLY for tall-capable types + // (registry max ≥ 2) — so single-storey types (video/milkbar/stall, [1,1]) must NEVER exceed 1. + // (The old `<= Math.max(mx,3)` was too lax — it would have passed a video at 3 storeys.) + ok(plan.shops.every(sh => { + const [mn, mx] = SHOP_TYPES[sh.type].storeys; + const pmax = mx >= 2 ? Math.min(3, mx + 1) : mx; + return sh.storeys >= mn && sh.storeys <= pmax; + }), `seed ${s}: storeys within registry range (corner-boost ≤ min(max+1,3); single-storey types never boosted)`); // one lot ↔ at most one shop const lotShopCounts = {};