The full qa.sh --strict --matrix run confirms the hold and sizes it. Green: selfcheck ALL GREEN 156,212/156,212 · MATRIX GREEN 10 towns x 7 gates · flags harness (carrying F's beta gate: distinctness, sourcing, zero 404s, 0 console errors) · consistency · figure-scale. Red: the manifest validator alone — atlas-QA, 14 identical errors, all 14 mint crates, one sourcing-blind assertion, one line. The real crate passes. Recording the ratio because it IS the argument: the beta is finished. What stands between it and the tag is a rule meeting a case written before the case existed — C's to rule, E's to land, and not F's to quietly loosen on its own behalf. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1208 lines
90 KiB
Markdown
1208 lines
90 KiB
Markdown
# LANE F — NOTES (integration runbook + QA gates)
|
||
|
||
*Lane F is integration + QA. It runs **after** A–E land. This file is the durable technical spec:
|
||
the exact wiring edits, the contracts F needs from each lane, and the QA gate procedures. It is
|
||
written **before** A–E finished so the wiring is a fast, mechanical pass the moment they do —
|
||
and so each lane can see the small hook F will ask of it and build it in up front.*
|
||
|
||
Status of this file: **living**. Update the ✅/⏳ markers as wiring lands. Snapshot of lane
|
||
readiness lives in [`F-progress.md`](../../F-progress.md); run `node tools/qa/scaffold_check.mjs`
|
||
for the live matrix.
|
||
|
||
> **v2.0 FROZEN (round 9).** Nine rounds: seed → walkable town → weather, a shopping crowd,
|
||
> follow-a-ped-into-a-shop presence, and real stock you can buy. Current gate reality + the
|
||
> final v2 flags table live in [`F-progress.md`](../../F-progress.md); `qa.sh --strict` GREEN 5/5
|
||
> (scaffold+determinism · consistency · citygen selfcheck · manifest --depot · v2 flags harness STRICT).
|
||
> CityPlan v2 contract frozen by Lane A in CITY_SPEC; validated live by selfcheck (1727/1727) + goldens.
|
||
|
||
---
|
||
|
||
## 0. Ground truth / environment
|
||
|
||
- Repo root on this machine: `/Users/m3ultra/Documents/procity` (the lane prompts say
|
||
`/Users/jing/Documents/PROCITY` — that path is from the authoring machine; **use the real path
|
||
above**). Reference repos are cloned alongside: `../90sDJsim`, `../thriftgod`. Ultra box
|
||
(existing 3D library / Blender / clip bank): `ssh johnking@100.91.239.7`.
|
||
- Serve: `cd web && python3 -m http.server 8130`. Node 26, Python 3.14 present.
|
||
- `web/package.json` has `"type":"module"` (Lane A) so `node web/js/**/*.js` runs as ESM.
|
||
- **F never edits another lane's internals.** F adds *wiring* — new files it owns, and small,
|
||
clearly-marked hook edits at the seams. Where a hook must live inside a lane's file, F first
|
||
looks for the hook that lane already exposed (see contracts below); the lane owns its file, F
|
||
owns the glue.
|
||
|
||
## 1. Files Lane F owns / creates
|
||
|
||
| path | purpose |
|
||
|---|---|
|
||
| `tools/qa.sh` | the QA gate runner (scaffold + citygen selfcheck + manifest validator) ✅ |
|
||
| `tools/qa/scaffold_check.mjs` | scaffold + PRNG determinism law + **live lane-readiness matrix** ✅ |
|
||
| `tools/shots.py` | Playwright tour-capture → `docs/shots/v1_tour/` + contact sheet (ported from 90sDJsim `tools/shots.py`) |
|
||
| `tools/soak.py` | Playwright 10-minute scripted-walk soak (chunk/heap/leak/error gate) |
|
||
| `web/js/citygen/index.js` | barrel so B's `import('./js/citygen/index.js')` resolves to A's `plan.js` ✅ (§3.1) |
|
||
| `web/js/world/interior_mode.js` | **interior bridge** — B's shell ↔ C's `buildInterior`; owns the `interior` mode branch ✅ (§3.2) |
|
||
| `docs/V2_IDEAS.md` | parked-scope catalogue ✅ |
|
||
| `docs/shots/` | reference shot tree (`laneA…E/`, `v1_tour/`, `before/`, `references/`) |
|
||
| `F-progress.md` | status report for fable |
|
||
|
||
The wiring is kept as **focused F-owned modules** (e.g. `interior_mode.js`) that the shell imports,
|
||
rather than logic sprinkled through B's files. Each seam edit inside `index.html` is one line,
|
||
commented `[Lane F integration]`, and reversible. That keeps ownership clean: B owns the shell +
|
||
mode machine, C/D own their libraries, F owns the glue modules and the marked seams that call them.
|
||
|
||
---
|
||
|
||
## 2. Contracts F depends on (verify these as lanes land)
|
||
|
||
Pulled from CITY_SPEC + each lane prompt. The scaffold check's readiness matrix tracks presence;
|
||
these are the **shapes** F wires against. If a lane ships a different shape, fix it here and in
|
||
the wiring, not by guessing.
|
||
|
||
> **⚠ Corrected against the REAL landed code (2026-07-14).** The bullets below are what the lanes
|
||
> actually shipped — several differ from the spec-derived guesses in an earlier draft (verified by
|
||
> reading each file + the Lane F self-audit workflow).
|
||
|
||
**Lane A — `web/js/citygen/plan.js`** *(landed, verified)*
|
||
- `generatePlan(citySeed) → CityPlan` (schema in CITY_SPEC §Layer 1). <3ms, deterministic, JSON-clean.
|
||
- `chunkIndex(plan) → { chunkSize, chunks }` where `chunks["cx,cz"] = { lots:[id…], shops:[…], edges:[…] }`
|
||
(a **plain object, not a callable**; a lot may bucket into several chunks). Also exports `chunkKey(cx,cz)`.
|
||
- `web/js/core/registry.js` exports `SHOP_TYPES` — per type `{ label, facades:[<full filename>.jpg…],
|
||
sign, interior, fittings:[…], storeys:[min,max], hours:{open,close} }`. **`facades` are full
|
||
filenames** (`facade-timber-teal.jpg`), not bare keys — see the skins seam in §7.
|
||
- `web/map.html` — 2D plan viewer (verified rendering real towns). `web/js/citygen/index.js` **is the
|
||
barrel Lane F added** so the shell's `import('./js/citygen/index.js')` resolves (§3.1).
|
||
|
||
**Lane B — `web/index.html` + `web/js/world/*`** *(landed, verified live)*
|
||
- Mode state machine `MODE ∈ {map, street, interior}` (interior branch not yet used), one renderer.
|
||
- **`createChunkManager(plan, scene, ctx)`** — a **factory**, not a class; returns
|
||
`{ update(pos), warmup(pos), getColliders(x,z), setNight(bool), getDoorMeshes(), count, dispose() }`.
|
||
**No `onChunkBuilt/onChunkDisposed` lifecycle hooks** — so §3.3 must drive citizens off the street
|
||
graph, not chunk lifecycle (or F adds a hook; prefer graph-driven).
|
||
- Door pick is wired via HUD: `createHUD({ onEnterShop: (shopId,name)=>enterShop(...) })`; `enterShop()`
|
||
dispatches `procity:enterShop` and is otherwise **a stub** (`hud.showToast('interiors coming')`) —
|
||
this is the seam F completes in §3.2. Interior returns via `procity:exitShop`.
|
||
- HUD shows draws / tris / fps / seed / time-of-day (F reads these for the budget gate).
|
||
- Exposes `window.PROCITY = { plan, scene, camera, renderer, chunks, lighting, player, skins }`
|
||
(F builds the `window.DBG` harness hook on top of this — §4).
|
||
|
||
**Lane C — `web/js/interiors/interiors.js`** *(landed, verified)*
|
||
- `buildInterior(shop, THREE, opts={registry,archetype,stockAdapter}) → { group, spawn:{x,z,ry},
|
||
exits:[…], places:[…], dims, recipe:{counterPos,…}, dispose() }`. Matches the wiring plan.
|
||
- Caller must attach lot dims: pass `{ ...shop, lot: plan.lots.find(l=>l.id===shop.lot) }`.
|
||
`dispose()` frees tracked GPU resources (soak gate asserts baseline).
|
||
|
||
**Lane D — `web/js/citizens/*`** *(landed, verified)*
|
||
- `sim.js` exports **`class CitizenSim`** — `new CitizenSim({ renderer, scene, camera, citySeed,
|
||
graph, fleet, group })`. It is **street-graph driven, not per-chunk**: there is no
|
||
`spawnRoster/despawn/setDaySegment` — build it once from the street graph and tick it in the loop.
|
||
- `keepers.js` exports **`class KeeperManager`** — `new KeeperManager({ camera, citySeed, fleet })`,
|
||
then `spawn(target, { x, z, ry, shopId, type }) → handle`, `remove(handle)`, `disposeAll()`,
|
||
`update(dt, playerPos)`. (Not `spawnFor/disposeFor`.)
|
||
|
||
**Lane E — `web/assets/manifest.json` + `pipeline/validate_manifest.py`**
|
||
- Manifest: `{depot, fittings:{id:{file,footprint,thumb}}, furniture:{…}, skins:{facade,sky,ground,wall,interior}}`.
|
||
- `python3 pipeline/validate_manifest.py` green (F's gate 5 runs it).
|
||
- B/C read the manifest for GLB upgrades; **primitive fallbacks must still fire under `?noassets=1`**.
|
||
|
||
---
|
||
|
||
## 3. Wiring checklist (LANE_F §Wiring — expanded to exact steps)
|
||
|
||
All of this lives in `web/js/world/wiring.js` (F-owned) + the minimal hooks each lane exposes.
|
||
|
||
### 3.1 Plan source swap (A → B) ✅ DONE (verified live)
|
||
- Lane B already reads `?seed=N` (default `20261990`), guards the citygen import, and surfaces the
|
||
town name + seed in the start panel and HUD. **But it imported `./js/citygen/index.js`, which did
|
||
not exist** (Lane A shipped `plan.js`), so it silently fell back to the 14-shop fixture.
|
||
- **Fix:** Lane F added `web/js/citygen/index.js` (barrel re-exporting `generatePlan/chunkIndex/chunkKey`
|
||
from `plan.js`). Verified live: shell logs "using Lane A generatePlan", HUD shows the real town
|
||
("Boolarra Heads", 523 shops), 31 chunks stream.
|
||
- Map key **M** is wired by Lane B to an in-shell minimap (not map.html). Fine for v1.
|
||
- **Gate:** plan determinism asserted green by `scaffold_check.mjs`. Map-PNG determinism pending the
|
||
shot harness + DBG hook (§4).
|
||
|
||
### 3.2 enterShop → interiors (B ↔ C) ✅ DONE (verified live, 2026-07-14)
|
||
Implemented as an **F-owned bridge module `web/js/world/interior_mode.js`** (`createInteriorMode`),
|
||
plus a thin marked seam in the shell. The bridge renders the interior into its **own dark
|
||
THREE.Scene** — the street scene is simply left frozen (not disposed → instant return, CITY_SPEC L3
|
||
"pause, not dispose"), which is cleaner than hiding B's ground+chunk meshes in a shared scene.
|
||
```
|
||
enterShop(shopId, name): // shell seam (was the toast stub)
|
||
shop = plan.shops.find(s => s.id === shopId) // id is numeric; matches door.shopId
|
||
lot = plan.lots.find(l => l.id === shop.lot) // attach {w,d} — Lane C sizes the room to it
|
||
interiorMode.enter({ ...shop, lot:{w,d} }, name); setMode('interior')
|
||
interiorMode.enter(shop): // F module
|
||
current = buildInterior(shop, THREE); scene.add(current.group) // Lane C
|
||
doorReturn = {x,y,z,ry of the player on the street}
|
||
camera → current.spawn (x, 1.6, z, ry); exitArmed = false
|
||
loop [MODE==='interior']: if interiorMode.update(dt,keys) leaveShop()
|
||
update: walk via current._debug.grid occ/bounds (mirrors interior_test.html) → renderer.render(ownScene)
|
||
exit arms once player steps >2m from the door, then fires within 1m of exits[].toStreet
|
||
leaveShop() / 'procity:exitShop' / Esc(unlock): // three exit routes, all → leaveShop
|
||
interiorMode.exit(): current.dispose() (frees GPU + unparents); camera → doorReturn
|
||
setMode('street') // pointer stayed locked → walking resumes at once
|
||
```
|
||
- **Verified live** (seed 20261990 "Boolarra Heads"): door → **Toy Shop "Numbat Playthings"**
|
||
renders (ivy wallpaper, stocked shelves, counter, exit doorway); exit state-machine correct
|
||
(no instant-exit at spawn; arms then fires at the door); **leak-free** — GPU memory 134 geo/37 tex
|
||
→ 217 loaded → **back to 134/37 after `dispose()`**. Swept **all 9 shop types** (toy/video/pawn/
|
||
opshop/book/record/stall/dept/milkbar), every one `ok+pathOK+leftClean`, worst build **8.4ms**
|
||
(budget 50), zero console errors.
|
||
- **Gate:** determinism (byte-identical placement per seed) is asserted by Lane C's own soak page;
|
||
memory-to-baseline after `dispose()` confirmed above. Real pointer-lock walk-in is the one bit the
|
||
automated browser can't drive — needs a human (or the DBG hook in §4).
|
||
|
||
### 3.3 Citizens + keepers (D)
|
||
**Keepers ✅ DONE (verified live).** Wired into the interior bridge (`interior_mode.js`), not the shell:
|
||
`new KeeperManager({ camera, citySeed: plan.citySeed })` — **no `fleet` passed → asset-free placeholder
|
||
actor** (rig-fleet upgrade is later, with §3.4). On `enter`, find the counter interactable
|
||
(`places.find(p => p.userData.keeperStand)` — Lane C tags `{x,z,ry}` there) and
|
||
`keepers.spawn(interior.group, { x, z, ry, shopId, type })`; tick `keepers.update(dt)` each interior
|
||
frame (playerPos defaults to the camera); `keepers.disposeAll()` on `exit`, before the room disposes.
|
||
- **Verified:** placeholder keeper (11-mesh humanoid) stands at the counter pose (record shop:
|
||
`{2.55,-2.46, ry 1.571}`), **turns to greet** a nearby player (0.21 rad) and eases back when they
|
||
leave (~0), disposes to 0. **Leak-free**: 5 enter/leave cycles over 5 shop types → after-leave
|
||
memory constant at 96 geo/16 tex, **net growth 0**; 0 console errors.
|
||
|
||
**CitizenSim (street peds) ✅ DONE (verified live).** Happy surprise: **`plan.streets` already IS the
|
||
graph** — `{ nodes:[{id,x,z}], edges:[{id,a,b,width,kind}] }` — so `graph: plan.streets` passes
|
||
straight in, **no adapter needed** (the runbook's earlier `streetGraphFrom` is unnecessary). In the
|
||
shell: `new CitizenSim({ renderer, scene, camera, citySeed: plan.citySeed, graph: plan.streets })`,
|
||
`setPopulation(140)` (`?pop=` tunable), then in the **street** branch each frame:
|
||
`setTimeOfDay((clk.seg + clk.frac)/6)` + **`update(dt)`** — note `update` takes **only dt** and reads
|
||
`this.camera.position` internally (the runbook's `update(dt,pos)` was wrong). `visibilitychange →
|
||
setPaused`. No fleet → asset-free placeholder(near)/impostor(mid) tiers; sim self-manages NEAR/MID/FAR
|
||
LOD off camera distance (not chunk-driven — chunks.js has no lifecycle hook).
|
||
- **⚠ gotcha (fixed):** `getClock().hour` is a **display string** (`'12:30'`), so `hour/24` → NaN →
|
||
0 peds. Use the numeric day fraction `(seg + frac)/6` instead.
|
||
- **Verified:** 140 pop → **96 active midday** (density curve; `~7` at night), peds walk footpaths at
|
||
~1.5 m/s, deterministic per seed (Lane D property; F passes the same graph+seed). **Budget:** 1 near
|
||
placeholder ≈ **11 draws** (11 meshes), bounded by `NEAR_MAX=24`; but a hamlet never fills it —
|
||
worst across all 6 main/high edges was **near 3, ~191 composer draws** (budget 300). Coexists with
|
||
interiors+keepers (round-trip regression clean); 0 console errors.
|
||
- **Note for §3.4:** placeholder near-tier is the draw cost; the **rig-fleet upgrade** (shared meshes +
|
||
impostor promotion) is what lets `?pop=` rise well past 140. Until then 140 is the safe lively default.
|
||
|
||
### 3.4 Manifest + `?noassets` + rig-fleet upgrade ✅ DONE (R3, verified live)
|
||
Wired in the shell (all gated by `NOASSETS = ?noassets=1`):
|
||
- **Rig fleet:** `loadPedFleet('models/peds/')` (19 char GLBs + walk/idle clips exist locally) → passed
|
||
to `new CitizenSim({…,fleet})` and (via `createInteriorMode({…,fleet})`) to `KeeperManager`. Peds +
|
||
keeper **upgrade placeholder→shared GLB rigs** once `fleet.whenReady` resolves (verified:
|
||
`citizens.mode==='rig'`, `keeperKind==='rig'`, detailed characters on the street + behind counters).
|
||
- **Interior GLB:** `preloadManifest()` at boot + `buildInterior(shop,THREE,{useGLB:!NOASSETS})` →
|
||
Lane C `glb.js` swaps fitting primitives for **depot GLBs** (`depot:<file>` → 3GOD, reachable).
|
||
Verified: bookshop fittings load real GLBs (+8 meshes, dense shelves). *Not all kinds map* — `counter`
|
||
is intentionally primitive; `record_crate` is a known-failing GLB → fail-soft to primitive.
|
||
- **`?noassets=1`:** `fleet=null`, manifest not preloaded, `useGLB=false`. Verified **zero network**
|
||
(Performance API: 0 GLB, 0 depot, 0 manifest, 0 ped-model requests); 100% primitive, still legible.
|
||
- **Leak-free:** rig keeper + interior GLBs dispose clean — the persistent geometry is the intentional
|
||
**shared `loadGLB` cache** (cold entry +N geo, re-entries flat: proven cycle2/3 = 189→189). soak.py
|
||
pre-warms this cache so the gate is honest.
|
||
- **⚠ finding (D/E):** GLB **character models are triangle-heavy** (~45k each near rig) → busiest view
|
||
tris ~279k, soak peak ~1.0M, over the **200k** soft budget. Draws are fine (~200/300). **Decimate the
|
||
ped GLBs** (and heavy fittings) to hit the tri budget; **F holds `?pop=140`** until then (raising pop
|
||
adds near rigs = more tris). See Cross-lane requests §9.
|
||
|
||
### 3.5 Hours / closed shops ✅ DONE (R3, verified live)
|
||
- `currentHour()` parses `lighting.getClock().hour` (`'HH:MM'`→float); `isOpen(shop)` = `open ≤ H < close`.
|
||
Both exposed on `window.PROCITY`. **`enterShop` gates on it**: closed → `🔒 CLOSED · opens HH:00`, no build.
|
||
- Lane A ships exactly one **`openLate:true`** landmark/town — always the **video rental** (Video Regal
|
||
`[11,23]` etc.). Verified: **midday** toy shop `[10,16]` enters; **night (22:00)** toy shop blocked,
|
||
**only the video shop opens** (the night destination).
|
||
- **Cross-lane (B):** the facade *visual* closed-state (dark windows + CLOSED plate) + a "CLOSED" door
|
||
tooltip are Lane B's rendering — `window.PROCITY.isOpen(shop)` is the predicate. See §9.
|
||
|
||
### 3.5 Hours / closed shops (A data → B facade + C gate) ⏳
|
||
- `isOpen(shop, seg)` from `shop.hours:[open,close]` vs the current day segment (map 6 segments →
|
||
hour). Closed ⇒ B renders dark windows + CLOSED plate (facade state); enterShop → locked toast.
|
||
- Exactly one late-night shop per town (Lane A seeds a shop whose `hours` run late). **F asserts it
|
||
exists**: `plan.shops.some(s => s.hours[1] >= 22)` (tune threshold to A's convention).
|
||
- **Gate:** at night, ≥1 open shop; closed shops visibly closed + un-enterable.
|
||
|
||
---
|
||
|
||
## 4. QA gates — ALL GREEN ✅ (v1.0 criterion met, R4). `tools/qa.sh --strict` is the static umbrella.
|
||
|
||
Measured against the fully-committed R4 tree (seed 20261990, "Boolarra Heads"), verified live +
|
||
by a 6-auditor adversarial v1-readiness workflow (2026-07-14) → **GO**.
|
||
|
||
| # | gate | criterion | result (R4) |
|
||
|---|---|---|---|
|
||
| 1 | **Determinism** | seed → identical plan (deep-equal + golden fingerprint) + selfcheck | ✅ scaffold_check green; selfcheck **1301/1301**, fingerprint **0x3fa36874** (intended A2 shift, not drift); two node processes → identical `sha256(plan)` per seed; chunkIndex covers every lot |
|
||
| 2 | **Soak** | walk ≥30 chunks, ≥15 shops; geo/tex baseline after each interior; heap stable; **0** console errors | ✅ **37 chunks, 20 shops, heap 38→38 MB, 0 errors, leak-free** |
|
||
| 3 | **Budget** | busiest intersection: ≤300 draws, ≤200k tris (settled clean measure) | ✅ **draws 138 · tris ~31k** (baseline 372k; peds decimated 924k→46k ≤3k each, D2 sign-off) |
|
||
| 4 | **Asset-free** | `?noassets=1` full playthrough, zero crashes, zero network | ✅ **heap 13→13 MB, 0 errors, 0 asset requests** |
|
||
| 5 | **Selfchecks** | `selfcheck.js` + `validate_manifest.py --depot` green via qa.sh | ✅ **qa.sh --strict 4/4**; manifest `--depot`: **23 GLBs live, 0 err** |
|
||
| 6 | **Shots** | `docs/shots/v1_tour/` 10 beauty shots + contact sheet | ✅ **10 full-frame 1280×720, no letterbox**, 3 new poses resolve; incl. §3.5 **night closed-facade** (CLOSED plates + lit openLate video) + real **record/opshop interiors** (keeper + hero props) |
|
||
|
||
> Note: soak peak-draw/tri *warnings* are transient teleport/impostor-atlas-bake sampling spikes;
|
||
> gate 3's settled busiest-intersection measure is the authoritative budget check (draws 138 / tris 31k).
|
||
|
||
### DBG hook — LANDED (Lane B `web/js/world/dbg.js`, `?dbg=1`)
|
||
`window.DBG` exposes `ready / shot(name) / teleport(x,z,ry) / setSegment(seg) / enterShop(id) /
|
||
exitShop() / info()` and drives `tools/shots.py` + `tools/soak.py` (mirrors 90sDJsim's `window.DBG`).
|
||
Bookmarks: street_noon, arcade, market_square, milkbar_dusk, night_neon, crossroads_busy,
|
||
residential_collar, warehouse_fringe. **Interior tour shots** (record/opshop) are captured by
|
||
`enterShop(id)` directly (not via `shot()`, which forces street mode) — see `tools/shots.py`.
|
||
|
||
---
|
||
|
||
## 5. Order of operations once lanes land
|
||
|
||
1. ✅ `node tools/qa/scaffold_check.mjs` — A's plan determinism + registry confirmed present.
|
||
2. ✅ Wire §3.1 (plan swap) → generated seed boots on the shell (barrel + skins fix, verified live).
|
||
3. ✅ Wire §3.2 (interiors) → door opens, enter/walk/exit + dispose verified across all 9 types.
|
||
4. ✅ Wire §3.3 (citizens + keepers) → keeper greets at the counter; peds walk the graph, budget-safe.
|
||
5. ✅ Wire §3.4 (manifest + `?noassets` + rig-fleet) and §3.5 (hours) — all verified live (R3).
|
||
6. ◑ Gates: 2 & 4 **PASS** (soak/asset-free), 5 **GREEN** (`--depot`), 3 tris-blocked on D/E decimation,
|
||
6 shots need Lane B bookmark poses. `docs/shots/v1_tour/` captured.
|
||
7. ⏳ `tools/qa.sh --strict` green + gate 3 tris ⇒ tag v1 (pending D/E ped-GLB decimation).
|
||
|
||
## 7. Live smoke-test findings (2026-07-14, real generated town, seed 20261990)
|
||
|
||
Booted the wired game in a browser and drove the camera via `window.PROCITY`. State:
|
||
`Boolarra Heads · 523 shops · 711 lots · 31 chunks live · 31 door meshes · 24.7k tris`.
|
||
|
||
- ✅ **Seed → real town** works after the barrel fix (§3.1). Contiguous strip, awnings, verandah
|
||
posts, doors, windows, gum trees, sky dome, 6-segment day cycle — all render.
|
||
- ✅ **Facade skins seam (FIXED).** `skins.js facadeMat` built `assets/gen/facade-<name>.jpg` but
|
||
Lane A's `facadeSkin` is the **full filename** (`facade-fibro-blue.jpg`) → every facade requested
|
||
`facade-facade-…jpg.jpg` (404 → flat colour). Lane F normalized the name at the `facadeMat`
|
||
chokepoint (strip a leading `facade-` + image extension; idempotent for bare keys). Verified:
|
||
facade JPEGs now return **200 OK**, town is textured. *Root cause is a cross-lane naming contract
|
||
ambiguity — registry stores filenames, skins.js expected bare keys. Worth a CITY_SPEC line so
|
||
ground/awning/interior skins don't hit the same trap.*
|
||
- ⚠ **Budget:** ~**334 draw calls** at spawn (radius 3), over the CITY_SPEC ≤300. Tris fine (~25k).
|
||
Options for Lane B: drop default radius to 2, or merge more per chunk. Gate 3 will fail until shaved.
|
||
- ⏳ **Interiors not wired** (§3.2), **citizens not wired** (§3.3), **manifest/`?noassets` not wired**
|
||
(§3.4) — the remaining integration passes.
|
||
|
||
## 8. Open questions for the lanes (surface, don't guess)
|
||
- **B:** will you expose the `window.DBG` hook (§4) and the `ChunkManager` lifecycle callbacks
|
||
(§3.3)? If not, F wires citizens via a MutationObserver-style scan — slower, please prefer callbacks.
|
||
- **A:** which field marks the "open late" shop, and what's the `hours` encoding (24h ints? segment idx)?
|
||
- **C:** does `places` tag the counter slot with a stable `kind:'counter'` so keepers find it?
|
||
- **E:** is `?noassets` honored inside your manifest-consuming code, or does F gate it at the shell?
|
||
- **D:** roster spawn/despawn API name + does it accept a chunk key directly?
|
||
|
||
---
|
||
|
||
## 9. Cross-lane requests (Lane F → others, R3) — the integration surfaced these; not silent-editing your files
|
||
|
||
**→ Lane D / Lane E — decimate the ped GLBs (blocks gate 3 tris).** The rig fleet (`web/models/peds/*.glb`)
|
||
is wired and works, but the character models are triangle-heavy: **~45k tris each near rig**, so the
|
||
busiest-intersection view hits **~279k tris** (soak peak ~1.0M) vs the **200k** CITY_SPEC budget. Draws are
|
||
fine (~200/300 — the shared-mesh sharing worked). Please **decimate/LOD the 19 ped GLBs** (target ≤~10–15k
|
||
tris each) so gate 3 passes; then F can raise `?pop=` past 140. Same applies to any heavy interior fitting GLB.
|
||
|
||
**→ Lane B — three dbg.js / shell items:**
|
||
1. **shots.py bookmark poses (gate 6).** `dbg.js BOOKMARKS` defines `street_noon/arcade/market_square/
|
||
milkbar_dusk/night_neon`, but F's v1_tour also uses `warehouse_fringe / residential_collar /
|
||
crossroads_busy` — these fall back to `street_noon` (identical frames). Please add those 3 poses.
|
||
2. **shots letterbox.** In headless (1280×720), `DBG.shot()`→`composer.render()` renders into a square
|
||
sub-region even though renderer/canvas/camera are correctly sized — the EffectComposer render targets
|
||
don't rebuild on the initial resize. A real viewport toggle in shots.py didn't fix it; likely the
|
||
composer needs an explicit `setSize`/reset on first `shot()`. (In-game, real window sizes render fine.)
|
||
3. **§3.5 closed-facade visual.** F gates *enterability* by hours (`window.PROCITY.isOpen(shop)`); the
|
||
*visual* closed state — dark windows + a CLOSED plate, and a "CLOSED" door tooltip in `hud.js` — is
|
||
your facade/HUD render. Predicate is ready on `window.PROCITY`.
|
||
|
||
**Note:** F-owned harness bugs found + fixed this round (so the gates actually run): `shots.py`/`soak.py`
|
||
polled `window.DBG` before its async install (race → false "absent"); read `DBG.plan` (unexposed → use
|
||
`window.PROCITY.plan`); `enterShop({shopId})` vs the bare-id contract; `shop.lot` treated as an object
|
||
(it's a lot **id**); soak now pins midday (else §3.5 closes shops mid-walk) + pre-warms the shared GLB
|
||
cache (else the one-time upload reads as a leak) + budget peaks demoted to warnings (DBG.info samples spike
|
||
during streaming; gate 3's clean single-pass is authoritative).
|
||
|
||
---
|
||
|
||
## 10. Round 5 — v1.1 close-out + v2 flag seams (Lane F tracking)
|
||
|
||
Round 5 (v1.1 polish + v2 foundations behind flags). **v2 prime law:** default boot stays
|
||
v1-identical; `GOLDEN.hash 0x3fa36874` must not move; `qa.sh --strict` green at every commit.
|
||
|
||
### v1.1 tag — ⏳ WATCH (blocked on C1 + E1)
|
||
Tag `v1.1` once **Lane C** re-maps `counter_till` + `record_crate` (C1) and **Lane E** bakes the
|
||
hero props ≤8k (E1). F then: verify record/milkbar/opshop interiors (GLB counter+till correct, bins
|
||
upgraded, baked props look right), re-capture ONLY the 2 interior tour shots if improved, qa green →
|
||
`git tag v1.1` (local). *Nothing to tag yet — HEAD `039c179`, no round-5 lane commits.*
|
||
|
||
### v2 flag seams — table (F wires `web/index.html` + `tools/` as lanes document them)
|
||
Every flag **default-off + independent**. Wire only from the owning lane's NOTES call-site spec.
|
||
|
||
| flag | owner | what | F wiring | state |
|
||
|---|---|---|---|---|
|
||
| `?plansrc=osm` | A | second plan source (OSM fixture) behind the CityPlan contract | select in shell plan bootstrap | ❌ **A did not run R5** — task carries to R6 |
|
||
| `?winmap=1` | B | parallax interior-mapping window shader | none needed (self-contained in `buildings.js`) | ✅ landed `2a923b6` |
|
||
| `?dig=1` | C | dig.js crate-riffle on procedural stock | wired by F (`26d68cd`): `DIG_ON` → `interiorMode`, Esc/pointer-lock guards | ✅ landed `7131f53` + F2 |
|
||
| `?roster=stream` | D | chunk-streamed citizen roster | none needed (self-contained in `sim.js`) | ✅ landed `93a3168` |
|
||
|
||
*(table updated by Fable in the R5 review — F3 harness extensions below did NOT land in R5; they
|
||
are the top Lane F item for R6.)*
|
||
|
||
### v2 QA harness extensions (F3) — build as flags land
|
||
- **Determinism gate:** pin `plan-hash` + `first-interior-hash` per `(seed, plansrc)`. Synthetic
|
||
stays `0x3fa36874`; add the osm-fixture hash when A lands.
|
||
- **Flags-off regression check:** boot no-flags → assert plan hash + a settled draws/tris snapshot
|
||
within tolerance of the v1 baseline (catches a v2 flag leaking into the default path). *This is the
|
||
enforcement arm of the v2 prime law — build early, before flags land.*
|
||
- **v2 smoke:** boot each flag on, walk 2 chunks, enter 1 interior (+1 dig if C landed), 0 console
|
||
errors. Wire into `qa.sh` as a **non-strict (warn) gate** this round; strict next round.
|
||
|
||
### Marshal
|
||
Shared-tree arbiter this round: F owns `web/index.html` + `tools/`; if two lanes collide on a file,
|
||
F arbitrates. Cross-lane asks → the owning lane's NOTES, not silent edits.
|
||
|
||
---
|
||
|
||
## 12. Round 12 — v3.0-alpha gig layer (`?gigs=1`). F wires it last; findings below.
|
||
|
||
**What F built.** `web/js/world/gig_state.js` (new, F-owned) — the Friday-night state machine; the
|
||
`?gigs=1` seam + `custom_bands.json` bootstrap in `index.html`; the cover charge at the door; the band +
|
||
crowd + gig-bed wiring in `interior_mode.js`; `smoke_gigs` in `tools/flags_check.py` (6 gates) + the
|
||
v3 arm of the flags-off regression.
|
||
|
||
### The state machine (`gig_state.js`) — the two things that are load-bearing
|
||
1. **It listens, it doesn't poll.** `lighting.js` dispatches `procity:segment` on every segment change;
|
||
the latch rides that. A poll-only machine sees only the segments it is sampled on — driving
|
||
NIGHT→DAWN→MORNING→NIGHT with nothing reading in between (player inside the pub, audio muted, so
|
||
neither F's street loop nor B's audio rAF is reading) **skipped the night roll entirely**: the night
|
||
never advanced and the cover was never due again. Caught live, fixed, re-verified.
|
||
2. **`state` is a getter, not a field.** B's audio engine self-ticks on its own rAF and reads
|
||
`PROCITY.gigs.state` while the player is *inside* a shop — i.e. when F's street loop is not running.
|
||
A pushed field goes stale there and the spill lies about the gig.
|
||
|
||
3. **The night rolls on WHERE the clock lands, not on watching `done` go by.** First cut incremented the
|
||
night when leaving `'done'` forwards — which meant a clock *jump* straight from NIGHT to MORNING (never
|
||
touching DAWN) never rolled the night at all, and **a broke punter walked in on last night's paid
|
||
stamp**. `smoke_gigs` caught it; stepping with `[`/`]` always passes through DAWN, so only a jump
|
||
(DBG/shots/soak all jump) exposed it. Now: the latch clears wherever it clears, and landing in the
|
||
**daytime** (MORNING…ARVO) is what rolls the night; landing on the **doors** means we rewound out of
|
||
the gig — same night, stays paid.
|
||
|
||
`done` is real, not decorative: the segment→hour map tops out at NIGHT (22:00) so the gig owns the whole
|
||
night segment, and "after the gig" can only be DAWN (closing time — band packing up, crowd draining per
|
||
A's closing-time ruling). Latch clears on any non-DAWN segment so rewinding the clock (`[`) doesn't leave
|
||
`done` stuck on a midday town — it did exactly that on the first verify.
|
||
|
||
### ⚠ Finding 1 (Lane C ↔ Lane E) — the gig bed key never met. **F bridged it; needs a contract line.**
|
||
`LANE_C_PUB.md` asks E to name the live bed **`gig-pubrock`** (C emits `room.audio.gigKey = 'gig-' +
|
||
genreKey`). E shipped it as **`pubrock-live`** (`gig:true`, `types:['pub']`) — which is also the key B's
|
||
street spill hardcodes. Neither is wrong; they just never met. Unresolved, `AUDIO.music['gig-pubrock']`
|
||
is `undefined` and **the pub plays to a silent room** — and because the audio law fails soft, nothing
|
||
errors: you just get a band miming. F resolves it at the glue (`interior_mode.js GIG_BED` + a generic
|
||
`gig-<genre>` → `<genre>-live` fallback). **This is the R1 facade-skin filename trap again** (registry
|
||
stored filenames, `skins.js` expected bare keys). → **C/E/Fable: pick one and put it in CITY_SPEC as a
|
||
named contract**, so genre #2 doesn't rediscover this. F's smoke now asserts `gigKey` resolves.
|
||
|
||
### ⚠ Finding 2 (Lane D + Lane E) — E's instrument GLBs landed; nothing wires them.
|
||
E shipped 5 instrument GLBs this round (`electric_guitar`, `bass_guitar`, `drum_kit`, `guitar_amp`,
|
||
`mic_stand`, all in `manifest.fittings` with footprints + thumbs). D's band holds **primitives** and
|
||
published `spawn(..., { instrumentFor })` as the injection seam — D-progress says E's GLBs "drop in via
|
||
`opts.instrumentFor(role)` **when they land**". They have landed. Nobody owns the join. Not blocking
|
||
(asset law: a primitive band is still a gig, and it reads fine in the shot), but it is a dangling R12
|
||
deliverable. F did **not** do it silently: picking manifest ids and orienting/scaling instruments inside
|
||
D's rig is D's call, and the R12 no-drummer question below wants answering in the same pass. → **R13.**
|
||
|
||
### ⚠ Finding 3 (ROUND12 spec ↔ Lane C) — the band has no drummer.
|
||
ROUND12 §Lane D.1 specifies "guitarist/bassist strum-sway, **drummer seated bob**", and E was asked for
|
||
"drum kit (one merged kit)". C's `stage.bandPoses` are **guitar / vocal / bass** — a front-line trio; the
|
||
drum riser is set-dressing with no pose. D built to C's poses and flagged it. So the shipped alpha band is
|
||
3-across-the-front with a mic stand where the spec expected a kit. Cosmetic, non-blocking, deterministic —
|
||
but it is a **spec/implementation divergence that no gate can catch**. → **Fable/John: rule for R13** (add
|
||
a 4th `drums` pose on the riser, or amend the spec to a front-line trio).
|
||
|
||
### Gate lesson — "no giants" must measure STATURE, not world crown.
|
||
The R10 gate asserts head-bone world Y ∈ [1.4, 2.0]. The band stands on C's **0.32 m stage deck**, so a
|
||
perfectly human 1.73 m guitarist has a world crown of **2.05 m** and a naive world-Y check **fails a
|
||
correct band** (it did, first run). `smoke_gigs` measures `crown − base.y` for the band/crowd and keeps
|
||
the world-crown assert only for "never clips the ceiling", which is the real R10 failure mode.
|
||
`tools/qa/interior_scale_check.py` is unchanged (keepers/browsers stand on the floor, base.y = 0) — but
|
||
**if anyone ever puts a keeper on a platform, that gate inherits this bug.**
|
||
|
||
### → Lane B (small, R13): no venue bookmark for shots
|
||
`dbg.js BOOKMARKS` has no venue pose, and `DBG.shot(name)` forces street mode **and re-poses/re-segments**
|
||
— so there is no way to drive a scripted "lit pub frontage at night, gig on" street shot (the alpha's other
|
||
money shot; your marquee + posters + spill are the whole street story of this round). F verified the
|
||
frontage lights correctly (`emissiveIntensity` 1.7 at `on`, 71 draws / 29.6k tris at the venue) but shot it
|
||
by hand. **Ask:** add a `venue_night` bookmark (pose at the pub frontage, `seg:5`) — then F's
|
||
`tools/qa/gig_shot.py` can capture the street side too. This is the same "3 missing bookmark poses" ask
|
||
from R3 §9, so it may be worth doing the set in one pass.
|
||
|
||
### Harness note — where the browser gates actually run
|
||
This session's box had **no Chromium and no Chrome**, so every Playwright gate (flags harness + scale
|
||
gate) is un-runnable locally until `tools/.venv/bin/python -m playwright install chromium`. The repo at
|
||
`/Volumes/m3ultra/Documents/procity` is a **mount of m3ultra's `~/Documents/procity`** — same tree, so
|
||
`ssh m3ultra@100.89.131.57` runs the real gates on the real files. **Gotcha:** node is not on the
|
||
non-interactive ssh PATH — `env PATH=/opt/homebrew/bin:$PATH tools/qa.sh --strict`, or every node gate
|
||
reports a bogus FAIL (`node: command not found`).
|
||
|
||
### R12 numbers (measured, seed 20261990 → "Boolarra Heads", venue = The Thornbury Hotel)
|
||
| thing | number |
|
||
|---|---|
|
||
| gig-night interior, rig fleet | **42–50 draws** · ~40k tris (budget **≤350**) |
|
||
| gig-night interior, `?noassets=1` placeholders (**worst**) | **171 draws** (budget ≤350) |
|
||
| gig-night street at the venue | 69 draws · 29.6k tris (budget 300 / 200k) |
|
||
| band / crowd | **3** on the deck · **8** at 8 watch points · **3 dancing / 5 standing** |
|
||
| figure scale | band stature 1.68–1.77 m (crown ≤2.09 m under a 4.0 m ceiling); crowd 1.60–1.77 m |
|
||
| leak, 4 gig enter/exit cycles | geo 66→66 · tex 63→63 (**zero**) |
|
||
| tonight's covers (7 nights) | `10, 0, 0, 3, 2, 0, 9` — 3 free of 7 (~half, as seeded) |
|
||
| custom band names surfacing | **7 of John's 10** fill all 7 nights; `?noassets=1` → pure generator |
|
||
|
||
---
|
||
|
||
## 13. Round 13 — v3.0-beta THE DISTRICT (`?gigs=1`). F wires it last; findings below.
|
||
|
||
**What F built (all 7 tasks from ROUND13 §Lane F).** All committed Lane-F-only; verified green on m3ultra
|
||
(`qa.sh --strict`, the focused district gate, the scale gate). A single WARN — the queue — is a documented
|
||
handoff to D, not a hole (below).
|
||
|
||
1. **Per-venue state machine** (`gig_state.js`, rewritten). One latch PER VENUE keyed by `venueShopId`; a
|
||
town of 2–4 venues runs 2–4 independent latches off the SAME `procity:segment` spine. All three hard-won
|
||
R12 laws are preserved verbatim inside `createVenueLatch` (listens-not-polls · `state` is a getter ·
|
||
night rolls on WHERE the clock lands). New runtime surface on `window.PROCITY.gigs`: **`.byVenue`** →
|
||
`{ [id]: state }` (B's `venue.js`/`audio.js` read it), per-venue **`stateOf/onOf/openOf/gigOf/coverOf/
|
||
bandNameOf/paidOf(id)` + `markPaidOf(id)` + `.venueShopIds`**. The R12 single-venue alias
|
||
(`.state/.on/.venueShopId/.cover/.paid/…` → the **primary** venue = `gigs[0]`'s) is kept **one round** so
|
||
B's mid-round single-venue callers don't break — it retires next round. Contract pinned in CITY_SPEC §v3.
|
||
*Tonight* per venue = its night-0 gig (matches A's posters + B's frontage, all night-0-keyed); a
|
||
dark-tonight venue latches 'quiet' all session, which is true to the seeded week.
|
||
|
||
2. **Walla + applause** (`interior_mode.js`). While a venue's gig is `on`, the crowd bed
|
||
`ambience['crowd-walla']` rides the interior TONE layer under the live band bed (you hear the room, not
|
||
the fridge hum) — it reuses B's existing `playInterior({musicKey,toneKey})`, no new audio API. On the
|
||
`on → done` edge while the player is inside (crowd's last-song → the clock rolls NIGHT→DAWN), a one-shot
|
||
`sfx['applause']` sting fires ONCE (`applauded` latch, re-armed per enter). Fail-soft throughout (no-op
|
||
muted / `?noassets` / pre-gesture). Verified live: `iMusic=gig-pubrock`, `iTone=crowd-walla`, edge fires,
|
||
0 errors.
|
||
|
||
3. **Cover charge PER VENUE** (`index.html`). The door seam now keys off the shop being ENTERED
|
||
(`openOf/coverOf/paidOf/markPaidOf(shopId)`), so a night at two venues is two covers; each latch holds
|
||
its own runtime paid-stamp. Verified: paid ($10 debited once, re-entry free), broke-knockback, free night.
|
||
|
||
4. **Debt #1 CLOSED — and the R12 bridge was a LIVE BUG.** E renamed the pub bed `pubrock-live → gig-pubrock`
|
||
this round (the canonical `gig-<genreKey>`), so F's R12 `GIG_BED`/`gigBedKey` bridge — which rewrote
|
||
`gig-pubrock → pubrock-live` — was now pointing at a manifest key that **no longer exists**: the band
|
||
would have mimed in a silent room. DELETED. The room's `audio.gigKey` (already the manifest key) feeds the
|
||
engine directly, zero mapping. Verified across two genres live: `gig-pubrock` (pub), `gig-covers` (rsl).
|
||
|
||
5. **Gate hardening — stature into the shared gate** (`interior_scale_check.py`, debt #6). The human-size
|
||
assert now measures **stature = crown − foot** (feet→crown span, platform-independent), with the
|
||
world-crown "never clips the ceiling" assert kept as its OWN line. The day a keeper stands on a platform,
|
||
the gate won't fail a correct figure. Floor figures foot≈0 ⇒ stature≈crown ⇒ backward-compatible.
|
||
Verified: 24 figs across 6 shops, statures 1.60–1.86 m.
|
||
|
||
6. **District smokes** (`flags_check.py smoke_gigs`, extended). band 3→**4** · a new **§2b DISTRICT** loop
|
||
that enters EVERY playing venue and asserts its `gigKey` resolves to a real manifest bar (debt #1 across 3
|
||
genres), per-venue crowd ≤ its own watchPoints, 4-piece, no-giants-by-stature · **§2c street budget** at
|
||
the busiest venue block (settled-floor sample) · queue probe (WARN-pending, below) · plus the R12
|
||
determinism / cover / noassets / flags-off arms. All green.
|
||
|
||
### ⚠ Cross-lane ask (Lane F → Lane D) — the QUEUE seam. F's smoke is READY; it WARN-skips until D exposes it.
|
||
D owns the outdoor queue (`web/js/citizens/queue.js` `VenueQueue`, charter item, landing this round). B has
|
||
already published the zone: **`window.PROCITY.venuePresentation.queueZoneFor(id) → {x,z,ry,len}`** and
|
||
`.queueZones`. F drives the surge per venue via `citizens.setGig(id, open)` (done). What F's smoke needs to
|
||
verify "queue ≤ cap and drains by done" is a read seam on the sim — **proposed contract:**
|
||
`window.PROCITY.citizens.queueInfo(venueShopId) → { count, cap }` (live count while doors/on; `0` once the
|
||
gig is `done`). Wire the queue's spawn to `queueZoneFor(id)` at `doors` and expose `queueInfo`, and F's smoke
|
||
flips WARN→assert with no further change. (If D picks a different accessor name, tell F and F adjusts the one
|
||
line in `smoke_gigs §2b`.)
|
||
|
||
### R13 numbers (measured, seed 20261990 → "Boolarra Heads", 3 venues)
|
||
| thing | number |
|
||
|---|---|
|
||
| district | **3 venues** — 2 playing tonight (pub #116 The Exchange Hotel · rsl #404), 1 dark (reads true) |
|
||
| live beds resolve | pub → `gig-pubrock` · rsl → `gig-covers` (both real manifest bars, debt #1 across genres) |
|
||
| band / crowd | **4-piece** each venue · pub crowd **8/8** watch points · **rsl crowd 12/12** (the cap stress case) |
|
||
| gig-night interior (rig) | **62 draws** · 34k tris (budget ≤350) · `?noassets` still a full 4-piece gig |
|
||
| street @ venue block, night | **99 draws** · 28k tris (budget 300 / 200k) — lit frontage + posters + queue zone in |
|
||
| figure scale | band+crowd stature **1.61–1.81 m**, tallest crown 2.12 m under the 4 m ceiling |
|
||
| cover, per venue | $10 debited once · re-entry free · broke knockback · free night (seed 1234) — all hold |
|
||
| flags-off prime law | fingerprint **0x3fa36874** unchanged · gig layer wholly absent · 0 errors |
|
||
|
||
**Status:** F's wiring is done, committed, and green. **Waiting on Lane D** for (a) the queue seam
|
||
(`queueInfo`) so the queue smoke goes live, and (b) the band polish (E's instrument GLBs in hand, drummer
|
||
facing/RY_FLIP) — the band already lands at 4, human-sized, 0 errors, so this is polish not a blocker. Once D
|
||
commits: re-run the gates, flip the queue assert, capture the district money shots, tag **v3.0-beta**.
|
||
|
||
*(R13 epilogue: D landed mid-session; F wired the queue (D shipped `VenueQueue`, left the street-side
|
||
lifecycle to F), the queue smoke went live, both money shots shipped, and **`v3.0-beta`** was tagged +
|
||
pushed. The `queueInfo` proposal above was superseded by D's `VenueQueue.count()` + F's shell `queueCountOf`.)*
|
||
|
||
---
|
||
|
||
## 14. Round 14 — F never ran; the brief carried in full to R15.
|
||
|
||
R14 was the "v3.0 release round." A–E landed excellent hardening (D's identity-continuity seam, B's
|
||
alias-reader retirement + `queue_night` hero + 19-bookmark tour audit, A's 400-seed invariant sweep + §v3
|
||
freeze, C's adversarial venue audit, E's provenance freeze) **but Lane F never ran** — so v3.0 was not
|
||
tagged and F's whole release brief (continuity wiring, alias deletion, week soak, the tour, the tag) was
|
||
untouched. Fable carried it verbatim into R15. Nothing F-authored landed in R14.
|
||
|
||
## 15. Round 15 — THE v3.0 RELEASE CLOSE (`?gigs=1`). F ran last; the six-item brief, all green.
|
||
|
||
A first (poster flip + golden re-pin) → B/C verify → D one-line amp move → **F closes**. All deps were in
|
||
before F ran (verified against origin). The six F items (ROUND15 §Lane F), each committed atomically by
|
||
pathspec:
|
||
|
||
1. **Continuity wiring + smoke** (ledger #3) — D published the seam in R14; F wired its three touches:
|
||
`crew.spawn(…, { roster: rosterOf(shop.id) })` in `interior_mode.js` (front crowd slots become tonight's
|
||
roster), `rosterOf:(id)=>citizens.tonightRoster(id)` + `onAdmit:(e)=>citizens.recordVenueEntry(id,e)` on
|
||
the queue in the shell (the clear is automatic on `setGig(id,false)`). New `smoke_continuity` asserts the
|
||
seam on **identities** (crowd ⊇ roster ∩ cap · every `member.pedIndex ∈ tonightRoster` · `crewInfo.fromRoster`),
|
||
the empty-roster path injects nobody (shell side of D's byte-identical law), and **settled tris**
|
||
(ledger #5). Commit `687d23f`.
|
||
2. **Alias deletion** (ledger #2) — the R12 single-venue scalar alias is **gone**; every read is keyed by
|
||
`venueShopId`. Deleted the alias block + `primaryId`/`P` in `gig_state.js`; migrated `flags_check.py` +
|
||
`gig_shot.py` (`.venueShopId→venueShopIds[0]` · `.state→stateOf(id)` · `.cover→coverOf(id)` ·
|
||
`.bandName→bandNameOf(id)`); deleted the two CITY_SPEC §v3 alias sentences + added the omitted
|
||
`nightOf(id)`. B's R14 sweep left the cross-lane readers clean; only F's tools remained. One atomic
|
||
commit `146b5f3`.
|
||
3. **Full-week soak** (`tools/qa/week_soak.py`, opt-in `qa.sh --soak`, non-strict) — 7 nights × every playing
|
||
venue in one context: latch state per `plan.gigs` (dark venues quiet), cover **stamped per venue per
|
||
night** (debited once, re-entry free, due again next night), roster **clears per night** (no
|
||
accumulation), and geo/tex return to a **warmed baseline** across ≥20 cycles — a leak is unbounded
|
||
per-cycle growth; warm-up cache residue **plateaus** (the memTrail proves it). Commit `edc0c47`.
|
||
4. **The money-shot tour** (`tools/qa/tour_shots.py`, ledger #4/#5) — one deterministic run → `docs/shots/
|
||
release_v3/` + `contact.html`: `venue_night`, `district_posters`, **`queue_night` (John's hero — A's
|
||
flipped frontage poster now reads from the street)**, `gig_interior` (4-piece + real instruments + a
|
||
roster crowd), `rain_verandah`, `tram_stop`. **Settled discipline**: measured after the async instrument
|
||
GLBs / posters / chunks land (shoot-twice), clean per-group loads; queue driven **live** (explicit admits,
|
||
not throttled rAF auto-drain). Commit `6a35b29`.
|
||
5. **Docs assembly** — README v3 section (what shipped · flags table · the tour), this §14/§15 runbook,
|
||
F-progress current, the five freezes confirmed committed (A `a3911a4`/`35da83c`, B `eee60e0`/`a0b5784`,
|
||
C `62e38c5`/`2446ae3`, D `9a65f51`/`66aec0f`, E `55f6698`).
|
||
6. **Acceptance + tag** — `qa.sh --strict` 6/6 + `smoke_continuity` + `week_soak.py` green → tag **`v3.0`**.
|
||
|
||
### Numbers (measured, seed 20261990 → "Boolarra Heads", 3 venues)
|
||
| thing | number |
|
||
|---|---|
|
||
| district | 3 venues — 2 playing (The Exchange Hotel `gig-pubrock` + an RSL `gig-covers`), 1 dark |
|
||
| gig interior, settled | 4-piece · crowd 8 (2 from the roster) · **56 draws** · **35k→182k tris** (pre-load→settled; gate is draws ≤350) |
|
||
| street @ venue block, night | `queue_night` 121 draws / 62k tris · `district_posters` 186 / 134k (busiest) — all ≤300/200k |
|
||
| week soak | 28 enter/exit cycles · $60 covers · geo/tex flat across the back half (115/86→115/84) · 0 errors |
|
||
| continuity seam | 2 queue admits carried into the crowd · empty roster 0-injected · `member.pedIndex ∈ tonightRoster` |
|
||
| **settled-tris honesty** (ledger #5) | C was right: the interior settles to **~182k** tris after the 5 instrument GLBs load, not the ~34k measured pre-load |
|
||
|
||
### The gotcha ledger closed this round
|
||
- **Alias retirement is a three-party move**: B swept cross-lane readers (R14), A left the spec sentences
|
||
for F (avoiding a doc-ahead-of-code race), F deleted the code + migrated its own tools + the spec in ONE
|
||
commit. No half-renamed state ever existed.
|
||
- **Settled measurement** (ledger #5): a headless smoke that reads `renderer.info` right after `enterShop`
|
||
sees the room BEFORE its async instrument GLBs decode — ~34k, not the ~182k settled truth. The tour + the
|
||
continuity smoke both wait/shoot-twice now; the interior gate stays on **draws** (which is load-order
|
||
stable), so there was never a budget violation, only a dishonest number.
|
||
- **Drive live-spawned things explicitly**: automation throttles rAF, so the queue's seeded auto-drain may
|
||
not fire on schedule — `smoke_continuity`/`week_soak`/`tour_shots` all call `admitOne()` / step the loop
|
||
explicitly rather than trust the timer.
|
||
|
||
---
|
||
|
||
## 16. Round 16 — v3.1 THE FLIP (`?classic=1` is the new covenant). F bookends; opening below, close after.
|
||
|
||
The town shows its best by default. After three v3 stages the gig layer flips ON, and the mature v2 flags
|
||
(`weather`, `winmap`, `tram`) flip with it. F owns the flip; A–E landed their v3.1 polish against it (D's
|
||
seated drummer, B's town-wide pools + default re-baseline, C's backline unification, A's corner-poster +
|
||
no-spine fixture, E's `sit.glb`).
|
||
|
||
### The flip (ledger #1) — the covenant MOVED from "flags off" to `?classic=1`
|
||
- **Shell** (`index.html`): `const CLASSIC = has('classic') && get('classic')!=='0'`, `const flagOn = name
|
||
=> !CLASSIC && get(name)!=='0'`. `gigs`/`weather`/`tram` construct via `flagOn`; `loadPedFleet({sit:!CLASSIC})`
|
||
gives the drummer his clip while keeping classic zero-fetch; `window.PROCITY.flags` publishes the intent.
|
||
- **`buildings.js`**: `winmap` reads the SAME rule (default-on, classic-off) — the one cross-lane flip line,
|
||
because winmap's flag lives in B's file. Marked `[Lane F R16 THE FLIP]`.
|
||
- **Two goldens, two gates:** classic → `0x3fa36874` (byte-identical v2 forever, the frozen regression
|
||
target); default → the gig golden `0xb1d48ea1`. `flags_off_regression` became **`classic_regression`**
|
||
(boot `?classic=1`); a new **`default_boot_gate`** proves the no-flag boot is the full experience (all four
|
||
flags on, gig layer present, gig golden green, budget). Every gig smoke re-points to the DEFAULT boot.
|
||
|
||
### Seated-stature gate (ledger #3) — published in the brief, no mid-round handshake
|
||
A seated figure fails the standing no-giants floor by design. Contract (in the round brief so D built to it):
|
||
D tags seated figs `fig.userData.procitySeated = true`; F's `interior_scale_check.py` + `flags_check.py`
|
||
accept them at **stature ∈ [0.9, 1.5] m** (still < ceiling, feet planted); standing stays [1.4, 2.0]. Landed
|
||
in the opening commit. Verified live: the seated drummer passes at **1.32 m stature**.
|
||
|
||
### Numbers (measured, seed 20261990 → "Boolarra Heads")
|
||
| thing | number |
|
||
|---|---|
|
||
| `?classic=1` | byte-identical v2: fingerprint 0x3fa36874, gig layer absent, streamed roster on, 160 draws / 21k tris |
|
||
| default boot | all four flags on · gig golden 0xb1d48ea1 · 14 gigs, 15 posters · **112 draws @ the venue block** (≤300) |
|
||
| district (default) | 3 venues, **3 playing** (corner-poster re-pin shifted the schedule) · 3 genres live |
|
||
| seated drummer | **1.32 m** stature — passes the seated band [0.9,1.5]; the standing crew 1.68–1.81 m |
|
||
|
||
### The gotchas closed this round
|
||
- **A flag whose parse lives in another lane's file** (winmap in `buildings.js`) still flips as part of F's
|
||
structural change — one marked line, same `!classic && !=='0'` rule as the shell.
|
||
- **selfcheck prints the base fingerprint in its summary but asserts the GIG golden silently**, so the
|
||
default-boot gate gates on the ALL-GREEN run (which includes the gig-golden assert), not a stdout substring.
|
||
- **`P.winmap` isn't exposed** — the default gate verifies winmap *intent* via `window.PROCITY.flags`; the
|
||
actual shader is B's own `winmap` smoke.
|
||
- **`?classic` must zero the fetch delta**, so `sit.glb` is gated on `!CLASSIC` (not just on the fleet) — a
|
||
raw sit fetch under classic would break the covenant even though the drummer never spawns there.
|
||
|
||
---
|
||
|
||
## 17. Round 17 — v3.2 THE TAIL + THE SCOUT. F: the town matrix + the close (ledger #7).
|
||
|
||
The tail drained (D's drummer lean + bench-sit, B's wind sway, C's backline verify, E's `--verify` fix) and
|
||
the real-map scout ran (E's 5 real AU town caches + A's cache contract + hardened `plan_osm`). F wired the
|
||
last mile and gated the whole matrix.
|
||
|
||
### Real towns boot in the engine (the F wiring)
|
||
E's caches sat on disk (`web/assets/towns/*.json`) + registered in the node selfcheck, but **the shell never
|
||
loaded them** — `?plansrc=osm&town=bendigo_real` silently fell back to Melbourne. F closed the gap: the barrel
|
||
(`index.js`) re-exports `registerTownCache`/`validateTownCache`; the shell fetches `assets/towns/<TOWN>.json`
|
||
+ registers it before `generatePlanFor` when the town isn't a checked-in fixture. **Fail-soft** — 404 /
|
||
invalid / `?noassets` → plan_osm's fixture fallback; the scout data is opt-in, never on the default or
|
||
`?classic` path. Verified: `?plansrc=osm&town=bendigo_real` → **Bendigo, 9 shops, a 3-venue district, 0 errors**.
|
||
|
||
### The town matrix (`tools/qa/town_matrix.py`, opt-in `qa.sh --matrix`) — the v4 evidence
|
||
Boots every town on the DEFAULT boot and gates boot / determinism / budget / district / `?noassets`. **All
|
||
10 × 5 green:**
|
||
|
||
| town | shops | budget (draws/tris) | district |
|
||
|---|---|---|---|
|
||
| synthetic/20261990 · 1234 | 493 · 486 | 124d/57k · 147d/71k | 3v ✓ |
|
||
| osm/melbourne · katoomba · silverton | 95 · 19 · 12 | 216d · 70d · 70d | 3v ✓ |
|
||
| real/bendigo · castlemaine · fremantle · katoomba · newtown | 9 · 6 · 21 · 20 · 18 | 82 · 86 · 159 · 69 · 93 d | 3v ✓ |
|
||
|
||
The headline: **the existing CityPlan contract eats real Australian geography** — Castlemaine at the 6-shop
|
||
floor still lands a 3-venue district; every real town is deterministic, in-budget, and silent under
|
||
`?noassets`. No new game systems — exactly the bounded scout the round asked for. This table is the evidence
|
||
John charters **v4 = THE REAL MAP** on.
|
||
|
||
### Gotchas
|
||
- **A published cache + selfcheck ≠ a bootable town.** The node sweep proved the *plan* generates; the
|
||
*browser* boot needed the shell to fetch+register the cache. Classic integration seam — F owns it.
|
||
- **`?noassets` can't fetch a real cache**, so a real-town + noassets boot correctly falls back to a fixture
|
||
(still silent-and-fine). The matrix's noassets gate tests that fallback, not the real town — as it should.
|
||
|
||
---
|
||
|
||
## 18. Round 18 — v4.0-alpha REAL ROADS. **F correctly did NOT run — the gate would have failed by design.**
|
||
|
||
The alpha gave `katoomba_real` its real OSM street geometry (A's schema v2 `roads[]` + graph lift; E's road
|
||
fetch). F's charter deliverable was "as much the failure list as the town" — and F, running the gate, found
|
||
the alpha **not green**, so F held the tag and filed the list rather than shipping a red release. This was
|
||
the round working as designed (Fable's R18 verdict agreed: "F never ran — correctly").
|
||
|
||
**What F caught (all Lane A plan-gen, surfaced with measurements):**
|
||
- **`qa.sh` selfcheck RED** — `katoomba_real`'s real-roads golden **UNPINNED** (the A→E→A finalization
|
||
handshake never fired: E's roads landed *after* A's commits), and the 4 trailing towns' goldens stale
|
||
(pinned on marched output, now roads).
|
||
- **Poster-kerb-clearance RED on all 5 real towns** — the real edge explosion (232 "main" edges) put spine
|
||
posters **1.4–3.9 m into the road**.
|
||
- **Fragmentation** — 20 shops scattered over 799 real edges / 607 m ⇒ no walkable strip; the money-shot
|
||
attempt was a vast empty field (D's finding: 105 components, 42% dead street-metres). And B's separate
|
||
finding: the `BIG_CITY` heuristic misfired (20 shops < 120 ⇒ small-town streaming on a 799-edge graph ⇒
|
||
**280k tris**, breaching the 200k gate).
|
||
|
||
**What was already green (the isolation held):** real roads ride the **cache schema, not a flag**, so
|
||
classic + default + synthetic + fixtures were untouched (`0x3fa36874` frozen) — the alpha changed nothing
|
||
outside the flagged path. The pipeline worked; the geometry needed the hardening the alpha existed to reveal.
|
||
|
||
## 19. Round 19 — the v4.0-alpha CLOSE. F: B's budget one-liner (early) + the gate (last).
|
||
|
||
A resolved the findings this round (fragmentation ruling — main component + joins, cull shopless islands;
|
||
poster clamp; goldens pinned; junction-protected DP), D re-measured and verified, C re-soaked, B shipped the
|
||
town selector. F ran two things:
|
||
|
||
1. **B's `BIG_CITY` one-liner** (`index.html`, landed EARLY per the order so D/B re-measures saw it):
|
||
`BIG_CITY = shops > 120 || (streets.edges > 200)`. A real town can have few shops but a huge road graph;
|
||
the graph now trips big-city streaming. **Verified: real Katoomba 799 edges → 61 draws / ~12k tris (was
|
||
280k), 0 errors.** B proved it; F owns `index.html` so F landed it.
|
||
2. **The alpha gate + the roads dimension** — `town_matrix.py` grew a `roads` gate: a real cache (schema v2
|
||
with `roads[]`) must build the REAL street graph (`norm.mode === 'roads'`, read via the plan_osm
|
||
`opts.report` sink), while synthetic/fixture towns stay marched/synthetic by design. Plus the classic +
|
||
default gates prove the alpha is still isolated to the cache path. Money shot: **Katoomba's real main
|
||
street** — asphalt + lane markings, footpaths, a pedestrian crossing, a walking crowd, gig posters,
|
||
streetlamps, B's town selector in-frame — PROCITY's look on real bones, in budget.
|
||
|
||
### The honest notes on the alpha (two, both filed for v4.0-beta)
|
||
1. **Density.** Real Katoomba's 19-shop cache is genuinely spread ~1 shop per real edge (`shopsOnBest = 1`),
|
||
so the alpha is a real *street* with the town's life on it, not a dense retail strip — that density is a
|
||
beta concern (more shops per cache, or focusing the retail spine).
|
||
2. **One open budget finding (non-blocking).** The matrix's worst-case view (`venue_night` — NIGHT, the lit
|
||
venue block, all posters up) measures **200,965 tris on `katoomba_real`, +965 over the 200k soft ceiling
|
||
(0.5%)**. Driver: the poster count scales with the real *edge* count (438 posters for 19 shops), not shop
|
||
count. Normal play (the midday money shot) is 121k — well under. `qa.sh --strict` is GREEN; this rides
|
||
the opt-in `--matrix` warn-gate, not a blocking gate. Beta hardening: A caps posters per real town.
|
||
The alpha's job was **real roads that boot, stream in budget in play, place a district, and read as a real
|
||
place** — that shipped; the venue_night stress view is the one filed edge.
|
||
|
||
---
|
||
|
||
## 20. Round 20 — v4.0-beta THE FIVE TOWNS (density). **F never ran — the honest record.**
|
||
|
||
R20 widened the towns (E: katoomba 20→80, five towns to 292 shops, "subtle" enforced by construction),
|
||
proved the directive from the crowd side (D's A/B: heroes kept footfall 17→19 while the street got 3.8×
|
||
busier), capped posters 438→31 (A), and ruled the tram by shop adjacency (B: the alpha tram fronted **zero
|
||
shops on 4 of 5 real towns** — "it really was a highway bus"). Excellent round.
|
||
|
||
**F did not run, and there was no tag.** Unlike R18 — where F ran the gate and *held* on evidence — R20's F
|
||
slot simply never executed. The round also left C's density door-soak undone, B's selector still hardcoding
|
||
`hud.js:11` instead of E's index, and D's relocation finding (katoomba's pub 1,000 m from the retail cluster
|
||
— a gig with no passing crowd) without an owner. R21 closed all four.
|
||
|
||
## 21. Round 21 — the v4.0-beta CLOSE. F: the beta gate + the tag (ledger #6).
|
||
|
||
Waves ran [A, B, E] → [C, D] → [F]. A biased `pickVenues` toward retail-cluster adjacency (D's finding) and
|
||
re-pinned once; B derived the selector from E's `index.json`; C soaked the five towns' doors at density; D
|
||
verified the relocation (**A's bias PASSES**); E pulled 22 towns / 1192 shops forward as non-gating data.
|
||
|
||
### The matrix — 10 towns × 7 gates (`--matrix`)
|
||
`boot · determ · district · roads · tram · noassets` **green on all ten**. Two budget flags (below).
|
||
Selfcheck **154141/154141**, `qa.sh --strict` GREEN.
|
||
|
||
### Tram (#6c) — B's R20 ruling verified exactly
|
||
| town | verdict |
|
||
|---|---|
|
||
| newtown_real | **runs, 16 stops** (B's phantom-stop fix: was 149) |
|
||
| castlemaine_real | runs, 10 stops |
|
||
| katoomba / fremantle / bendigo | **fenced** — 3 / 2 / 1 shops fronted, all < the 5-shop floor |
|
||
|
||
New seam: the shell now exposes `window.PROCITY.tram` — B built `routeInfo` *for F's smoke* but nothing
|
||
published it. F owns index.html, so F wired it. The matrix reads `{fenced, stops, shopsFronted}`.
|
||
|
||
### ⚠ The venue_night re-measure (#6b) — RECORDED, and the premise came out false
|
||
The brief expected A's poster cap to close F's R19 `+965`-tri edge. **It did not — it widened:**
|
||
| | R19 | R21 |
|
||
|---|---|---|
|
||
| katoomba_real venue_night | 200,965 (+965, **+0.5%**) | **221,935 (+21,935, +11%)** |
|
||
| bendigo_real venue_night | — | 201k (**+0.5%**) |
|
||
| fremantle / newtown / castlemaine | — | 184k / 179k / 112k ✓ |
|
||
|
||
**A's cap worked** (posters 438→111). The cause is the *density widening*: katoomba 19→72 shops + D's 3.8×
|
||
busier street more than ate the savings. **Draws are fine everywhere** (151/300 — half budget); this is
|
||
tris-only, at the worst night stress view. Not a blocking gate (it rides the opt-in `--matrix` warn), and
|
||
the brief's instruction was "record the new number" — recorded. **Filed as R22's first item**, where the
|
||
tri-diet backlog (instrument LOD · tram tri diet) already lives. katoomba is the outlier: a 966-edge graph
|
||
carrying 72 shops.
|
||
|
||
### Ruling (#6d) — selector gating: **always in the HUD**, not `?dbg`-gated
|
||
THE REAL MAP's product *is* the towns; gating their discovery behind a debug flag would hide the epoch's
|
||
headline feature. It's unobtrusive, shows friendly names, and lives in the HUD overlay. **Corollary (F's own
|
||
gap):** money shots must hide the HUD — the R19 alpha shot had the selector in-frame because F's harness
|
||
screenshotted the full viewport. The beta shot hides it.
|
||
|
||
### The beta money shot (#6e)
|
||
[`docs/shots/v4_beta/fremantle_mainstreet.png`](../shots/v4_beta/fremantle_mainstreet.png) — Fremantle's real
|
||
street at golden hour: buildings lining both sides down the corridor, footpaths, a crossing, gum trees,
|
||
streetlamps, and a genuinely busy crowd (the density widening, visible). 126 draws / 167k tris, HUD hidden.
|
||
Posed by projecting the densest **spatial** shop cluster (10 shops within 60 m) onto its front edge and
|
||
standing on the road — per-edge shop counts are ~1 on a real graph, so "densest edge" is the wrong pose
|
||
heuristic for real towns.
|
||
|
||
---
|
||
|
||
## 22. Round 22 — **v4.0: THE EPOCH CLOSE.** F: the re-measure, the selector, the tour, the tag.
|
||
|
||
### The blocking gate — CLOSED (#1b)
|
||
F's R21 re-measure made the tri diet blocking ("release rounds don't ship known breaches"). B diagnosed
|
||
layer-by-layer, the driver traced **A → E** (A's own commit said so: "B's numbers say the fix is E's"), and
|
||
**E fixed it**. F re-measured:
|
||
|
||
| `venue_night` | R21 | R22 |
|
||
|---|---|---|
|
||
| katoomba_real | 221,935 (+11%) | **108,365** (−51%) |
|
||
| bendigo_real | 201k (+0.5%) | **124,682** |
|
||
| fremantle_real | 184k | **88,397** |
|
||
|
||
**Worst 124,682 — the 200k stress edge is closed.** Selfcheck **161,300/161,300** (A's 36 goldens + the
|
||
23-town pack). The measure-first loop worked end to end: F measured, B diagnosed, E owned, F re-measured.
|
||
|
||
### `DBG.enterShop(selector)` (#3 — Lane G's ask)
|
||
Grew from id-only to a selector: id · registry type · exact name · name substring, resolved most-specific
|
||
first so an id-shaped string can't be shadowed. `DBG.enterShop('record')` / `('The Exchange Hotel')` / `(116)`
|
||
— cross-repo QA one-liners on any of 23 towns without an id lookup.
|
||
|
||
### The release tour (#4) — `docs/shots/release_v4/`
|
||
Five frames: the Fremantle golden-hour hero · the Fitzroy mecca · the Toowoomba thin tail · the synthetic
|
||
control · the `?classic=1` covenant. Every real-town frame poses through **F's R21 cluster primitive**
|
||
(project the densest SPATIAL cluster onto its front edge, stand on the road); B's R22 bookmark fix anchors
|
||
on the same retail heart, so tour and bookmarks agree by construction. HUD hidden throughout.
|
||
|
||
**The primitive earned itself twice.** First cut of the tour posed via `DBG.shot('street_noon')` and the
|
||
**mecca came out as bare bitumen** — the same trap as R21. Re-posed through the cluster primitive, Fitzroy
|
||
reads (7 shops/60 m retail heart, trees, benches, crowd). *Per-edge is always the wrong heuristic on a real
|
||
graph.*
|
||
|
||
### ⚠ The honest thin-tail finding (filed for v4.x)
|
||
The brief asked for a thin-tail frame as **"proof the floor holds."** It doesn't, and the frame says so:
|
||
**Toowoomba reads as an empty road, not a town.** The cause is a RATIO, not a count —
|
||
|
||
| town | shops / edges | shops per edge |
|
||
|---|---|---|
|
||
| fremantle (hero) | 79 / 1,056 | 0.075 |
|
||
| fitzroy (mecca) | 139 / 2,408 | 0.058 |
|
||
| **toowoomba (thin tail)** | **12 / 1,459** | **0.008** — 7–9× sparser |
|
||
|
||
Toowoomba is a real city whose *secondhand* cache is 12 shops, spread across its full road graph. The floor
|
||
isn't "≥N shops" — it's **shops-per-street-metre**. v4.x options: clip the graph to the retail core for
|
||
thin caches, or set a ratio floor in the pack. The frame ships **labelled honestly** rather than swapped for
|
||
a flattering town — same discipline as the R21 re-measure that got worse and said so.
|
||
|
||
---
|
||
|
||
## 23. Round 23 — v5.0-alpha THE FIRST REAL CRATE. **F HELD the tag — the crate has no street.**
|
||
|
||
**Verdict: `v5.0-alpha` NOT tagged.** The round's headline artifact is real and good — G's 120 real
|
||
records off a real shop's POS are in the tree, byte-identically re-bakeable. It cannot be reached from
|
||
the game. `#7a` (riffle the real crate, buy it) is not a gate that failed; it is a gate that **cannot be
|
||
run**. R18's law: *release rounds don't ship known breaches.*
|
||
|
||
### What F did run
|
||
|
||
| gate | result |
|
||
|---|---|
|
||
| **`qa.sh --strict --matrix`** | **7 passed · 0 failed · 0 warn** — QA GREEN |
|
||
| **town-matrix** | **MATRIX GREEN — 10 towns × 7 gates**, all green |
|
||
| citygen selfcheck | **ALL GREEN 152,015/152,015** (was 161,300 — E retired toowoomba) |
|
||
| flags harness (classic regression · default boot · STRICT) | **GREEN, 0 warnings** — the covenant holds through the merge |
|
||
| manifest validator · scaffold · consistency · no-giants | GREEN |
|
||
| tier 0 — `?noassets=1` | **zero fetches, zero console errors** — the offline law holds |
|
||
| tier 0 — default boot, godverse town | town loads (18 real Newtown shops), stock → parody |
|
||
| tier 1 (v2, town-wide) — `?stock=real` | 350-item pack resolves, real cover art |
|
||
| **tier 1 (v5, per-shop) — the round's deliverable** | **NEVER FETCHED** |
|
||
| tier 2 | N/A by law (no server code this round) |
|
||
|
||
### The merge that never executed (F did it — charter law #5)
|
||
Fable's mid-round merge never ran: `main` had **no `web/assets/stock_godverse/**` at all**, so the branch
|
||
F was asked to gate and tag did not contain the round's headline. The review had happened (the shared
|
||
checkout was parked on `lane-G-godverse` for it) and C verified the atlas from a temp worktree —
|
||
deliberately pre-merge — and passed. Only the `git merge` was missing. F executed it as the integration
|
||
lane, disjointness **verified before merging, not assumed** (G: `G-progress`, `G3 doc`,
|
||
`pipeline/godverse_*`, `stock_godverse/**` · main-since-fork: `C-progress`, `LANE_C_PUB` · overlap: none).
|
||
Zero conflicts, exactly as law #5 predicted. → `43b035a`.
|
||
|
||
### ⚠ FINDING 1 — the atlas contract contradicts itself. Nobody was wrong; the contract was.
|
||
|
||
| | says the index is |
|
||
|---|---|
|
||
| **C's published contract** — `LANE_C_PUB:271` | `stock_shop_<godverseShopId>_index.json` |
|
||
| **C's own runtime loader** — `stockpack.js:25` | `` fetch(`${base}stock_${type}_index.json`) `` |
|
||
|
||
**These are mutually incompatible.** An atlas named per the published contract can *never* be loaded by
|
||
the code that is supposed to load it. G shipped `3962749/stock_record_index.json` — **runtime-compatible**,
|
||
which is exactly why C's base-override verification passed. E globbed `stock_shop_*_index.json` —
|
||
**contract-compatible**, which is why E's validator never saw the atlas. Two lanes implemented opposite
|
||
halves of a self-contradicting spec and both were correct.
|
||
|
||
**F's recommendation (measured, not preferred):** amend the contract to G's shape —
|
||
`stock_godverse/<godverseShopId>/stock_<type>_index.json`. It is the only shape the runtime can load,
|
||
it keys per-shop by directory, and it costs G nothing (already emitted). E's glob follows. C owns the
|
||
wording (LANE_C_PUB); this is the amendment ledger #3 anticipated.
|
||
|
||
### ⚠ FINDING 2 — the gate built to catch this was blind, and green
|
||
|
||
`validate_atlas.py` globs for a filename that cannot exist at runtime, finds nothing, prints *"no
|
||
per-shop atlases yet — nothing to validate"* and **`return 0`**. `validate_manifest.py:158` propagates
|
||
that green (its comment states the intent: *"No atlases yet ⇒ clean pass"*). So the round's QA gate passes
|
||
whether the atlas is **absent, misnamed, or perfect** — it has never once looked at one. E built it in
|
||
wave 1 with no atlas to test against; on first contact (F, at close) it was blind.
|
||
|
||
The flaw is precise and the fix is a one-liner: the validator **conflates "no atlases exist" with "no
|
||
atlases matched my glob."** The first is a legitimate clean pass; the second is exactly the failure it
|
||
was written to catch. Cross-check the directory — if `stock_godverse/*/` is non-empty but no index
|
||
matched, that is an **ERR**, not a pass. *A gate that returns 0 on an empty set had better be certain the
|
||
set is empty.*
|
||
|
||
Pointing it at G's index by hand (filename drift removed) shows the drift is **deeper than the name**:
|
||
- **`ERR missing/empty provenance ['license','attribution','generator','snapshot']`** — but G *carries*
|
||
`generator` and `snapshot`. E's line 68 reads `idx.get(f)` — **top-level** — while E's own docstring
|
||
(line 14) specifies them **nested under `provenance`**, which is where G put them. **E's code
|
||
contradicts E's doc**; G's atlas would fail even with E's exact field names.
|
||
- **`licence` vs `license`** — G used the British spelling; E's `REQUIRED_PROV` wants American.
|
||
- **`attribution`** — G folds the substance into `licence.covers`; E wants the field.
|
||
- **`WARN 2 atlases — C's rule is 1/shop`** — E built to C's *pre-correction* ceiling; C amended §7.2 to
|
||
`≤2 atlases @≤2048²` in the same wave. Stale by one round.
|
||
|
||
### ⚠ FINDING 3 — `#7a` would have passed vacuously
|
||
The brief says: *"boot `newtown_godverse` with `?stock=real` … riffle — **real covers render**."* They do.
|
||
F walked into **The Vintage Record** (a real Newtown shop, on its real road) via `DBG.enterShop`, and the
|
||
crates filled with **real cover art** — from E's **350-item town-wide v2 pack** (`Sunburnt Milk Bar Vol. 2`,
|
||
`Midnight Departure`), the same pack every record shop in every town has had since v2. **Not** Monster
|
||
Robot Party's 120. Network confirms: every stock fetch goes to `assets/models/`; **zero requests to
|
||
`assets/stock_godverse/`, ever.** Had F eyeballed the gate as written, it would have gone green and proven
|
||
nothing. *Two vacuous gates in one round — same species: both test the presence of a thing rather than
|
||
the identity of it.*
|
||
|
||
### The exact failure list for R24 — three gaps, none of them F-only
|
||
|
||
1. **The crate has no street.** [E + G] No town cache contains shop `3962749`; `redhill_real` does not
|
||
exist. Needs a `build_towns.py` TOWNS entry (~`center (-27.4553, 153.0064), span_km 2.4`) + one
|
||
outward-facing Overpass fetch → `redhill_real`, then `godverse_town.py redhill_real redhill_godverse`
|
||
with monsterrobot injected into `shops[]`. **F did not do this: E's file, G's generator, and an
|
||
outward-facing fetch — inventing town data is not F's to invent.**
|
||
2. **No identity field exists to key an atlas by.** [A + G] Verified in the live runtime: *every* shop in
|
||
`newtown_godverse` reports `godverse_id: null` — the cache schema carries no such field at all. C's
|
||
rule is *"C consumes whatever identity field lands, C does not invent it"* — so nobody has landed it.
|
||
A owns the CityPlan/cache contract; G emits it.
|
||
3. **The runtime wire.** [C + F] `stockpack.js` keys `_packs`/`_resolved` by **type alone** and returns a
|
||
cache hit before `base` is ever read (`stockpack.js:24`), so two stocked shops collide **and fail-soft
|
||
breaks** — an unstocked shop of a cached type inherits another shop's crate. C specced the seam (key by
|
||
`type+base`, per-shop preload on entry, LRU=1 + dispose) and owns the file; F then passes the per-shop
|
||
base at `interior_mode.js:192` and drops the town-wide preload at `index.html:199`.
|
||
|
||
**F deliberately did not build its half of #3 this round.** It is untestable while #1 and #2 are open
|
||
(no street to walk to, no identity field to pass), and F would be writing against three-quarters of a
|
||
spec that does not exist yet. Filed, not guessed. R24 order: **A+G (identity) → E (the town) → C (the
|
||
keying) → F (the wire + re-gate)**, and the crate walks.
|
||
|
||
### Also found
|
||
- **The godverse town is in no gate.** `newtown_godverse` is a shipping town cache that boots fine (F
|
||
verified: 18 real shops, real roads) but the town-matrix covers `newtown_real`, not it — so the one
|
||
town the whole epoch is built on is ungated. F's matrix, F's gap; it lands with the R24 re-gate rather
|
||
than a held round.
|
||
- **A's spacing warn earns immediately:** with toowoomba retired, **`ballarat_real` now warns at median
|
||
255 m > 150 m**. The thin tail has a second member — A's warn is doing exactly its job (warn, not error;
|
||
E curates). Filed for E's next curation pass.
|
||
- **`?noassets=1` + `&town=<key>` silently boots a *different town*** (the fixture, not the cache) — F's
|
||
own R17 wiring gates the cache fetch on `!NOASSETS`, correctly (noassets = zero network), but the tier
|
||
ladder's *"same shop under `?noassets`"* can't mean the same shop for a real town. Tier 0 for **stock**
|
||
is provable on the default boot; tier 0 for the **town** is a fixture fallback by design. Wording, not a bug.
|
||
|
||
---
|
||
|
||
## 24. Round 24 — v5.0-alpha THE ALPHA COMPLETION. **The crate WALKS. F held the tag anyway — A's gate.**
|
||
|
||
**THE CRATE WALKS.** You can boot `redhill_godverse`, walk down Musgrave Road into **Monster Robot
|
||
Party**, riffle **its own real crate**, and buy a real record for its real price. Proven falsifiably,
|
||
not by eyeball:
|
||
|
||
| assertion | result |
|
||
|---|---|
|
||
| room resolved base | **`assets/stock_godverse/3962749/`** (not the town-wide base) |
|
||
| **title equality** | **120/120 in Monster Robot's atlas · 0/120 in the generic town pack** |
|
||
| **texture provenance** (the pixels) | **2 atlases, both `stock_godverse/3962749/stock_record_atlas_0*.webp` · 0 from the generic pack** |
|
||
| network | `stock_godverse/3962749/` index + both atlases, `200` |
|
||
| **buy** | `rec_0000` *"Abstract Latin Lounge II (Part II)"* @ **$15** → cash **191 → 176**, debited exactly 15, count 0→1 |
|
||
| gone-this-session → back next boot | reboot ⇒ wallet 191/0 restored (no persistence layer exists) |
|
||
|
||
**Tier ladder, re-proved end to end:** tier 0 `?noassets` on a godverse town → **zero network requests**,
|
||
no base, no pack (the offline law survives the wire) · **soft fall live**: `Restoration Station`
|
||
(keyed 2289, no atlas) → `404` → null → parody, and the boot log shows those 404s happening in
|
||
production · **no cross-contamination**: after visiting other shops the real crate still resolves its
|
||
own 120 · **plain towns untouched**: `newtown_real` → 0 keyed, base `null`, 350 items, textures from
|
||
`models/` — **F's wire is a complete no-op off the godverse path**, byte-identical to pre-v5.
|
||
|
||
### What F built (#6a)
|
||
`godverseBaseFor(shop)` — **exported and used by both** the shell's boot preload and the room's lookup,
|
||
deliberately: C's cache identity is the LITERAL `(type, base)` pair, so two sides building that string
|
||
differently would miss the cache and drop a real shop to parody — a bug that looks exactly like "no
|
||
stock" and would sail straight past a covers-render eyeball. One function, one string, one key.
|
||
Boot-preloads keyed shops (`getStockPack` is sync and the room is built the instant the door opens, so
|
||
a pack first requested at `enter()` is not resolved in time), gated on `stockReal` and never on the id
|
||
alone, so `?noassets` fetches nothing.
|
||
|
||
### ⚠ FINDING 1 — the honest-gate spec was itself vacuous. The law's first application broke the law.
|
||
The brief specified #7a assert **"rendered titles ∈ the atlas index (id equality, not 'covers render')."**
|
||
**Id equality cannot discriminate.** Measured: both packs number their items **positionally** —
|
||
`rec_0000, rec_0001, …` — so the atlas's ids are `rec_0000..rec_0119` and the town-wide pack's are
|
||
`rec_0000..rec_0349`. **All 120 atlas ids are also generic-pack ids (120/120 overlap).** An id-equality
|
||
gate goes **green on the generic pack** — the exact failure the law was written to stop, reproduced
|
||
inside the fix for it. *Ids here are slot numbers, not identity.*
|
||
**What actually discriminates:** titles (**0 overlap**) and, strongest, the **texture URL** — the file
|
||
the GPU samples. A pack that merely looks right cannot fake the URL its pixels come from. F's gate
|
||
asserts on both, and `DBG.stockInfo()` exposes them so the assertion is scriptable, not eyeballed.
|
||
|
||
### ⚠ FINDING 2 — F's own selector was lying about the epoch's shop
|
||
`DBG.enterShop(3962749)` returned **`no shop matching`**. A GODVERSE shop has *two* ids — its plan id
|
||
(local, renumbered per town: Monster Robot is **8**) and its `godverseShopId` (**3962749**, the real POS
|
||
id, which is what the atlas, every G-side query and every cross-repo bug report names it by). F's R22
|
||
selector only knew the first. Fixed (plan id still matches first, so no existing call shifts). *The
|
||
harness F shipped for Lane G could not name the one shop the epoch is built on.*
|
||
|
||
### The 8 reds — F did NOT clear them, and did not touch A's file
|
||
`156,193/156,201`. They are **not** product defects; F proved the product correct above. They split:
|
||
|
||
**2 — goldens moved by G's re-emit** (`newtown_godverse` base `0xcf69b387`, gig `0x1e266f60`), exactly as
|
||
ledger #2 intends: adding `godverseShopId` changes the cache, so it changes the plan. A ran *before* G,
|
||
so A could not pin what G had not yet emitted. Plus 4 UNPINNED warns (`redhill_godverse`,
|
||
`redhill_real`). **F did not re-pin**: if conflict #2 resolves by changing the caches, these move again,
|
||
and the amendment law wants the spec change and the pin in ONE commit. Pin them with the resolution.
|
||
|
||
**6 — conflict #2: A's identity sweep contradicts Fable's own standing Ruling 2.** Ruling 2
|
||
([FABLE_TO_LANE_G.md:33](FABLE_TO_LANE_G.md)) *mandates* mixed caches — "merge census heroes **WITH** the
|
||
donor's texture shops (cafes/bakeries…)". A cafe lifted from OSM has no GODVERSE identity and never will;
|
||
minting one would invent identity, which is charter risk #3 itself. A's sweep asserts
|
||
`keyed === cache.shops.length` — i.e. that no such cache may exist. It is a gate authored in the same
|
||
wave as the thing it gates, with no mixed cache in existence to test against: **the identical species as
|
||
E's blind validator in R23**, one round later, in the opposite direction. G measured it and escalated it
|
||
to "A's call, Fable's ruling"; nobody ruled; it arrived at F.
|
||
|
||
**F's measurement of the hazard the gate guards: it does not exist.** `redhill_godverse` 10 keyed /
|
||
**10 unique**; `newtown_godverse` 18 keyed / **18 unique**; **zero duplicates**. And partial keying does
|
||
not mis-stock — F watched shop 2289 fall soft to parody in the browser, which is precisely what charter
|
||
risk #3 prescribes ("fail-soft to tier 0, never mis-stocked"). **A's own two checks already disagree**:
|
||
`validateTownCache` reasons it out correctly — *"Missing = tier 0 (soft). Duplicate = mis-stocked (hard).
|
||
They are not the same failure"* — and warns; the sweep hard-fails the same condition.
|
||
|
||
### ⚠ FINDING 3 — the uniqueness check is a silent risk-#3 hole (new; G did not catch this one)
|
||
`ok(new Set(seated.map(s => s.godverseShopId)).size === seated.length, 'godverse ids unique')`. On a
|
||
mixed cache every texture shop maps to `undefined`, and **all of them collapse into ONE Set entry** — so
|
||
the check fails on the undefineds, *not* on a duplicate, and it **cannot detect a real duplicate** while
|
||
any shop is unkeyed. It is the arm A called "the arm that earns its keep" for charter risk #3, and on
|
||
every cache Ruling 2 mandates it is **structurally incapable of doing its job, in both directions**.
|
||
Whoever fixes the coverage assert must not paper over this one: uniqueness belongs **over the defined
|
||
ids** (`seated.filter(s => s.godverseShopId !== undefined)`), where it would actually bite. That change
|
||
makes the risk-#3 guard **stronger**, not weaker.
|
||
|
||
### Why F held the tag — and why this hold is not R23's
|
||
R23 held because the **product** was broken. **R24's product is finished and proven.** What is red is a
|
||
**gate**, in **Lane A's file**, on a **schema question that is A's to answer** ("what marks the godverse
|
||
layer?" has at least three valid designs). F did not resolve it, for one reason above all: **F is the
|
||
party that benefits.** F changing a red gate to green, in another lane's file, to unblock F's own tag,
|
||
on the one check guarding "never mis-stocked" with Monster Robot's real crate as the prize — that move
|
||
would retroactively devalue every green F has ever reported. F's only asset is that F never shades a
|
||
result. The cost of waiting is an hour; the cost of F being quietly wrong is the charter's worst risk.
|
||
**Recommended (A's call): the sweep follows A's own validator — missing ⇒ warn (tier 0, soft), duplicate
|
||
⇒ error (hard, over defined ids), coverage asserted over the keyed layer, not the inherited one.** Then
|
||
A pins the four goldens in the same commit and F re-gates and tags. It is one commit's work.
|
||
|
||
### ⚠ FINDING 4 — the licence flag stopped reaching a human, and the gate says OK
|
||
R23's blind validator is **fixed** — E's `validate_atlas` now finds and validates the atlas
|
||
(`3962749/stock_record_index.json · Monster Robot Party · items=120 atlases=2 px=2048 OK`). But G's
|
||
conflict-#1 leftover is real and still open: `validate_atlas.py:198` prints from
|
||
`provenance.licence` — **British only** — while G's re-emit carries **`provenance.license`** (American,
|
||
per C's corrected contract). So `lic = {}`, and the licence line **silently never prints**. E's *check*
|
||
(:105) accepts either spelling, so the gate goes **OK**; only the evidence vanished.
|
||
|
||
That evidence is not decoration. E's own comment at :199 says *"the licence is PRINTED — clearing it is
|
||
a human call"* — it is the **mechanism** by which charter law #3 reaches a person, and the string it
|
||
failed to print is **`FLAG BEFORE ANY PUBLIC/COMMERCIAL RELEASE`**, on an atlas of a real shop's real
|
||
product photographs with real artist names. A gate that is green while the one thing it exists to
|
||
surface is invisible is the vacuous-gate law's own species, wearing the licence law's coat.
|
||
**Fix (E, one line, G already specified it):** `.get("licence") or .get("license")` at :198.
|
||
|
||
**F did not patch it.** It is E's file, F is not blocked on it (the tag is held regardless), and G
|
||
already routed it. Filing beats reaching across a lane boundary for a one-liner — the same restraint F
|
||
applied to A's gate, for the same reason.
|
||
|
||
### ⚠ FINDING 5 — F's own gate cried the loudest wolf we own (F's bug, F fixed it)
|
||
The classic-regression gate reported **`plan fingerprint != golden 0x3fa36874 — determinism/prime-law
|
||
broken`**. The covenant was never touched (`0x3fa36874`, node-side, unmoved; G said so too). F wrote
|
||
this gate in R16 with `if hash_ok and r.returncode == 0` — **two assertions, one verdict, reported with
|
||
the fingerprint's message** — so *any* unrelated red anywhere in Lane A's selfcheck (here: 8
|
||
godverse-identity checks in a different town) screams that the byte-identical-forever covenant is
|
||
broken. Fixed: two assertions, two verdicts, and the selfcheck failure now names its own lane and
|
||
quotes its own reds. *A gate must name what it actually measured — the same law as the vacuous gates,
|
||
one turn further round: it isn't enough to fail for a reason, you have to fail for YOUR reason.*
|
||
|
||
### ⚠ FINDING 6 — the id spaces collide, and it bit F's own selector inside the anti-vacuous gate
|
||
Plan ids are per-town (1..N); godverse ids are real POS ids (31, 767, 3962749). **They share a numeric
|
||
space.** Silky Oak Furnature's godverseShopId is **31** — and another shop's *plan* id is also **31**.
|
||
F's first cut at the R24 selector tried plan id first, so `enterShop(31)` silently entered **Wholefood
|
||
Cafe** instead. The smoke's soft-fall assertion then **passed on an unkeyed cafe** — a vacuous pass,
|
||
inside the gate F wrote to stop vacuous passes, in the round whose whole theme is *ids that aren't
|
||
identity*. Fixed both ends: `'g:<n>'` selects a godverse id explicitly; a bare number stays the plan id
|
||
(back-compatible) and now **reports the collision** rather than choosing quietly; and the smoke enters
|
||
via `g:` and asserts the shop it landed in **is the one it asked for** before asserting anything about it.
|
||
|
||
*The lift itself is clean* — F checked: cache 10 keyed → plan 9 keyed (Empire Revival didn't seat), every
|
||
one a real secondhand shop. **No cafe is keyed. There is no mis-stocking.**
|
||
|
||
### ⚠ FINDING 7 — fail-soft-by-404 vs the 0-console-errors law (a contract gap, filed not papered)
|
||
F's boot preload must resolve a keyed shop's pack *before* the door opens (`getStockPack` is sync and
|
||
the room builds on the same tick), so it probes every keyed shop. 9 of Red Hill's 10 have no atlas →
|
||
`404` → null → parody. **That is the fail-soft law working**, and it is also **8 console errors on every
|
||
godverse boot** — a browser-level log no `.catch()` can suppress. The house law is zero.
|
||
**The tension is structural: you cannot probe blind and stay silent.** The fix is not to loosen the law
|
||
but to stop probing for what isn't there: **an atlas index in G's namespace** (or a `hasStock` flag on
|
||
the cache's shop entries, A+G) so the shell preloads exactly the shops that have atlases — 404s then
|
||
mean a genuinely missing file, which is what fail-soft is *for*. **Filed as a contract ask, not
|
||
invented.** Until it lands, F's smoke counts the probes, names them, and fails on any other error —
|
||
tolerance stated out loud, never blanket-ignored.
|
||
|
||
---
|
||
|
||
## 26. Round 26 — v5.0-beta EVERY CRATE DIFFERENT. F: the manifest consumed, the tolerance retired.
|
||
|
||
### The 404 tolerance is gone — and that is the point
|
||
R24's wire preloaded a pack for **every keyed shop** and ate a `404` for each one with no atlas. That was
|
||
the fail-soft law working, and it was also **console errors on every godverse boot**. F's smoke could only
|
||
cope by *naming* the probes — and **a tolerance is a confession, not a fix**. F filed the real fix as a
|
||
contract ask rather than inventing it: *an atlas index in G's namespace, so we never ask for what isn't
|
||
there.* G built it. **F now consumes it** (`assets/stock_godverse/index.json` → `{godverseShopId, types[],
|
||
sourcing}`): the shell preloads only manifest-listed `(shop, type)` pairs, and the gate asserts **ZERO
|
||
404s with no attribution exceptions**. The 404s are gone because there is nothing absent to ask for — not
|
||
because the gate stopped looking. Fail-soft still covers the case that matters (manifest says yes, file is
|
||
gone), which is what fail-soft was always *for*. Fetched only when the town has keyed shops, so a plain
|
||
town stays byte-identical: no manifest, no fetch.
|
||
|
||
### Distinctness — G reported 15/15; F measured it, from the indexes of record
|
||
Not by entering one lucky shop: **105 pairs across all 15 keyed crates, 0 sharing a title**; **0 crates
|
||
overlapping the generic v2 pack**; **0 ids reused across crates** — G's sku fix holds at scale, which is
|
||
the R24 vacuity dead at the source rather than papered over in F's gate. 4 crates in Red Hill + 11 in
|
||
Newtown = the manifest's 15 exactly, and the manifest lists nothing that isn't on disk.
|
||
|
||
### Sourcing, not tier — C was right and the gate follows C
|
||
C ruled the brief's `tier` field wrong: `tier` is the **charter's ladder rung**, and a mint atlas is *also*
|
||
rung 1 — a mint crate is still a deterministic file, still offline, still tier 1. What differs is where the
|
||
stock **came from**, which is `sourcing: real|mint`. E built to C's contract over Fable's brief on the
|
||
principle that *the contract's author is the authority for the contract's shape*, and Fable ratified it.
|
||
F's gate asserts `sourcing`: every index declares it, every index agrees with the manifest, and **a mint
|
||
crate can never report as real**. The gate prints it per room, because provenance a human never reads is
|
||
provenance that isn't doing anything — the same lesson as R24's silent licence line.
|
||
|
||
*Note for whoever reads the runtime:* C's pack (`stockpack.js`) does **not** carry `sourcing` through —
|
||
the gate reads it from the index of record, which is correct (the index is the artifact; B's street mark
|
||
reads the manifest). Not a gap; recorded so nobody re-derives it.
|
||
|
||
### ⚠ The tolerance came out and immediately caught the leftover
|
||
F wired the **boot** preload to the manifest, ran the gate with the 404 tolerance **deleted**, and it went
|
||
red on exactly one 404: `stock_godverse/31/stock_opshop_index.json`. That was F's *other* probe — R24 also
|
||
preloaded a shop's pack **on entry**, so walking into a keyed shop whose type has no atlas fired a fetch
|
||
that could only ever 404. Removed: the door does a lookup, never a fetch. The manifest is consulted once,
|
||
at boot, and by the time a door opens the pack is either resolved or was never going to exist.
|
||
|
||
The lesson is the round's, not just the bug's: **the tolerance was hiding a second instance of the very
|
||
thing it tolerated.** While the gate said "8 atlas-probe 404s, expected", the ninth would have been
|
||
invisible too. A named exception is still an exception; it earns its keep only until the real fix exists,
|
||
and the moment it does, the exception must die or it starts protecting new bugs.
|
||
|
||
### The gate, green
|
||
`DISTINCTNESS 15 crates / 105 pairs / 0 shared · 0 vs the generic pack · SOURCING 15/15 declared, all
|
||
agreeing with the manifest · Monster Robot Party → sourcing REAL, 120/120 own titles, 2 own textures / 0
|
||
generic · Presents of Mind → sourcing MINT, 16/16 own, 1 own / 0 generic · soft fall silent · ZERO 404s ·
|
||
0 console errors, no attribution exceptions.`
|
||
|
||
### ⚠ THE HOLD — `v5.0-beta` NOT tagged. `atlas-QA FAIL — 14 error(s)`, and it is not the product.
|
||
`qa.sh --strict --matrix` → **6 passed · 1 failed · 0 warn**. The one failure is the manifest validator:
|
||
E's `validate_atlas` rejects **all 14 mint crates** (the real crate passes). Everything else is green —
|
||
selfcheck **ALL GREEN 156,212/156,212** · **MATRIX GREEN 10 towns × 7 gates** · flags harness (carrying
|
||
F's beta gate) · consistency · figure-scale. **One gate, one assertion, 14 identical errors, one line.**
|
||
That ratio is the whole argument for the hold: the beta is finished, and what stands between it and the
|
||
tag is a rule meeting a case it was written before.
|
||
|
||
**The conflict:** `SLOT_ID_PREFIX = "sku_"` (`validate_atlas.py:73`) is a module constant applied to
|
||
**every** pack — E reads `sourcing` at :178 but the slot-id check is **sourcing-blind**. C §7.2a's rule is
|
||
*"derive from the source's own stable key, never position, unique across ALL packs"*, and it specifies
|
||
`sku_<POS sku>` **"for a real-stock (godverse) pack"**. **A mint crate has no POS sku by construction** —
|
||
it was never in that shop's till. Demanding one demands a thing that cannot exist: the same shape as R24's
|
||
sweep demanding a cafe carry a `godverseShopId`, and as the brief's `tier` demanding a rung where the
|
||
question was provenance.
|
||
|
||
**F measured the rule's INTENT, and it holds:** mint ids are `mint_<dealgod listing id>` — a real external
|
||
key, **non-positional**, items sorted by it, the bake seeded (`random.Random(158)` over an id-ordered
|
||
pool), and **0 ids reused across all 15 crates** (F's own pairwise measurement). Stable, unique,
|
||
non-positional. Only the literal prefix fails.
|
||
|
||
**G refused to go green the easy way, and was right to** — worth recording, because it is F's own R24
|
||
finding grown up: *"a POS sku and a dealgod listing id are different id spaces; filing dealgod listing ids
|
||
under `sku_` recreates that bug INSIDE the economy — tier-2's sold-means-gone looks up POS skus, and
|
||
`sku_6031122` (a dealgod id) is indistinguishable by shape from a real sku."* R24's plan-id-31 vs
|
||
godverse-id-31 collision entered the wrong shop; the same collision in the money layer sells the wrong
|
||
record. G caught it **before** shipping rather than after. Emitting `sku_` would have made this gate green
|
||
and the economy wrong.
|
||
|
||
**Why F did not fix E's one line:** identical to R24's conflict #2, and the answer has not changed. It is
|
||
**E's file implementing C's contract**, the question ("does §7.2a's sku rule bind a mint pack?") is **C's
|
||
to answer as the contract's author** — the principle Fable ratified when C overruled `tier` — and **F is
|
||
the party that benefits**, since the fix is the only thing between F and the tag it was sent to place.
|
||
F's whole worth is that F never shades a result. Cost of waiting: one line. Cost of F being quietly wrong:
|
||
a gate that green-lights id-space collisions in the layer that moves money.
|
||
|
||
**The fix (C rules, E lands it, one line):** gate `SLOT_ID_PREFIX` on `sourcing == 'real'`; for `mint`,
|
||
assert `mint_`-prefixed + non-positional + unique — which F has already measured true. Then F re-gates
|
||
and tags. **Everything else for the beta is done and green.**
|