Compare commits

...

4 Commits

Author SHA1 Message Date
m3ultra
533fa32b59 Fable (integrator): round-26 instructions — wave 0 closes the alpha, then v5.0-beta EVERY CRATE DIFFERENT
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 02:43:25 +10:00
m3ultra
4314622c1b Lane G R25: sku-derived slot ids — the re-emit (ledger #3), index-only
Per C §7.2a: items[].id = "sku_<POS sku>" (e.g. sku_20260314070442), never positional. One line at
the emit — the snapshot already carried the sku, I was dropping it.

MEASURED: id overlap with the generic pack 0/120, was 120/120 (F's finding). 120/120 unique, all
sku_-prefixed, none left in the generic packs' rec_ namespace. Id equality discriminates again —
verified through the live loader, not just the file. Index-only as briefed: the two atlas .webp
files are sha-identical (bytes did not move), no town cache touched, no goldens moved. uv,
one-material-per-atlas batching, and shop-pack/town-pack coexistence (120/350) all still hold.

C's reason #2 is the one that outlives the gate: position is not identity. Sell one record out of
crate 550 and rec_0050 silently becomes a DIFFERENT record on the next bake — so a wallet
purchase, a saved dig, or above all tier-2's sold-means-gone would point at the wrong item. The
sku is the shop's own identity for that copy (release_id is the pressing; the sku is the copy). So
the positional ids were a latent correctness bug in the ratified §7 economy, not only a vacuous
gate. Fixed while there are two atlases to migrate instead of fifty.

Conflict #1 closed: E's R25 validator prints "LICENCE: ... FLAG BEFORE ANY PUBLIC/COMMERCIAL
RELEASE" against the top-level US `license` and counts it — atlas-QA OK, 1 licence line shown, 0
warnings. The flag guarding real product photos is unmissable. Nothing owed either way.

Scoped: committed by pathspec on main (G's first as a full lane). F's six staged files and E's
in-flight validate_atlas.py left exactly as their sessions have them.

-> F (your file, your call, not touched mid-flight): interior_mode.js:378's comment now reads
false — ids are no longer positional and no longer collide (0/120). Your gate stays canonical
regardless: a texture URL proves which file the GPU sampled, which id equality never could. Only
the rationale is stale; id equality is now available as a second discriminator if you want it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 02:28:44 +10:00
m3ultra
a493c0a13c Lane A round 25 (v5.0-alpha): the sweep reconciled with Ruling 2 + the six pins (ledger #1)
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/<id>/ 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 <noreply@anthropic.com>
2026-07-17 02:23:57 +10:00
m3ultra
0d8d59a22a Lane C R25: sku-derived slot ids — F's id-collision finding made structural (ledger #3)
One contract line, published first so G re-emits against it. §7.2a added.

THE COLLISION, MEASURED: both packs number items positionally from zero, so all 120 godverse
ids are also generic-pack ids — 120/120 overlap. An id-equality gate cannot discriminate which
pack a rendered item came from; that is exactly why the brief's own #7a spec was vacuous. (The
vacuous-gate law's first application broke the law — and my own R23 metric had the same disease.)

THE RULE (§7.2a): items[].id MUST derive from the source's stable key, never from position, and
MUST be unique across ALL packs. Real-stock packs: id = "sku_" + <POS sku> (G's snapshot already
carries `sku` per item, e.g. 20260314070442 — it's just dropped at emit, so this is index-only).
Generic packs keep rec_NNNN as their namespace; real-stock must not emit into it.

TWO REASONS, and the second outlives the gate:
1. positional ids collide by construction (the 120/120 above) -> id-equality gates can't
   discriminate. Sku ids fix that permanently, now, at two atlases instead of fifty.
2. POSITION IS NOT IDENTITY: a positional id renumbers on every re-bake — sell one record out of
   crate 550 and rec_0050 silently becomes a different record. Anything holding an id across
   snapshots (a wallet purchase, a saved dig, a gate assertion, and above all tier-2's
   sold-means-gone) would point at the wrong item. The POS sku is stable because it is the shop's
   own identity for that copy (release_id identifies the pressing, not the copy). So this is also
   the identity tier-2 will need, bought now for free.

SCOPE: index-only — no atlas bytes, no town cache, no goldens, no C code (id isn't read by the
loader, §7.3; the dig/buy consumers and gates read it).
-> G: re-emit with sku_ ids. -> E: assert the form (present, unique within pack, sku_-prefixed).

Committed by explicit pathspec — F's six staged files sit untouched, as its session left them.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 02:18:37 +10:00
12 changed files with 538 additions and 176 deletions

View File

@ -3,7 +3,39 @@
*Status: **v1 complete & verified**. Standalone interiors library + test page. Every shop door opens
into a unique, seeded, themed interior, generated on demand in ~4ms, byte-identical every revisit.*
Last updated: 2026-07-17 (round 24) · owner: PROCITY-C · reviewer: Fable
Last updated: 2026-07-17 (round 25) · owner: PROCITY-C · reviewer: Fable
---
## Update 2026-07-17 (round 25, the tag earned) — sku-derived slot ids (ledger #3, C's one line)
R25 §Lane C: one contract line, published first so G re-emits against it. **§7.2a added** — F's id-collision
finding made structural, while there are two atlases to migrate instead of fifty.
**The collision, measured:** both packs number items positionally from zero, so **all 120 godverse ids are
also generic-pack ids — 120/120 overlap.** An id-equality gate cannot discriminate which pack a rendered
item came from; that's exactly why the brief's own #7a spec was vacuous. (The vacuous-gate law's first
application broke the law — and my own R23 metric had the same disease. It's a persistent species.)
**The rule (§7.2a):** `items[].id` MUST derive from the source's stable key, never from position, and MUST
be unique across ALL packs. Real-stock packs: **`id = "sku_" + <POS sku>`** — G's snapshot already carries
`sku` per item (e.g. `20260314070442`), it's just dropped at emit, so this is index-only. Generic packs keep
`rec_NNNN` as their namespace; real-stock must not emit into it.
**I gave it two reasons, and the second is the one that outlives the gate.** The collision is the presenting
symptom. The deeper defect is that **position is not identity**: a positional id renumbers on every re-bake,
so selling one record out of crate 550 silently makes `rec_0050` a *different* record. Anything holding an
id across snapshots — a wallet purchase, a saved dig, a gate assertion, and above all **tier-2's
sold-means-gone** — would point at the wrong item. The POS sku is stable because it's the shop's own
identity for that copy (`release_id` identifies the pressing, not the copy). So this line isn't only a gate
fix; it's the identity tier-2 will need, bought now for free.
**Scope kept honest:** index-only, no atlas bytes, no town cache, no goldens. `id` isn't read by the loader
(§7.3) — the dig/buy consumers and the gates read it, so no C code changes. **→ G:** re-emit with `sku_`
ids. **→ E:** assert the form (present, unique within the pack, `sku_`-prefixed for real-stock).
*Process note:* F's six files were staged in the shared tree mid-flight; committed by explicit pathspec, so
F's session is untouched.
---

View File

@ -3,6 +3,45 @@
*Lane brief: `docs/LANES/LANE_G_GODVERSE.md`. This file follows your progress-file convention —
newest round first. Fable-on-ultra writing.*
## Round 25 (2026-07-17) — the sku re-emit (ledger #3), index-only
**Landed (main — G is a full lane now, pathspec-atomic; F's six staged files and E's in-flight
`validate_atlas.py` untouched):**
- `godverse_stock.py` emits **`id = "sku_<POS sku>"`** per C §7.2a. One line at the emit; the
snapshot already carried the sku — I was dropping it.
- `stock_godverse/3962749/stock_record_index.json` re-emitted. **Index-only, as briefed:**
| check | result |
|---|---|
| **id overlap with the generic pack** | **0/120** — was **120/120** (F's finding) |
| ids unique in-pack / all `sku_`-prefixed / none in `rec_` namespace | 120/120 · yes · yes |
| **atlas .webp bytes moved** | **none** — sha-identical both files |
| town caches / goldens touched | none |
| ids stable when a record sells (drop item 50, re-derive) | **0 drift** |
| E's validator (their R25 build, live in-tree) | `atlas-QA OK · 1 licence line(s) shown · 0 warnings` |
| live loader: shop pack vs town pack | 120 / 350, coexist; uv + one-material-per-atlas hold |
**Id equality is discriminating again** — verified through the live loader, not just the file.
**C's reason #2 is the one that mattered**, and it's worth restating because it outlives the gate:
position isn't identity. Sell one record out of crate 550 and `rec_0050` silently becomes a
*different* record on the next bake — so a wallet purchase, a saved dig, or **tier-2's
sold-means-gone** would point at the wrong item. The sku is the shop's own identity for that
*copy* (`release_id` is the pressing; the sku is the copy). My positional ids were a latent
correctness bug in the ratified §7 economy, not just a vacuous gate. Caught while there were two
atlases to migrate instead of fifty.
**Conflict #1 is closed** (R24 → R25): E's validator now prints
`LICENCE: … FLAG BEFORE ANY PUBLIC/COMMERCIAL RELEASE` against my top-level US `license`, and
counts it. The flag a human must read on real product photos is unmissable. Nothing owed.
**→ F (one line, your file, your call — not touching it mid-flight):** `interior_mode.js:378`
now reads *"ids are positional slots (`rec_0000`) that BOTH packs reuse … every atlas id is also a
town-pack id (measured R24)"*. As of this commit that's false — 0/120. **Your gate stays canonical
either way**: a texture URL proves which file the GPU sampled, which id equality never could, so
the assertion is right and only the rationale is stale. Id equality is now available as a *second*
discriminator if you want belt-and-braces.
## Round 24 (2026-07-17) — the re-emit + THE SHOP'S REAL STREET (ledger #5)
**THE WRITE-BACK RULING — John, 2026-07-17: A THEN B.** G3 §7 is **ratified** with the ruling

View File

@ -1,11 +1,38 @@
# LANE A — CITYGEN · progress (PROCITY-A)
*Status: **all deliverables landed; self-check all-green (152,030/152,030); rounds 224 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 225 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/<id>/` 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/<id>/`. Schema **stays v2**; absent

View File

@ -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/<id>/` 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

View File

@ -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/<id>/` 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)

View File

@ -12,6 +12,9 @@
> held F's tag. §7 now states what `stockpack.js` actually implements, extended per-shop, and the
> `type+base` cache keying is landed. **G's R23 atlas layout is ratified** (G re-emits provenance field
> names only, §7.3). §0§6 unchanged.
> **v5.0-alpha amendment (2026-07-17, R25 ledger #3):** **§7.2a added — slot ids must be sku-derived,**
> unique across packs and stable across re-emits (F's id-collision finding, made structural). Index-only;
> no atlas bytes, no goldens. §0§6 unchanged.
*The district ships **three** venue archetypes behind `?gigs=1``pub`, `band_room`, `rsl`. Lane A
converts a chosen shop to a venue kind in place (`shop.type = kind`); C keys the interior recipe off
@ -257,6 +260,32 @@ The loader is `preloadStockPack(type, { base })` → it fetches **`<base>stock_<
- **`uv: [u0,v0,u1,v1]`, origin TOP-LEFT** — the loader flips V itself (`uvRect`). Do not pre-flip.
- Read by the dig/buy consumers (not by the loader): **`id`, `title`, `artist`, `price`, `price_band`**.
### 7.2a Slot ids — unique across packs, stable across re-emits (v5.0-alpha, R25 ledger #3)
**Rule: `items[].id` MUST derive from the source's own stable key — never from position — and MUST be
unique across ALL packs.** For a real-stock (godverse) pack that key is the shop's POS sku:
**`id = "sku_" + <POS sku>`** (e.g. `sku_20260314070442`; G's snapshot already carries `sku` per item, it's
just dropped at emit). The shipped generic packs keep `rec_NNNN` as their namespace — **a real-stock pack
must not emit into it.**
**Why — two reasons, both measured:**
1. **Positional ids collide by construction.** Both packs number from zero, so **all 120 godverse ids are
also generic-pack ids — 120/120 overlap (measured R25)**. An id-equality gate therefore cannot
discriminate which pack a rendered item came from: that is what made the brief's own #7a spec vacuous.
F caught it and replaced it with title-sets + texture-URL provenance (now the canonical honest-gate
pattern for stock). Sku ids make id-equality discriminating again — permanently, and while there are
two atlases to migrate instead of fifty.
2. **Position is not identity — and this one outlives the gate.** A positional id renumbers on every
re-bake: sell one record out of crate 550 and `rec_0050` silently becomes a *different* record. Anything
holding an id across snapshots — a wallet purchase, a saved dig, a gate assertion, and above all
**tier-2's sold-means-gone** — would then point at the wrong item. The POS sku is stable precisely
because it is the shop's own identity for that record: the same principle that makes `godverseShopId`
the POS shop id. *(`release_id` identifies the pressing, not the copy — the sku is the copy.)*
**Scope: index-only.** `id` is not read by the loader (§7.3) — the dig/buy consumers and the gates read it.
**No atlas bytes, no town cache, no golden moves.**
**→ Lane E:** assert the form — `id` present, **unique within the pack**, and `sku_`-prefixed for
real-stock packs.
### 7.3 Fields the runtime IGNORES — and the provenance ruling (the drift dies here)
`version`, `atlas_px`, `cell`/`cell_w`/`cell_h`, `count`, `kind`, `tier`, `shop`, `crate`,
`provenance`**`stockpack.js` reads none of them.** So the authority for their shape is not C's

View File

@ -0,0 +1,99 @@
# PROCITY — Round 26 lane instructions (from Fable, integrator)
Date: 2026-07-17 · **R25 verdict: one commit and one harness verdict from done.** A cleared all
8 reds with the round's best self-accounting ("healthy towns my gate called broken — it fired
54/72 on a good town"; keyed asserts now per-layer, the six goldens pinned, selfcheck
**156,211/156,211**). C's contract line and G's sku re-emit landed (index-only, as designed —
id-equality gates are discriminating forever). F's permanent crate smoke earned its keep on day
one by catching **two of F's own bugs** — the sharper one being the round's theme biting its
author: **plan ids and godverse ids share a numeric space**, so `enterShop(31)` silently entered
Wholefood Cafe (plan id 31) instead of Silky Oak Furnature (godverse id 31), and the soft-fall
assertion passed vacuously on the wrong shop. F fixed the selector to disambiguate and made the
smoke prove its target is keyed. *Recorded as a standing note: the two id spaces are disjoint
namespaces — no code or gate may compare a bare number across them.*
**Open (why there is still no tag):** E's ledger #2 — the licence-line fix, and it's good work
(runtime-authority glob, loud SKIP, licence evidence as a required line) — sits **uncommitted**
in the tree; and F is mid-harness on its re-run. Round 26 opens by closing both, then the beta.
## WAVE 0 — close the alpha (before anything else starts)
1. **E: commit your own work.** `pipeline/validate_atlas.py` carries your R25 rebuild, unstaged.
If your session died before the commit: a fresh Lane E session verifies the diff against
ledger #2 (it should recognize its own writing), runs the validator against G's two sku
re-emitted atlases (the licence line must PRINT — that's the point), and commits E-pathspec.
Nobody else touches this file — it's yours.
2. **F: land the verdict.** Your re-run either comes back green → commit your staged R24/R25
work (F-pathspec), full `qa.sh --strict --matrix`, README teaser with the tag, tag
**`v5.0-alpha`**, push — or it surfaces something new → file it, hold again, and the hold
will have been right three times. Nothing downstream starts until the tag verdict is known.
## Then: v5.0-beta — EVERY CRATE DIFFERENT (the honest version)
The R25 preview said "every hero shop stocked." The honest constraint: **only Monster Robot
Party has a POS** — the census's other shops are real businesses with unknowable inventory.
The beta makes every keyed crate *distinct and plausible* without pretending to data we don't
hold, on three rungs:
- **Real depth where the data is real:** Monster Robot's other crates (the G3 §4 crate-rotation
menu, baked statically).
- **Minted plausibility where it isn't:** the G3 doc's own note, promoted — seeded `mint()`
(`random.Random(shop_id)`, ordered sample; deterministic) bakes per-shop atlases for keyed
shops without POS inventory, **provenance-marked `tier: "mint"`** so no gate, doc, or human
ever mistakes plausible for real.
- **Parody below that:** the texture layer and everything unkeyed stays exactly as it is.
## The ledger
1. **The mint contract line (C, first — half-day rule).** §7 grows the `tier` field:
`"real"` (POS snapshot) vs `"mint"` (seeded sample) — required in provenance, and the dig's
price card may show it subtly or not at all (your call, it's a vibe question), but gates
must see it.
2. **The mint baker + Monster Robot depth (G).** Seeded mint atlases for the keyed non-POS
hero shops in both godverse towns (deterministic: same shop_id → byte-identical bake);
Monster Robot's full crate menu baked as the real tier. Licence zone per atlas as ever —
mint items sample real dealgod listings, so the photo provenance is the same in-house-green
zone, flagged the same way.
3. **The validator grows the tier arm (E).** `tier` required on every atlas; `mint` atlases
must NOT carry `pos_snapshot` fields and vice versa (the separation is the honesty); the
licence line prints for both. Vacuous-gate law applies: an atlas with no tier FAILS.
4. **The dig UX for real items (C).** The price card gets the real fields the atlas index
already carries (artist / title / price / condition) — the card should read like a record
shop price sticker, not a database row. Budget ceilings unchanged.
5. **B returns — dig-adjacent street polish (your pick, at last).** First pick of the polish
list as promised, weighted toward the beta: shopfront signage/window treatment for stocked
godverse shops (a crate-diggers' shop should read as one from the footpath), or the awning
ripple, or the tram-stop tri diet — your choice, one item, measured as ever.
6. **The beta gate (F).** Per-shop distinctness (no two keyed crates identical — assert on
title sets, the R24 pattern), tier provenance visible in gate output (a mint crate never
reports as real), the ladder re-proof, `qa.sh --strict --matrix`, tag **`v5.0-beta`**, push.
7. **A / D — on-call.** A: nothing pending (your R25 close was clean). D: nothing — unless F's
distinctness gate wants a crowd check at a stocked shop (patronage at Monster Robot on a
Saturday feels like a D flourish, not a requirement).
## Laws
Charter v5 + vacuous-gate + the id-namespace note above. Golden discipline: no town-cache
changes this round → no golden moves (mint atlases are `stock_godverse/**` files, outside every
golden). No server code — tier 2 stays design until the beta ships and the v5.0 round charters
it against the ratified G3.
## THE EXACT RUN ORDER
0. **WAVE 0: E commits; F lands the verdict and (on green) tags `v5.0-alpha`.** Hard stop until
the tag verdict is known.
1. **Start together: C (#1 contract line + #4 dig card) + B (#5 pick).**
2. **When C's line lands: G (#2 mint baker + depth) and E (#3 tier arm) in parallel.**
3. **When G + E are in: F (#6)** — distinctness gate, ladder, tag **`v5.0-beta`**, push.
Waves: **[E-commit, F-verdict] → [C, B] → [G, E] → [F]**. A/D on-call.
## Standing
- **John — nothing needs you.** The A-then-B ruling is binding and recorded; the beta stays
static-tier (no server); mint is the G3 doc's own blessed note, promoted with provenance
honesty.
- **v5.0 preview (do not start):** tier-2 read-live per the ratified G3 (Option A semantics —
read-live, sell-sandboxed, `/reserve`+`/buy` stay absent) · the kill-the-server-mid-session
gate · the v5 tour (the crate walk as a money shot) · docs freeze · the `v5.0` tag · the
epoch retro.

View File

@ -196,7 +196,14 @@ def pack(args):
sheet.paste(cov, (gx * CELL_W, gy * CELL_H))
u0, v0 = gx * CELL_W / W, gy * CELL_H / H
idx_items.append({
"id": f"rec_{len(idx_items):04d}", "title": it["title"], "artist": it["artist"],
# C §7.2a (R25): sku-derived, never positional. `rec_NNNN` is the generic packs'
# namespace and a real-stock pack must not emit into it — positionally, all 120 of
# our ids WERE generic-pack ids, which is what made an id-equality gate vacuous.
# And position isn't identity: sell one record out of the crate and `rec_0050`
# silently becomes a different record on the next bake, which is exactly what
# tier-2's sold-means-gone must never do. The POS sku is the shop's own identity
# for that copy (release_id identifies the pressing; the sku identifies the copy).
"id": f"sku_{it['sku']}", "title": it["title"], "artist": it["artist"],
"price": int(round(it["price"])), "price_band": price_band(it["price"]),
"atlas": aname, "uv": [round(u0, 4), round(v0, 4),
round(u0 + CELL_W / W, 4), round(v0 + CELL_H / H, 4)],

View File

@ -49,7 +49,7 @@
},
"items": [
{
"id": "rec_0000",
"id": "sku_20260314070442",
"title": "Abstract Latin Lounge II (Part II)",
"artist": "Various",
"price": 15,
@ -63,7 +63,7 @@
]
},
{
"id": "rec_0001",
"id": "sku_20250507152003",
"title": "The Lowdown Sampler Vol 1",
"artist": "Various",
"price": 20,
@ -77,7 +77,7 @@
]
},
{
"id": "rec_0002",
"id": "sku_20260208003559",
"title": "Yoshitoshi Artists - In House We Trust / Deep Dish - Penetrate Deeper",
"artist": "Various",
"price": 50,
@ -91,7 +91,7 @@
]
},
{
"id": "rec_0003",
"id": "sku_20260313083125",
"title": "Voyage Direct: First Mission Sampler 1",
"artist": "Various",
"price": 20,
@ -105,7 +105,7 @@
]
},
{
"id": "rec_0004",
"id": "sku_20250726020618",
"title": "Local Talk 5 1/2 Years Later (Part Two)",
"artist": "Various",
"price": 25,
@ -119,7 +119,7 @@
]
},
{
"id": "rec_0005",
"id": "sku_20260307032128",
"title": "Bar 1 | Autumn.04 | The Vinyl Edition (Part 2)",
"artist": "Various",
"price": 25,
@ -133,7 +133,7 @@
]
},
{
"id": "rec_0006",
"id": "sku_20250821021324",
"title": "Distille Cup",
"artist": "Various",
"price": 25,
@ -147,7 +147,7 @@
]
},
{
"id": "rec_0007",
"id": "sku_20260314234401",
"title": "The Esensual EP",
"artist": "Various",
"price": 20,
@ -161,7 +161,7 @@
]
},
{
"id": "rec_0008",
"id": "sku_20260313073221",
"title": "First Filtration Of The Duplex Brains",
"artist": "Various",
"price": 15,
@ -175,7 +175,7 @@
]
},
{
"id": "rec_0009",
"id": "sku_20250410004205",
"title": "Get Down",
"artist": "Various",
"price": 60,
@ -189,7 +189,7 @@
]
},
{
"id": "rec_0010",
"id": "sku_20260312011415",
"title": "It's A DJ Thing 3",
"artist": "Various",
"price": 50,
@ -203,7 +203,7 @@
]
},
{
"id": "rec_0011",
"id": "sku_20250529095405",
"title": "Lost On Arrival (Part 1 of 2)",
"artist": "Various",
"price": 40,
@ -217,7 +217,7 @@
]
},
{
"id": "rec_0012",
"id": "sku_20250410003605",
"title": "Moovemints Vol. 1",
"artist": "Various",
"price": 40,
@ -231,7 +231,7 @@
]
},
{
"id": "rec_0013",
"id": "sku_20250601121101",
"title": "The Innercity Experience Part 1",
"artist": "Various",
"price": 15,
@ -245,7 +245,7 @@
]
},
{
"id": "rec_0014",
"id": "sku_20260320031744",
"title": "Moving House",
"artist": "Various",
"price": 25,
@ -259,7 +259,7 @@
]
},
{
"id": "rec_0015",
"id": "sku_20250521143700",
"title": "Star Chasers",
"artist": "4 Hero",
"price": 20,
@ -273,7 +273,7 @@
]
},
{
"id": "rec_0016",
"id": "sku_20250223130605",
"title": "Feel So High",
"artist": "51 Days",
"price": 30,
@ -287,7 +287,7 @@
]
},
{
"id": "rec_0017",
"id": "sku_20250322223205",
"title": "Forbidden Love",
"artist": "52 Pik - Up",
"price": 25,
@ -301,7 +301,7 @@
]
},
{
"id": "rec_0018",
"id": "sku_20260212004619",
"title": "Music Of Life",
"artist": "ACME",
"price": 15,
@ -315,7 +315,7 @@
]
},
{
"id": "rec_0019",
"id": "sku_20251005025944",
"title": "Thus Far EP",
"artist": "AlphaMotive",
"price": 15,
@ -329,7 +329,7 @@
]
},
{
"id": "rec_0020",
"id": "sku_20251005030122",
"title": "Blue Blackness",
"artist": "Alan Smithee",
"price": 20,
@ -343,7 +343,7 @@
]
},
{
"id": "rec_0021",
"id": "sku_20250223165003",
"title": "Blue Blackness",
"artist": "Alan Smithee",
"price": 20,
@ -357,7 +357,7 @@
]
},
{
"id": "rec_0022",
"id": "sku_20250731022038",
"title": "Quarter Of A Century (Part 3 Of 3)",
"artist": "ADNY & The Persuader",
"price": 25,
@ -371,7 +371,7 @@
]
},
{
"id": "rec_0023",
"id": "sku_20250320144603",
"title": "Community / Everything Is Alright (Part Two)",
"artist": "Audio Soul Project",
"price": 20,
@ -385,7 +385,7 @@
]
},
{
"id": "rec_0024",
"id": "sku_20250731021731",
"title": "Quarter Of A Century - Part 2 Of 3",
"artist": "ADNY & The Persuader",
"price": 30,
@ -399,7 +399,7 @@
]
},
{
"id": "rec_0025",
"id": "sku_20250529161104",
"title": "The John Gore EP",
"artist": "Alex Agore / James Johnston (5)",
"price": 20,
@ -413,7 +413,7 @@
]
},
{
"id": "rec_0026",
"id": "sku_20250904081809",
"title": "LabSamples",
"artist": "Atjazz",
"price": 30,
@ -427,7 +427,7 @@
]
},
{
"id": "rec_0027",
"id": "sku_20250509114903",
"title": "Simple",
"artist": "Alumna",
"price": 20,
@ -441,7 +441,7 @@
]
},
{
"id": "rec_0028",
"id": "sku_20260325010301",
"title": "Cascades Of Colour",
"artist": "Ananda Project, The Feat. Gaelle Adisson",
"price": 40,
@ -455,7 +455,7 @@
]
},
{
"id": "rec_0029",
"id": "sku_20260228030040",
"title": "Time And Space EP",
"artist": "Andrew Macari",
"price": 25,
@ -469,7 +469,7 @@
]
},
{
"id": "rec_0030",
"id": "sku_20260208023822",
"title": "Hot 'N' Spycy (The Album)",
"artist": "Angel Moraes",
"price": 40,
@ -483,7 +483,7 @@
]
},
{
"id": "rec_0031",
"id": "sku_20251017085011",
"title": "I Like It (I Like How You're Lov'in Me)",
"artist": "Angel Moraes",
"price": 15,
@ -497,7 +497,7 @@
]
},
{
"id": "rec_0032",
"id": "sku_20250303003804",
"title": "Release Yourself / Deep Inside Your Love",
"artist": "Angel Moraes",
"price": 30,
@ -511,7 +511,7 @@
]
},
{
"id": "rec_0033",
"id": "sku_20251005041410",
"title": "Heaven Knows (I Can't Understand)",
"artist": "Angel Moraes Featuring Basil Rodericks",
"price": 20,
@ -525,7 +525,7 @@
]
},
{
"id": "rec_0034",
"id": "sku_20250815063017",
"title": "Tout Est Bleu (Fran\u00e7ois K. Rmx)",
"artist": "\u00c2me Strong",
"price": 50,
@ -539,7 +539,7 @@
]
},
{
"id": "rec_0035",
"id": "sku_20250927001753",
"title": "Are You Ready",
"artist": "Affair (3), The",
"price": 20,
@ -553,7 +553,7 @@
]
},
{
"id": "rec_0036",
"id": "sku_20250509104701",
"title": "It's Been Too Long",
"artist": "Alyoa",
"price": 15,
@ -567,7 +567,7 @@
]
},
{
"id": "rec_0037",
"id": "sku_20250216121604",
"title": "Change",
"artist": "Bugge Wesseltoft",
"price": 30,
@ -581,7 +581,7 @@
]
},
{
"id": "rec_0038",
"id": "sku_20250513235504",
"title": "Die Hard + 2",
"artist": "Baba Stiltz",
"price": 25,
@ -595,7 +595,7 @@
]
},
{
"id": "rec_0039",
"id": "sku_20260203022809",
"title": "Living Room Chronicles Vol. III: Forward Movement",
"artist": "DJ Buck",
"price": 50,
@ -609,7 +609,7 @@
]
},
{
"id": "rec_0040",
"id": "sku_20250301154200",
"title": "Lone Cat (Holding On)",
"artist": "Ben Watt",
"price": 30,
@ -623,7 +623,7 @@
]
},
{
"id": "rec_0041",
"id": "sku_20260320051206",
"title": "Music Is Our Life",
"artist": "Back To Earth",
"price": 20,
@ -637,7 +637,7 @@
]
},
{
"id": "rec_0042",
"id": "sku_20250817064358",
"title": "All True (African)",
"artist": "Basil",
"price": 15,
@ -651,7 +651,7 @@
]
},
{
"id": "rec_0043",
"id": "sku_20250207224901",
"title": "Rated X (Re-Make)",
"artist": "Benny Bobby Boogie",
"price": 15,
@ -665,7 +665,7 @@
]
},
{
"id": "rec_0044",
"id": "sku_20260326061120",
"title": "Rock To The Rhythm Of Love",
"artist": "Beloved, The",
"price": 12,
@ -679,7 +679,7 @@
]
},
{
"id": "rec_0045",
"id": "sku_20260320032136",
"title": "7 Days",
"artist": "Big Strick",
"price": 25,
@ -693,7 +693,7 @@
]
},
{
"id": "rec_0046",
"id": "sku_20250410104801",
"title": "Volume II",
"artist": "Black Traxx",
"price": 35,
@ -707,7 +707,7 @@
]
},
{
"id": "rec_0047",
"id": "sku_20250318010204",
"title": "Tried So Hard",
"artist": "Bobby Pruit",
"price": 40,
@ -721,7 +721,7 @@
]
},
{
"id": "rec_0048",
"id": "sku_20250731015554",
"title": "Best Intentions For Failed Inventions",
"artist": "Brett Johnson",
"price": 30,
@ -735,7 +735,7 @@
]
},
{
"id": "rec_0049",
"id": "sku_20250824052221",
"title": "Highlights / 405",
"artist": "DJ Buck",
"price": 90,
@ -749,7 +749,7 @@
]
},
{
"id": "rec_0050",
"id": "sku_20250406234001",
"title": "The Purist",
"artist": "Byron Stingily",
"price": 30,
@ -763,7 +763,7 @@
]
},
{
"id": "rec_0051",
"id": "sku_20250816021721",
"title": "Boogie Drive 678 (Remix & Dub)",
"artist": "Boo",
"price": 35,
@ -777,7 +777,7 @@
]
},
{
"id": "rec_0052",
"id": "sku_20260313082548",
"title": "Costa Del Sol",
"artist": "Bad Boy Bill Feat. Nadine Ren\u00e9e",
"price": 15,
@ -791,7 +791,7 @@
]
},
{
"id": "rec_0053",
"id": "sku_20260222035021",
"title": "When I Fall In Love (Remixes)",
"artist": "Blaze Featuring Sybil",
"price": 30,
@ -805,7 +805,7 @@
]
},
{
"id": "rec_0054",
"id": "sku_20250321121804",
"title": "Fly Life Extra (Unreleased Mixes)",
"artist": "Basement Jaxx",
"price": 30,
@ -819,7 +819,7 @@
]
},
{
"id": "rec_0055",
"id": "sku_20250406095702",
"title": "Beach Flea EP",
"artist": "Beach Flea",
"price": 25,
@ -833,7 +833,7 @@
]
},
{
"id": "rec_0056",
"id": "sku_20251017005854",
"title": "Be Yourself",
"artist": "Blaze Presents Underground Dance Artists United For Life Feat. Joi Cardwell",
"price": 15,
@ -847,7 +847,7 @@
]
},
{
"id": "rec_0057",
"id": "sku_20250419165802",
"title": "Why Can't We See",
"artist": "Blind Truth featuring Tata Vega and Toney Lee",
"price": 25,
@ -861,7 +861,7 @@
]
},
{
"id": "rec_0058",
"id": "sku_20250201132301",
"title": "I Think Of You",
"artist": "Blaze Featuring Amira",
"price": 15,
@ -875,7 +875,7 @@
]
},
{
"id": "rec_0059",
"id": "sku_20250926055332",
"title": "Intro To (R)Evolution / Asphalt Yards",
"artist": "Carl Hancock Rux",
"price": 12,
@ -889,7 +889,7 @@
]
},
{
"id": "rec_0060",
"id": "sku_20250413163600",
"title": "Ngbaka Chant / Come Fly With Me",
"artist": "Calm Presents Kiyotaka Fukagawa",
"price": 30,
@ -903,7 +903,7 @@
]
},
{
"id": "rec_0061",
"id": "sku_20250711035800",
"title": "The Shiva Chant",
"artist": "Coco Da Silva",
"price": 20,
@ -917,7 +917,7 @@
]
},
{
"id": "rec_0062",
"id": "sku_20260325035717",
"title": "Eternally / Fruitcake",
"artist": "Can 7 Feat. Angela Caran",
"price": 15,
@ -931,7 +931,7 @@
]
},
{
"id": "rec_0063",
"id": "sku_20250206134701",
"title": "Chara Claridade",
"artist": "Clara Moreno",
"price": 15,
@ -945,7 +945,7 @@
]
},
{
"id": "rec_0064",
"id": "sku_20250409122501",
"title": "Take Me Higher",
"artist": "CZR Featuring Alexander East",
"price": 15,
@ -959,7 +959,7 @@
]
},
{
"id": "rec_0065",
"id": "sku_20250409220600",
"title": "Cinemascope EP",
"artist": "Cinnamone",
"price": 35,
@ -973,7 +973,7 @@
]
},
{
"id": "rec_0066",
"id": "sku_20260329044244",
"title": "Club Hit EP",
"artist": "Contriva",
"price": 20,
@ -987,7 +987,7 @@
]
},
{
"id": "rec_0067",
"id": "sku_20250926093442",
"title": "Eternally / Fruitcake",
"artist": "Can 7 Feat. Angela Caran",
"price": 15,
@ -1001,7 +1001,7 @@
]
},
{
"id": "rec_0068",
"id": "sku_20250215122904",
"title": "The Sun Will Shine",
"artist": "Cast Of Thousands Featuring Mimi Johnson",
"price": 25,
@ -1015,7 +1015,7 @@
]
},
{
"id": "rec_0069",
"id": "sku_20251011030603",
"title": "Northern Lights / The Crystal Wave",
"artist": "Caucasian Boy / Century Falls",
"price": 50,
@ -1029,7 +1029,7 @@
]
},
{
"id": "rec_0070",
"id": "sku_20250923041304",
"title": "The Only One (Remixed By Groove Armada)",
"artist": "Cerrone",
"price": 15,
@ -1043,7 +1043,7 @@
]
},
{
"id": "rec_0071",
"id": "sku_20260314065922",
"title": "Dance To The Music (Body Action) / Jazz It Up",
"artist": "C.F.M. Band",
"price": 20,
@ -1057,7 +1057,7 @@
]
},
{
"id": "rec_0072",
"id": "sku_20250507155205",
"title": "Return Of The Big Tool",
"artist": "Chris Lum",
"price": 25,
@ -1071,7 +1071,7 @@
]
},
{
"id": "rec_0073",
"id": "sku_20250831024809",
"title": "Tick Tock",
"artist": "Chiapet",
"price": 40,
@ -1085,7 +1085,7 @@
]
},
{
"id": "rec_0074",
"id": "sku_20250731022109",
"title": "Wish (Remixes)",
"artist": "Chymera",
"price": 20,
@ -1099,7 +1099,7 @@
]
},
{
"id": "rec_0075",
"id": "sku_20250528174602",
"title": "The Return",
"artist": "Circulation (2)",
"price": 40,
@ -1113,7 +1113,7 @@
]
},
{
"id": "rec_0076",
"id": "sku_20260313010844",
"title": "All I Do",
"artist": "Cleptomaniacs",
"price": 40,
@ -1127,7 +1127,7 @@
]
},
{
"id": "rec_0077",
"id": "sku_20250323151903",
"title": "Right Here And Now",
"artist": "Clinton Daniel",
"price": 15,
@ -1141,7 +1141,7 @@
]
},
{
"id": "rec_0078",
"id": "sku_20250703002101",
"title": "Burning Sunshine",
"artist": "Coco Bongo",
"price": 12,
@ -1155,7 +1155,7 @@
]
},
{
"id": "rec_0079",
"id": "sku_20250927021639",
"title": "I'm Attracted To You",
"artist": "Cookie Watkins",
"price": 25,
@ -1169,7 +1169,7 @@
]
},
{
"id": "rec_0080",
"id": "sku_20260325040240",
"title": "Wishing On A Star",
"artist": "Cover Girls, The",
"price": 25,
@ -1183,7 +1183,7 @@
]
},
{
"id": "rec_0081",
"id": "sku_20260208011045",
"title": "Pirates Life EP",
"artist": "Cpen",
"price": 90,
@ -1197,7 +1197,7 @@
]
},
{
"id": "rec_0082",
"id": "sku_20260307022426",
"title": "Go Around",
"artist": "Criminal Element Orchestra",
"price": 12,
@ -1211,7 +1211,7 @@
]
},
{
"id": "rec_0083",
"id": "sku_20250322111402",
"title": "Fried",
"artist": "Crispin J. Glover",
"price": 20,
@ -1225,7 +1225,7 @@
]
},
{
"id": "rec_0084",
"id": "sku_20250319130303",
"title": "Waiting Hopefully",
"artist": "D*Note",
"price": 15,
@ -1239,7 +1239,7 @@
]
},
{
"id": "rec_0085",
"id": "sku_20260318005638",
"title": "The Dizko Knights Remixes Vol. 1",
"artist": "Dizko Knights, The",
"price": 15,
@ -1253,7 +1253,7 @@
]
},
{
"id": "rec_0086",
"id": "sku_20250802054759",
"title": "Dreamin'",
"artist": "D3",
"price": 12,
@ -1267,7 +1267,7 @@
]
},
{
"id": "rec_0087",
"id": "sku_20250406095005",
"title": "Key Of Soul",
"artist": "Dan Curtin And Warren Harris Present Key Of Soul",
"price": 20,
@ -1281,7 +1281,7 @@
]
},
{
"id": "rec_0088",
"id": "sku_20250426152700",
"title": "Twentygreenfoursday EP",
"artist": "Daniel Madlung & Johannes Retschke",
"price": 25,
@ -1295,7 +1295,7 @@
]
},
{
"id": "rec_0089",
"id": "sku_20250816031139",
"title": "Wow!",
"artist": "Danny Tenaglia Presents Hambone",
"price": 25,
@ -1309,7 +1309,7 @@
]
},
{
"id": "rec_0090",
"id": "sku_20250322215200",
"title": "More Than Anything",
"artist": "Darryl D'Bonneau",
"price": 25,
@ -1323,7 +1323,7 @@
]
},
{
"id": "rec_0091",
"id": "sku_20260228031623",
"title": "The Future Of The Future (Stay Gold)",
"artist": "Deep Dish With Everything But The Girl",
"price": 35,
@ -1337,7 +1337,7 @@
]
},
{
"id": "rec_0092",
"id": "sku_20250731011503",
"title": "The Way I Feel ",
"artist": "DFLN",
"price": 30,
@ -1351,7 +1351,7 @@
]
},
{
"id": "rec_0093",
"id": "sku_20250510130705",
"title": "When The World Is Running Down (You Can't Go Wrong)",
"artist": "Different Gear Vs. Police, The",
"price": 15,
@ -1365,7 +1365,7 @@
]
},
{
"id": "rec_0094",
"id": "sku_20250815043151",
"title": "10 Steps To Heaven",
"artist": "DJ Disciple",
"price": 30,
@ -1379,7 +1379,7 @@
]
},
{
"id": "rec_0095",
"id": "sku_20250802024708",
"title": "Take Me Higher / Sueno De Bahia",
"artist": "Don Carlos",
"price": 30,
@ -1393,7 +1393,7 @@
]
},
{
"id": "rec_0096",
"id": "sku_20260301015349",
"title": "Bug Eyed EP",
"artist": "Dubble D",
"price": 15,
@ -1407,7 +1407,7 @@
]
},
{
"id": "rec_0097",
"id": "sku_20250226165204",
"title": "Dom Dom 05",
"artist": "Deep, The And Fred Melosax",
"price": 25,
@ -1421,7 +1421,7 @@
]
},
{
"id": "rec_0098",
"id": "sku_20250815070301",
"title": "Misturada One (Mixing)",
"artist": "Friends From Rio",
"price": 30,
@ -1435,7 +1435,7 @@
]
},
{
"id": "rec_0099",
"id": "sku_20251010035246",
"title": "Beijo",
"artist": "Earth, Wind & Fire",
"price": 70,
@ -1449,7 +1449,7 @@
]
},
{
"id": "rec_0100",
"id": "sku_20251017042537",
"title": "Starlite (Remixes)",
"artist": "Eddy & Dus",
"price": 12,
@ -1463,7 +1463,7 @@
]
},
{
"id": "rec_0101",
"id": "sku_20250312135004",
"title": "Work This Pussy",
"artist": "Ellis-D presents Boom Boom (2)",
"price": 25,
@ -1477,7 +1477,7 @@
]
},
{
"id": "rec_0102",
"id": "sku_20250315005602",
"title": "The Nia Deep Project Vol. 1",
"artist": "En' Sense Ltd.",
"price": 20,
@ -1491,7 +1491,7 @@
]
},
{
"id": "rec_0103",
"id": "sku_20250316154104",
"title": "Havana",
"artist": "Eric Kupper Presents Organika",
"price": 20,
@ -1505,7 +1505,7 @@
]
},
{
"id": "rec_0104",
"id": "sku_20250801001005",
"title": "We Are One (Les Remixes)",
"artist": "Ernest Saint Laurent Feat. Chezer\u00e9",
"price": 15,
@ -1519,7 +1519,7 @@
]
},
{
"id": "rec_0105",
"id": "sku_20250328124404",
"title": "Berlingo",
"artist": "D'Julz",
"price": 15,
@ -1533,7 +1533,7 @@
]
},
{
"id": "rec_0106",
"id": "sku_20260221235504",
"title": "Blackness Of The Sea",
"artist": "Deepchild",
"price": 12,
@ -1547,7 +1547,7 @@
]
},
{
"id": "rec_0107",
"id": "sku_20251009025229",
"title": "You Don't Notice Me",
"artist": "Etienne Br\u00e9nche",
"price": 12,
@ -1561,7 +1561,7 @@
]
},
{
"id": "rec_0108",
"id": "sku_20250406094801",
"title": "K-Scope 4",
"artist": "Eric Kupper Presents K-Scope (2)",
"price": 15,
@ -1575,7 +1575,7 @@
]
},
{
"id": "rec_0109",
"id": "sku_20250606124303",
"title": "Notes From The Underground Part 3",
"artist": "Freaks",
"price": 15,
@ -1589,7 +1589,7 @@
]
},
{
"id": "rec_0110",
"id": "sku_20250223163203",
"title": "Sound Effects For Clubs & DJs Vol. 2",
"artist": "Flower S.E. Productions",
"price": 30,
@ -1603,7 +1603,7 @@
]
},
{
"id": "rec_0111",
"id": "sku_20250816015945",
"title": "House Of LZ",
"artist": "Faze II",
"price": 30,
@ -1617,7 +1617,7 @@
]
},
{
"id": "rec_0112",
"id": "sku_20260304060646",
"title": "In The Trees (Carl Craig Unreleased Mixes)",
"artist": "Faze Action",
"price": 90,
@ -1631,7 +1631,7 @@
]
},
{
"id": "rec_0113",
"id": "sku_20250424111303",
"title": "Unbreakable / First Fruit",
"artist": "Felix Dickinson & Nick The Record",
"price": 20,
@ -1645,7 +1645,7 @@
]
},
{
"id": "rec_0114",
"id": "sku_20250824043324",
"title": "Voyage EP",
"artist": "Flow",
"price": 80,
@ -1659,7 +1659,7 @@
]
},
{
"id": "rec_0115",
"id": "sku_20250409112205",
"title": "Midnite Sun",
"artist": "Frankie Valentine",
"price": 20,
@ -1673,7 +1673,7 @@
]
},
{
"id": "rec_0116",
"id": "sku_20250420114001",
"title": "Untitled",
"artist": "Freestyle Man",
"price": 40,
@ -1687,7 +1687,7 @@
]
},
{
"id": "rec_0117",
"id": "sku_20250815061653",
"title": "Open Up",
"artist": "Free Radikalz, The",
"price": 35,
@ -1701,7 +1701,7 @@
]
},
{
"id": "rec_0118",
"id": "sku_20250927044701",
"title": "Do I",
"artist": "French Connection, The",
"price": 12,
@ -1715,7 +1715,7 @@
]
},
{
"id": "rec_0119",
"id": "sku_20260405023654",
"title": "Entertainment",
"artist": "Floppy Sounds Feat. Sarah Jones",
"price": 20,

View File

@ -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.

View File

@ -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.

View File

@ -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)`);
}
}
}