From a493c0a13c9cf8f5277596a9792659c4be1e80b2 Mon Sep 17 00:00:00 2001 From: m3ultra Date: Fri, 17 Jul 2026 02:23:57 +1000 Subject: [PATCH] Lane A round 25 (v5.0-alpha): the sweep reconciled with Ruling 2 + the six pins (ledger #1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All 8 tree reds cleared. selfcheck ALL GREEN 156,211/156,211; scaffold + consistency green; classic 0x3fa36874 and gig 0xb1d48ea1 frozen. F: the tag is unblocked from my side, nothing of mine is pending. SIX OF THE EIGHT REDS WERE MY OWN GATE ASSERTING RULING 2 AWAY. I built the R24 identity arm on an assumption that was true when I wrote it and false the moment G shipped: that a godverse cache is ALL census shops. Ruling 2 makes it MIXED -- a keyed GODVERSE layer plus an inherited OSM texture layer that G measured to be load-bearing, not cosmetic (census-only Red Hill: 306 m spacing warn + a poster inside a kerb). So newtown_godverse (18 keyed + 54 texture) and redhill_godverse (10 + 27) were HEALTHY TOWNS MY GATE CALLED BROKEN -- it fired 54/72 on a good town. Keyed asserts now run PER-LAYER over the keyed subset only; unkeyed texture shops raise nothing. The validator's "N/M missing" warn dies with it. The one honest requirement left at that layer: a `godverse+osm` cache must have a NON-EMPTY godverse layer (else it's an osm cache wearing the wrong `source`) -- still a warn, for the R24 charter reasons that still hold. THE UNIQUENESS CHECK WAS BROKEN IN BOTH DIRECTIONS. `new Set(all ids).size === shops.length` collapses every `undefined` into ONE Set entry, so it (a) failed structurally on any mixed cache -- 54 texture shops became one entry -- and (b) could HIDE A REAL DUPLICATE behind that same collapse. Now over DEFINED ids only. And per the vacuous-gate law I made it prove it bites rather than asserting that it does: validateTownCache rejects duplicates at the front door, so a dup can never reach the plan that way -- the test feeds one STRAIGHT TO THE LIFT (generatePlanOSM does not validate) and asserts the predicate catches it, with a healthy mixed cache passing the same predicate. A check nobody can watch fail is not a check. NEW -- THE ORPHANED-ATLAS GATE (my own R24 finding, now enforced instead of filed). The lift legitimately drops shops (overlap-resolve, counted since R19). Dropping a shop that HAS a committed atlas means real stock keyed to a shop that isn't in the town -- exactly the failure I filed in R24, and one F's #7b could only catch in a browser at the end of a round. Now a node gate: for each godverse town, every stock_godverse// on disk belonging to that cache MUST be seated, else FAIL. Subject-aware -- it SKIPs by name when a town has no atlas, rather than passing quietly. It already found one, benign today: redhill's lift drops keyed shop 2286 "Empire Revival" (10 keyed in cache -> 9 seated). No atlas, so nothing is orphaned and the gate passes -- but it prints, because a drop must never be silent (the R19/R20 ruling). G: stocking Empire Revival at the beta would orphan it. Monster Robot Party is seated (lot 8), which is the one that matters this round. THE SIX PINS, cross-checked two ways before landing -- byte-identical across two fresh processes, AND matching the three values G stated independently in 86e2985: newtown_godverse base 0xcf69b387 gig 0x1e266f60 (moved by G's keyed re-emit) redhill_real base 0x6046700f gig 0x673785ee redhill_godverse base 0xb2224939 gig 0xc6f80e18 No other golden moved -- the diff is exactly these six lines. Two lines still print; both deliberate, neither pending: ⊘ SKIP newtown_godverse orphaned-atlas -- correct, the only committed atlas (3962749) keys to Red Hill. ⚠ redhill_godverse: 1 keyed shop dropped -- the honest disclosure above. Committed strictly by pathspec: F's six staged files sit untouched, exactly as its session left them. Co-Authored-By: Claude Opus 4.8 --- docs/A-progress.md | 35 ++++++++++-- docs/CITY_SPEC.md | 26 ++++++--- docs/LANES/LANE_A_NOTES.md | 46 ++++++++++++++++ web/assets/towns/README.md | 22 ++++++-- web/js/citygen/plan_osm.js | 28 +++++----- web/js/citygen/selfcheck.js | 107 ++++++++++++++++++++++++++++-------- 6 files changed, 210 insertions(+), 54 deletions(-) diff --git a/docs/A-progress.md b/docs/A-progress.md index cbce5fc..f6ed2ed 100644 --- a/docs/A-progress.md +++ b/docs/A-progress.md @@ -1,11 +1,38 @@ # LANE A — CITYGEN · progress (PROCITY-A) -*Status: **all deliverables landed; self-check all-green (152,030/152,030); rounds 2–24 closed** (see below). -R24 (v5.0-alpha): the **`godverseShopId`** identity field — schema v2 (optional), carried through the lift -onto `plan.shops[]`, **no golden moved**. R23: the spacing warn (150 m). classic `0x3fa36874` / -gig `0xb1d48ea1` frozen. (Pack is 21 real + 1 godverse — E retired toowoomba in R23.)* +*Status: **all deliverables landed; self-check ALL GREEN (156,211/156,211); rounds 2–25 closed** (see below). +R25 (v5.0-alpha): the sweep reconciled with **Ruling 2** (keyed asserts are per-layer), the uniqueness check +rebuilt so it can actually fail, the **orphaned-atlas gate** added, and **six goldens pinned** — F's tag is +unblocked from my side. classic `0x3fa36874` / gig `0xb1d48ea1` frozen.* *Date: 2026-07-17 · owner: PROCITY-A (Opus 4.8)* +## Round 25 (2026-07-17) — v5.0-alpha: the sweep vs Ruling 2 + the six pins (ledger #1) + +All 8 tree reds cleared — **6 were my own gate asserting Ruling 2 away**, 2 were goldens awaiting my pin. +selfcheck **ALL GREEN 156,211/156,211**, scaffold + consistency green, classic frozen. + +- **My R24 gate encoded an assumption that Ruling 2 falsified.** I built the identity arm believing a + godverse cache is *all* census shops. It's **mixed** by design — a keyed GODVERSE layer + an inherited OSM + texture layer that G measured to be load-bearing (census-only Red Hill: 306 m spacing warn + a poster + inside a kerb). So `newtown_godverse` (18 keyed + 54 texture) and `redhill_godverse` (10 + 27) were + **healthy towns my gate called broken**. Keyed asserts now run **per-layer over the keyed subset only**; + the "N/M missing" warn is gone. The one honest requirement left: a `godverse+osm` cache must have a + **non-empty** godverse layer (else it's an osm cache with the wrong `source`) — still a warn. +- **The uniqueness check was broken in both directions.** `new Set(all ids).size === shops.length` collapses + every `undefined` into one entry, so it failed structurally on mixed caches *and* could hide a real + duplicate behind the same collapse. Now over **defined ids only** — and, per the vacuous-gate law, proven + able to fail: the validator rejects dups at the front door, so the test feeds one **straight to the lift** + (`generatePlanOSM` doesn't validate) and asserts the predicate catches it. +- **New: the orphaned-atlas gate** — my own R24 finding, enforced rather than filed. A committed + `stock_godverse//` whose shop the lift dropped = real stock keyed to a shop that isn't in the town. + Node-side, subject-aware (SKIPs by name when a town has no atlas), so it catches in seconds what F's #7b + could only catch in a browser at the end of a round. **It already found one, benign today:** redhill drops + keyed shop 2286 "Empire Revival" (unstocked ⇒ no orphan ⇒ gate passes). Filed for G: stocking it at the + beta would orphan. Monster Robot Party is seated (lot 8). +- **The six pins**, cross-checked two ways before landing — byte-identical across two fresh processes, and + matching the values G stated independently in `86e2985`: `newtown_godverse` `0xcf69b387`/`0x1e266f60`, + `redhill_real` `0x6046700f`/`0x673785ee`, `redhill_godverse` `0xb2224939`/`0xc6f80e18`. + ## Round 24 (2026-07-17) — v5.0-alpha: the identity field (ledger #2) `shop.godverseShopId` — the optional key resolving `stock_godverse//`. Schema **stays v2**; absent diff --git a/docs/CITY_SPEC.md b/docs/CITY_SPEC.md index 1166ea1..6c250d6 100644 --- a/docs/CITY_SPEC.md +++ b/docs/CITY_SPEC.md @@ -437,16 +437,28 @@ census shops use thriftgod `shop.id` (max **2992**), while **Monster Robot Party census at all** and uses its **dealgod store id 3962749**. So: never derive it from `id`, never assert equality, never build a mapping table (the R12 gigKey law — one key, zero mapping). **G emits, all read.** +**A godverse cache is MIXED, by design — RULING 2 (confirmed as law, ROUND25).** It carries a **keyed +GODVERSE layer** (census shops + injected shops like Monster Robot) **plus an inherited OSM texture layer +that is legitimately unkeyed**. G measured that the texture layer is load-bearing, not cosmetic: +census-only Red Hill shipped a 306 m spacing warn and a poster inside a kerb; with the texture layer it +passes. **So "shops without an id" is the design, and no gate may assert it away** — mine did, in R24, and +failed 54/72 on a healthy town. + **`validateTownCache` arms** (split by hazard, not tidiness): a **malformed** id (not a positive integer) is an **error** — it names a path segment, and `"3962749"` vs `3962749` is exactly the coercion drift that broke the atlas contract; a **duplicate** id is an **error** — two shops sharing one atlas *is* mis-stocking -(charter risk #3: "never mis-stocked"); a **missing** id on a `source: "godverse+osm"` cache is a **warn**, -and those shops fall soft to **tier 0**. Missing is deliberately *not* an error: the charter's determinism -boundary says *world = seeded and gated, stock = data tiers*, so a town must never fail to load because its -**stock** identity is absent — tier 0 is the designed ladder, not a defect. The teeth live in `selfcheck`: -**partial keying FAILS** (a half-keyed town silently mis-stocks the rest) and zero keying prints an explicit -**SKIP** naming the town and count, per the round's vacuous-gate law. *(Recorded departure from the ledger's -"requires" — reasons above; Fable's to overrule.)* +(charter risk #3: "never mis-stocked"); a godverse cache with an **entirely empty GODVERSE layer** (no id on +any shop) is a **warn** — it's an osm cache wearing the wrong `source`, and it stays a warn because the +charter's determinism boundary says *world = seeded and gated, stock = data tiers*: a town must never fail +to load because its **stock** identity is absent. Tier 0 is the designed ladder, not a defect. + +**The `selfcheck` arm runs per-layer** — every assert is over the **keyed subset only**: the layer reaches +the plan, its ids are **unique over defined ids only** (the R24 version compared a Set of *all* ids, which +collapses every `undefined` into one entry — it failed structurally on a mixed cache *and* could hide a real +duplicate behind that collapse), and the lift invents no identity. Plus the **orphaned-atlas gate**: a +committed atlas under `stock_godverse//` whose shop the lift dropped means real stock keyed to a shop +that isn't in the town — it fails, and SKIPs by name when a town has no atlas. Dropped keyed shops are +printed, never silent (the R19/R20 drop-and-count ruling). **It survives the lift:** `plan_osm` copies it onto `plan.shops[]` (the runtime reads the plan, never the cache — otherwise the field is decoration and F's per-shop `base` can never resolve). Absent ⇒ the key is diff --git a/docs/LANES/LANE_A_NOTES.md b/docs/LANES/LANE_A_NOTES.md index 73785dd..334d8a7 100644 --- a/docs/LANES/LANE_A_NOTES.md +++ b/docs/LANES/LANE_A_NOTES.md @@ -2,6 +2,52 @@ Durable answers to questions other lanes raised against Lane A. Full status in `A-progress.md`. +## Round 25 (2026-07-17) — v5.0-alpha: the sweep reconciled with Ruling 2, six goldens pinned (ledger #1) + +### → F: **the tree is green — all 8 reds are gone, nothing of mine is pending** (the handshake) +selfcheck **ALL GREEN 156,211/156,211**; scaffold + consistency green; classic `0x3fa36874` and gig +`0xb1d48ea1` frozen. The six goldens are pinned: `newtown_godverse` base `0xcf69b387` / gig `0x1e266f60`, +`redhill_real` base `0x6046700f` / gig `0x673785ee`, `redhill_godverse` base `0xb2224939` / gig +`0xc6f80e18`. Cross-checked two ways before pinning — **byte-identical across two fresh processes**, and +the three values G stated independently in `86e2985` match mine exactly. **Your third attempt is unblocked +from my side.** + +Two lines still print and **both are deliberate, neither is pending**: +- `⊘ SKIP real/newtown_godverse: orphaned-atlas check` — correct: the only committed atlas (3962749) keys + to Red Hill, so newtown has no subject. The vacuous-gate law says say so rather than pass quietly. +- `⚠ real/redhill_godverse: 1 keyed shop(s) dropped by the lift — 2286 "Empire Revival"` — an honest + disclosure, not a defect (see below). + +### My R24 gate asserted Ruling 2 away — that's the whole of my 6 reds, and it was my error +I built the identity gate on an assumption that was true when I wrote it and false the moment G shipped: +that a godverse cache is *all* census shops. Ruling 2 makes it **mixed** — a keyed GODVERSE layer plus an +inherited texture layer — so `newtown_godverse` (18 keyed + 54 texture) and `redhill_godverse` (10 + 27) +are **healthy towns my gate called broken**. Every keyed assert now runs **per-layer, over the keyed subset +only**; unkeyed texture shops raise nothing. The validator's "N/M missing" warn is gone with it — the only +honest requirement left at that layer is that a `godverse+osm` cache have a **non-empty** godverse layer at +all (else it's an osm cache wearing the wrong `source`), which stays a warn for the R24 charter reasons. + +### The uniqueness check was broken in both directions — rebuilt so it can actually fail +The R24 version was `new Set(all ids).size === shops.length`. Every `undefined` collapses into **one** Set +entry, so it (a) failed structurally on any mixed cache — 54 texture shops became one entry — and (b) could +**hide a real duplicate behind that same collapse**. It now runs over **defined ids only**. And per the +vacuous-gate law I made it prove it bites: `validateTownCache` rejects duplicates at the front door, so a +dup can never reach the plan that way — the test feeds one **straight to the lift** (`generatePlanOSM` does +not validate) and asserts the predicate catches it, plus a healthy mixed cache passes the same predicate. +A check nobody can watch fail is not a check. + +### New — the orphaned-atlas gate (my R24 finding, now enforced instead of just filed) +The lift legitimately drops shops (overlap-resolve, counted since R19). Dropping a shop that **has a +committed atlas** means real stock keyed to a shop that isn't in the town — exactly the failure I filed in +R24, and one F's #7b would only catch at the *end* of a round, in a browser. It's now a node gate: for each +godverse town, every `stock_godverse//` on disk whose id belongs to that cache **must be seated**, else +FAIL. Subject-aware — SKIPs by name when a town has no atlas. + +**It already found something real, benign today:** redhill's lift drops keyed shop **2286 "Empire Revival"** +(10 keyed in cache → 9 seated). It has no atlas, so nothing is orphaned and the gate passes. **→ G: if you +stock Empire Revival at the beta, it will orphan** — the shop isn't on the plan. Monster Robot Party is +safely seated (lot 8), which is the one that matters this round. + ## Round 24 (2026-07-17) — v5.0-alpha: the identity field (ledger #2) ### → G: **`godverseShopId` is live — emit it, and mind the id-space trap** (the handshake for your #5) diff --git a/web/assets/towns/README.md b/web/assets/towns/README.md index c393362..12e197d 100644 --- a/web/assets/towns/README.md +++ b/web/assets/towns/README.md @@ -68,17 +68,29 @@ is about. Two id spaces ride this field, and G verified they're disjoint: So treat it as an **opaque key**: never derive it from `id`, never assume equality, never build a mapping table (the R12 gigKey lesson — one key, zero mapping; **G emits it, everyone else reads it**). +**A godverse cache is MIXED — that's the design (Ruling 2, law as of R25).** It carries a **keyed GODVERSE +layer** (census shops + injected shops like Monster Robot) **plus an inherited OSM texture layer that is +legitimately unkeyed**. The texture layer is load-bearing, not cosmetic — G measured that census-only Red +Hill was unshippable (306 m spacing warn, a poster inside a kerb) and the texture layer fixed it. Real +shape today: `newtown_godverse` = 18 keyed + 54 texture; `redhill_godverse` = 10 keyed + 27 texture. +**Unkeyed shops are not a gap and no gate may assert them away.** + **Rules** — split by what is actually a hazard, not by tidiness: - **Malformed → ERROR.** Must be a **positive integer**. It names a path segment, so `"3962749"` and `3962749` are the same folder but different JSON; that coercion drift is the species that produced the `licence`/`license` atlas break. One truth. - **Duplicate within a cache → ERROR.** Two shops keyed to one atlas *is* mis-stocking (charter risk #3: "fail-soft to tier 0, **never mis-stocked**"). -- **Missing on a `source: "godverse+osm"` cache → WARN, and those shops fall soft to tier 0 (parody).** - Not an error: the charter's determinism boundary is *world = seeded and gated, stock = data tiers*, so a - town must never fail to load because its **stock** identity is absent. The teeth are in `selfcheck`, - where **partial keying FAILS** (a half-keyed town silently mis-stocks the rest) and zero keying prints an - explicit **SKIP** with the count, per the vacuous-gate law. +- **An entirely empty GODVERSE layer → WARN.** A `godverse+osm` cache with no id on *any* shop is an osm + cache wearing the wrong `source`. Still a warn, not an error: the charter's determinism boundary is + *world = seeded and gated, stock = data tiers*, so a town must never fail to load because its **stock** + identity is absent — tier 0 is the designed ladder. +- **Mixed → silent.** Some keyed, some not, is normal and raises nothing. + +`selfcheck` gates the keyed subset per-layer: the layer reaches the plan, ids are unique **over defined ids +only**, the lift invents no identity — plus an **orphaned-atlas gate** (a committed atlas whose shop the +lift dropped = real stock keyed to a shop that isn't in the town → FAIL). Dropped keyed shops are printed, +never silent. **It survives the lift.** `plan_osm` copies it onto `plan.shops[]`, because the runtime reads the *plan*, never the cache — without that the field would be decoration and F's per-shop `base` could never resolve. diff --git a/web/js/citygen/plan_osm.js b/web/js/citygen/plan_osm.js index 788069d..256b2f3 100644 --- a/web/js/citygen/plan_osm.js +++ b/web/js/citygen/plan_osm.js @@ -178,20 +178,20 @@ export function validateTownCache(cache) { } if (badGid) errors.push(`${badGid} shop(s) with a malformed godverseShopId (want a positive integer — it is a path segment, not a label)`); if (dupGid) errors.push(`${dupGid} duplicate godverseShopId(s) — two shops cannot share one stock atlas (mis-stock)`); - // WHY MISSING IS A WARN AND NOT AN ERROR (ROUND24 ledger #2 says "requires"; recorded as a - // departure, with reasons, per measurement-over-brief): - // 1. The charter's determinism boundary: "world = seeded, frozen, gated. Stock = data tiers." - // An error here inverts it — the TOWN would fail to load because its STOCK identity is absent. - // Tier 0 must always boot; a shop with no id is a parody shop, which is the designed ladder. - // 2. Charter risk #3 names the remedy for identity gaps: "fail-soft to tier 0, never mis-stocked." - // Missing = tier 0 (soft). Duplicate = mis-stocked (hard). They are not the same failure. - // 3. Sequencing: `newtown_godverse` carries 0 ids today and is G's to re-emit in wave 2 (their - // namespace, and the census DB isn't on this machine). An error would red the tree for C and E - // through all of wave 1 to convey what this warn already says. - // The teeth live in selfcheck: PARTIAL coverage fails, and zero coverage prints an explicit SKIP - // naming the town and the count (the vacuous-gate law) — so this cannot pass unnoticed. - if (isGodverseCache(cache) && withGid < cache.shops.length) { - warnings.push(`${cache.shops.length - withGid}/${cache.shops.length} shop(s) missing godverseShopId on a '${GODVERSE_SOURCE}' cache → those shops fall soft to tier 0 (parody); G re-emits`); + // RULING 2 (ROUND25, confirmed as law): a godverse cache is deliberately MIXED — a keyed GODVERSE + // layer (census shops + injected shops like Monster Robot) PLUS an inherited OSM texture layer that + // is legitimately unkeyed. G measured that the texture layer is load-bearing, not cosmetic: + // census-only Red Hill shipped a 306 m spacing warn and a poster inside a kerb; with the texture + // layer it passes. So "shops without an id" is the DESIGN, and my R24 warn (which counted every + // unkeyed shop as a gap) was asserting the design away — it fired 54/72 on a healthy town. + // + // What is still worth saying: a cache that CLAIMS godverse provenance but carries NO identity at all + // has no godverse layer — it is an osm cache wearing the wrong `source`. That is the only honest + // requirement left at this layer, and it's a warn for the R24 reasons that still hold (the charter's + // determinism boundary: a town must never fail to load because its STOCK identity is absent; tier 0 + // is the designed ladder). Missing = tier 0 (soft), duplicate = mis-stocked (hard) — different arms. + if (isGodverseCache(cache) && withGid === 0) { + warnings.push(`'${GODVERSE_SOURCE}' cache carries no godverseShopId on any of ${cache.shops.length} shops — the GODVERSE layer is empty (every shop falls to tier 0; is this really a godverse cache?)`); } // wide-spread warning: an over-broad Overpass bbox marches into an unusable multi-km strip (the R17 // real-data risk). Metadata only — plan_osm still boots a valid plan; it just isn't a coherent town. diff --git a/web/js/citygen/selfcheck.js b/web/js/citygen/selfcheck.js index ba494cb..bf403f6 100644 --- a/web/js/citygen/selfcheck.js +++ b/web/js/citygen/selfcheck.js @@ -16,6 +16,7 @@ import { xmur3 } from '../core/prng.js'; const HERE = dirname(fileURLToPath(import.meta.url)); const ASSETS = join(HERE, '..', '..', 'assets', 'gen'); +const STOCK_GODVERSE_DIR = join(HERE, '..', '..', 'assets', 'stock_godverse'); // G's tier-1 atlases (orphan check) let failures = 0, checks = 0; const ok = (cond, msg) => { checks++; if (!cond) { failures++; console.error(' ✗ ' + msg); } }; @@ -462,29 +463,58 @@ for (const [label, cache, needle] of [ const v = validateTownCache(gv(withIds.map((s, i) => i === 1 ? { ...s, godverseShopId: withIds[0].godverseShopId } : s))); ok(!v.ok && v.errors.some(e => /duplicate godverseShopId/.test(e)), `godverse cache: duplicate id REJECTED (mis-stock)`); } - // MISSING → warn + fail-soft, NOT an error (the charter's determinism boundary: the world must not - // fail to load because stock identity is absent). Partial coverage still warns and still boots. + // RULING 2 (ROUND25): a MIXED cache — keyed GODVERSE layer + unkeyed texture layer — is the DESIGN, + // not a gap. My R24 arm counted every unkeyed shop as missing and fired 54/72 on a healthy town; it + // was asserting the design away. A mixed cache is now simply normal: valid, and silent. { - const v = validateTownCache(gv(withIds.map((s, i) => i < 2 ? { ...s, godverseShopId: undefined } : s))); - ok(v.ok, `godverse cache: missing ids stay VALID (fail-soft to tier 0, never a boot failure)`); - ok(v.warnings.some(w => /2\/8 shop\(s\) missing godverseShopId/.test(w)), `godverse cache: missing ids WARN with a count (${v.warnings.find(w => /godverseShopId/.test(w)) || 'NO WARN'})`); + const mixed = gv(withIds.map((s, i) => i < 2 ? { ...s, godverseShopId: undefined } : s)); + const v = validateTownCache(mixed); + ok(v.ok, `godverse cache: MIXED (keyed + texture) is VALID — Ruling 2 is the design`); + ok(!v.warnings.some(w => /godverseShopId/.test(w)), `godverse cache: MIXED raises NO identity warn (texture shops are legitimately unkeyed)`); + } + // ...but a godverse cache with NO identity at all has no godverse layer — an osm cache wearing the + // wrong `source`. That's the one honest requirement left at this layer, and it stays a warn. + { + const v = validateTownCache(gv(cluster(8))); + ok(v.ok, `godverse cache: empty GODVERSE layer stays VALID (tier 0 must always boot)`); + ok(v.warnings.some(w => /GODVERSE layer is empty/.test(w)), `godverse cache: empty layer WARNS (${v.warnings.find(w => /GODVERSE/.test(w)) || 'NO WARN'})`); } // a plain osm cache must never be asked for ids - ok(!validateTownCache(mkCache(cluster(8))).warnings.some(w => /godverseShopId/.test(w)), `osm cache: no identity warn (godverse-only requirement)`); + ok(!validateTownCache(mkCache(cluster(8))).warnings.some(w => /godverseShopId|GODVERSE/.test(w)), `osm cache: no identity warn (godverse-only requirement)`); // THE SEAM THAT MATTERS: the id survives the lift onto plan.shops. Without this the field is a // cache-only decoration and F's per-shop base can never resolve — the runtime reads the PLAN. { - const cache = gv(withIds); + const cache = gv(withIds.map((s, i) => i < 2 ? { ...s, godverseShopId: undefined } : s)); // mixed, like the real ones const p = generatePlanOSM(20261990, 'gvtest', { cache }); const keyed = p.shops.filter(s => s.godverseShopId !== undefined); - ok(keyed.length === p.shops.length, `godverse lift: every seated shop carries godverseShopId (${keyed.length}/${p.shops.length})`); + ok(keyed.length > 0 && keyed.length <= p.shops.length, `godverse lift: the keyed layer survives onto plan.shops (${keyed.length} of ${p.shops.length} seated)`); ok(keyed.every(s => Number.isSafeInteger(s.godverseShopId) && s.godverseShopId > 0), `godverse lift: ids survive as positive integers`); - ok(new Set(keyed.map(s => s.godverseShopId)).size === keyed.length, `godverse lift: ids stay unique through the lift (no atlas collision)`); - // and an unkeyed cache writes NO key — that absence is what keeps all 22 pinned goldens frozen today + ok(p.shops.filter(s => !('godverseShopId' in s)).length === p.shops.length - keyed.length, `godverse lift: texture shops carry NO key (absent, not undefined)`); + // and an unkeyed cache writes NO key — that absence is what keeps every pinned osm golden frozen const plain = generatePlanOSM(20261990, 'plaintest', { cache: mkCache(cluster(8)) }); ok(plain.shops.every(s => !('godverseShopId' in s)), `plain lift: the key is ABSENT, not undefined (byte-identical JSON ⇒ goldens frozen)`); } + + // THE UNIQUENESS CHECK, REBUILT SO IT CAN ACTUALLY FAIL (ROUND25 ledger #1). The R24 version compared + // `new Set(all ids).size === shops.length`, which collapses every `undefined` into ONE entry — so on a + // mixed cache it fails structurally (54 texture shops → 1 entry) while a REAL duplicate among two + // defined ids could hide behind that same collapse. Uniqueness now runs over DEFINED ids only. + // + // And per the vacuous-gate law: a check nobody can see fail is not a check. `validateTownCache` + // rejects duplicates, so a dup can never reach the plan through the front door — `generatePlanOSM` + // does NOT validate, so this feeds one straight to the lift and proves the assert bites. + { + const dupCache = gv(withIds.map((s, i) => i === 3 ? { ...s, godverseShopId: withIds[0].godverseShopId } : s)); + ok(!validateTownCache(dupCache).ok, `uniqueness: the validator still rejects a duplicate at the front door`); + const p = generatePlanOSM(20261990, 'duptest', { cache: dupCache }); // bypasses validation, as the lift does + const ids = p.shops.map(s => s.godverseShopId).filter(id => id !== undefined); + ok(new Set(ids).size < ids.length, `uniqueness: a duplicate that BYPASSES the validator is visible on the plan (${ids.length} ids → ${new Set(ids).size} unique) — the check can fail`); + // the same predicate on a healthy mixed cache must pass — no false positive from the undefineds + const okIds = generatePlanOSM(20261990, 'gvtest2', { cache: gv(withIds.map((s, i) => i < 2 ? { ...s, godverseShopId: undefined } : s)) }) + .shops.map(s => s.godverseShopId).filter(id => id !== undefined); + ok(new Set(okIds).size === okIds.length, `uniqueness: a healthy MIXED cache passes the same predicate (no undefined-collapse false positive)`); + } } // (d) the registry seam: register → osmTownKeys() sees it → generatePlanOSM resolves it { @@ -559,13 +589,15 @@ const REAL_TOWN_GOLDENS = { launceston_real: 0x32b1ab7b, marrickville_real: 0x60148e37, newcastle_real: 0xbb2c3050, - newtown_godverse: 0x24e63feb, + newtown_godverse: 0xcf69b387, newtown_real: 0x741c18ec, northbridge_real: 0x1a645848, // toowoomba_real RETIRED by E in R23 (cache deleted) — pin removed with it. A golden for a town that // cannot load is dead config: it can never fire, so it can never fail. Same species as the round's // vacuous-gate law, one layer down. (E tried the re-bbox first and it GAMED my spacing metric — median // NN 395.7 → 89.4 m "passes" while hub density fell 3/12 → 2/12, the pack's worst. Right call.) + redhill_godverse: 0xb2224939, + redhill_real: 0x6046700f, westend_real: 0x5b1e5ffe, }; // ROUND20: the GIG-layer golden per real town (district + capped posters) — the base golden above is @@ -592,10 +624,12 @@ const REAL_TOWN_GIG_GOLDENS = { launceston_real: 0xeaf7ff4d, marrickville_real: 0xea21be51, newcastle_real: 0x66fc2bf9, - newtown_godverse: 0xc5ed0b92, + newtown_godverse: 0x1e266f60, newtown_real: 0xacdb3eee, northbridge_real: 0x83a1d5be, // toowoomba_real RETIRED by E in R23 — see the note on the base map above. + redhill_godverse: 0xc6f80e18, + redhill_real: 0x673785ee, westend_real: 0xb2c7ddb6, }; // `index.json` is E's towns INDEX (key/town/state/shops/roads for B's selector), not a town cache — skip it. @@ -618,22 +652,47 @@ for (const f of townFiles) { const gwant = REAL_TOWN_GIG_GOLDENS[key]; if (gwant === undefined) console.log(` ⚠ real/${key}: gig UNPINNED — add REAL_TOWN_GIG_GOLDENS['${key}'] = 0x${ghash.toString(16).padStart(8, '0')}`); else ok(ghash === (gwant >>> 0), `real/${key}: gig golden 0x${ghash.toString(16)} matches pinned 0x${(gwant >>> 0).toString(16)}`); - // ROUND24 ledger #2 — the identity gate on the REAL godverse caches, written to the round's own - // vacuous-gate law: it names its subject, it proves it touched it (a count), and it CANNOT pass while - // its subject is absent. Zero ids ⇒ explicit SKIP with the reason and the count. Partial ⇒ FAIL (a - // half-keyed town silently mis-stocks half its shops). Full ⇒ assert the ids reach plan.shops. + // ROUND25 ledger #1 — the identity gate, RECONCILED WITH RULING 2. A godverse cache is deliberately + // MIXED: a keyed GODVERSE layer + an inherited OSM texture layer that G measured to be load-bearing. + // So every assert runs over the GODVERSE LAYER ONLY (shops carrying an id); an unkeyed texture shop is + // the design, not a defect. My R24 arm asserted every-shop-keyed and failed 54/72 on a healthy town — + // it was asserting Ruling 2 away. Still written to the vacuous-gate law: names its subject, proves it + // touched it with a count, SKIPs loudly rather than passing when the subject is absent. if (cache.source === 'godverse+osm') { - const keyed = cache.shops.filter(s => Number.isSafeInteger(s && s.godverseShopId) && s.godverseShopId > 0).length; - if (keyed === 0) { + const cacheKeyed = cache.shops.filter(s => Number.isSafeInteger(s && s.godverseShopId) && s.godverseShopId > 0); + if (!cacheKeyed.length) { console.log(` ⊘ SKIP real/${key}: godverse identity — 0/${cache.shops.length} shops carry godverseShopId. ` + - `Subject absent: G re-emits with ids (R24 ledger #2). This gate binds the moment one id lands.`); + `Subject absent (the GODVERSE layer is empty). This gate binds the moment one id lands.`); } else { - ok(keyed === cache.shops.length, - `real/${key}: godverse identity — ${keyed}/${cache.shops.length} shops keyed (partial keying mis-stocks the rest)`); const seated = generatePlanOSM(20261990, key, { cache }).shops; - const lifted = seated.filter(s => s.godverseShopId !== undefined).length; - ok(lifted === seated.length, `real/${key}: godverse identity survives the lift (${lifted}/${seated.length} seated shops keyed)`); - ok(new Set(seated.map(s => s.godverseShopId)).size === seated.length, `real/${key}: godverse ids unique on the plan (no shared atlas)`); + const seatedIds = seated.filter(s => s.godverseShopId !== undefined).map(s => s.godverseShopId); + const cacheIds = new Set(cacheKeyed.map(s => s.godverseShopId)); + ok(seatedIds.length > 0, + `real/${key}: GODVERSE layer reaches the plan — ${seatedIds.length}/${seated.length} seated shops keyed (${cache.shops.length - cacheKeyed.length} texture shops legitimately unkeyed)`); + // uniqueness over DEFINED ids only: the R24 Set-collapse version folded every `undefined` into one + // entry, so it failed structurally on a mixed cache AND could hide a real duplicate behind them. + ok(new Set(seatedIds).size === seatedIds.length, + `real/${key}: godverse ids unique on the plan (${new Set(seatedIds).size}/${seatedIds.length} distinct — no shared atlas)`); + ok(seatedIds.every(id => cacheIds.has(id)), + `real/${key}: every seated godverse id came from the cache (the lift invents no identity)`); + // ORPHANED-ATLAS GATE. The lift legitimately drops shops (overlap-resolve, counted since R19) — but + // dropping a shop that HAS a committed atlas means real stock keyed to a shop that isn't in the + // town: exactly the failure R24 predicted and F's #7b would only catch at the end of a round. + // Subject = the atlas dirs on disk, so it SKIPs by name when this town has none. + const atlasIds = (existsSync(STOCK_GODVERSE_DIR) ? readdirSync(STOCK_GODVERSE_DIR) : []) + .filter(d => /^\d+$/.test(d)).map(Number).filter(id => cacheIds.has(id)); + if (!atlasIds.length) { + console.log(` ⊘ SKIP real/${key}: orphaned-atlas check — no committed atlas under stock_godverse/ keys to this town.`); + } else { + const seatedSet = new Set(seatedIds); + const orphans = atlasIds.filter(id => !seatedSet.has(id)); + ok(orphans.length === 0, + `real/${key}: no orphaned atlas — ${atlasIds.length} stocked shop(s) on disk, all seated` + + (orphans.length ? ` (ORPHANED: ${orphans.join(', ')} — real stock keyed to a shop the lift dropped)` : '')); + } + // a dropped keyed shop is legitimate but must never be silent (the R19/R20 drop-and-count ruling) + const dropped = cacheKeyed.filter(s => !new Set(seatedIds).has(s.godverseShopId)); + if (dropped.length) console.log(` ⚠ real/${key}: ${dropped.length} keyed shop(s) dropped by the lift — ${dropped.map(s => `${s.godverseShopId} "${s.name}"`).join(', ')} (unstocked today; would orphan an atlas if stocked)`); } } }