Compare commits
223 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ef751e1e3 | ||
|
|
25cef5983e | ||
|
|
55b984e0b0 | ||
|
|
c9e233a7e5 | ||
|
|
e528c92fed | ||
|
|
b048f51640 | ||
|
|
776b10b599 | ||
|
|
7b26e45fa0 | ||
|
|
37773bc81d | ||
|
|
ce71908bb5 | ||
|
|
9aae167d64 | ||
|
|
bb30767b62 | ||
|
|
75e5e06288 | ||
|
|
f9517f14ab | ||
|
|
046668e5af | ||
|
|
b3394027be | ||
|
|
3c5d0a1e95 | ||
|
|
67913f8b66 | ||
|
|
bb5f36f1e0 | ||
|
|
2491f1c8ec | ||
|
|
50385c0f08 | ||
|
|
d96cbde004 | ||
|
|
7a7da29132 | ||
|
|
2c9cb94b8e | ||
|
|
5e115688ed | ||
|
|
cb03f07fb2 | ||
|
|
f419c1a5e2 | ||
|
|
7b7227af73 | ||
|
|
207bcffb54 | ||
|
|
369a2be23e | ||
|
|
53fcd3ca1b | ||
|
|
c8503a5a2e | ||
|
|
83ef8c9234 | ||
|
|
e2345b544d | ||
|
|
bebaa66196 | ||
|
|
fe00c11d2c | ||
|
|
a80ad55f47 | ||
|
|
10025d4082 | ||
|
|
09dff6b788 | ||
|
|
cdce67c8f8 | ||
|
|
488067004c | ||
|
|
640bdbb55d | ||
|
|
999c9d653f | ||
|
|
afad68b114 | ||
|
|
d9100962f6 | ||
|
|
e93bece0f4 | ||
|
|
b2d29ba112 | ||
|
|
c1e862cf7c | ||
|
|
94fce8ea4b | ||
|
|
8a1d2e91e8 | ||
|
|
4c2702fc58 | ||
|
|
83ed848f16 | ||
|
|
630042fbe1 | ||
|
|
f91d734b32 | ||
|
|
39cfc3d3ed | ||
|
|
70743db47e | ||
|
|
bee7563a76 | ||
|
|
cedc5872fb | ||
|
|
6960bdd279 | ||
|
|
01f398960b | ||
|
|
839575b28d | ||
|
|
2cc6c32c73 | ||
|
|
78848ed254 | ||
|
|
bd6f576c00 | ||
|
|
f2989858aa | ||
|
|
53e83308a0 | ||
|
|
0b4bc6c1f9 | ||
|
|
7ba7ab20a8 | ||
|
|
528829eeba | ||
|
|
a9a5ef05d8 | ||
|
|
8fd73acfa7 | ||
|
|
36d10ca4ae | ||
|
|
33577a9675 | ||
|
|
2f4ab5912f | ||
|
|
4f0adf12d8 | ||
|
|
e7d58eaad5 | ||
|
|
9b1df5cca0 | ||
|
|
4a80ea8505 | ||
|
|
29942002eb | ||
|
|
533fa32b59 | ||
|
|
4314622c1b | ||
|
|
a493c0a13c | ||
|
|
0d8d59a22a | ||
|
|
2c1ae37160 | ||
|
|
0608f9c8c1 | ||
|
|
86e298531e | ||
|
|
5815dfbd58 | ||
|
|
1eb0b45bd0 | ||
|
|
ba901d4a75 | ||
|
|
0740b4697d | ||
|
|
57973e0540 | ||
|
|
43b035aa2e | ||
|
|
d2eb9df03f | ||
|
|
84f3641791 | ||
|
|
84e0a1b764 | ||
|
|
47f05ecaa8 | ||
|
|
292b07aa91 | ||
|
|
f14e4d1974 | ||
|
|
98fec6cadf | ||
|
|
8ac3b18b36 | ||
|
|
b0e4ce103e | ||
|
|
15ff53685f | ||
|
|
d81e499194 | ||
|
|
d485b18ecc | ||
|
|
af8a6db020 | ||
|
|
9c9ad730a3 | ||
|
|
3face97046 | ||
|
|
a7289d0550 | ||
|
|
0c4dad5acc | ||
|
|
4590197259 | ||
|
|
dd4489c70e | ||
|
|
201a2bb868 | ||
|
|
01c925816e | ||
|
|
8a383e494e | ||
|
|
c134584b88 | ||
|
|
110c82833b | ||
|
|
4b0144989b | ||
|
|
e3917dd35b | ||
|
|
223fbcaf72 | ||
|
|
d678138ec1 | ||
|
|
0d992803c5 | ||
|
|
5d29863b78 | ||
|
|
94e56ed5fa | ||
|
|
62cbb955b1 | ||
|
|
078245f7bd | ||
|
|
85745d2017 | ||
|
|
c7ca39b4f7 | ||
|
|
955ec3497b | ||
|
|
06fe64875a | ||
|
|
9029e4cbc5 | ||
|
|
ddd1e6827e | ||
|
|
96b3dea70f | ||
|
|
73694c97aa | ||
|
|
6fc4b40632 | ||
|
|
f17babd8d7 | ||
|
|
e6fcb7b365 | ||
|
|
55d5c634d9 | ||
|
|
db33faa628 | ||
|
|
d4fcf68860 | ||
|
|
10db373e67 | ||
|
|
dbb52c2390 | ||
|
|
c7525a682a | ||
|
|
a78dc76f84 | ||
|
|
4dc80c2146 | ||
|
|
cf1334fc73 | ||
|
|
e4a49b5bc8 | ||
|
|
64ded4ea01 | ||
|
|
483a4b75c6 | ||
|
|
6671cf08f4 | ||
|
|
6fffd19043 | ||
|
|
ee16545941 | ||
|
|
abcadcba13 | ||
|
|
b6404ff7a2 | ||
|
|
2ce1b9a640 | ||
|
|
9277238462 | ||
|
|
a4946b7ca2 | ||
|
|
ab7a8d0edf | ||
|
|
b322c998db | ||
|
|
2b13dd4ad7 | ||
|
|
54a030b38b | ||
|
|
42b11e717d | ||
|
|
b9e3f3a92d | ||
|
|
0490bdd07e | ||
|
|
6a35b29352 | ||
|
|
edc0c479c1 | ||
|
|
687d23f654 | ||
|
|
146b5f3d2d | ||
|
|
2446ae3a5d | ||
|
|
66aec0f83c | ||
|
|
a0b578477c | ||
|
|
35da83c347 | ||
|
|
e466491300 | ||
|
|
9a65f51a88 | ||
|
|
eee60e05a5 | ||
|
|
62e38c5df8 | ||
|
|
a3911a4590 | ||
|
|
55f6698fb2 | ||
|
|
f8ab837985 | ||
|
|
e4e16c34ed | ||
|
|
a358c5d9ef | ||
|
|
fefbb4b806 | ||
|
|
5dac2cac5a | ||
|
|
46127c7e95 | ||
|
|
82b711f962 | ||
|
|
bf98b41b16 | ||
|
|
6fba79a1cb | ||
|
|
79852ca220 | ||
|
|
2498fdc892 | ||
|
|
f0a0a99207 | ||
|
|
888335d633 | ||
|
|
7795780336 | ||
|
|
05e020e3b8 | ||
|
|
acf734a1e5 | ||
|
|
22a0741ad1 | ||
|
|
8c275c711b | ||
|
|
f7dd44f55a | ||
|
|
89f278b603 | ||
|
|
be9f967d19 | ||
|
|
4b4e040032 | ||
|
|
aa322365a9 | ||
|
|
9b1c54ea81 | ||
|
|
3432fccd1f | ||
|
|
8bd4676593 | ||
|
|
cb3aed196a | ||
|
|
5a24f8aa52 | ||
|
|
16c4450cd9 | ||
|
|
23c0667dca | ||
|
|
e5d4679471 | ||
|
|
34145e24a7 | ||
|
|
9c7c65f57c | ||
|
|
c68bba8f32 | ||
|
|
5ae635a1e9 | ||
|
|
5529d62263 | ||
|
|
e8f92a0da2 | ||
|
|
9cc00ad29c | ||
|
|
3daf8df3ec | ||
|
|
fbc8ffa1c8 | ||
|
|
f952f513d0 | ||
|
|
a667283cf3 | ||
|
|
900b274a9a | ||
|
|
081755bb71 | ||
|
|
1a20501e55 | ||
|
|
16d4f2b86d |
10
.gitignore
vendored
@ -8,7 +8,17 @@ saves/
|
|||||||
node_modules/
|
node_modules/
|
||||||
.env
|
.env
|
||||||
pipeline/.genprops/
|
pipeline/.genprops/
|
||||||
|
pipeline/.genaudio/
|
||||||
web/assets/models/
|
web/assets/models/
|
||||||
pipeline/.peds_orig/
|
pipeline/.peds_orig/
|
||||||
pipeline/.props_orig/
|
pipeline/.props_orig/
|
||||||
pipeline/.peds_r6/
|
pipeline/.peds_r6/
|
||||||
|
pipeline/_stockpack/
|
||||||
|
pipeline/_records.tsv
|
||||||
|
pipeline/_books.tsv
|
||||||
|
pipeline/_toys.tsv
|
||||||
|
|
||||||
|
# local harness/session files (not repo material)
|
||||||
|
.claude/
|
||||||
|
settings.local.json
|
||||||
|
procityprogress.txt
|
||||||
|
|||||||
@ -4,6 +4,58 @@
|
|||||||
plus the game shell. It runs on my hand-written fixture **and** auto-integrates with Lane A's
|
plus the game shell. It runs on my hand-written fixture **and** auto-integrates with Lane A's
|
||||||
`generatePlan` (which landed mid-session). Everything below was verified live in a browser.
|
`generatePlan` (which landed mid-session). Everything below was verified live in a browser.
|
||||||
|
|
||||||
|
## Round 30 (v7.0-alpha ledger #4) — THE COLLECTION UI + SLEEP surface · qa GREEN 6/6
|
||||||
|
|
||||||
|
The crate panel + game bar in `hud.js` (my DOM-overlay patterns), on F's §30.1 `PROCITY.game`
|
||||||
|
contract — and verified against F's REAL committed core (`207bcff` landed mid-round; my test
|
||||||
|
snapshot diffed byte-identical to it — **nothing stubbed**, unlike C's wave-1 run).
|
||||||
|
- **Game bar** (bottom-right): `day N · $cash` · `CRATE n` (count in the standing HUD; opens the
|
||||||
|
panel, `C` toggles) · `☾ SLEEP` → `game.sleep()`, enabled any time (alpha).
|
||||||
|
- **Crate panel**: cover thumb (pack-atlas CSS crop off the entry's sku — per-shop godverse base or
|
||||||
|
town-wide; parody = type glyph by design), title/artist, `paid $p · where · day d`, newest first,
|
||||||
|
header totals. 50-row rebuild 0.9 ms; 0 draws (DOM-only).
|
||||||
|
- **Construction-gated on the game object itself** (lazy, R19-selector style): `?classic=1`/`?game=0`
|
||||||
|
⇒ zero game DOM, zero listeners, help line byte-identical, 0 console errors (measured, clean tabs).
|
||||||
|
`?noassets` ⇒ 0 stock fetches, placeholders stand.
|
||||||
|
- **SLEEP proven end-to-end**: day 1→2, weekNight 0→1, wake DAWN seg 0, `procity-save/1` on disk,
|
||||||
|
next boot loads it and the UI reads it. **venue.js §30.4 filing fixed as filed** — compat
|
||||||
|
`venueShopId` is now a live weekNight-keyed getter (116 → 490 across a sleep, measured).
|
||||||
|
- Full measurement table + honest notes: LANE_B_NOTES §30. Verified on a port-isolated no-store
|
||||||
|
:8151 snapshot server (not :8130/:8144 — Lane F was mid-flight in the shared tree).
|
||||||
|
|
||||||
|
## Round 9 (Fable's ROUND9 → Lane B) — v2 tour bookmarks + tram (`?tram=1`) · qa GREEN
|
||||||
|
|
||||||
|
Ship-v2.0 round. Two deliverables, both v2-law clean, `qa.sh --strict` GREEN, flags-off byte-identical.
|
||||||
|
|
||||||
|
- **8 v2 tour bookmarks** in `js/world/dbg.js` (16 total): `rain_verandah` (hero — open VIDEO shop w/
|
||||||
|
winmap parallax rooms, CLOSED dark neighbours, night rain), `patronage_door`, `dig_real`,
|
||||||
|
`katoomba_main`, `tram_stop`, `rain_street`, `night_crowd`, `shopfront_detail`. All un-letterboxed,
|
||||||
|
113–188 draws, 0 errors. `tram_stop` picks the **nearest-origin** stop (open framing; spine-end stop
|
||||||
|
was walled in). F shoots these — best hero combo `?winmap=1&weather=rain&dbg=1` seg 5.
|
||||||
|
- **Tram** (`js/world/tram.js`, `?tram=1`, NON-BLOCKING for the tag): one small bus, out-and-back loop
|
||||||
|
on the main-street spine, 3.5 s door-dwell at each `busShelterStops` stop, ping-pong at the ends.
|
||||||
|
**Deterministic** (verified byte-identical across fresh instances). **Exactly 2 draws** (verified via
|
||||||
|
`renderer.info` delta). Composes w/ weather+night (headlights 0.15→1.6 via `lighting.isNight()`).
|
||||||
|
"TOWN LOOP" canvas livery reads in daylight; `ring()` door-bell stub (audio parked). Shell-wiring
|
||||||
|
(2 lines) documented for F in LANE_B_NOTES; no collision in v0 (noted for v3).
|
||||||
|
- **Rain polish** (weather.js, quick R9 debt): capped `gl_PointSize` (clamp 2–22px) + lower base — near
|
||||||
|
drops are now thin streaks, not big blobs. Re-verified: rain smoke still GREEN, money-shot re-shot.
|
||||||
|
- Still deferred (cut line): wind sway on trees/awnings (touches other lanes' materials).
|
||||||
|
|
||||||
|
## Round 8 (Fable's ROUND8 → Lane B) — weather (`?weather=1`), carried from R7 · qa GREEN
|
||||||
|
|
||||||
|
New `js/world/weather.js` + `lighting.setSky()`. Seeded per citySeed → clear/overcast/rain
|
||||||
|
(~57/24/19% over 400 seeds, deterministic). **Rain = ONE draw** (camera-following `Points` + streak
|
||||||
|
shader) + wet ground (roughness/darken on shared ground mats, restored on dispose) + matching rainy
|
||||||
|
sky (`lighting.setSky`). Fully procedural (0 depot fetch). Worst view **141 draws** (≤300); composes
|
||||||
|
with `?winmap`+night — **rain outside a lit interior-mapped window** shot saved. Deterministic, 0
|
||||||
|
console errors, **flag-off byte-identical** (shell never constructs it).
|
||||||
|
- **Exposed the `window.PROCITY.weather` contract for Lane D** (decision #1): `{ state, intensity }`,
|
||||||
|
documented in LANE_B_NOTES with the exact shell-wiring 3 lines for F (I don't touch index.html).
|
||||||
|
`?weather=1` seeded (default town rolls clear); `?weather=rain|overcast|clear` forces a state.
|
||||||
|
- Deferred (cut line): vertex-shader wind sway on trees/awnings — noted, not done (touches other lanes'
|
||||||
|
materials); everything else in the R7 brief landed. Tram stretch not attempted.
|
||||||
|
|
||||||
## Round 6 (Fable's ROUND6 → Lane B) — placed the orphaned street props · qa GREEN
|
## Round 6 (Fable's ROUND6 → Lane B) — placed the orphaned street props · qa GREEN
|
||||||
|
|
||||||
Wired the two published-but-unconsumed street GLBs (`street_bin_01`, `bus_shelter_01`) via the
|
Wired the two published-but-unconsumed street GLBs (`street_bin_01`, `bus_shelter_01`) via the
|
||||||
@ -56,6 +108,45 @@ use-if-ready furniture path (fail-soft; `?noassets` → primitives, 0 network).
|
|||||||
(same as the ped rigs).** File: `furniture.js`.
|
(same as the ped rigs).** File: `furniture.js`.
|
||||||
|
|
||||||
|
|
||||||
|
## Round 12 (v3.0-alpha, VENUES & GIGS, `?gigs=1`) — Lane B street-side — done
|
||||||
|
Built the pub's street presence for the one-pub vertical slice. Dependencies (A venue+`plan.gigs`,
|
||||||
|
C watchPoints, E posters+gig audio, D band+crowd) all landed; F wires it last.
|
||||||
|
- **`web/js/world/venue.js`** (new, B-owned; shell/F constructs under `?gigs`) —
|
||||||
|
`createVenuePresentation(plan, skins, scene) → {group, update(gigState), dispose, venueShopId}`.
|
||||||
|
**Posters:** E's 4 poster skins at each `plan.posters` (x,z,ry) with the gig's `bandName`
|
||||||
|
overprinted in `nameZone` (canvas composite; seeded template; flat fallback if JPEG missing).
|
||||||
|
Verified "THE WOMBATTS" over the band-photo art. **Lit frontage:** warm marquee + emissive bulb row
|
||||||
|
over the venue door, `update(gigState)` ramps the glow. Verified: "The Thornbury Hotel" lit at night.
|
||||||
|
- **Muffled-gig spill** (extended `audio.js`): near the venue (≤26 m) while gig `doors`/`on`, plays
|
||||||
|
`pubrock-live` through a 470 Hz **lowpass** at low distance-gain. Reads `window.PROCITY.gigs` (F)
|
||||||
|
with a clock+`plan.gigs` fallback. Verified: gain ~0.14 muffled at night, fades off far/day.
|
||||||
|
- **Prime flag law held:** flags-off = no gigs/posters/venue (goldens frozen, no citygen edits), audio
|
||||||
|
gig layer created but inert (`plan.gigs` absent → skipped). Byte-identical boot.
|
||||||
|
- **Stretch deferred:** streetlamp-pool night-readability on the venue block (touches shared furniture
|
||||||
|
rendering) — flagged as next polish. **Note to A:** main-street poster positions land mid-road on
|
||||||
|
intersection nodes — a verge/pole offset would seat them better.
|
||||||
|
- Static gates green (I didn't touch citygen; manifest/selfcheck unaffected). Files: `web/js/world/
|
||||||
|
venue.js` (new) + `web/js/world/audio.js` (spill extension) + docs. **F wiring documented in LANE_B_NOTES.**
|
||||||
|
|
||||||
|
## Round 11 (AUDIO) — Lane B WebAudio engine — done
|
||||||
|
Built **`web/js/world/audio.js`** (B-owned) + a guarded create-call in the shell. One `AudioContext`
|
||||||
|
unlocked on the first gesture; self-contained (reads `window.PROCITY`, self-ticks); consumes Lane E's
|
||||||
|
`manifest.audio` (23 assets, 8.7 MB, ≤25 MB) with a 3× fetch retry.
|
||||||
|
- **Layers** (crossfaded, no per-frame alloc): day/night ambience (lighting segment), rain
|
||||||
|
(`weather.intensity`), footsteps (player movement), **shop-door music spill** (nearest open
|
||||||
|
music-shop, distance-gain — verified `video-synth` at ~0.08 by Video Barn's door), tram rumble+bell
|
||||||
|
(`?tram`), SFX (doorbell/door-open on `procity:enterShop`).
|
||||||
|
- **House audio law — all verified live:** boots silent (nothing plays/fetches pre-gesture, 0 console
|
||||||
|
errors); `?mute=1` → silent surface (exists for smokes, 0 fetches); `?noassets=1` → 0 audio/manifest
|
||||||
|
fetches; beds lazy-load; spill is a pure function of shop type.
|
||||||
|
- **For Lane F:** `window.PROCITY.audio` exposes `playInterior({musicKey,toneKey})`/`stopInterior()`
|
||||||
|
(street beds auto-duck while inside), `playSfx`, `setMasterGain`, `mute`, and a `state` diag surface.
|
||||||
|
- **For Lane C:** confirmed `room.audio = { musicKey, toneKey }` on `buildInterior` is the right shape
|
||||||
|
(musicKey → `manifest.music`, toneKey → `manifest.ambience`).
|
||||||
|
- Gates green (manifest validator 0 errors incl. audio 23; selfcheck 1727/1727). `qa.sh --strict`
|
||||||
|
hangs on a headless Playwright gate — ran the static gates directly. Files: `web/js/world/audio.js`
|
||||||
|
(new) + a small `[Lane B R11 audio]` loader in `web/index.html` + these docs.
|
||||||
|
|
||||||
## Round 3 (Fable's ROUND3_INSTRUCTIONS → Lane B) — done
|
## Round 3 (Fable's ROUND3_INSTRUCTIONS → Lane B) — done
|
||||||
- **Task 1 (draw budget, re-measured WITH citizens).** Radius-2 auto is already the spawn default;
|
- **Task 1 (draw budget, re-measured WITH citizens).** Radius-2 auto is already the spawn default;
|
||||||
no config change needed. Drove the real loop (`chunks.update` + `citizens.update`, pop 140, up to
|
no config change needed. Drove the real loop (`chunks.update` + `citizens.update`, pop 140, up to
|
||||||
|
|||||||
884
C-progress.md
@ -3,7 +3,889 @@
|
|||||||
*Status: **v1 complete & verified**. Standalone interiors library + test page. Every shop door opens
|
*Status: **v1 complete & verified**. Standalone interiors library + test page. Every shop door opens
|
||||||
into a unique, seeded, themed interior, generated on demand in ~4ms, byte-identical every revisit.*
|
into a unique, seeded, themed interior, generated on demand in ~4ms, byte-identical every revisit.*
|
||||||
|
|
||||||
Last updated: 2026-07-14 (round 7) · owner: PROCITY-C · reviewer: Fable
|
Last updated: 2026-07-18 (round 30) · owner: PROCITY-C · reviewer: Fable
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Update 2026-07-18 (round 30, v7.0-alpha THE SAVE AND THE CRATE) — the sell counter: cash comes back, always less
|
||||||
|
|
||||||
|
R30 ledger #2 — contract first (LANE_C_PUB §9, its own commit), implementation second. F's save core
|
||||||
|
(ledger #1) had **not landed** when this was built: no `save.js`, no `window.PROCITY.game` anywhere in
|
||||||
|
the tree — so everything below is built to the R30-published shape and verified against a **stub** of
|
||||||
|
exactly that shape (noted loudly below).
|
||||||
|
|
||||||
|
### The contract (§9) — the one number and why
|
||||||
|
**`sellOffer(p) = min(p − 1, max(1, floor(p × 0.5)))`, never < 0.**
|
||||||
|
- **0.5** is the secondhand-dealer margin — the keeper pays half of what he'll sticker it at.
|
||||||
|
- **`min(p − 1, …)` is the no-pump law made STRUCTURAL:** the offer is strictly below `pricePaid` for
|
||||||
|
every `p ≥ 1`, so buy-then-immediately-sell loses ≥ $1 per round trip **by construction** — no
|
||||||
|
balance retune can break F's monotonic-loss gate without violating the contract. At `p = 1` the
|
||||||
|
offer is $0 and SELL disables (the keeper won't buy what he can't sticker).
|
||||||
|
- **Alpha basis = `pricePaid`** (every alpha buy is at asking price, so it IS the buy-side price the
|
||||||
|
player faced; there are no guide bands yet). **Beta swaps the basis to `bandLow`, not the shape.**
|
||||||
|
- Matching is **type = type, strict, fail-closed**: an untyped item sells NOWHERE (a matcher passing
|
||||||
|
untyped items would "match" everywhere — vacuous-gate law applied to a matcher).
|
||||||
|
|
||||||
|
### What shipped (C's files only)
|
||||||
|
- **`web/js/interiors/sell.js`** — `createSell()` + pure `sellOffer` / `sellableIn` / `nearCounter`.
|
||||||
|
DOM-only (zero draws, zero GPU), the dig's manila price-sticker mirrored to the left with a green
|
||||||
|
offer sticker; one item at a time, `‹ ›` cycling, ✕/Esc; till rings from the counter emitter (R28
|
||||||
|
one-shot pattern). Same consumer pattern as `dig.js`: the single side-effect is `onSell(item,
|
||||||
|
offer)` — **C never mutates `game.collection`** (read-only law, §9.3).
|
||||||
|
- **`web/js/interiors/wallet.js`** — `wallet.sell(item, offer)`: the credit mirror of `buy()`.
|
||||||
|
This file never prices; the offer comes in from `sellOffer`.
|
||||||
|
- **`web/interior_test.html`** — E-at-counter routing (bin → dig · counter → sell · shelf → buy) gated
|
||||||
|
on `window.PROCITY.game` presence (the in-game classic-pure law, exactly); `?sellstub=1` stubs the
|
||||||
|
published game shape for verification until F's core lands.
|
||||||
|
|
||||||
|
### Verified (fresh context, no-store server on :8144 — NOT :8130, zero console errors throughout)
|
||||||
|
- **The sale:** record shop seed 1990, at the bench (`nearCounter` true at 0.9 m, false across the
|
||||||
|
room) → card `THE FIBROS — "Servo at Midnight" · you paid $24 · offer $12` → SELL → **cash 163 →
|
||||||
|
175 (+$12 < $24 paid)**, collection 4 → 3, sold item gone, toast + till fired.
|
||||||
|
- **Type fence:** stub carried 2 records + 1 book + 1 untyped item. Record shop cards **exactly the 2
|
||||||
|
records**; book shop cards **exactly the book** ($7 on $15); the untyped item cards **nowhere**.
|
||||||
|
- **The disabled edge:** the $1 record offers $0 → button reads NOT WORTH BUYING, disabled.
|
||||||
|
- **No-pump round-trip (C's mini-gate, precursor to F's #5):** buy-then-sell through the real wallet +
|
||||||
|
card path, prices {2,3,5,9,24,60,61,137} → **8/8 strictly negative** (−1,−2,−3,−5,−12,−30,−31,−69).
|
||||||
|
- **The gate discriminated first** (vacuous-gate law, proven live): my first loop pushed a 999-priced
|
||||||
|
item into the collection **without checking `wallet.buy` succeeded** — broke wallet, no debit, then
|
||||||
|
a +$499 "sale". A pump — from an item never paid for. **Finding for F below.**
|
||||||
|
- **Fail-soft:** boot with no stub (= no game layer) → E at the counter does nothing, `open()` false,
|
||||||
|
zero DOM, zero errors. `dispose()` leaves 0 panels/0 toasts in the DOM.
|
||||||
|
- **Budgets:** draws with the card open ≡ closed (16 ≡ 16 — DOM is zero-draw); `buildInterior` and all
|
||||||
|
room code untouched this round; 50-room soak re-run green in the same session: **avg 3.4 ms · worst
|
||||||
|
8.2 ms · leak geo 0 / tex 0 · determinism identical · worst draws 73 ≤ 350**.
|
||||||
|
|
||||||
|
### Findings / asks (→ F, for the save core + gates)
|
||||||
|
1. **The collection item needs `type`** (registry stock type, stamped at buy time). The R30 shape
|
||||||
|
(`{townKey, godverseShopId|shopId, sku|slotId, pricePaid, dayFound}`) doesn't carry it, and without
|
||||||
|
it **nothing is sellable anywhere** (my matcher is fail-closed on purpose). `title`/`artist` are
|
||||||
|
strongly asked too (B's collection UI needs them; my card falls back to the sku label — honest, ugly).
|
||||||
|
2. **`pricePaid` must only ever be written by a SUCCESSFUL debit.** The no-pump law's structure
|
||||||
|
assumes pricePaid was actually paid — my broken first loop showed a collection entry that skipped
|
||||||
|
the wallet debit converts straight into a money pump. F's no-pump gate should include this negative
|
||||||
|
control: a collection entry minted without a debit must be impossible (or the gate must fail).
|
||||||
|
3. **The removal handoff:** in-game, the sold item must leave the collection **via the game API** —
|
||||||
|
`onSell` is where F calls it. My harness splices its own stub (its array, its right); C's modules
|
||||||
|
never touch game state. When F publishes the API name, §9.3 references it verbatim.
|
||||||
|
|
||||||
|
R28 §Lane C (ledger #3) — my own idea, parked since R7, brief states acceptance only. Wave 1, alongside E+B.
|
||||||
|
|
||||||
|
### The trap my own parked note set
|
||||||
|
v3.0's deferred note said: *"`userData.audioEmitter = musicKey` so Lane B can position/spatialise the
|
||||||
|
bed"*. **Both halves were wrong**, and the second one is dangerous:
|
||||||
|
|
||||||
|
1. **The shape.** The engine holds no reference to the room group — it would have to traverse a scene it
|
||||||
|
cannot reach. Emitters belong on `room.audio` as data, where they ride the **existing**
|
||||||
|
`playInterior(room.audio)` call: no new lifecycle, no traverse, no dispose hook, no F plumbing.
|
||||||
|
2. **The method.** "Spatialise" invites a `PannerNode`. **Nothing in this tree sets `ctx.listener`** (zero
|
||||||
|
hits, whole tree), so a panner resolves against a listener frozen at (0,0,0) — and since rooms are built
|
||||||
|
in room-local coords at the origin, that is *the room's own centre, forever*. Measured with
|
||||||
|
`OfflineAudioContext` on a real pub stage bed:
|
||||||
|
|
||||||
|
| wiring | RMS at door | RMS at stage | ratio |
|
||||||
|
|---|---|---|---|
|
||||||
|
| panner, listener never set (**the engine as it ships**) | 0.106905 | 0.106905 | **1.0000** |
|
||||||
|
| panner + listener tracking the player | 0.055665 | 0.500000 | 8.98 |
|
||||||
|
|
||||||
|
It **plays, throws nothing, warns nothing, and never changes by one sample as you cross the room** —
|
||||||
|
while failing at the only thing it exists to do. *"Routed through a panner?"* yes. *"Audible in a
|
||||||
|
venue?"* yes. *"In a shop?"* yes. All green, all worthless. **The only assertion that can see this is a
|
||||||
|
DELTA: gain at the door ≠ gain at the stage.** Vacuous-gate law, 4th application — and this one had no
|
||||||
|
author to blame but me. There is no listener code anywhere to teach a lane otherwise.
|
||||||
|
|
||||||
|
**The house idiom was already right**: tram rumble (R=55), door spill (R=9), gig spill (R=26) are all
|
||||||
|
hand-rolled distance-gains against `PROCITY.camera.position`. No listener, no panner. Emitters match it.
|
||||||
|
|
||||||
|
### What shipped (C's files only)
|
||||||
|
- **`room.audio.emitters`** = `{ stage?, counter? }`, room-local, 3-dp, **18/18 same-seed deep-equal**.
|
||||||
|
`r` (reach) is the **room diagonal, not a constant** — measured diagonals 9.81–15.63 m, and scaling to
|
||||||
|
the room keeps door→stage proximity in a tight **0.42–0.59** band from a pokey band_room to a hall pub.
|
||||||
|
`floor` is the room-filling residue, **never 0** — a PA fills a pub; what changes as you walk in is the
|
||||||
|
*balance*, not the presence. Swing **2.65–4.52 dB** (John R20: *subtle changes are great*).
|
||||||
|
- **The till rings from the counter** — *audible positioning in a normal shop with **no engine change***,
|
||||||
|
via B's already-public `playSfx(key,{gain})`: C does its own distance math, hands B a scalar. Measured
|
||||||
|
through the real BUY button in a real record shop: **0.9489 @ 0.60 m vs 0.6436 @ 4.19 m = 3.37 dB**;
|
||||||
|
negative control (no emitters) fires at exactly **1.0**. `sfx-till.ogg` had been on disk since 15 Jul and
|
||||||
|
had **never once played**. Routing, not content — exactly as the brief said.
|
||||||
|
- **`?mute=1` now silences the crate riffle.** See below.
|
||||||
|
|
||||||
|
**Why the venue half is an ask, not a ship:** a **one-shot** resolves its gain once, at fire time, from a
|
||||||
|
position C already knows. A **bed** must be re-gained every frame inside the engine's `update()` loop —
|
||||||
|
B's file. The split is structural, not political. B got a ~12-line paste-ready diff in the house idiom; C
|
||||||
|
did not touch audio.js. The letter of "consulted not modified" could have been satisfied by reaching
|
||||||
|
through B's exposed `state.layers` diag to rewire its graph from outside — **that backdoor is strictly
|
||||||
|
worse than the edit it avoids** (invisible to B, breaks on any change of B's). Refused.
|
||||||
|
|
||||||
|
### Two bugs found on the way
|
||||||
|
- **F drops emitters on the one night they matter.** `interior_mode.js:289` rebuilds the spec on a gig
|
||||||
|
night: `{ musicKey: ra.gigKey, toneKey: WALLA_KEY }` — every unnamed field dies. Measured on a real
|
||||||
|
gig-night pub: `quietNight_emittersSurvive: true`, **`gigNight_emittersSurvive: false`**. Silent, fail-
|
||||||
|
soft, no error. -> F: don't just spread `ra` — pass emitters **deliberately, gig-night only**, because on
|
||||||
|
a quiet night the radio and room-tone genuinely *are* room-filling.
|
||||||
|
- **`?mute=1` never silenced the riffle (R5 → R28).** `dig.js` opened a **second, private AudioContext**
|
||||||
|
straight to `destination`, breaking both of audio.js's stated laws (*ONE AudioContext*; *mute forces
|
||||||
|
silence*). It survived three epochs because **no gate asserts mute ⇒ silence**: the engine returns its
|
||||||
|
silent surface and looks honest *from its own side*, while the sound came from somewhere it never knew
|
||||||
|
existed. *A gate that asks the engine whether it is muted can only ever answer yes.* Fixed by reading
|
||||||
|
B's **public** `.muted` getter (consulted, not modified), URL fallback when standalone. Measured with a
|
||||||
|
positive control so the probe is provably able to see sound: live 1 ctx / 1 source; **muted 0 / 0**.
|
||||||
|
Blips stay procedural on purpose — zero-fetch, deterministic, and they still work under `?noassets=1`
|
||||||
|
where a sampled riffle cannot. Only mute was broken; only mute was fixed.
|
||||||
|
|
||||||
|
### Harness honesty (again)
|
||||||
|
First till rig reported a clean 3.37 dB — from a **contaminated harness**. `createDig` appends a panel per
|
||||||
|
call and `close()` doesn't remove it, so `querySelector` was clicking **trial 1's stale button** every
|
||||||
|
time. The tell was `panelUp: false` on all three trials, and the **negative control** returning 0.6436
|
||||||
|
where it must return 1.0. The gains happened to match the formula exactly — I still refused them and
|
||||||
|
re-ran isolated (`panelCount: 1`, `panelUp: true`, control **1.0** ✓). R27's lesson holds: *never report a
|
||||||
|
number from a rig you have watched lie, even when you like the number.* The control is what caught it.
|
||||||
|
|
||||||
|
QA GREEN (6/0/0/0). Docs: **LANE_C_AUDIO.md → v3.1** (contract + both asks + every measurement above).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Update 2026-07-17 (round 27, v5.0 THE LIVE CRATE) — the tier-2 seam + LANE_C_PUB v5-FROZEN
|
||||||
|
|
||||||
|
R27 §Lane C (ledgers #2 + #6). Wave 1 — ran after F tagged `v5.0-beta` (`78848ed`, 7/0/0).
|
||||||
|
|
||||||
|
### The seam ruling (§8): bless the direct wire — but the *form* was the real question
|
||||||
|
Fable asked: publish a post-build hook, or bless the direct wire? **Answer: bless it — no lifecycle hook.**
|
||||||
|
F already holds `currentAdapter` (`interior_mode.js:67`) and passes it to `dig.open()` **per dig** (`:111`),
|
||||||
|
so the live read lands, F updates the pack, and the next riffle picks it up. Read not landed / slow / dead ⇒
|
||||||
|
the adapter is simply the tier-1 pack ⇒ the dig never blocks. No C lifecycle change.
|
||||||
|
|
||||||
|
**But the question underneath it — *how* `gone` applies — is C's, and the obvious wiring is wrong.**
|
||||||
|
`pickRealOffers` indexes `pack.items[(rr()*len)|0]`, so **filtering the array reshuffles the crate**.
|
||||||
|
Measured across 5 crates: **one record selling changed 21 OTHER records** (6/2/2/6/5 collateral). Pick-then-
|
||||||
|
omit — draw from the stable full list, drop gone *after* — has **0 collateral**: a crate with 3 sold shows
|
||||||
|
13, not a different 16. Selling one record must remove one record, not re-stock the shop.
|
||||||
|
|
||||||
|
**And the trap: both forms hide the sold record.** So F's planned sandbox assertion — "a real `gone` item
|
||||||
|
never renders" — **goes green on the wrong wiring**. It cannot see the 21. → **F: assert on collateral (the
|
||||||
|
survivors are unchanged across a `gone` delta), not absence.** That's the vacuous-gate law's third
|
||||||
|
application, and the cheapest to miss, because it passes.
|
||||||
|
|
||||||
|
**Landed** (`dig.js`): `pack.gone` (Set|array) omitted at pick time; **F's invariant published** — update
|
||||||
|
item *fields* in place (live price) freely, **never add/remove/reorder `items[]`**; that identity+order IS
|
||||||
|
the seeded pick's stability (the same lesson as R25's positional ids, one layer up).
|
||||||
|
**Determinism boundary sits exactly here:** server down ⇒ byte-identical tier 1 (gate-able); server up ⇒ the
|
||||||
|
crate legitimately changes — tier 2 is non-deterministic *by design*, so **no gate may assert dig determinism
|
||||||
|
with the server up**, and `pack.gone` never enters a seeded stream or a golden.
|
||||||
|
**Scoped honestly:** the bin fan is decorative (a different seeded pick from the dig's offers), so a sold
|
||||||
|
cover may linger there until rebuild — that's when I'd publish a post-build hook, and the epoch's claim
|
||||||
|
doesn't need it, because *the crate you riffle is true*.
|
||||||
|
|
||||||
|
**Verified** — pure-function measurement for the collateral claim, then end-to-end through the real dig's
|
||||||
|
public surface (pull → Esc → scroll → pull): sold record gone · **survivors 4/4 · collateral 0** · `items[]`
|
||||||
|
untouched at 120 · live in-place price shows on the next pull. *My first dig harness returned the same title
|
||||||
|
4× (I never un-pulled), so its "survivors" number was a harness artifact — I re-ran it properly rather than
|
||||||
|
report a metric I'd just watched lie.*
|
||||||
|
|
||||||
|
### Docs freeze (§6): LANE_C_PUB is **v5.0-FROZEN**, whole document
|
||||||
|
Marked, with the case law kept at the top rather than buried: three of this doc's amendments exist because a
|
||||||
|
rule of mine met a case written *after* it (§7's naming vs its own loader; §7.2a's `sku_` before mint) — and
|
||||||
|
both times the lane building to my rule took the red. The header now says it plainly: **when a line here
|
||||||
|
meets a case it didn't foresee, the line is the stale thing — amend it, don't bend the case.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Update 2026-07-17 (round 26 wave 4) — the mint-id fence: my rule was the stale thing
|
||||||
|
|
||||||
|
F's fourth hold traced to **my** §7.2a. I wrote "`sku_`-prefixed for real-stock packs" in R25 — **before mint
|
||||||
|
existed** — so it names one space where there are two. E implemented it faithfully and its validator did
|
||||||
|
exactly what I told it to: demand `sku_` on 14 mint crates. **The check wasn't wrong; my rule was.** Same
|
||||||
|
shape as R23, and I'll say it plainly: this is twice now that a contract line of mine met a case written
|
||||||
|
after it and the lane building to it took the red.
|
||||||
|
|
||||||
|
**Amended (§7.2a): the id form is sourcing-scoped, and the prefix IS the fence.**
|
||||||
|
| `sourcing` | id form | the space it names |
|
||||||
|
|---|---|---|
|
||||||
|
| `real` | `sku_<POS id>` | the shop's POS inventory — a physical copy on a shelf |
|
||||||
|
| `mint` | `mint_<dealgod listing id>` | a dealgod listing — a plausible stand-in, not that shop's stock |
|
||||||
|
|
||||||
|
**Why G was right to refuse the easy green.** Filing dealgod listing ids under `sku_` would have gone green
|
||||||
|
in one line and recreated the R24 collision **inside the economy layer** — where **tier-2's sold-means-gone
|
||||||
|
looks up POS skus**. R24's collision entered the *wrong shop*; the same collision in the money layer **sells
|
||||||
|
the wrong record** — a real copy, out of a real shop, to satisfy a minted stand-in. A prefix that lies costs
|
||||||
|
money, not a gate. Blessed as design: disjoint namespaces, and no gate, lookup or economy path may treat
|
||||||
|
them as one (same standing note as plan-ids vs godverse-ids).
|
||||||
|
|
||||||
|
**Verified against every crate in the tree before handing E the line:** 15 crates (1 real + 14 mint) —
|
||||||
|
**0 fails** under the amended rule (prefix matches `sourcing`, ids unique within pack), and **0 ids appear in
|
||||||
|
both spaces** across 344 distinct ids. The fence already holds in the data; only my wording didn't.
|
||||||
|
|
||||||
|
**→ E:** assert the form against `sourcing` (you already read it at line 178) — `sku_` for real, `mint_` for
|
||||||
|
mint; a crate wearing the other's prefix FAILS. That's **stronger** than the check it replaces, not looser.
|
||||||
|
**→ F:** 6/1/0 should flip to 7/0/0. Doc-only from C; no code, no atlas bytes, no goldens.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Update 2026-07-17 (round 26, v5.0-beta EVERY CRATE DIFFERENT) — three contract lines + the price sticker
|
||||||
|
|
||||||
|
R26 §Lane C, wave 1 (ledgers #1, #4, #6). Contract lines published FIRST — G and E build to them.
|
||||||
|
|
||||||
|
### The brief said `tier`. It can't be `tier` — that name is taken twice (§7.2b)
|
||||||
|
G's atlases already emit **`tier: 1`** = the **charter's ladder rung** (0 parody · 1 static-real · 2 live),
|
||||||
|
and `kind` is already the stock type. **A mint atlas is also rung 1** — same mechanism (a statically baked
|
||||||
|
per-shop atlas); only the data's *origin* differs. Real-vs-mint is an **orthogonal axis, not a rung**.
|
||||||
|
Overloading `tier` would put two meanings in one name — the same species as the plan-id/godverse-id
|
||||||
|
collision that just became a standing note, and as R25's positional ids. **Ruling: top-level
|
||||||
|
`sourcing: "real" | "mint"`, REQUIRED; `tier` stays the numeric rung.** A mint index MUST NOT carry
|
||||||
|
POS-snapshot fields (the separation IS the honesty); gates read `sourcing`, and an index without it FAILS.
|
||||||
|
|
||||||
|
### `condition` isn't in the index — the ledger assumed it was (§7.2c)
|
||||||
|
Ledger #4 said the card gets "the fields the atlas index already carries (artist/title/price/**condition**)".
|
||||||
|
It carries artist/title/price/price_band — **not condition**. G's *snapshot* has `condition` + `sleeve_cond`
|
||||||
|
and drops them at emit, so `pickRealOffers` mapped `cond: ''` and the card rendered dangling separators
|
||||||
|
around a hole. That *was* the "database row". Ruled: `condition`/`sleeve_cond` are **optional** index fields,
|
||||||
|
emitted when the source has them; **no invented grades** on mint items — a plausible-but-fictional grade is
|
||||||
|
exactly what `sourcing` exists to prevent.
|
||||||
|
|
||||||
|
### The atlas manifest (§7.2d) — existence declared, never probed
|
||||||
|
`web/assets/stock_godverse/index.json` is the authority for which shops carry atlases; **no entry ⇒ no
|
||||||
|
fetch ⇒ zero 404s**, so the zero-console-errors law holds with no attribution exception. Shape + both-ways
|
||||||
|
consistency (atlas without manifest entry FAILS, and vice versa) in §7.2d. A tree with no godverse atlases
|
||||||
|
and no manifest is a legitimate silent zero-shop state, not an error.
|
||||||
|
|
||||||
|
### The price sticker (ledger #4, code)
|
||||||
|
The pull panel is now the shop's **price sticker**, not a data readout: manila card, slight rotation +
|
||||||
|
shadow, typewriter caps, italic title, a rubber-stamped condition grade, and **the round yellow $ sticker
|
||||||
|
the sleeve art already uses** (dig.js's own canvas language — the panel now echoes it instead of fighting
|
||||||
|
it). Fields compose only when present: **a missing field never leaves a stray separator.**
|
||||||
|
|
||||||
|
**Verified — five card shapes, both star branches, fresh context:**
|
||||||
|
| case | card |
|
||||||
|
|---|---|
|
||||||
|
| real grail | `$28` · Deep Dish · *Penetrate Deeper* · `VG+/VG` · **★ GRAIL** |
|
||||||
|
| real standard, graded | `$15` · `VG+` · `STANDARD` |
|
||||||
|
| real ungraded (today's index / mint) | `$15` · `COLLECTOR` — grade omitted cleanly |
|
||||||
|
| parody | `$15` · THE VERANDAHS · *Tinny* · `NM` · `1973 PUNK` |
|
||||||
|
| parody rare (seed 4) | `$16` · HILLS HOIST · *Servo at Midnight* · `VG` · `1973 POP` · **★ RARE** |
|
||||||
|
|
||||||
|
**The verification caught a defect in my own card:** a real *grail* rendered **"GRAIL ★ GRAIL"** — `s` carried
|
||||||
|
the price_band *and* `rare` derived from that same band. (Parody is immune: its `s` is a genre.) Fixed at the
|
||||||
|
mapping — grail becomes the ★ stamp and leaves `s` empty. And I did not leave the parody-rare branch on
|
||||||
|
assumption: scanned seeds until a rare pull appeared (seed 4) and asserted it says RARE, not GRAIL. An
|
||||||
|
unexercised branch is an unproven one.
|
||||||
|
|
||||||
|
**Budgets:** the card is DOM — zero draws, zero tris, no 3D change. Ceilings untouched.
|
||||||
|
**→ G:** `sourcing` + `condition`/`sleeve_cond` + the manifest. **→ E:** assert `sourcing` (absent ⇒ FAIL),
|
||||||
|
the mint/POS-field separation both ways, and manifest↔files both ways. **→ F:** consult the manifest before
|
||||||
|
fetching — that's what retires the 404 attribution exception.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Update 2026-07-17 (round 25, the tag earned) — sku-derived slot ids (ledger #3, C's one line)
|
||||||
|
|
||||||
|
R25 §Lane C: one contract line, published first so G re-emits against it. **§7.2a added** — F's id-collision
|
||||||
|
finding made structural, while there are two atlases to migrate instead of fifty.
|
||||||
|
|
||||||
|
**The collision, measured:** both packs number items positionally from zero, so **all 120 godverse ids are
|
||||||
|
also generic-pack ids — 120/120 overlap.** An id-equality gate cannot discriminate which pack a rendered
|
||||||
|
item came from; that's exactly why the brief's own #7a spec was vacuous. (The vacuous-gate law's first
|
||||||
|
application broke the law — and my own R23 metric had the same disease. It's a persistent species.)
|
||||||
|
|
||||||
|
**The rule (§7.2a):** `items[].id` MUST derive from the source's stable key, never from position, and MUST
|
||||||
|
be unique across ALL packs. Real-stock packs: **`id = "sku_" + <POS sku>`** — G's snapshot already carries
|
||||||
|
`sku` per item (e.g. `20260314070442`), it's just dropped at emit, so this is index-only. Generic packs keep
|
||||||
|
`rec_NNNN` as their namespace; real-stock must not emit into it.
|
||||||
|
|
||||||
|
**I gave it two reasons, and the second is the one that outlives the gate.** The collision is the presenting
|
||||||
|
symptom. The deeper defect is that **position is not identity**: a positional id renumbers on every re-bake,
|
||||||
|
so selling one record out of crate 550 silently makes `rec_0050` a *different* record. Anything holding an
|
||||||
|
id across snapshots — a wallet purchase, a saved dig, a gate assertion, and above all **tier-2's
|
||||||
|
sold-means-gone** — would point at the wrong item. The POS sku is stable because it's the shop's own
|
||||||
|
identity for that copy (`release_id` identifies the pressing, not the copy). So this line isn't only a gate
|
||||||
|
fix; it's the identity tier-2 will need, bought now for free.
|
||||||
|
|
||||||
|
**Scope kept honest:** index-only, no atlas bytes, no town cache, no goldens. `id` isn't read by the loader
|
||||||
|
(§7.3) — the dig/buy consumers and the gates read it, so no C code changes. **→ G:** re-emit with `sku_`
|
||||||
|
ids. **→ E:** assert the form (present, unique within the pack, `sku_`-prefixed for real-stock).
|
||||||
|
|
||||||
|
*Process note:* F's six files were staged in the shared tree mid-flight; committed by explicit pathspec, so
|
||||||
|
F's session is untouched.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Update 2026-07-17 (round 24, THE ALPHA COMPLETION) — the contract fix (ledger #1, the root cause)
|
||||||
|
|
||||||
|
R24 §Lane C. **The root cause was mine and it's closed.** F held the tag because my published atlas
|
||||||
|
contract and my own loader were mutually incompatible; the ruling — **the runtime is the authority** — is
|
||||||
|
now the section's first line. Published FIRST (half-day rule; G and E re-emit against it).
|
||||||
|
|
||||||
|
**What was actually wrong.** My R22 §7 invented `stock_shop_<godverseShopId>_index.json`. The loader
|
||||||
|
(`stockpack.js:25`) fetches `` `${base}stock_${type}_index.json` ``. **An index named per my contract can
|
||||||
|
never be fetched by my own code.** So G built runtime-compatible (correct), E built contract-compatible
|
||||||
|
(also correct — the contract said so), and E's validator globbed `stock_shop_*_index.json` → **zero files
|
||||||
|
→ vacuous pass**. Nobody was wrong; the contract was. I wrote it, so I own it.
|
||||||
|
|
||||||
|
**§7 rewritten against the code (LANE_C_PUB):**
|
||||||
|
- **Authority rule:** `stockpack.js` IS the contract; if the doc and the loader disagree, the doc is a bug.
|
||||||
|
- **Layout ratified as G shipped it:** `assets/stock_godverse/<godverseShopId>/stock_<type>_index.json`.
|
||||||
|
`type` is load-bearing (it picks the slot via `SLOT_FOR`: record→sleeve). `stock_shop_*` is **retired**
|
||||||
|
→ **E: the validator glob becomes `stock_godverse/**/stock_*_index.json`** (that's the vacuous gate's
|
||||||
|
actual cause).
|
||||||
|
- **Runtime-read vs runtime-ignored, stated exactly.** Reads `atlases[]` + `items[{atlas, uv}]` (uv origin
|
||||||
|
**top-left**; the loader flips V — don't pre-flip). Ignores `version/atlas_px/cell*/count/kind/tier/shop/
|
||||||
|
crate/provenance` entirely.
|
||||||
|
- **The provenance drift dies (§7.3):** since the runtime reads none of it, the authority is E's validator
|
||||||
|
+ the 23 shipped town caches — both use **top-level `license` (US), `attribution`, `generator`,
|
||||||
|
`snapshot`**. British `licence` retired; **G lifts+renames those four**, keeps its rich nested
|
||||||
|
`provenance{}` (source/sha256/determinism) as colour. `atlas_px` stays REQUIRED (E checks it against real
|
||||||
|
dims — a genuine falsifiable check). `cell*` informational: **C reads `uv`, never `cell`** — and don't
|
||||||
|
shrink the cell for VRAM, the dig renders covers close-up.
|
||||||
|
|
||||||
|
**Code landed (the `type+base` keying, F's filed item):**
|
||||||
|
- `stockpack.js`: cache identity is now `` `${base}|${type}` ``; `getStockPack(type, base = DEFAULT_STOCK_BASE)`.
|
||||||
|
- `interiors.js`: new **`opts.stockBase`** — the seam F wires (`buildInterior(shop, THREE, { stock:'real',
|
||||||
|
stockBase: 'assets/stock_godverse/<id>/' })`). Omitted ⇒ town-wide packs, byte-identical to today.
|
||||||
|
- Pre-v5 callers untouched (`getStockPack('record')` still means the town pack).
|
||||||
|
|
||||||
|
**Verified fresh — and I had to fix my own metric first.** R23 I claimed "8/8 atlas-backed"; that checked
|
||||||
|
`material.map` exists — **but a parody canvas has a map too**. That's the same species as the vacuous gate
|
||||||
|
I'd just been handed. Re-asserted falsifiably:
|
||||||
|
- **Every sleeve samples G's atlas FILE** — texture sources are exactly `stock_godverse/3962749/
|
||||||
|
stock_record_atlas_00.webp` / `_01.webp`; pack items ≡ index items **by id**. Titles are real
|
||||||
|
(*Yoshitoshi Artists – In House We Trust / Deep Dish – Penetrate Deeper*).
|
||||||
|
- **Negative control (what makes it a gate):** the parody room's stock textures are `(canvas, no src)` and
|
||||||
|
touch the godverse atlas **zero** times — the metric discriminates.
|
||||||
|
- **Collision FIXED:** shop pack (120 items) + town pack (350) coexist as distinct objects.
|
||||||
|
- **Fail-soft FIXED with a populated cache:** missing base ⇒ `null` ⇒ parody (the R23 correctness bug —
|
||||||
|
an unstocked shop inheriting a cached crate — is gone).
|
||||||
|
- Ceilings: **98 draws** with real covers via `opts.stockBase`; `pathOK`; 0 carves; **0 console errors**.
|
||||||
|
|
||||||
|
**Scoped, not slid:** the **LRU + dispose** (§7.6) lands at **v5.0-beta**, not here — with one stocked shop
|
||||||
|
there's no accretion, and safe eviction needs a room-exit hook (a live room holds the pack's shared
|
||||||
|
material), which arrives with F's per-shop preload. Written into the contract so it can't slide silently.
|
||||||
|
|
||||||
|
**→ F:** the seam is `opts.stockBase`; §7.5 has the call. **→ G:** layout ratified, re-emit provenance field
|
||||||
|
names only (§7.3). **→ E:** the glob (§7.1) is the vacuous gate's cause.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Update 2026-07-16 (round 23, v5.0-alpha THE FIRST REAL CRATE) — the crate in the room (ledger #3)
|
||||||
|
|
||||||
|
R23 §Lane C. **Verdict: the contract holds — G's atlas needs no re-emit.** Verified against G's
|
||||||
|
`stock_godverse/3962749/` (120 real records from Monster Robot Party's DEEP HOUSE crate 550, 2 × 2048²,
|
||||||
|
cell 256²) in a fresh context. Ran **before** Fable's merge deliberately — if the contract failed, that
|
||||||
|
blocks the merge. It didn't.
|
||||||
|
|
||||||
|
**PASS — the first real crate renders in a PROCITY room:**
|
||||||
|
- **Resolves through the existing `stockAdapter` path** via a base override — no C code change needed for
|
||||||
|
the single-shop alpha. 120 items · 2 atlases · **8/8 stock meshes atlas-backed** (real covers). The
|
||||||
|
sleeves are legible on screen: *peperecordings · DEEPCHILD · "39" · mff FREAKS · The Angels Instinct feat
|
||||||
|
Charlie Ashcroft* — a real deep-house crate.
|
||||||
|
- **Ceilings hold:** **118 draws** with real covers (≤350 law), `pathOK`, 0 carves.
|
||||||
|
- **Fail-soft holds** (clean cache): missing atlas → `null` → parody (20 parody meshes, 110 draws); no
|
||||||
|
adapter (the `?noassets` shape) → parody, zero fetch. **0 console errors.**
|
||||||
|
- **G's index is compliant:** `items[]` sorted by id (my R22 determinism ask honoured), provenance present,
|
||||||
|
`uv ⊂ [0,1]`, 2 atlases = exactly my cap, `cell_w/cell_h` harmless (C reads `uv`, never `cell`).
|
||||||
|
|
||||||
|
**I got a number wrong, and the first real data caught it (LANE_C_PUB §7.2 CORRECTED).** My R22 ceiling
|
||||||
|
said "≤8 MB decoded" — derived from assuming a "typical shop" is ~16 items @1024². **A real record shop is
|
||||||
|
120 records.** At the contract's own 256² cell that's 2 × 2048² ≈ **32 MB**, 4× my figure. The *structural*
|
||||||
|
rules held (≤2 atlases; 2048² for 60+ items — G landed exactly on the cap); only the MB number was wrong,
|
||||||
|
because it described a shop that doesn't exist. **Corrected:** ≤2 atlases/shop @≤2048², **LRU = 1 shop's
|
||||||
|
atlas set resident ⇒ ≤32 MB peak** (still bounded; still far better than the 181 MB unbounded accretion E
|
||||||
|
measured). And explicitly: **don't shrink the cell below 256² to save VRAM** — the dig shows a cover
|
||||||
|
close-up; resolution is the one thing the player actually sees.
|
||||||
|
|
||||||
|
**G's ask #1 CONFIRMED — and worse than reported (LANE_C_PUB §8: C's spec, C's fix).** `stockpack.js` keys
|
||||||
|
`_packs`/`_resolved` by **`type` alone**, so `base` is **ignored on a cache hit**. Measured:
|
||||||
|
`preloadStockPack('record', {base:'…/3962749/'})` then `preloadStockPack('record', {base:'assets/models/'})`
|
||||||
|
**returned the identical object**. Consequences: (1) two stocked shops collide — the 2nd serves the 1st's
|
||||||
|
crate (blocks v5.0-beta, as G said); (2) **it breaks the fail-soft law** — an *unstocked* shop of a cached
|
||||||
|
type inherits that crate instead of parody. The second is a correctness bug, not a mix-up. Seam spec
|
||||||
|
published (cache key = `type + base`; per-shop preload on entry; LRU=1 + dispose; **C consumes whatever
|
||||||
|
identity field lands — C does not invent it**). **C owns and lands it** — the §7 promise coming due.
|
||||||
|
|
||||||
|
**Two gaps block F's gate #7a (neither is C's; both flagged):** (1) **the crate has no street** —
|
||||||
|
`redhill_real` doesn't exist and monsterrobot isn't in any town's `shops[]` (G's ask #2 → E/Fable);
|
||||||
|
(2) the shell's `interior_mode.js:192` calls `getStockPack(shop.type)` with `base` fixed to
|
||||||
|
`assets/models/`, so it can't reach a per-shop pack until §8's seam + the identity field land (C+F).
|
||||||
|
|
||||||
|
*Process note:* the shared tree was checked out on `lane-G-godverse` for Fable's review, so I committed
|
||||||
|
these C docs from a temporary `git worktree` on `main` — the shared checkout was never touched, and G's
|
||||||
|
branch keeps the disjoint namespace the two-studio merge law depends on.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Update 2026-07-16 (round 22, v4.0 THE EPOCH CLOSE) — 23-town door-soak (#5) + the G2a contract (#3)
|
||||||
|
|
||||||
|
R22 §Lane C, both items. Wave 3 — ran **after A pinned the 36 goldens** (`3face97`) and E's tri-diet fix
|
||||||
|
(`d81e499`), so this soaks the final v4.0 world.
|
||||||
|
|
||||||
|
### Ledger #5 — sampled door-soak across all 23 towns
|
||||||
|
|
||||||
|
The five-town harness scaled: **~10 doors/town, sample takes one of every type present then stride-fills**, so
|
||||||
|
every archetype is covered town-set-wide. **238 doors · 1,133 in-plan shops · 23 towns.**
|
||||||
|
|
||||||
|
| town | shops | doors | worst draws | | town | shops | doors | worst draws |
|
||||||
|
|---|---|---|---|---|---|---|---|---|
|
||||||
|
| adelaide | 110 | 11 | 158 | | katoomba | 72 | 10 | 149 |
|
||||||
|
| ballarat | 19 | 10 | 154 | | launceston | 30 | 10 | 137 |
|
||||||
|
| bendigo | 35 | 10 | 141 | | marrickville | 26 | 10 | 149 |
|
||||||
|
| bowral | 30 | 10 | 156 | | newcastle | 34 | 10 | 152 |
|
||||||
|
| braddon | 19 | 10 | 158 | | **newtown_godverse** | 18 | 10 | 134 |
|
||||||
|
| brunswick | 92 | 11 | 149 | | newtown | 67 | 11 | 149 |
|
||||||
|
| castlemaine | 23 | 10 | 150 | | northbridge | 58 | 11 | 149 |
|
||||||
|
| darwin | 12 | 10 | 154 | | toowoomba | 12 | 10 | 138 |
|
||||||
|
| daylesford | 26 | 10 | 153 | | westend | 41 | 10 | 146 |
|
||||||
|
| **fitzroy (mecca)** | **139** | 10 | **159** | | fremantle | 79 | 11 | 152 |
|
||||||
|
| geelong | 62 | 11 | 145 | | glebe | 51 | 11 | 151 |
|
||||||
|
| hobart | 78 | 11 | 157 | | | | | |
|
||||||
|
|
||||||
|
**Verdict: 23/23 PASS — 0 throws · 0 carves · 0 path-fails · 0 over-budget; worst 159 draws (fitzroy) ≤350.**
|
||||||
|
- **All 12 archetypes covered town-set-wide** (band_room, book, dept, milkbar, opshop, pawn, pub, record, rsl,
|
||||||
|
stall, toy, video) — the whole registry.
|
||||||
|
- **23/23 towns landed 3 venues** — A's cluster bias + district placement hold from the 12-shop thin tail
|
||||||
|
(darwin, toowoomba) to the 139-shop mecca (fitzroy). Fitzroy, 2× anything absorbed before, is the *cheapest*
|
||||||
|
end of nothing — worst draw 159, same band as everywhere else: interiors don't care about town scale.
|
||||||
|
- **Lane G's `newtown_godverse` passes** (18 shops, 8 types, worst 134) — the merged town needs nothing from C.
|
||||||
|
|
||||||
|
### Ledger #3 — the G2a per-shop atlas contract (C's half) → published in LANE_C_PUB **§7**
|
||||||
|
|
||||||
|
Answered Fable's priors + E's half. **All three priors hold; atlas-per-shop is strictly better for draws than
|
||||||
|
today's type-packs. The binding constraint is VRAM, not draws — and E's dispose ask is granted: it's my seam.**
|
||||||
|
- **Draws:** 1 atlas/shop = **1 draw** (shared material + baked UV → batch.js merges). Rule: 1, hard cap 2;
|
||||||
|
60+ item shops spill to the type-pack. Headroom is enormous (stock rooms 41–58 draws vs the ≤350 law).
|
||||||
|
- **VRAM (the real ceiling):** E's 181 MB audit is right and the cause is my own design note
|
||||||
|
(`stockpack.js:11` caches atlases "NOT per-room, like depot GLBs") — correct for 3 bounded type-packs,
|
||||||
|
**wrong for unbounded per-shop atlases**. Ruling: **≤2 resident shop atlases, ≤8 MB decoded, ≤1024² typical**
|
||||||
|
(2048² only for 60+ items); **C lands the LRU cap + dispose in `stockpack.js`**. Scheduling: the accretion is
|
||||||
|
pre-existing and `?stock=real` is **opt-in** (`index.html:196`) so it can't touch the default boot — **not a
|
||||||
|
v4.0 blocker**; I land it as **C's first v4.x item, before G2a ships**.
|
||||||
|
- **Schema:** E's index confirmed verbatim, keyed per shop; UV origin stays top-left (C flips V); C reads `uv`
|
||||||
|
never `cell` (so book's 176×256 portrait is fine). **Fail-soft/parody/`?noassets`:** unchanged law.
|
||||||
|
- **One ask back to G:** emit `items[]` in **deterministic order (sort by id)** — C's seeded per-bin pick
|
||||||
|
indexes into it, so a re-emit would silently reshuffle every crate.
|
||||||
|
|
||||||
|
**C→F: 23-town interior verdict GREEN** (nothing gates the tag from C). Docs-only. Both C items closed for v4.0.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Update 2026-07-16 (round 21, v4.0-beta close) — the five-town density door-soak (ledger #3)
|
||||||
|
|
||||||
|
R21 §Lane C (wave 2 — ran **after A's venue cluster-adjacency bias** landed, `110c828`, so the soak reads the
|
||||||
|
relocated world + A's re-pinned goldens). The carried R20 #6: **every one of the five beta towns at its new
|
||||||
|
density**, deterministically sampled ~25 doors/town — the sample takes **one of every type present** then
|
||||||
|
stride-fills, so the texture classes' first real appearance (milkbar/dept/stall on real shops) is always covered.
|
||||||
|
|
||||||
|
| town | shops (in plan) | sampled | venues | types (all sampled) | throws | carves | path-fails | over-budget | worst draws |
|
||||||
|
|---|---|---|---|---|---|---|---|---|---|
|
||||||
|
| `katoomba_real` | 72 | 25 | 3 | 10 | 0 | 0 | 0 | 0 | **157** |
|
||||||
|
| `newtown_real` | 67 | 25 | 3 | 11 | 0 | 0 | 0 | 0 | 155 |
|
||||||
|
| `fremantle_real` | 79 | 25 | 3 | 11 | 0 | 0 | 0 | 0 | 153 |
|
||||||
|
| `bendigo_real` | 35 | 25 | 3 | 10 | 0 | 0 | 0 | 0 | 154 |
|
||||||
|
| `castlemaine_real` | 23 | 23 (all) | 3 | 8 | 0 | 0 | 0 | 0 | 156 |
|
||||||
|
|
||||||
|
**123 doors · 0 throws · 0 carves · 0 path-fails · 0 over-budget · worst 157 draws (≤350).** In every town
|
||||||
|
`typesInTown === typesSampled` — every archetype present was entered, including all three venue kinds
|
||||||
|
(pub/band_room/rsl) and the texture classes. **All five towns landed 3 venues** → A's cluster bias + district
|
||||||
|
placement hold at density.
|
||||||
|
|
||||||
|
**§6 mapping verified live (John's SUBTLE directive, working by construction):** katoomba's cache is **20
|
||||||
|
heroes + 60 texture** — exactly E's 3× cap, heroes untouched. In-plan (after A's counted drops): 17 heroes
|
||||||
|
(opshop 10 · book 4 · record 2 · video 1) + 52 texture (milkbar 29 · dept 18 · stall 5) + 3 venues = 72.
|
||||||
|
The texture classes' **first real appearance** renders right: **"Blue Mountains Chocolate Company"** — a real
|
||||||
|
Katoomba `shop=chocolate` — now wears `milkbar` (70s geo wallpaper, cube shelves of price-tagged goods, counter,
|
||||||
|
wall rack): a believable confectionery counter shop. The widening reads as texture, not takeover.
|
||||||
|
|
||||||
|
*(Cache→plan deltas — e.g. katoomba 80→72 — are A's overflow/fragmentation drops, counted by cause in A's R20
|
||||||
|
accounting; not an interior concern, and every surviving door builds clean.)* **C→F: density verdict GREEN.**
|
||||||
|
Docs-only. C otherwise on-call; my §6 mapping + this soak close both C items for the beta.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Update 2026-07-16 (round 20, v4.0-beta FIVE TOWNS) — the class→archetype mapping (ledger #2, C's half)
|
||||||
|
|
||||||
|
R20 §Lane C (I lead — E builds to it, half-day rule): **published the OSM shop-class → interior archetype
|
||||||
|
mapping** in [LANE_C_PUB.md](docs/LANES/LANE_C_PUB.md) **§6** (new v4.0-beta amendment; the frozen venue
|
||||||
|
contract §0–§5 untouched). This is the density widening — real towns fetch **general retail**, not just the
|
||||||
|
thriftgod secondhand classes, so the main street holds its real bakery/cafe/chemist/clothes shops.
|
||||||
|
|
||||||
|
**Design (John's directive: SUBTLE — texture, not takeover):**
|
||||||
|
- **No new archetypes.** General retail wears **existing** recipes: food/grocer/newsagent/chemist → `milkbar`
|
||||||
|
(the canonical AU counter shop); clothes/hardware/big-retail → `dept`; produce/florist → `stall`.
|
||||||
|
- **Secondhand stays the hero** — charity/antiques→opshop, books→book, music→record, video/games→video,
|
||||||
|
pawn→pawn, toys→toy; never subsampled.
|
||||||
|
- **Unknown/unlisted → `opshop`** (R18-proven safe). Included a copy-paste JSON map for E's `build_towns.py`
|
||||||
|
+ a tasteful fetch list (skip vehicle/funeral/industrial/pure-services like hairdresser/bank).
|
||||||
|
- **Guardrail for E:** if a town's general retail would visually drown the secondhand strip, seed-subsample
|
||||||
|
the texture classes toward ~2–3× the secondhand count and count the drops in `norm.dropped`.
|
||||||
|
|
||||||
|
**Mechanic note:** `plan_osm` keeps only registry types (unknowns flatten to opshop), so E bakes a **registry
|
||||||
|
type** per the map into each cache's `shop.type` — no C code change, all targets are existing recipes/registry
|
||||||
|
entries, budgets unchanged. **C→E handshake: §6 is live.**
|
||||||
|
|
||||||
|
**Pre-validated the mapping (measurement over folklore, before E builds to it):** milkbar/dept/stall/opshop all
|
||||||
|
build valid at the katoomba lot (8.5×14) — 0 carves, ≤143 draws, pathOK. Rendered `dept` (my one flagged case
|
||||||
|
— the escalator centrepiece): reads as a clean **dressed-retail floor** (cube shelves of goods + clothes rack +
|
||||||
|
counter + "GROUND FLOOR/HABERDASHERY" signage) in the 7.9×11.7 hall, escalator does *not* dominate — so
|
||||||
|
`clothes`/`hardware` → `dept` is confirmed, not a concern.
|
||||||
|
|
||||||
|
**Pending (ledger #6, after E re-emits the 5 widened caches):** the five-town sampled door-soak — every town,
|
||||||
|
sampled doors at the new density, all types resolve, no carve, budgets; one table, five rows. I'll watch that
|
||||||
|
`dept` reads right on small real lots (the escalator centrepiece). Docs-only this commit; door-soak follows E.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Update 2026-07-16 (round 19, v4.0-alpha CLOSE) — re-soak katoomba on REAL roads (against A's in-flight pass)
|
||||||
|
|
||||||
|
R19 §Lane C (on-call, ledger #5): E's roads landed (`dbb52c2` — katoomba_real is now **schema v2, 484 real
|
||||||
|
roads**), so I re-ran the 20-door soak against the real street graph — closing the R18 "re-run when roads land"
|
||||||
|
caveat. **Honest caveat: the working tree also carried Lane A's UNCOMMITTED R19 fragmentation pass** (plan_osm.js
|
||||||
|
WIP — `JOIN_TOL`/`BRIDGE_MAX`, junction-protected Douglas–Peucker, shopless-island cull / shop-island bridge),
|
||||||
|
so the dev server served A's *in-progress* finalization, not a frozen state. Read this as "interiors survive
|
||||||
|
A's real-graph pipeline as it stands mid-round"; a clean confirm follows A's commit.
|
||||||
|
|
||||||
|
**Robust result (holds whatever A's WIP does):** every shop the plan produced builds a **valid interior** —
|
||||||
|
0 throws · 0 carves · 0 path-fails · 0 over-budget · worst **147** draws (min 115); 3 gig venues
|
||||||
|
(pub/band_room/rsl — **district placement holds on the real graph**); plan deterministic (byte-identical
|
||||||
|
re-gen). Lot dims **unchanged** (7.5–8.5 × 14) → interiors are dim-identical to R18 (road-agnostic confirmed;
|
||||||
|
my R18 degenerate-lot proof already spans anything the real graph can seat).
|
||||||
|
|
||||||
|
**Shop count observed = 19 (NOT filed as a defect).** The plan yielded 19 shops here, but against A's in-flight
|
||||||
|
fragmentation code that's expected, not a bug — A's ruling explicitly culls shopless islands and bridges/
|
||||||
|
drops-with-count shop-bearing ones, so the final count is A's to pin (could land at 20 connected or an explicit
|
||||||
|
19 with a `norm.dropped` count). I'm not attributing it to A as a finding; I only confirm interiors build clean
|
||||||
|
whichever way it resolves. (If A's committed pass surprises with a shop that *can't* build an interior, that
|
||||||
|
would be the finding — none did in this run.)
|
||||||
|
|
||||||
|
**Final C spot-confirm pending A's COMMITTED finalization + golden pin** (per Fable's sequencing, C confirms
|
||||||
|
after A). When A pins `katoomba_real`'s real-roads golden I'll re-run once against the frozen state. Docs-only
|
||||||
|
round for C.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Update 2026-07-16 (round 18, v4.0-alpha REAL ROADS) — interior spot-soak of katoomba_real (ledger #6)
|
||||||
|
|
||||||
|
R18 §Lane C (small): enter every `katoomba_real` shop and assert valid interiors + no carve + budgets.
|
||||||
|
Built the town plan the way the shell does — `registerTownCache(katoomba_real)` → `generatePlanFor(seed,
|
||||||
|
'osm', {town, gigs:true})` (gigs on = the default boot) → `buildInterior` per shop (lot dims resolved from
|
||||||
|
`plan.lots`). **All 20 doors PASS:**
|
||||||
|
|
||||||
|
| metric | result |
|
||||||
|
|---|---|
|
||||||
|
| shops entered | **20 / 20 built** |
|
||||||
|
| throws · carves · path-fails · over-budget | **0 · 0 · 0 · 0** |
|
||||||
|
| worst draws | **153** (shop 16, opshop/hall) ≤ 350 |
|
||||||
|
| types resolved | opshop · book · record · video + venues pub · rsl · band_room |
|
||||||
|
| gig venues (default boot) | 3 (ids 3/5/19) — all built valid gig interiors (stage + watchPoints) |
|
||||||
|
| draw range | 108–153 |
|
||||||
|
|
||||||
|
**Degenerate-lot stress (preempts A's real-road re-seating):** 32 combos (8 extreme lots {2×3, 1×1, 3×30,
|
||||||
|
40×4, 100×100, **0×0**, 4.2×4.1, 2.5×18} × 4 types incl. venues) → **0 throws · 0 carves · 0 path-fails**;
|
||||||
|
every room floors at **≥4×4 m** (`computeDims Math.max(4, lot−0.6)`). So whatever frontage the real-graph
|
||||||
|
lift seats shops on, the interior contract absorbs it.
|
||||||
|
|
||||||
|
**Caveat (honest):** audited against the CURRENT `katoomba_real.json` — schema v1, `roads: none`, so it boots
|
||||||
|
the **marched fallback** (E's katoomba roads cache isn't committed yet; A's lift `a78dc76` is in). Interiors
|
||||||
|
are **road-agnostic** (they consume lot dims + type + seed, never road geometry), and the degenerate-lot proof
|
||||||
|
covers any lot the real graph produces, so the conclusion is stable. **Interior lane contributes 0 failures to
|
||||||
|
the alpha's failure list.** A one-line re-run once E's `roads[]` cache lands (lot dims may shift with real
|
||||||
|
frontage) — I'll re-confirm if asked, but the interior contract already absorbs the range. C otherwise on-call.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Update 2026-07-16 (round 17, v3.2 THE TAIL) — backline handshake CLOSED + real-town type fallback
|
||||||
|
|
||||||
|
R17 §Lane C (ledger #3): close the R16-pending render-verify of D's wired amp. D landed the wiring
|
||||||
|
(`ab7a8d0`): `band.js _addBackline` reads `stage.backline[0]` with an immediate primitive `ampPrim()` fallback
|
||||||
|
(asset-free) + GLB upgrade. **Handshake CLOSED — verified fresh (seed 20261990), 3 archetypes × 2 modes:**
|
||||||
|
- amp plants at `backline[0]` **exactly** (offset 0.00 GLB / 0.01 prim — rounding); **1 amp per stage** (no
|
||||||
|
more C primitive → the double-amp is gone).
|
||||||
|
- **no interpenetration** — amp↔nearest-band AABB edge gaps all positive: pub **0.40/0.44 m**, band_room
|
||||||
|
**0.27/0.29 m** (tightest; the bass extends downstage away from the up-stage amp), rsl **0.54/0.57 m**
|
||||||
|
(GLB/primitive). Rendered band_room gig confirms the read (amp up-stage-right behind the bassist, clear).
|
||||||
|
|
||||||
|
**On-call (proactive, for A/E's real-map scout):** confirmed C's interior type fallback eats real OSM shop
|
||||||
|
types. 18 real `shop=` values (hairdresser/bakery/convenience/supermarket/chemist/butcher/… + ''/null/
|
||||||
|
undefined/`SHOP`/`Op_Shop`/`antiques`) all build with **0 throws, all pathOK**, resolving to `opshop`
|
||||||
|
(theme.js `canonicalType`) or a mapped alias — so a player entering any real-town shop gets a valid interior.
|
||||||
|
Venue types (pub/band_room/rsl) are recognized; everything else degrades to opshop. No C change needed for v4.
|
||||||
|
|
||||||
|
Docs-only round (verification confirmed shipped behavior). C otherwise on-call.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Update 2026-07-16 (round 16, v3.1 THE FLIP) — the backline handoff (C's half of ledger #5)
|
||||||
|
|
||||||
|
R16 §Lane C (ledger #5, C's half): unify the amp. **`room.stage.backline[]`** now exposes 2 seeded up-stage
|
||||||
|
amp slots `{x,z,ry,y}` (pure data, no meshes), and C's **primitive ampStacks are dropped** from `placeStage` —
|
||||||
|
Lane D plants E's `guitar_amp` GLB at `backline[0]` (its own primitive fallback under `?noassets`), one amp
|
||||||
|
system. Shape published in [LANE_C_PUB.md](docs/LANES/LANE_C_PUB.md) §3 (v3.1 amendment marker; rest of the
|
||||||
|
frozen doc unchanged). **C→D handshake: backline shape is live.**
|
||||||
|
|
||||||
|
- `backline[0]` = stage-right `(stage.x + stage.w·0.36, deckY, stage.z − stage.d·0.22)` == D's R15-verified
|
||||||
|
amp pose exactly, so wiring D's plant to it moves **nothing** on screen. `backline[1]` = stage-left spare
|
||||||
|
(mirror). ry=π, y=deckY.
|
||||||
|
- **Verified fresh (port-isolated, seed 20261990):** both slots up-stage, on-deck, clear of all 4 bandPoses
|
||||||
|
(nearest-bass **0.87 m pub / 0.70 m band_room / 0.92 m rsl**) and all watchPoints (≥1.95 m); determinism
|
||||||
|
byte-identical (placement + stage + watchPoints); 0 leftover ampStack prop groups; drawSweep glb-off venues
|
||||||
|
**pub 122 · band_room 120 · rsl 131** (was 126/124/135 — the dropped primitives shaved ~4 draws each), worst
|
||||||
|
161 (opshop/hall, unchanged). ≤350 ✓.
|
||||||
|
- **Determinism/golden safety:** removing the ampStack loop only drops draws on the `stage` rng sub-stream,
|
||||||
|
which nothing downstream reads; the `placement` snapshot excludes ampStacks (never tracked), so it's
|
||||||
|
byte-identical. Interiors aren't hashed into any golden; the `?classic=1` boot has no venues → untouched.
|
||||||
|
- Disjoint from F's flip (interiors don't read flags) — landed early so D isn't blocked. **Verify with D
|
||||||
|
once wired** (ledger #5 D-half): re-render each archetype, confirm the GLB at `backline[0]` doesn't
|
||||||
|
interpenetrate (pending D's R16 commit).
|
||||||
|
|
||||||
|
**Code:** `layout.js` placeStage (backline[] + drop the amp loop), `interiors.js` API doc. `theme.js` untouched
|
||||||
|
(recipes carry no ampStack fitting — the primitives were placeStage-planted, not recipe fittings).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Update 2026-07-16 (round 15, v3.0: closing the release) — verified D's amp fix
|
||||||
|
|
||||||
|
R15 §Lane C (one item): verify D's `guitar_amp` reposition (ledger #4). D moved the plant (commit `66aec0f`)
|
||||||
|
to C's §3 corrected pose exactly — `band.js:236 amp.position.set(stage.x + stage.w·0.36, deckY, stage.z −
|
||||||
|
stage.d·0.22)`. **Verified (fresh port-isolated context, seed 20261990, D's real GigCrew + GLB backline):**
|
||||||
|
matches §3 (both venues); **bass clearance 0.87 m (pub) / 0.92 m (rsl)** — the R14 interpenetration (0.29 m)
|
||||||
|
is gone; on-deck within footprint; flanks C's primitive ampStack (opposite side, 4.4 m pub / 3.5 m rsl) as a
|
||||||
|
clean up-stage backline; no clash with band/crowd/watchPoints. Rendered pub gig confirms the read (amp up-stage
|
||||||
|
behind the bassist, primitive up-stage-left, band clear). LANE_C_PUB §3 updated RESOLVED. Freeze otherwise
|
||||||
|
stands; no other C change this round. Nothing else blocks on C — F runs the release (continuity wiring + smoke,
|
||||||
|
alias deletion, week soak, tour, tag `v3.0`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Update 2026-07-16 (round 14, v3.0: the release) — venue audit + docs freeze (light round)
|
||||||
|
|
||||||
|
R14 §Lane C: interior release audit, the `guitar_amp` ruling, and the docs freeze. C's interior code was
|
||||||
|
**unchanged** since R13, so venue geometry is stable by construction; the audit re-verified it against the
|
||||||
|
current tree (D's R13 continuity + RY-fix + real instrument GLBs landed) and froze the contracts. Ran an
|
||||||
|
adversarial audit **workflow** (10 agents: conformance + D-integration/amp + docs-freeze edit list, then a
|
||||||
|
verify pass) — it earned its keep (caught the amp defect I'd first waved through + two stale comments).
|
||||||
|
|
||||||
|
**Empirical re-verify (fresh context, port-isolated server):**
|
||||||
|
- Venue contract **byte-identical to R13**: caps 8/8/12, deckY 0.32/0.20/0.40, riserY 0.48/0.36/0.56, gigKeys
|
||||||
|
gig-pubrock/grunge/covers, quiet-night = no gigKey, determinism true.
|
||||||
|
- `drawSweep` glb-off: band_room 124 · pub 126 · rsl 135 (worst 161 opshop/hall) ≤350 — unchanged.
|
||||||
|
- **Gig-night draws with D's real `GigCrew` + GLB instruments** (localdepot): pub **60** · band_room **52** ·
|
||||||
|
rsl **71** ≤350. Asset-free worst RSL 237. All 4 instruments load (guitar/bass/mic/drums).
|
||||||
|
- **Release-law confirmed** (workflow, CONFIRMED): D's continuity + the RY fix ride a disjoint rng namespace
|
||||||
|
(`rng(citySeed,kind,id)`) from C's `ctx.stream(salt)`; the RY fix is a constant Y-rotation in `buildFigure`
|
||||||
|
(no rng). Cannot perturb C's placement or any golden.
|
||||||
|
|
||||||
|
**`guitar_amp` ruling (debt for C) → Lane D:** **NOT blessed as-is — reposition up-stage.** D plants the GLB
|
||||||
|
at `(stage.x+stage.w·0.32, deckY, stage.z+0.15)` — that `+0.15` is *down-stage*, landing the 0.5 m amp 0.29 m
|
||||||
|
from the bass rig (**interpenetrates the bassist**, confirmed by render + 3 audit agents), and it's redundant
|
||||||
|
with C's own primitive ampStacks (`placeStage` plants 1–2 up-stage at `(±stage.w·0.28, deckY, stage.z −
|
||||||
|
stage.d·0.18)`). Corrected pose in [LANE_C_PUB.md](docs/LANES/LANE_C_PUB.md) §3: move up-stage-right ≈
|
||||||
|
`(stage.w·0.36, deckY, stage.z − stage.d·0.22)`, clear of the bass and offset from C's primitives → a clean
|
||||||
|
flanking backline. Full amp unification (C exposes `stage.backline[]`, GLB replaces primitive) = v3.1.
|
||||||
|
Cosmetic + `?gigs=1` opt-in → does not block the tag.
|
||||||
|
|
||||||
|
**Tri observation → E/F (not a C gate):** E's decimated instrument GLBs are **14k tris each** (5 on a stage =
|
||||||
|
70k; peds ~2.7k each), so a settled RSL gig-night interior is ~**182k tris**. The interior gate is *draws*
|
||||||
|
(passes at ≤71); the 200k tris is the *street* budget, so no violation. But F's gig-night smoke reports ~34k
|
||||||
|
because it measures **before** the async instrument GLBs load — the real settled scene is ~5× heavier. A
|
||||||
|
camera-distance instrument LOD is a v3.1 candidate.
|
||||||
|
|
||||||
|
**Docs freeze (v3.0-FROZEN markers added):**
|
||||||
|
- `LANE_C_PUB.md` — retitled + frozen marker; §0 ry convention reframed past-tense (the +Z fleet was fixed at
|
||||||
|
the source in D's `rigs.js buildFigure`, R13 — no per-consumer flips); 4-piece stated as shipped (veto
|
||||||
|
language removed); the amp ruling added (§3); budgets updated to R14; in-flight handoffs removed.
|
||||||
|
- `LANE_C_AUDIO.md` — frozen marker + a scope note (this covers base interior audio; gig audio `gigKey`
|
||||||
|
lives in LANE_C_PUB); the deferred `audioEmitter` idea re-targeted to v3.1.
|
||||||
|
|
||||||
|
**Code (comment-only, zero runtime/golden/rng impact):** fixed two stale R12 inline return-shape comments in
|
||||||
|
`interiors.js` (`watchPoints` 6..8 → 8/8/12 caps; `stage` `bandPoses[3]` → `[4]` + `riserY`) + one in
|
||||||
|
`layout.js`; added a note at the gigKey hand-build explaining it's deliberately standalone (byte-identical to
|
||||||
|
citygen's `gigKeyFor`, not imported — keeps the interiors lib citygen-free). `theme.js` untouched.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Update 2026-07-15 (round 13, v3.0-beta: the district) — two more rooms + the drummer
|
||||||
|
|
||||||
|
R13 §Lane C. The district: two new venue archetypes + the 4-piece band + the RY-facing audit. Interfaces
|
||||||
|
(re)published in [docs/LANES/LANE_C_PUB.md](docs/LANES/LANE_C_PUB.md) — **read §0 first (the pinned ry
|
||||||
|
convention + the keeper/browser back-to-front finding for Lane D).** Committed C-only by pathspec (the tree
|
||||||
|
had E/B/D changes landing concurrently — [[procity-shared-tree-hazard]]).
|
||||||
|
|
||||||
|
**Shipped (C-owned files only): `theme.js`, `layout.js`, `interiors.js`.**
|
||||||
|
- **`band_room` + `rsl` recipes** (`theme.js`, both `venue:true`), reusing the pub venue machinery with a new
|
||||||
|
`venueSpec` field. `band_room` = small/scuzzy tin shed (cosy/pokey bias, low 3.4 m ceiling, deck barely a
|
||||||
|
riser `deckY 0.2`, near-empty standing floor). `rsl` = carpet-and-flock club (hall/wide bias, tall 4.0 m,
|
||||||
|
proper stage `deckY 0.4`, bistro tables, the biggest crowd cap). A converts a shop with `shop.type = kind`;
|
||||||
|
C keys the recipe off it. `SHOP_TYPES` now auto-enrols both in `drawSweep`/`soak`.
|
||||||
|
- **4-piece band (debt #3):** `placeStage` grows `stage.bandPoses` to **4** — front-line trio
|
||||||
|
(guitar/vocal/bass, `y=deckY`) + a **drummer** (`role:'drums'`, `seated:true`, up-stage on the drum riser,
|
||||||
|
`y=riserY=deckY+0.16`). Each pose now carries its own `y`; `stage.riserY` added. Applies to all three
|
||||||
|
archetypes (the pub gains a drummer too). Pure geometry — no new rng draw, determinism intact.
|
||||||
|
- **Per-kind venue tuning:** `placeStage`/`placeWatchPoints` read `recipe.venueSpec` → deck size/height +
|
||||||
|
**crowd cap** (pub/band_room **8**, rsl **12** — the crowd-cap stress case). Pub values == the R12
|
||||||
|
constants, so the pub stage geometry is byte-identical (bar the new drummer).
|
||||||
|
- **Robust gigKey (debt #1):** `audio.gigKey = 'gig-'+genreKey`, genre resolving `opts.gig.genreKey` →
|
||||||
|
`shop.genreKey` (A sets it) → a kind default. Matches E's renamed manifest beds `gig-pubrock`/`gig-grunge`/
|
||||||
|
`gig-covers` exactly (verified: manifest has all three, zero stale `pubrock-live`). One key, no mapping.
|
||||||
|
|
||||||
|
**RY-facing audit (debt #4) — see LANE_C_PUB.md §0 for the full write-up + fix. Headline:**
|
||||||
|
- Canonical convention pinned: **rig-front = local −Z at ry=0** (every C pose + the box placeholder use it).
|
||||||
|
- **The GLB fleet mesh faces +Z (opposite).** Verified in a fresh context two ways: toe-bone probe
|
||||||
|
(`toe−foot = (0,+0.99)` on 5 peds) and dotting a fleet rig planted at `counter.stand.ry` against the
|
||||||
|
intended customer-facing (`−0.99` = 180° backward; `+π` → `+0.99`).
|
||||||
|
- **Finding: keepers + browsers have been facing 180° backward whenever the GLB fleet is loaded** —
|
||||||
|
`keepers.js` spawns at `pose.ry` with no flip, and the mesh front is +Z. The R12 band reads right only
|
||||||
|
via `band.js RY_FLIP=π`. Placeholder (−Z) and GLB (+Z) are opposite, so it's inconsistent both ways.
|
||||||
|
- **Recommended fix for Lane D (visual-only, no golden moves):** normalise at the source — rotate the clone
|
||||||
|
`inner` by π in `rigs.js buildFigure` so every GLB rig faces −Z; then `keepers.js` stays flip-free (now
|
||||||
|
correct) and `band.js` DROPS `RY_FLIP`. Repro below.
|
||||||
|
|
||||||
|
**→ Lane F (flags_check.py): two smokes are RED by design — bump `band == 3` → `== 4`.** My 4th bandPose
|
||||||
|
makes the band a 4-piece (D's `band.js` already iterates `bandPoses`, so it spawns 4 today), which trips F's
|
||||||
|
R12 assertions verbatim as the round-13 instructions anticipated ("F's band==3 smoke moves to 4"):
|
||||||
|
- `tools/flags_check.py:674` — `if night['band'] == 3` → `== 4`.
|
||||||
|
- `tools/flags_check.py:794` — `if na['inRoom'] and na['band'] == 3 and na['crowd'] > 0` → `== 4`. *(The
|
||||||
|
noassets gig still happens — band 4, crowd 8, 0 fetches, 0 console errors; silent-and-happy holds. It only
|
||||||
|
"fails" because 3 is hardcoded.)* These are the ONLY qa reds from C; the other 5 gates pass.
|
||||||
|
|
||||||
|
**→ Lane D:** lift each band member to `pose.y` (front line `deckY` unchanged; drummer sits at `riserY`);
|
||||||
|
`seated:true` on the drums pose → seated bob; raise `band.js CROWD_CAP` to the watchPoint cap so the RSL's 12
|
||||||
|
fill (I measured the full 12). And apply the §0 facing fix. Repro for the audit (paste in the interior test page):
|
||||||
|
```js
|
||||||
|
const rigs = await import('./js/citizens/rigs.js'); const THREE = await import('three');
|
||||||
|
const fleet = rigs.loadPedFleet('models/peds/'); await fleet.whenReady;
|
||||||
|
const sp = rigs.spawnRig(fleet.all[2], { ry: 0, height: 1.75 }); sp.fig.updateWorldMatrix(true, true);
|
||||||
|
let foot, toe, wp = new THREE.Vector3();
|
||||||
|
sp.inner.traverse(o => { if (o.isBone){ o.getWorldPosition(wp);
|
||||||
|
if (/LeftFoot$/i.test(o.name)) foot = wp.clone(); if (/LeftToeBase$/i.test(o.name)) toe = wp.clone(); }});
|
||||||
|
console.log('fleet forward at ry=0:', toe.clone().sub(foot).setY(0).normalize()); // → (0, +1) = +Z
|
||||||
|
```
|
||||||
|
|
||||||
|
**Budgets (measured, fresh context, seed 20261990):**
|
||||||
|
- Empty shells (`drawSweep` glb-off): band_room 124 · pub 126 · rsl 135 (worst town-wide 161 opshop/hall) ≤350.
|
||||||
|
- **Gig-night incl. full crowd + 4-piece:** worst **RSL 237 draws** asset-free (16 rigs) / 77 GLB, 40.9k tris.
|
||||||
|
pub 176/56, band_room 173/53. ≤350 draws / 200k tris. ✓
|
||||||
|
- 90-build venue soak: 0 determinism · 0 carve · 0 path-fail · worst 8.3 ms; leak geo0/tex0. `soak(60)` in
|
||||||
|
the page: PASS 60 rooms, leak 0/0, determinism identical, worst draws 85. qa figure-scale gate: 24 figs
|
||||||
|
human-sized. Shot: [docs/shots/laneC/rsl_gig_r13.jpg](docs/shots/laneC/rsl_gig_r13.jpg) — RSL, 4-piece
|
||||||
|
facing the crowd, crowd facing the stage (facing reads correct via band.js's flip).
|
||||||
|
|
||||||
|
**Drummer veto (John):** default is Fable's 4-piece ruling (executed). To revert to a front-line trio, drop
|
||||||
|
`bandPoses[3]` in `layout.js placeStage` (1 line) + F reverts the smoke to `== 3` — say so before D wires it.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Update 2026-07-15 (round 12, v3.0-alpha) — the pub venue: archetype + watchPoints
|
||||||
|
|
||||||
|
R12 §Lane C (venues & gigs, behind `?gigs=1`). Interface published in
|
||||||
|
[docs/LANES/LANE_C_PUB.md](docs/LANES/LANE_C_PUB.md) — the "publish interface first" deliverable D/F build on.
|
||||||
|
|
||||||
|
- **`pub` archetype** (`theme.js`, `venue:true`): low **stage** + PA cabinets + amps at the back wall (new
|
||||||
|
`stage`/`paSpeaker`/`ampStack` fittings, GLB slots `stage`/`pa_speaker`/`amp` for E), bar counter (`corner`
|
||||||
|
— the barkeep is just a keeper), sticky-carpet floor, the blocked back doorway = the green room.
|
||||||
|
- **`room.watchPoints[]`** — 6–8 seeded audience floor poses `{x,z,ry,dance,slotIndex}` facing the stage
|
||||||
|
centre (`counter.stand` ry convention). `dance` seeded **~⅓ true** (John: "a bit of both"). Walkable,
|
||||||
|
reachable, clear of keeper/browse/stage. **`room.stage`** = `{x,z,w,d,deckY,frontZ,bandPoses[3]}` so D
|
||||||
|
drops the band on the deck.
|
||||||
|
- **`room.audio.gigKey`** — F passes `opts.gig` (gig state); a gig-on pub returns `gigKey='gig-'+genreKey`
|
||||||
|
(`gig-pubrock` for the alpha), else a quiet-night interior. F prefers gigKey over musicKey. (→ E: name the
|
||||||
|
live bed `gig-pubrock`.)
|
||||||
|
- **Flags-off byte-identical**: venue code runs only for `recipe.venue`, on its own `stage`/`watch` sub-
|
||||||
|
streams — non-pub rooms have `watchPoints:[]`, `stage:null`, unchanged audio + placement.
|
||||||
|
- Verified: 40-seed pub sweep — 0 throws · **0 carves** (PA `areaFree`-guarded off the counter strip) · 0
|
||||||
|
path-fails · 8 watchPoints each · dance 0.38. 60-room mixed soak (incl. pub) — 0 throws/det/path fails,
|
||||||
|
**leak geo0/tex0**, worst 19 ms. Non-pub rooms confirmed unchanged.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Update 2026-07-15 (round 11, audio) — `room.audio` contract
|
||||||
|
|
||||||
|
R11 §Lane C ("one contract answer"). **Confirmed** Fable's proposal: `buildInterior` now returns
|
||||||
|
`room.audio = { musicKey, toneKey }`, seeded per shop — F's interior_mode plays it, B's audio.js
|
||||||
|
resolves the keys. Full contract + F wiring recipe: [docs/LANES/LANE_C_AUDIO.md](docs/LANES/LANE_C_AUDIO.md).
|
||||||
|
|
||||||
|
- `toneKey` → a `manifest.audio.ambience` interior room-tone (always set); `musicKey` → a
|
||||||
|
`manifest.audio.music` bed, or **null** (no music). Keys, not files — buildInterior stays synchronous
|
||||||
|
and asset-free; silence on missing/`?mute=1`/`?noassets=1` (house audio law).
|
||||||
|
- Maps mirror the manifest's per-type `types` arrays (one source of truth): record/milkbar/video/dept/
|
||||||
|
arcade always play their bed; opshop/book/toy/pawn/stall are room-tone-only, except a seeded ~1-in-3
|
||||||
|
keeps a general radio on (the `general`-tagged milk-bar bed).
|
||||||
|
- Verified (9 types × 8 seeds): 0 bad keys, 0 nondeterminism, every key resolves + matches manifest
|
||||||
|
`types` membership; deterministic per shop. `qa.sh --strict` green.
|
||||||
|
- Optional visual-source prop (`userData.audioEmitter`) deferred to v2.2 (in the note) — out of round scope.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Update 2026-07-15 (round 9) — ship v2.0: browse points (C→D→F seam) + buy-anywhere
|
||||||
|
|
||||||
|
Round-9 §Lane C. The round hangs off C1 (two lanes build on it), so it shipped first as a handshake +
|
||||||
|
working code; C2 widens buying beyond the dig.
|
||||||
|
|
||||||
|
**C1 — browse points (the round's one cross-lane seam).** `layout.js placeBrowsePoints()` exposes
|
||||||
|
`room.browsePoints[]` — 1–3 deterministic floor poses in front of browsable fittings, facing the goods,
|
||||||
|
where Lane D stands browser rigs (F wires occupancy → points). Same coord frame + `ry` convention as
|
||||||
|
`counter.stand`, so D's fleet clones drop straight in. Pure data — **no meshes, no draws**; the ≤350 law
|
||||||
|
is untouched by C1. Guarantees (all asserted headless): walkable (occ-free, not reserved), reachable from
|
||||||
|
spawn (the door→counter flood-fill), ≥0.6 m apart + clear of the keeper stand, deterministic per
|
||||||
|
`shop.seed`, ≥1 point in every archetype that has a browsable fitting. A rich candidate ring (4 sides × 2
|
||||||
|
gaps + diagonals) + a nearest-free-cell fallback covers tight/mismatched rooms. Interface published in
|
||||||
|
[LANE_C_NOTES.md](docs/LANES/LANE_C_NOTES.md) **before** code (decision #1 half-day handshake) → committed
|
||||||
|
first so D/F unblock. Validated: **180 type×archetype×seed combos → 0 empties** (177×3 pts, 3×1 via
|
||||||
|
fallback), determinism 0 fails, min-sep 0.7 m, 0 keeper clashes. Test page: cyan browse-post markers on the
|
||||||
|
`path` overlay + a HUD count.
|
||||||
|
|
||||||
|
**C2 — buy-anywhere (decision #2).** Book spines + toy boxes get pull-and-buy over the **same wallet**;
|
||||||
|
records keep the deep dig. New `stockpack.buildBuyableShelf()` builds each real shelf as merged,
|
||||||
|
per-item-**addressable** meshes — one mesh per atlas (all covers on a shelf drawn from a single seeded
|
||||||
|
atlas → **one draw/shelf**), tagged `noBatch` so `batch.js` leaves them intact. Aiming a spine/box raises a
|
||||||
|
pull card (title / author-or-maker / $price / band from the pack index); BUY debits the wallet, adds to
|
||||||
|
inventory, and `collapseBuyItem()` zero-areas that item's quad in place (**removed from the shelf**, one
|
||||||
|
draw, no geometry churn). Parody stock has no buy mesh → **no card** ("not for sale"). Fail-soft per pack.
|
||||||
|
Validated: raycast→card→BUY works end-to-end (item collapses to its centre, cash/inventory update, card
|
||||||
|
closes); **buy-soak 6/6 bought, `collapseOk`, leak geo 0 / tex 0**; draws with `?stock=real` **record 41 ·
|
||||||
|
book 58 · toy 57** (≤350); full drawSweep unchanged (GLB-off 168 · GLB-on 344, both pass); placement
|
||||||
|
determinism 0 fails. Shot: [browse_buy_r9.jpg](docs/shots/laneC/browse_buy_r9.jpg).
|
||||||
|
|
||||||
|
**→ D**: consume `room.browsePoints` (contract in LANE_C_NOTES) — stand rigs at the first
|
||||||
|
`min(occupancy, 3)` points. **→ F**: two new warn smokes (hooks in LANE_C_NOTES): browse-points present +
|
||||||
|
valid, and shelf-buy (aim → card → wallet debit → collapse). C's soak covers keeper+stock+dig+buy together.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Update 2026-07-15 (round 8) — the town comes alive: buy loop v0 + book/toy real stock
|
||||||
|
|
||||||
|
Round-8 §Lane C, theme "the town comes alive". Two deliverables + a verify pass.
|
||||||
|
|
||||||
|
**C1 — buy loop v0** (new `web/js/interiors/wallet.js`). A shallow, **runtime-only** economy: seeded
|
||||||
|
starting cash, purchases, an in-memory bag. Reload resets; world generation is **untouched** (purchases
|
||||||
|
never enter the plan or the seeded room build — the wallet is a separate object the shell reads, never
|
||||||
|
writes back into gen).
|
||||||
|
- `createWallet(seed)` → `{ cash, start, canBuy, buy(offer), inventory, count, onChange }`. Start is
|
||||||
|
seeded ~$60–200 (`mulberry32`). `buy` deducts + records on success, **returns false unchanged if broke**.
|
||||||
|
`onChange` lets F's street shell subscribe the same wallet to a cash chip.
|
||||||
|
- **Test page** (`interior_test.html`): a `#cashChip` HUD (`$163 · 🛍 N`, street + interior) + an
|
||||||
|
inventory panel toggled with **I**. The dig's pull card gets a **BUY IT** button (price + band); buying
|
||||||
|
pulls the sleeve into the bag, decrements cash, updates the chip, and **depletes that bin** (bought bins
|
||||||
|
offer fewer sleeves on re-riffle). Broke → the button no-ops (can't buy). `digSoak` now buys up to 2/visit.
|
||||||
|
- Offer shape is the dig offer (`{a,t,price,s}`) OR a pack item (`{title,artist,price,price_band}`) — one
|
||||||
|
`buy()` eats both.
|
||||||
|
|
||||||
|
**C2 — book spines + toy boxes wired to `?stock=real`.** E shipped the generalized packs
|
||||||
|
(`stock_book_*` 311 items / 4 atlases, `stock_toy_*` 273 items / 5 atlases, alongside `stock_record_*`
|
||||||
|
350/6). `stockpack.js` now keys each pack to its slot via `SLOT_FOR = {record:'sleeve', book:'spine',
|
||||||
|
toy:'box'}`; `stock.js shelfLine` consults the adapter and builds real cover planes for book spines
|
||||||
|
(bookshelves) and toy boxes (cube shelves) — **static shelf stock, no dig** for those, fail-soft per pack.
|
||||||
|
Every real mesh samples its atlas material with a baked per-item UV, so a whole shelf still batches to one
|
||||||
|
draw per atlas.
|
||||||
|
|
||||||
|
**C3 — validated** (all three packs live, `?stock=real`):
|
||||||
|
- **Draws ≤350** (the law): record **41**, book **42**, toy **51** worst over seeds {1990,7,42(,101)}.
|
||||||
|
- **Determinism**: same seed → identical draw calls + mesh count, all three types (0 fails).
|
||||||
|
- **Leak-free**: 0 geometry/texture delta over 20 build/dispose cycles; **buy-soak** 12 record-dig visits,
|
||||||
|
5 bought (wallet depletes to broke → exercises the can't-buy path), `leakGeo 0 / leakTex 0`; resting geo
|
||||||
|
back to baseline after close. Atlas textures shared/cached (not per-room); per-item geo ctx-tracked & disposed.
|
||||||
|
- **Reload resets** the wallet (fresh boot → seeded start again); **flag-off + `?noassets` untouched**.
|
||||||
|
- Shot: [buyloop_r8.jpg](docs/shots/laneC/buyloop_r8.jpg) — toy boxes, book spines, record dig → BUY card.
|
||||||
|
|
||||||
|
**→ F**: two new warn-level smokes (hooks in [LANE_C_NOTES.md](docs/LANES/LANE_C_NOTES.md)): (1) **buy loop** —
|
||||||
|
`createWallet` deduct/broke/inventory is pure and headless-testable; (2) **book/toy packs** — enter book or
|
||||||
|
toy shop with `?stock=real`, assert `isStock` meshes on a shared atlas material (drawSweep ≤350). Buy UI is
|
||||||
|
test-page-local; the reusable piece for the street shell is `wallet.js` + `onChange`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
126
D-progress.md
@ -1,5 +1,131 @@
|
|||||||
# PROCITY-D — progress (Lane D · Citizens)
|
# PROCITY-D — progress (Lane D · Citizens)
|
||||||
|
|
||||||
|
## Round 12 (v3.0-alpha) — the band and the crowd, behind `?gigs=1`. (2026-07-15)
|
||||||
|
|
||||||
|
Friday night at The Thornbury Hotel: a band on C's stage and a mixed crowd at C's watch points.
|
||||||
|
New `web/js/citizens/band.js` (`GigCrew`) + a gig surge in `sim.js`. Verified against A's real
|
||||||
|
`plan.gigs` + C's real venue interior (F wires the `?gigs=1` shell seam last, so I drove A+C+D
|
||||||
|
directly in a standalone scene). Deterministic from `citySeed`; asset-free-safe (placeholder crew).
|
||||||
|
|
||||||
|
**Band trio (task 1)** — 3 rigs on `stage.bandPoses` at `deckY`, **distinct fleet peds** (seeded
|
||||||
|
shuffle per gig — a band with two identical members reads wrong), each with a primitive instrument
|
||||||
|
that rides the chest: guitar (strum-sway), mic stand (vocal sway), bass (strum-sway). E's GLBs drop
|
||||||
|
in via `opts.instrumentFor(role)` when they land. NB C's `bandPoses` roles are **guitar/vocal/bass**
|
||||||
|
(a front-line trio) — there is no drummer pose (C's drum riser is set-dressing); flagging in case a
|
||||||
|
drummer was expected.
|
||||||
|
|
||||||
|
**Crowd (task 2)** — up to 8 rigs at `watchPoints` on the same gate-protected `spawnRig` path.
|
||||||
|
`dance:false` → subtle weight-shift idle; `dance:true` → bounce + sway + step, **seeded phase so the
|
||||||
|
crowd never metronomes**. Verified 3/8 dancing (C's ~⅓).
|
||||||
|
|
||||||
|
**Gig surge (task 3)** — `sim.setGig(venueShopId, on)`: while on, patronage pulls peds from further
|
||||||
|
(`GIG_RANGE` 34 m) and ducks in harder (`GIG_SURGE` 0.55) at the venue; occupants **drain at close
|
||||||
|
per A's closing-time ruling** (no special handling — `_openAt` + the dwell timer do it). Measured a
|
||||||
|
mock venue climb **1 → peak 5** occupants; identity signature unchanged; **gig-off byte-identical**
|
||||||
|
(guarded by `_gigVenue==null`, so the frozen goldens don't move — prime flag law).
|
||||||
|
|
||||||
|
**Verified (standalone, fresh browser):** band **3** · crowd **8** · dancing **3** · **all 11 figures
|
||||||
|
human-sized** (heights 1.60–1.83 m, crown < 4.0 m ceiling — the R10 no-giants gate holds for the gig
|
||||||
|
rigs) · band faces audience + crowd faces stage · **deterministic** (identical twice) · **leak-free**
|
||||||
|
(8 spawn/dispose cycles, geo/tex delta 0). **Budget:** the crew adds **~40 draws** (11 rigs + 3
|
||||||
|
instruments); the pub interior itself is the bulk — F measures the full gig-night ≤350 in the shell.
|
||||||
|
|
||||||
|
**Figures human-sized relative to doors/fittings: yes** — measured (1.60–1.83 m under a 4.0 m ceiling)
|
||||||
|
and shown in `docs/shots/laneD/r12_gig_night.jpg` (distinct band with instruments, mixed crowd watching).
|
||||||
|
|
||||||
|
**⚠ Cross-lane flag for C/F — facing convention.** C's `watchPoints`/`bandPoses` `ry` point the fleet
|
||||||
|
mesh **180° from where `spawnRig` plants it** (verified in-scene: `ry=π` faced the band at the backdrop;
|
||||||
|
the watch-point `ry` faced the crowd at the door). The fleet mesh's visual front is its local **+Z**,
|
||||||
|
not the `−Z` the sim/keeper comments assume. `band.js` compensates with a `RY_FLIP = π` on spawn so
|
||||||
|
band→audience and crowd→stage. **If C changes the gig-pose `ry` convention (or the fleet is re-fronted),
|
||||||
|
remove `RY_FLIP`.** Worth a look at whether keepers/browsers are subtly back-to-front too.
|
||||||
|
|
||||||
|
**For Lane F (wiring the seam):**
|
||||||
|
```js
|
||||||
|
import { GigCrew } from './js/citizens/band.js';
|
||||||
|
const crew = new GigCrew({ citySeed: plan.citySeed, fleet });
|
||||||
|
// on gig doors/on, inside the venue interior:
|
||||||
|
crew.spawn(room.group, { stage: room.stage, watchPoints: room.watchPoints, gig: tonight });
|
||||||
|
// each interior frame: crew.update(dt); on gig-off / room exit: crew.disposeAll();
|
||||||
|
citizens.setGig(gig.venueShopId, on); // street surge while doors/on; setGig(null,false) to end
|
||||||
|
```
|
||||||
|
|
||||||
|
## Round 10 (blocker fix) — interior giants → human-sized. qa --strict GREEN. (2026-07-15)
|
||||||
|
|
||||||
|
Fixed the R9 ship-blocker: interior rig figures were ~2× too tall (keeper crown at 3.83 m in a
|
||||||
|
3.4 m room, clipping the ceiling). Measured live, not from notes.
|
||||||
|
|
||||||
|
- **Root cause (rigs.js `buildFigure`)**: it normalised scale by `height / headY`, where `headY` is
|
||||||
|
the head bone's world Y in bind pose. But the Mixamo skeleton's ORIGIN is the hips (~mid-body), so
|
||||||
|
`headY` is head-**above-hips** (~half the standing height) — not the feet→crown height. Result:
|
||||||
|
every rig ~1.83–2.0× too tall. (NB Fable's "bind-pose 82 vs posed 180" hypothesis was close on the
|
||||||
|
numbers but the head does NOT move when posed — bind crown 3.209 m vs idle-posed 3.205 m. The 82 is
|
||||||
|
head-above-hips; the 175 is the true feet→crown span. So it's a hips-relative measure, not animation.)
|
||||||
|
- **Fix (one logic line)**: normalise by the feet→crown **span** = `headY - minY` (both already
|
||||||
|
computed). Now `buildFigure(rig, h)` lands the crown at exactly `h` above planted feet. Verified all
|
||||||
|
19 fleet rigs → crown == 1.75 m (feet 0); live keeper in "Second Time Redfern" 3.83 m → **1.82 m**.
|
||||||
|
This one change fixes keepers, browser rigs, near-tier street rigs AND the impostor bake (all ride
|
||||||
|
`buildFigure`) — street peds looked fine only because they're mostly impostors, which are sized by
|
||||||
|
requested height and so masked the bug.
|
||||||
|
- **Regression guard (required)** — new `tools/qa/interior_scale_check.py` (Playwright): enters a
|
||||||
|
sample of open shops, injects browser rigs at every browse point, and asserts EVERY interior fig's
|
||||||
|
crown ∈ [1.4, 2.0] m AND < room `dims.H` AND feet planted. Wired into `tools/qa.sh --strict` (gate 6,
|
||||||
|
`run_gate` ⇒ a giant fails qa). Proven **bidirectional**: PASS on the fix (24 figs / 6 shops), FAIL
|
||||||
|
on a reintroduced giant (crowns 3.4–4.05 m, all 6 shops red). `qa.sh --strict` = **6 passed, 0 failed**.
|
||||||
|
- **Determinism + leak re-verified**: same seed → same keeper height + ped (shop 18 → 1.823 m, idx 10,
|
||||||
|
identical twice; buildFigure crown stable & == requested); 15 spawn/dispose cycles → geo/tex delta 0.
|
||||||
|
- **Re-shot** `laneD/r9_browsers_in_shop.jpg` → `laneD/r10_browsers_fixed.jpg` (kept r9 as the
|
||||||
|
documented before-state).
|
||||||
|
|
||||||
|
**Figures human-sized relative to doors/fittings: yes** — verified live (measured crowns 1.6–1.86 m
|
||||||
|
across 6 shop types under 3.4–4.0 m ceilings) and visible in `r10_browsers_fixed.jpg` (a hi-vis worker
|
||||||
|
and a hoodie browser both well under the ceiling, in proportion to the shelves and door).
|
||||||
|
|
||||||
|
Files touched (mine only): `web/js/citizens/rigs.js` (fix), `tools/qa/interior_scale_check.py` (new
|
||||||
|
gate), `tools/qa.sh` (+gate block, authorized by ROUND10 §Lane D), `docs/shots/laneD/r10_*.jpg`, docs.
|
||||||
|
|
||||||
|
## Round 9 (v2.0 ship) — interior presence: occupancy truth + browser rigs. qa GREEN.
|
||||||
|
|
||||||
|
Built the D side of the one cross-lane seam (C browse points → D occupancy+browsers → F handoff).
|
||||||
|
Follow a ped into a shop and find them browsing. Full numbers + F wiring in `LANE_D_NOTES.md`.
|
||||||
|
|
||||||
|
- **Occupancy truth (D1)**: patronage records who's inside which shop by shopId. New
|
||||||
|
`citizens.occupancyOf(shopId) → {count, occupants:[{seed,enteredAt,pedIndex}]}`. Cleaned on
|
||||||
|
emerge/chunk-drop/stream-toggle. **Deterministic** (17 shops + occupants byte-equal across two runs).
|
||||||
|
- **Browser rigs**: `keepers.js` gained `browse` (faces shelf, no greet) + `pedIndex` (the browser IS
|
||||||
|
the ped who ducked in) + `seedKey`. Validated 12 enter/exit cycles: count == min(occupancy,3) **12/12**,
|
||||||
|
worst **61 draws ≤350**, **leak-free** (0 GPU delta), disposed every exit. Merged peds = ~1 draw each.
|
||||||
|
- **Consistency (D2)**: inside peds hidden on the street (0 rendered), re-emerge at the door.
|
||||||
|
- **F handoff** (verified by running F's exact wiring): enter → `occupancyOf` → `browsePoints.slice(0,
|
||||||
|
min(count,3))` → `keepers.spawn({...browse:true, pedIndex})`. `web/index.html` `setShops` door points
|
||||||
|
need `shopId: s.id` added. Closing-time handled by the dwell model (pending A's explicit ruling).
|
||||||
|
- Evidence: `docs/shots/laneD/r9_browsers_in_shop.jpg`. v1 path untouched.
|
||||||
|
|
||||||
|
Changed `web/js/citizens/sim.js` (occupancy) + `keepers.js` (browser mode) + `citizens_test.html`
|
||||||
|
(shopId in synthetic shops).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Round 8 (the crowd comes alive) — shop patronage v0 + weather reaction. qa GREEN.
|
||||||
|
|
||||||
|
Also did the R7 post-flip shell verification (F's flip landed) + pushed the tags to origin (main
|
||||||
|
1a20501→900b274; v1.0/v1.1/v2.0-alpha/v2.0-beta all on origin).
|
||||||
|
|
||||||
|
- **Post-flip verify** ✅ (real shell): no-flag boot → streamMode true, determinism (188 ids), leak-free
|
||||||
|
(0 delta), setNightLivelyChunks wired; `?roster=v1` → fixed roster, golden identity byte-identical.
|
||||||
|
- **Shop patronage v0 (D1)** ✅: streamed peds duck into open shops they pass (going→inside→emerge, hidden
|
||||||
|
while inside), hours-aware — noon variety (up to ~38 inside, 7 types), **night video-only** (the
|
||||||
|
open-late shop draws the crowd). Deterministic (150 chunk-keyed ids), leak-free (0 GPU delta over the
|
||||||
|
churn), **budget-neutral** (draws −1, inside peds hidden). Behind `?patronage=0`, default-on for stream.
|
||||||
|
- **Weather reaction (D2)** ✅: reads B's `PROCITY.weather` (no import). Rain → density **−56%** (40–60%
|
||||||
|
target), walk speed +~14%, more sheltering (inside 6→25); overcast −10%; clear = v1.
|
||||||
|
- v1 path untouched (no patron fields, golden identity). Full numbers + F's `setShops`/`setWeather`/
|
||||||
|
`?patronage=0` wiring in `LANE_D_NOTES.md`.
|
||||||
|
|
||||||
|
Only `web/js/citizens/sim.js` + `web/citizens_test.html` (synthetic-shop demo + flags) changed.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Round 7 (the flip) — MERGE VALIDATED, GO given to F, defaults + escape hatch ready. qa GREEN.
|
## Round 7 (the flip) — MERGE VALIDATED, GO given to F, defaults + escape hatch ready. qa GREEN.
|
||||||
|
|
||||||
Validated Lane E's ped-merge (the gate my R6 memo named) and handed F the **GO** for the roster flip.
|
Validated Lane E's ped-merge (the gate my R6 memo named) and handed F the **GO** for the roster flip.
|
||||||
|
|||||||
@ -1,8 +1,91 @@
|
|||||||
# LANE E — ASSETS · progress (PROCITY-E)
|
# LANE E — ASSETS · progress (PROCITY-E)
|
||||||
|
|
||||||
*2026-07-14. Owner: PROCITY-E. Scope: `pipeline/*`, `web/assets/*`, `web/assets/manifest.json`.
|
*Owner: PROCITY-E. Scope: `pipeline/*`, `web/assets/*`. Nothing in other lanes' files was touched.*
|
||||||
Reviewed against `docs/LANES/LANE_E_ASSETS.md` + `docs/LANES/ROUND3_INSTRUCTIONS.md`. Nothing in
|
|
||||||
other lanes' files was touched.*
|
## Round 12 (v3.0-alpha gig layer, `?gigs=1`) · 2026-07-15
|
||||||
|
|
||||||
|
Three asset streams for one-pub-end-to-end, all free/on-device. C works the pub interior in parallel
|
||||||
|
(I touched none of `web/js/interiors/*`).
|
||||||
|
|
||||||
|
- **Instruments (5 GLBs)** — `flux_local` concept → `trellis_mac` → `normalize.py`, via
|
||||||
|
`gen_props.py --batch gig_instruments.json`. **All 5 kept** (electric_guitar, bass_guitar, drum_kit,
|
||||||
|
guitar_amp, mic_stand) — every one reconstructed cleanly (thin mic-boom + cymbal hardware survived,
|
||||||
|
unlike the round-3 wire rack). Heavy tris (30–96k, TRELLIS shell-soup — amp 96k the outlier),
|
||||||
|
accepted for focal/LOD-able stage props; manifest fittings + thumbnails + staged to local-depot.
|
||||||
|
D's rigs hold them; primitive fallback if absent.
|
||||||
|
- **Posters (4 skins)** — `gen_skins.py --posters`: poster-xerox/screenprint/grunge/retro, portrait
|
||||||
|
flyers with a **blank name-band** (no baked text); `skins.poster[].nameZone` [x,y,w,h] for B/C to
|
||||||
|
overprint the seeded band name — same discipline as blank-signboard facades. Eyeballed, zero rejects.
|
||||||
|
- **Gig audio (3)** — extended `gen_audio.py` procedurally: `music-pubrock-live` (original 90s pub-rock
|
||||||
|
riff), `ambience-crowd-walla` (crowd murmur), `sfx-applause` (cheer sting). All `gig:true` in the
|
||||||
|
manifest for F. Pack now 26 assets, 9.1 MB of 25. Verified (spectrogram + seams/peaks).
|
||||||
|
- **Manifest**: `skins.poster` + audio gig assets + 5 instrument fittings; `build_manifest.py` reads
|
||||||
|
`_gig_results.json`; `validate_manifest.py` covers audio. Provenance in `AUDIT.md` (§ Round 12).
|
||||||
|
- **Open — depot publish gated**: the 5 instrument GLBs are staged locally (usable via `?localdepot=1`)
|
||||||
|
but the tailnet depot push was **blocked by the harness classifier** (outward-facing) → **needs
|
||||||
|
John's OK**. Until then `validate --depot` / qa Gate 3 flag provenance-drift on the 5. F owns the
|
||||||
|
v3.0-alpha tag; I did not commit yet pending the publish call.
|
||||||
|
|
||||||
|
## Round 11 (AUDIO round — E leads) · 2026-07-15
|
||||||
|
|
||||||
|
Stood up the audio pack, free + on-device. **Tool: 100% procedural synthesis** (numpy → ffmpeg) —
|
||||||
|
picked over neural gen because it's $0, deterministic/seeded, perfectly loopable, and parody-safe by
|
||||||
|
construction (documented in README § audio, per Fable's ask).
|
||||||
|
|
||||||
|
- **`pipeline/gen_audio.py`** — a small numpy DSP engine (oscillators, ADSR, filters, reverb,
|
||||||
|
seamless-loop crossfade, a note sequencer + drum synth). 23 assets, seeded, `--dry-run`/`--only`.
|
||||||
|
- **`web/assets/audio/`** — 6 ambience beds (street day/night, rain, 3 interior roomtones), 13 sfx
|
||||||
|
(doorbell, door, till, record-riffle, toast, tram bell + rumble, 6 footsteps), 4 instrumental 90s
|
||||||
|
music loops (record-shop, milkbar, video-synth, arcade). OGG/Opus + M4A/AAC. **8.1 MB** of 25 MB.
|
||||||
|
- **Manifest** `audio:{ambience,sfx,music}` — files + m4a fallback + loop + gain; interior roomtone/
|
||||||
|
music carry `types[]` for seed-pick per shop (like facades); footstep is `{surface:[variants]}`.
|
||||||
|
`build_manifest.py` builds it, `validate_manifest.py` checks it. **qa green, audio 23, 0 errors.**
|
||||||
|
- **Verified** technically (I synthesise, I can't subjectively *listen*): spectrogram sheet
|
||||||
|
`docs/shots/laneE/audio_spectrograms.png` + numeric — peaks ≤ -0.4 dBFS (no clip), **all loops
|
||||||
|
seamless**, durations correct. **Subjective ear-check is John's/F's** (flagged honestly).
|
||||||
|
- Provenance in `AUDIT.md` (§ Round 11). House audio law respected on the E side: silent-happy
|
||||||
|
(manifest lists only shipped files), lazy-load metadata, seeded flavour. B wires WebAudio, C gives
|
||||||
|
the `room.audio` contract, F integrates + owns the **v2.1 tag/push** — I did **not** push.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Round 3 and earlier below.*
|
||||||
|
*Reviewed against `docs/LANES/LANE_E_ASSETS.md` + `docs/LANES/ROUND3_INSTRUCTIONS.md`.*
|
||||||
|
|
||||||
|
## Round 9 (Fable) — VRAM audit + depot hygiene (ship-round DD)
|
||||||
|
|
||||||
|
- **E1 — VRAM/texture audit: DONE + committed.** Measured every texture source (decoded RGBA+mips).
|
||||||
|
Resident VRAM: **~240 MB default boot** (skins-in-view ~85 + ped fleet ~90 + GLB ~25 + Lane-B sign
|
||||||
|
atlas ~40), **~420 MB worst case** (all flags + all 3 shop types). **Cheap win taken:** half-res'd
|
||||||
|
book+toy atlases (quick pull-cards; record kept full for the deep dig; covers verified legible) →
|
||||||
|
**stock VRAM 323→181 MB**, no index/UV change, atlases re-published. **Verdict: fine to ship v2.0**
|
||||||
|
— no compression/lazy-load needed for the tag. Flagged for v3: stockpack cache-cap (C) + KTX2/Basis.
|
||||||
|
- **E2 — depot/manifest hygiene: confirmed green.** `validate --depot` 0 err · pack-QA 3/3 · provenance
|
||||||
|
-drift clean (`_published.json` == 27 live) · 15 stock atlases live, **0 orphans**.
|
||||||
|
- **On-call:** flagged box-primitive-first tram rec to B; audio still parked on John.
|
||||||
|
|
||||||
|
## Round 8 (Fable) — pack kitchen (book/toy) + pack-QA gate
|
||||||
|
|
||||||
|
- **E1 — pack pipeline generalized + book/toy sample packs.** `build_stock_pack.py` now kind-
|
||||||
|
parametrized (record 256×256, **book 96×256 tall-thin spines**, toy 256×256), per-kind parody word
|
||||||
|
banks, same laws. Book/toy sample packs built + staged (`?localdepot`) + indexes committed to
|
||||||
|
`pipeline/packs/` so **Lane C wires book/toy shelves now**. Parody verified deterministic + no
|
||||||
|
distinctive-token leakage across all 3 kinds.
|
||||||
|
- **E2 — pack-QA gate: DONE.** `validate_pack.py` (wired into `validate_manifest.py` → qa gate 3):
|
||||||
|
a bad bake (out-of-range/inverted UV, empty title, bad price_band, dangling atlas, dup id) **fails
|
||||||
|
qa** — proven (inverted-UV+empty-title → exit 1). qa `--strict` GREEN 5/5.
|
||||||
|
- **Tags pushed** (John-delegated): origin/main = HEAD (1a20501) + v1.0/v1.1/v2.0-alpha/v2.0-beta all
|
||||||
|
live on origin — verified.
|
||||||
|
- **REAL packs LIVE (John unblocked DB access).** All 3 packs rebuilt from GODVERSE (m1ultra
|
||||||
|
Postgres clones, read-only SELECT, peer auth): **record 350** (`discogs_full.inventory` — John's
|
||||||
|
real store stock + real Discogs covers), **book 311** + **toy 273** (`dealgod.products` by
|
||||||
|
category, real product photos + prices). Metadata parody-transformed (no real trademarks); covers
|
||||||
|
are real art (decision #2); book prices seeded (info-only source). 15 atlases published to the
|
||||||
|
depot, indexes committed to `pipeline/packs/` + staged, all pass the pack-QA gate. `build_stock_pack.py`
|
||||||
|
gained `--from-tsv` (extract on the DB box → build here) + portrait book cell + seeded-price
|
||||||
|
fallback. Access path: deployment DB was Docker-internal/SSH-denied → used the m1ultra clones per
|
||||||
|
John. No fal spend.
|
||||||
|
- **On-call** (B rain-streak/puddle, D door-swing tell): none requested.
|
||||||
|
|
||||||
## Round 7 (Fable) — the two feeders (ped merge + stock pack)
|
## Round 7 (Fable) — the two feeders (ped merge + stock pack)
|
||||||
|
|
||||||
|
|||||||
586
F-progress.md
@ -4,7 +4,591 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Round 7 (the flip round → v2.0-beta) — IN PROGRESS
|
## Round 35 (v7.0 close, wave 2) — THE FIRST FIVE MINUTES (Fable, solo, JING5)
|
||||||
|
|
||||||
|
A fresh game arrives HUNTING: one seeded artist want (`rng(citySeed,'firstwant',0)` × the
|
||||||
|
parody roster) named on the splash with the pocket money ("You're hunting HILLS HOIST — $191
|
||||||
|
in your pocket"), pointing the rumors and the tour at the same target. Three toast-beats walk
|
||||||
|
the hustle off existing events (walk-in → digOpen → first find → first sale-or-listing, then
|
||||||
|
the listener removes itself). Returning saves get no tutorial; ?game=0/classic never reach it.
|
||||||
|
#pc-toast body-mounted so beats and morning papers surface inside shops too. STRICT
|
||||||
|
`smoke_first_five` green (roster want + splash · real-path beats · returning/?game=0 controls
|
||||||
|
· cross-browser seed determinism). Full record: LANE_F_NOTES §35.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Round 34 (v7.0 close, wave 1) — GODBAY: the offline auction (Fable, solo, JING5)
|
||||||
|
|
||||||
|
⚖ LIST from the crate (mail-in consignment) → `listings` on procity-save/1 → resolves the day
|
||||||
|
after, wherever the day moves (sleep, travel, imports — adopt() and sleep() share the
|
||||||
|
resolver). THE ONE-HAMMER LAW: identity-seeded (never day-seeded) — a passed-in item re-lists
|
||||||
|
to the same result forever, no retry farm. 15% pass · uniform-across-band hammer (grail 2×low)
|
||||||
|
· 12% war ×1.5–2 · 12% fees, net floor $1; EV ≈ 0.8×mid so random flips bleed and only finds
|
||||||
|
profit; keeper = floor, auction = bounded ceiling. The morning paper rides the wake/arrival
|
||||||
|
toast ("⚖ SOLD 'Cane Toad Blues' — hammer $22, $19 after fees"). Band table imported from C's
|
||||||
|
bible.js (one authority), PRNG from core. STRICT `smoke_godbay` green (real-DOM consign ·
|
||||||
|
byte-identical double-sleep · bounded honest resolution · travel hammers · game=0 control).
|
||||||
|
Full record: LANE_F_NOTES §34.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Round 33 (v7.0-beta wave 2) — THE HUNT: wantlist · rumor · travel (Fable, solo, JING5)
|
||||||
|
|
||||||
|
☆ WANT on the pull card → `wants` on procity-save/1 (optional, capped, deduped); buying a
|
||||||
|
matching item clears the hunt; THE WANTLIST section in the crate panel. The keeper gossips:
|
||||||
|
seeded 45% banner mutter naming a hunted want + a real cached town. TRAVEL COSTS A DAY:
|
||||||
|
adopting a save from another townKey (town OR seed) charges +1 day in save.js adopt() — load
|
||||||
|
and import both pay, same-town stays free (byte-equality gates untouched); arrival toast names
|
||||||
|
the fare. New STRICT `smoke_wantlist_travel` (real-card want · rumor scan · auto-clear ·
|
||||||
|
+1-day with intact cash/collection · same-town control · return fare · ?game=0 zero-DOM).
|
||||||
|
Full record: LANE_F_NOTES §33.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Round 32 (v7.0-beta wave 1) — THE BIBLE + scarcity + the §31.6 queue (Fable, solo, JING5)
|
||||||
|
|
||||||
|
Guide bands derived in `js/interiors/bible.js` (E's thresholds mirrored, no pack re-emit) →
|
||||||
|
the pull/sell cards carry the **guide stamp**; parody bins roll seeded bands + 10% collector/
|
||||||
|
grail **gems** (asking under the keeper's bandLow offer — margin by construction); §9.1 sell
|
||||||
|
basis swapped to bandLow exactly as pre-ratified (alpha entries keep the pricePaid basis).
|
||||||
|
**Scarcity:** optional `pulls` on procity-save/1 — a bought dig slot stays gone all day
|
||||||
|
(pick-then-omit, zero collateral), pruned on sleep; real-sourced pulls never expire. §31.6
|
||||||
|
queue: cluster-pose spawn (main-street doors, classic keeps the frozen pose) · THE CRATE +
|
||||||
|
game bar live in interiors (C anywhere, yields to the cards) · DAWN dome swaps to high-cirrus
|
||||||
|
(weather override wins). **Gates:** new STRICT `smoke_bible` (band law over every bin ·
|
||||||
|
ledger closes to the cent on the GEM's bin, Σask $333 → Σoffer $135, gems +$21 · emptied bin
|
||||||
|
0 across reload · `?game=0` control re-opens 16/16) + sell_routing amended to the exact
|
||||||
|
formula (the fence moved from the receipt to the band). Full record: LANE_F_NOTES §32.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Round 31 (v7.0-alpha CLOSE) — THE DIG FLIP + the tag
|
||||||
|
|
||||||
|
**Fable's wave-0 playtest found the one alpha-blocker: the dig was default-OFF on a plain boot.**
|
||||||
|
`index.html` still carried the v2-era opt-in (`params.has('dig')`) six epochs after the README
|
||||||
|
claimed "dig on" — and every R30 game-loop gate booted `dig=1` explicitly, so the suite was green
|
||||||
|
while E at a bin did nothing on the boot every player gets. Fixed with the one-line flip
|
||||||
|
(`DIG_ON = flagOn('dig')` — ?dig=0 opt-out, ?classic=1 off, `dig` on the flags surface) and fenced
|
||||||
|
with two new STRICT legs: **default_boot_gate asserts dig REACHABILITY on a NAKED boot** (E at an
|
||||||
|
aimed bin → digActive true → closed clean) and **classic_regression asserts dig INERT at the bin**
|
||||||
|
(zero `pcdg-*` DOM, zero Storage calls — measured, not assumed). *Harness lesson #4: the
|
||||||
|
explicit-flag blindfold — a gate that force-feeds its subject's flag can never notice the default
|
||||||
|
is broken; every claimed-default-on flag needs at least one flag-ABSENT gate.* Splash de-staled
|
||||||
|
("Every door opens." — the "(soon)" is over; controls line = E riffle/sell · C crate · SLEEP to
|
||||||
|
save) + the README manual row. `PROCITY_QA_PORT` override so the suite never silently reuses
|
||||||
|
whatever holds :8130 (this round: Fable's own preview server).
|
||||||
|
|
||||||
|
**The re-gate (final tree, :8931): `qa.sh --strict --matrix` 7 passed · 0 failed · 0 warn ·
|
||||||
|
0 skipped; flags_check 0/0 incl. both new legs; MATRIX 10 towns green.** Money shot:
|
||||||
|
`docs/shots/v7_alpha/crate_panel.png` — Monster Robot Party's frontage, THE CRATE with 3 real
|
||||||
|
finds (real cover thumbs, honest paid/where/day), game bar day 1 · $116 · CRATE 3 · SLEEP, driven
|
||||||
|
through the real loop with the dig param absent. **Tag `v7.0-alpha` — annotated, LOCAL ONLY**
|
||||||
|
(Fable pushes). THE BETA QUEUE (Fable's design wants, filed NOT fixed) is in LANE_F_NOTES §31.6.
|
||||||
|
Commits: `50385c0` (flip + gates + splash) · shot + notes commit (tag target). Full record: §31.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Round 30 (v7.0-alpha — THE SAVE AND THE CRATE) — F: the contract first, then the save core + tomorrow
|
||||||
|
|
||||||
|
**The contract shipped before a line of implementation** (`53fcd3c`): `window.PROCITY.game =
|
||||||
|
{ day, cash, collection[], townKey, save(), load(), sleep(), export(), import(json), wallet,
|
||||||
|
recordFind(), removeFind() }` + the `procity-save/1` schema + the rotation law, published in
|
||||||
|
LANE_F_NOTES §30 — and it worked exactly as the order intended: **B picked up §30.4 within the
|
||||||
|
session** (venue.js "tonight" already reads `gigs.weekNight`; hud.js collection/SLEEP surface in
|
||||||
|
flight), and C's already-published §9 asks (`type` stamped at buy, `title`/`artist`, removal via the
|
||||||
|
game API, `wallet.sell` mirrored) are honoured in the entry shape.
|
||||||
|
|
||||||
|
**Then the layer** (`207bcff`): `web/js/world/save.js` (F-owned, new) — versioned localStorage save,
|
||||||
|
THE DELTA LAW BY SHAPE (payload keys are exactly `[cash, collection, day, savedAt, schema, town]` —
|
||||||
|
no world field can even be expressed); corrupted/foreign → **loud reject, raw stashed, fresh start,
|
||||||
|
town boots**; save on sleep + beforeunload; export/import for moving machines. The wallet is a
|
||||||
|
game-backed facade of C's exact v0 interface, so every proven seam (dig onBuy, shelf buy, cover,
|
||||||
|
C's onSell) is unchanged — cash just became authoritative from the save. Buy seams record finds;
|
||||||
|
`?classic=1`/`?game=0` never construct the layer — **ZERO Storage calls, instrumented, not trusted**.
|
||||||
|
|
||||||
|
**SLEEP=TOMORROW:** `sleep()` → day+1 → gig `weekNight` re-keys the EXISTING week schedule → wake at
|
||||||
|
DAWN → save. Stock rotation is a runtime day salt on the EXISTING `stk-*` streams (2 marked seam
|
||||||
|
lines in C's interiors/layout) + the dig's `binSeed` — **plan generation never sees `day`** (A's #6
|
||||||
|
boundary). **Day 1 IS the pre-v7 town** (measured: `?game=0` stock fp == fresh-game day-1 fp), and
|
||||||
|
**REAL-sourced crates never rotate** (Monster Robot fp identical across sleeps; the mint crate rotates).
|
||||||
|
|
||||||
|
**Two amendments filed loudly, with measurement, not slipped in:** gig night = **(day − 1) % 7**, not
|
||||||
|
the brief's literal `day % 7` (day starts at 1 — the literal form flips a fresh boot's gigs off night 0,
|
||||||
|
breaking the R13 cover/band gates before the player ever sleeps); and sleep saves AFTER incrementing
|
||||||
|
(disk always carries the morning you woke into). Fable ratifies or overrules at review.
|
||||||
|
|
||||||
|
**Numbers:** 7 verify legs green in fresh no-store contexts — $12 dig buy == pricePaid recorded · sell
|
||||||
|
halves credit/remove correctly · parody fp `3396372961 → 1818111125` across a sleep, byte-identical
|
||||||
|
same-day AND across reboots · corrupt blob = 1 loud reject + stash + clean boot · `qa.sh --strict`
|
||||||
|
**6 passed · 0 failed · 0 warn** with the game layer default-ON. Two F-harness bugs caught and fixed
|
||||||
|
in the run (the post-sleep CLOSED door; a transform-based fingerprint that was vacuous over batched
|
||||||
|
parody stock — hash the VERTEX data). Full record: `LANE_F_NOTES` §30.
|
||||||
|
|
||||||
|
**Held for the later F session (per the brief):** ledger #5 gates + the sell-card E-key routing.
|
||||||
|
|
||||||
|
### Wave 3 — THE GATES SESSION: the routing landed, five STRICT gates, the loop closes end-to-end
|
||||||
|
|
||||||
|
**The held join is in.** E inside a shop now routes: **aimed bin → dig · near counter with sellables →
|
||||||
|
C's sell card · forgiving nearest-bin → dig · shelf-buy** (`interior_mode.js`; 3 marked shell seams in
|
||||||
|
`index.html` — `procity:sellOpen` unlock + `sellActive` on the Esc/relock guards, the dig's exact
|
||||||
|
contract). `onSell` does F's two §9.3 halves in the safe order: `removeFind` first (veto ⇒ nothing
|
||||||
|
moves), credit second. **The loop, measured through the real DOM:** dig pull **$9 debited** → walk to
|
||||||
|
the counter → E → card → SELL **+$4** (< $9, the clamp) → collection 1→0 → street HUD **$186 ·
|
||||||
|
CRATE 0**. One amendment filed with its measurement: the old nearest-bin fallback ignored aim and would
|
||||||
|
have swallowed E at any counter near a bin — aimed-raycast now outranks the sell trigger, the
|
||||||
|
forgiving pick runs after it; both branches proven live (Moe Vinyl counter: card opens; Kelpie Sound
|
||||||
|
bin, wrong aim: dig opens).
|
||||||
|
|
||||||
|
**The gates (ledger #5), all landed as STRICT suite smokes in `flags_check.py` — no more session-local
|
||||||
|
instruments:** (a) **save/load determinism** — scripted buy 2/sell 1/sleep 2× via B's real button →
|
||||||
|
export → fresh browser → import **byte-equal** (state 243 B, normalized export 269 B), plan fp
|
||||||
|
`0xb1c7843e` **unchanged** across the import, weekNight re-keyed 2; (b) **no-pump v0** — 5 real
|
||||||
|
UI round trips **all strictly negative** (Σ −$30), plus **C's negative control**: broke player,
|
||||||
|
`wallet.buy` invoked-and-false on the real shelf path ⇒ **recordFind unreachable, zero entry** (dig BUY
|
||||||
|
disables outright), with a positive arm proving the control discriminates; (c) **classic purity** —
|
||||||
|
Storage-prototype instrument pre-page-script: `?classic=1`/`?game=0` **0 calls, 0 game DOM ids**,
|
||||||
|
positive control sees the default boot's `procity-save` read + all 7 ids, plan fp game-on == game-off;
|
||||||
|
(d) **rotation determinism over VERTEX DATA** (the wave-3 binding) — day 1/2/3 fps all distinct,
|
||||||
|
re-entry byte-identical each day, **cross-boot byte-identical off the persisted save**, `?game=0` ==
|
||||||
|
day 1 (88,320 hashed bytes / 20 stock meshes printed — the subject provably present). Harness bug #3
|
||||||
|
caught en route: the HUD is hidden in interiors by design; the gate read it on the street.
|
||||||
|
**NO TAG** — per the brief, the alpha tags when it plays (Fable's call, likely R31). Full record:
|
||||||
|
`LANE_F_NOTES` §30.6.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Round 26 (v5.0-beta — EVERY CRATE DIFFERENT) — F: the manifest consumed, the tolerance retired
|
||||||
|
|
||||||
|
**Every crate is different, and F measured it rather than taking G's word.** Not by entering one lucky
|
||||||
|
shop — from the indexes of record: **15 keyed crates, 105 pairs, 0 sharing a title**; **0 crates overlap
|
||||||
|
the generic v2 pack**; **0 ids reused across crates** (G's sku fix holds at scale — the R24 vacuity dead
|
||||||
|
at the source, not 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.** `tier` is the charter's ladder rung, and a mint atlas is *also* rung
|
||||||
|
1: still a file, still offline, still deterministic. What differs is where the stock came from, which is
|
||||||
|
`sourcing: real|mint`. E built to C's contract over the brief on the principle that the contract's author
|
||||||
|
owns the contract's shape. F's gate asserts it: 15/15 declare sourcing, every index agrees with the
|
||||||
|
manifest, **a mint crate can never report as real**, and the gate PRINTS it per room — provenance a human
|
||||||
|
never reads isn't doing anything (the R24 licence lesson, one round on).
|
||||||
|
|
||||||
|
**The 404 tolerance is deleted, not tuned.** R24 probed every keyed shop and ate a 404 per atlas-less one;
|
||||||
|
F's smoke could only cope by *naming* them — and a tolerance is a confession. F filed the real fix as a
|
||||||
|
contract ask instead of inventing it; G built the manifest; **F now consumes it**, and the shell asks for
|
||||||
|
nothing that isn't listed. Zero 404s because nothing absent gets asked for, not because the gate stopped
|
||||||
|
looking. **And deleting the tolerance immediately caught a leftover**: F had wired the *boot* preload but
|
||||||
|
left a second blind probe at the *door*, which 404'd on every keyed-but-unfetchable shop. While the gate
|
||||||
|
said "8 probes, expected", a ninth would have been invisible too. A named exception protects new bugs the
|
||||||
|
moment the real fix exists.
|
||||||
|
|
||||||
|
**Gate: `flags_check` GREEN** — distinctness · sourcing (real + mint rooms) · soft fall silent · **ZERO
|
||||||
|
404s** · **0 console errors, no attribution exceptions**. Full record: `LANE_F_NOTES` §26.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Round 24 (v5.0-alpha — THE ALPHA COMPLETION) — **the crate WALKS; F held the tag on A's gate**
|
||||||
|
|
||||||
|
**THE CRATE WALKS.** Boot `redhill_godverse`, walk down Musgrave Road into **Monster Robot Party**,
|
||||||
|
riffle **its own real crate**, buy a real record at its real price. Proven falsifiably: room base =
|
||||||
|
`assets/stock_godverse/3962749/` · **titles 120/120 in its atlas, 0/120 in the generic pack** · **the
|
||||||
|
GPU samples `stock_godverse/3962749/stock_record_atlas_0*.webp`, 0 textures from the generic pack** ·
|
||||||
|
bought *"Abstract Latin Lounge II (Part II)"* **@ $15**, cash **191 → 176**, exact debit, count 0→1 ·
|
||||||
|
reboot restores 191/0 (gone-this-session, back next boot). Ladder re-proved: `?noassets` → **zero
|
||||||
|
network requests**; keyed-but-atlas-less shop 2289 → 404 → **parody, live**; `newtown_real` → base
|
||||||
|
`null`, 350 items — **F's wire is a no-op off the godverse path**. Full record: `LANE_F_NOTES` §24.
|
||||||
|
|
||||||
|
**F held `v5.0-alpha` anyway. The tree is red — 156,193/156,201 — and none of it is the product.**
|
||||||
|
2 reds are goldens G's re-emit legitimately moved (A ran *before* G, so A could not pin them). 6 are
|
||||||
|
**conflict #2: A's identity sweep contradicts Fable's own standing Ruling 2**, which *mandates* mixed
|
||||||
|
caches ("merge census heroes **WITH** the donor's texture shops"). A cafe from OSM has no GODVERSE
|
||||||
|
identity and never will. G measured it, escalated it to "A's call, Fable's ruling", nobody ruled, and
|
||||||
|
it arrived at F.
|
||||||
|
|
||||||
|
**F measured the hazard that gate guards: it does not exist.** 10/10 and 18/18 unique ids, **zero
|
||||||
|
duplicates**; and partial keying does not mis-stock — F watched 2289 fall soft to parody in the
|
||||||
|
browser, exactly as charter risk #3 prescribes. **A's own two checks disagree** — `validateTownCache`
|
||||||
|
reasons it correctly ("Missing = tier 0 (soft). Duplicate = mis-stocked (hard). They are not the same
|
||||||
|
failure") and warns; the sweep hard-fails the same condition.
|
||||||
|
|
||||||
|
**Why F didn't just fix it: F is the party that benefits.** 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. The
|
||||||
|
cost of waiting is an hour. Recommended (A's call): the sweep follows A's own validator; A pins the
|
||||||
|
four goldens in the same commit; F re-gates and tags. One commit.
|
||||||
|
|
||||||
|
**Four findings:**
|
||||||
|
1. **The honest-gate spec was itself vacuous.** The brief specified id equality — but both packs number
|
||||||
|
items **positionally** (`rec_0000…`), so **all 120 atlas ids are also generic-pack ids**. An
|
||||||
|
id-equality gate goes green on the generic pack: the exact failure the vacuous-gate law was written
|
||||||
|
to stop, reproduced inside its own first application. Titles (0 overlap) and the **texture URL** —
|
||||||
|
the file the pixels come from — are what discriminate. F's gate asserts on those.
|
||||||
|
2. **F's own selector was lying about the epoch's shop.** `DBG.enterShop(3962749)` → "no shop matching":
|
||||||
|
a GODVERSE shop has two ids and F's R22 selector knew only the plan id. Fixed.
|
||||||
|
3. **A's uniqueness check is a silent risk-#3 hole** (new — G didn't catch this): on a mixed cache every
|
||||||
|
texture shop maps to `undefined` and they **all collapse into one Set entry**, so it fails on the
|
||||||
|
undefineds and **cannot detect a real duplicate**. Fix it *over the defined ids* — that makes the
|
||||||
|
guard stronger, not weaker.
|
||||||
|
4. **The licence flag stopped reaching a human.** E's validator now sees the atlas (R23's blind gate is
|
||||||
|
closed ✓) but prints `provenance.licence` (British) while G now emits `license` (American, per C) —
|
||||||
|
so `FLAG BEFORE ANY PUBLIC/COMMERCIAL RELEASE`, on real product photos, **silently never prints**
|
||||||
|
while the gate says OK. E, one line, G already specified it.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Round 23 (v5.0-alpha — THE FIRST REAL CRATE) — **F HELD the tag**
|
||||||
|
|
||||||
|
**`v5.0-alpha` is NOT tagged. The crate is real; it has no street.** G's 120 real records off a real
|
||||||
|
shop's POS are in the tree and byte-identically re-bakeable — genuinely the artifact the charter asked
|
||||||
|
for. Nothing in the game can reach them. `#7a` isn't a gate that failed, it's a gate that **cannot be
|
||||||
|
run**. R18's law: *release rounds don't ship known breaches.* Full record: `LANE_F_NOTES` §23.
|
||||||
|
|
||||||
|
**F executed the merge that never happened** (charter law #5) — `main` had no `stock_godverse/**` at all,
|
||||||
|
so the branch F was asked to tag lacked the round's headline. Review had happened, C had verified
|
||||||
|
pre-merge and passed; only the `git merge` was missing. Disjointness verified before merging, not
|
||||||
|
assumed. Zero conflicts → `43b035a`.
|
||||||
|
|
||||||
|
**What passed — everything except the crate.** `qa.sh --strict --matrix` **7 passed · 0 failed · 0 warn**;
|
||||||
|
**MATRIX GREEN — 10 towns × 7 gates**; selfcheck **ALL GREEN 152,015/152,015** (was 161,300 — E retired
|
||||||
|
toowoomba); flags harness green, so the `?classic=1` covenant still holds through the merge. Tier 0
|
||||||
|
`?noassets` → **zero fetches, zero console errors** (the offline law holds); tier 0 default boot on the
|
||||||
|
godverse town; tier 1 (v2 town-wide) `?stock=real`. **Tier 1 (v5, per-shop) — the round's whole
|
||||||
|
deliverable — is never fetched.** A, D, E and C's work is sound; only the rung this round was built to
|
||||||
|
add is unattached.
|
||||||
|
|
||||||
|
**Three findings, in order of how much they cost:**
|
||||||
|
1. **The atlas contract contradicts itself.** `LANE_C_PUB:271` publishes `stock_shop_<id>_index.json`;
|
||||||
|
C's own loader at `stockpack.js:25` fetches `${base}stock_${type}_index.json`. **Incompatible.** G
|
||||||
|
built runtime-compatible (so C's base-override check passed); E built contract-compatible (so E's
|
||||||
|
validator can't see it). **Nobody was wrong — the contract was.** F recommends amending to G's shape;
|
||||||
|
it's the only one the runtime can load.
|
||||||
|
2. **The gate built to catch that was blind and green.** `validate_atlas.py` globs a filename that can't
|
||||||
|
exist at runtime, finds nothing, and `return 0` — so it passes whether the atlas is absent, misnamed,
|
||||||
|
or perfect. It had never seen one until F pointed it at G's by hand, which then surfaced *deeper* drift
|
||||||
|
(E's code reads provenance top-level, E's own docstring says nested, G nested it; `licence` vs
|
||||||
|
`license`; a ceiling stale by one round). *A gate that returns 0 on an empty set is not a gate.*
|
||||||
|
3. **`#7a` would have passed vacuously.** F walked into The Vintage Record (real shop, real road) and the
|
||||||
|
crates filled with **real covers — from E's 350-item town-wide v2 pack**, not the crate. Zero requests
|
||||||
|
to `stock_godverse/`, ever. Eyeballed as written, the gate goes green and proves nothing.
|
||||||
|
|
||||||
|
**R24 order — three gaps, none F-only:** **A+G** land a `godverse_id` (verified: *every* godverse shop
|
||||||
|
reports `godverse_id: null`; the schema has no such field, and C's rule is "C does not invent it") →
|
||||||
|
**E** builds `redhill_real` (one Overpass fetch — E's file, outward-facing; **not F's to invent**) →
|
||||||
|
**C** keys the pack cache by `type+base` (today a cache hit returns before `base` is read, so two stocked
|
||||||
|
shops collide *and fail-soft breaks*) → **F** passes the per-shop base and re-gates. F deliberately did
|
||||||
|
**not** build its half: untestable with no street and no identity field. Filed, not guessed.
|
||||||
|
|
||||||
|
**Also:** A's spacing warn earned on day one — with toowoomba retired, **ballarat now warns at 255 m**.
|
||||||
|
The thin tail has a second member.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Round 22 (v4.0 — THE EPOCH CLOSE) — F closed it
|
||||||
|
|
||||||
|
- **The blocking gate CLOSED.** F's R21 re-measure made the tri diet blocking; B diagnosed, the driver
|
||||||
|
traced A→**E**, E fixed it, F re-measured: katoomba `venue_night` **221,935 → 108,365 (−51%)**, worst town
|
||||||
|
124,682 — the 200k stress edge is closed. Selfcheck **161,300/161,300** (A's 36 goldens + 23 towns).
|
||||||
|
- **`DBG.enterShop(selector)`** (Lane G's ask) — id · type · exact name · substring, most-specific-first.
|
||||||
|
- **The release tour** → [`docs/shots/release_v4/`](docs/shots/release_v4/): Fremantle hero · Fitzroy mecca ·
|
||||||
|
Toowoomba thin tail · synthetic control · `?classic=1` covenant. All posed through F's R21 cluster
|
||||||
|
primitive — which earned itself twice: the first cut posed via `street_noon` and **the mecca came out as
|
||||||
|
bare bitumen**; re-posed through the cluster, Fitzroy reads.
|
||||||
|
- **⚠ Honest finding (filed v4.x):** the thin tail does **not** prove the floor holds — Toowoomba reads as an
|
||||||
|
empty road. The floor is a **ratio**, not a count: 0.008 shops/edge vs the heroes' 0.06–0.08. The frame
|
||||||
|
ships labelled honestly rather than swapped for a flattering town.
|
||||||
|
- Docs (README v4 "the towns are the product", notes §22), `qa.sh --strict --matrix` green, tag **`v4.0`**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Round 20–21 (v4.0-beta THE FIVE TOWNS) — F never ran R20; closed R21
|
||||||
|
|
||||||
|
**R20: F never ran** (no gate, no tag) — the honest record is in LANE_F_NOTES §20. Unlike R18 (where F ran
|
||||||
|
and *held* on evidence), the slot simply didn't execute; the round also left C's door-soak, B's selector
|
||||||
|
index, and D's relocation finding open. R21 closed all four.
|
||||||
|
|
||||||
|
**R21: the beta gate.** Waves [A,B,E] → [C,D] → [F]. All five lanes in; F gated:
|
||||||
|
- **Matrix 10 towns × 7 gates** — boot/determ/district/roads/**tram**/noassets green on all ten; selfcheck
|
||||||
|
154141/154141; `qa.sh --strict` GREEN.
|
||||||
|
- **Tram dimension (new)** — B's R20 ruling verified exactly: Newtown runs 16 stops (was 149 — B's phantom
|
||||||
|
fix), Castlemaine 10; Katoomba/Fremantle/Bendigo **fenced** at 3/2/1 shops fronted. Needed a new seam:
|
||||||
|
the shell now exposes `window.PROCITY.tram` (B built `routeInfo` for F's smoke; nothing published it).
|
||||||
|
- **⚠ venue_night re-measure — recorded, premise falsified.** A's cap worked (posters 438→111) but the
|
||||||
|
density widening ate it: katoomba **200,965 (+965) → 221,935 (+21,935, +11%)**; bendigo 201k. Draws fine
|
||||||
|
(151/300). Filed as R22's first item (the tri-diet backlog already lives there).
|
||||||
|
- **Selector ruling** — always in the HUD (the towns *are* the product), + the corollary that F's harness
|
||||||
|
must hide the HUD for beauty shots (the R19 alpha shot's selector-in-frame was F's own gap).
|
||||||
|
- **Beta money shot** — [Fremantle's real street at golden hour](docs/shots/v4_beta/fremantle_mainstreet.png):
|
||||||
|
a corridor of shopfronts, footpaths, a crossing, and a genuinely busy crowd. Tag **`v4.0-beta`**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Round 18–19 (v4.0-alpha REAL ROADS) — F held R18 correctly, then closed R19
|
||||||
|
|
||||||
|
**R18: F did NOT run — the gate would have failed by design, so F held the tag** and filed the failure list
|
||||||
|
(the alpha's charter deliverable). Real roads landed structurally but the selfcheck was RED (katoomba golden
|
||||||
|
unpinned + poster-kerb breach on all 5 real towns) and the town was fragmented (20 shops over 799 edges = an
|
||||||
|
empty field). Classic/default stayed frozen — the alpha changed nothing outside the cache path. Fable's
|
||||||
|
verdict agreed: "F never ran — correctly."
|
||||||
|
|
||||||
|
**R19: the close.** A resolved the findings (fragmentation cull/join, poster clamp, goldens pinned), D
|
||||||
|
re-measured, B shipped the town selector. F ran:
|
||||||
|
- **B's `BIG_CITY` one-liner** (`e6fcb7b`, early): `shops>120 || edges>200` — real Katoomba's 799-edge graph
|
||||||
|
now streams like a big city. **280k → ~12k tris.** B proved it; F owns index.html.
|
||||||
|
- **The alpha gate** — `town_matrix.py` grew a `roads` dimension (a v2 cache must build the REAL graph,
|
||||||
|
`mode='roads'` via the plan_osm report sink); classic/default proven untouched; budgets in the green.
|
||||||
|
- **The money shot** — [Katoomba's real main street](docs/shots/v4_alpha/katoomba_mainstreet.png): asphalt +
|
||||||
|
lane markings, footpaths, a pedestrian crossing, a walking crowd, gig posters, B's town selector in-frame.
|
||||||
|
**PROCITY's look on real bones.** Tag **`v4.0-alpha`**. (Honest note: 19 real shops sit ~1/edge — a real
|
||||||
|
*street*, not yet a dense strip; density is a v4.0-beta concern.)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Round 17 (v3.2 THE TAIL + THE SCOUT) — F: the town matrix + the close
|
||||||
|
|
||||||
|
The tail drained (drummer lean + bench-sit, wind sway, backline verify, --verify fix) and E's real-map scout
|
||||||
|
landed (5 real AU town caches + A's cache contract + hardened plan_osm). **F closed it.**
|
||||||
|
|
||||||
|
- **Real towns boot in the engine** — the barrel re-exports `registerTownCache`; the shell fetches +
|
||||||
|
registers `assets/towns/<key>.json` before the plan generates (fail-soft; opt-in; never on default/classic).
|
||||||
|
`?plansrc=osm&town=bendigo_real` → **Bendigo, 9 shops, 3-venue district, 0 errors**.
|
||||||
|
- **Town-matrix smoke** (`tools/qa/town_matrix.py`, `qa.sh --matrix`) — **10 towns × 5 gates all green**:
|
||||||
|
2 synthetic + 3 fixtures + 5 real caches, each boot / deterministic / in-budget (69–216 draws) / a 3-venue
|
||||||
|
district / silent under `?noassets`. Castlemaine at the 6-shop floor still lands a district. **The existing
|
||||||
|
CityPlan contract eats real Australian geography** — the v4 = THE REAL MAP evidence.
|
||||||
|
- Close: `qa.sh --strict --soak` green, docs (LANE_F_NOTES §17, README), tag **`v3.2`**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Round 16 (v3.1 THE FLIP) — F bookends; the town shows its best by default
|
||||||
|
|
||||||
|
The gig layer soaked through three v3 stages, so v3.1 flips it (and the mature v2 flags) ON by default.
|
||||||
|
F owns the flip; A–E landed their polish against it (seated drummer, town-wide pools, backline, corner-poster).
|
||||||
|
|
||||||
|
**Opening commit `a4946b7` (the round's foundation):**
|
||||||
|
- **The flip** — `index.html` `CLASSIC` + `flagOn()`: gigs/weather/tram default-on, `loadPedFleet({sit:!CLASSIC})`,
|
||||||
|
`window.PROCITY.flags`; `buildings.js` winmap default-on + classic-aware. **`?classic=1`** forces the exact v2 boot.
|
||||||
|
- **The covenant moved** — `flags_off_regression → classic_regression` (byte-identical `0x3fa36874`); a new
|
||||||
|
`default_boot_gate` (all four flags on, gig layer present, gig golden `0xb1d48ea1`, budget); every gig smoke
|
||||||
|
re-pointed to the DEFAULT boot; qa.sh gate renamed.
|
||||||
|
- **Seated-stature gate** (ledger #3) — `interior_scale_check.py` + `flags_check.py` accept `procitySeated` figs
|
||||||
|
at [0.9,1.5] m; the drummer passes at **1.32 m**.
|
||||||
|
|
||||||
|
Verified: `?classic` byte-identical (0x3fa36874, gig absent); default = full town (3 venues / 3 genres, gig golden,
|
||||||
|
112 draws @ venue block); classic/default/district/continuity/cover all green. **Close: re-baseline, re-shot tour
|
||||||
|
→ `release_v3_1/` + a `?classic=1` before/after, docs (CITY_SPEC v3.1 flip+classic law, README flags table,
|
||||||
|
LANE_F_NOTES §16), tag `v3.1`.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Round 15 (v3.0 RELEASE CLOSE, `?gigs=1`) — F ran last; the release is closed
|
||||||
|
|
||||||
|
R14 landed A–E's hardening but **F never ran**, so v3.0 stayed untagged. R15 = the close. All deps were in
|
||||||
|
before F ran (A's poster flip + new golden `0x4f4a549d`, D's amp + R14 continuity seam, B/C verifies). The
|
||||||
|
six-item brief, each committed atomically:
|
||||||
|
|
||||||
|
- **Continuity wiring + smoke** (`687d23f`) — the ped you followed is in the crowd: `crew.spawn({roster})` +
|
||||||
|
the queue `onAdmit → recordVenueEntry` relay; `smoke_continuity` asserts the seam on identities + settled tris.
|
||||||
|
- **Alias deletion** (`146b5f3`) — the R12 scalar alias retired; every read keyed by `venueShopId`; tools + spec migrated.
|
||||||
|
- **Full-week soak** (`edc0c47`, `tools/qa/week_soak.py`) — 7 nights × all venues: cover/roster per night, leak-free (plateau), 28 cycles.
|
||||||
|
- **Money-shot tour** (`6a35b29`, `tools/qa/tour_shots.py`) — `docs/shots/release_v3/` + contact.html; hero `queue_night` shows A's now-visible poster; settled measurements.
|
||||||
|
- **Docs** — README v3 section, LANE_F_NOTES §14/§15, this note, five freezes confirmed.
|
||||||
|
- **Acceptance + tag** — `qa.sh --strict` 6/6 + continuity smoke + week soak green → tag **`v3.0`**.
|
||||||
|
|
||||||
|
**Ledger #5 vindicated C:** the gig interior settles to **~182k tris** after the 5 instrument GLBs load (not
|
||||||
|
the ~34k measured pre-load); the interior gate is **draws** (≤56), so no budget violation — the tour + smoke
|
||||||
|
now measure settled. Full runbook: [`LANE_F_NOTES.md §15`](docs/LANES/LANE_F_NOTES.md).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Round 13 (v3.0-beta — THE DISTRICT, `?gigs=1`) — WIRED + VERIFIED + TAGGED
|
||||||
|
|
||||||
|
A–E landed, then D (4-piece + instruments + queue + RY fix). F wired the district together and it fires.
|
||||||
|
**Seed 20261990 → "Boolarra Heads": 3 venues, 2 playing tonight — The Exchange Hotel (pub, `gig-pubrock`)
|
||||||
|
+ an RSL (`gig-covers`), one dark (reads true).** All 7 F tasks done, `qa.sh --strict` GREEN (6/6, 0 warn),
|
||||||
|
tag **`v3.0-beta`**. Shots: `docs/shots/laneF/r13_v3beta_gig_interior.png` + `r13_v3beta_venue_night.png`.
|
||||||
|
|
||||||
|
**What F did (ROUND13 §Lane F):**
|
||||||
|
1. **Per-venue state machine** — `gig_state.js` rewritten: one latch per venue off the shared
|
||||||
|
`procity:segment` spine, keyed by `venueShopId`; all three hard-won R12 laws preserved. New runtime
|
||||||
|
surface `window.PROCITY.gigs.byVenue` + per-venue getters (`onOf/openOf/gigOf/coverOf/paidOf…`); the
|
||||||
|
R12 single-venue alias kept one round. B's `venue.js`/`audio.js` + D's `setGig` read it.
|
||||||
|
2. **Walla + applause** — the crowd bed `crowd-walla` rides the interior tone layer under the live band bed
|
||||||
|
while `on`; an `applause` sting fires once on the `on→done` edge while inside. Verified: `iMusic=gig-pubrock`,
|
||||||
|
`iTone=crowd-walla`.
|
||||||
|
3. **Cover per venue** — the door seam keys off the shop entered, so a night at two venues is two covers.
|
||||||
|
4. **Debt #1 closed — was a LIVE BUG** — E renamed `pubrock-live→gig-pubrock`, so F's R12 `GIG_BED` bridge
|
||||||
|
(rewrote `gig-pubrock→pubrock-live`) pointed at a dead key → the band would have mimed in silence.
|
||||||
|
Deleted; the room's `audio.gigKey` (already the manifest key) feeds the engine directly.
|
||||||
|
5. **Stature gate** — `interior_scale_check.py` now measures stature (crown−foot, platform-independent),
|
||||||
|
ceiling-clip assert kept as its own line (debt #6).
|
||||||
|
6. **District smokes** — `smoke_gigs`: band→4, a per-venue loop (every gigKey resolves across 3 genres,
|
||||||
|
crowd ≤ its own watchPoints, no-giants), street budget at the busiest venue block, the queue assert,
|
||||||
|
plus the R12 determinism/cover/noassets/flags-off arms. All green.
|
||||||
|
7. **The outdoor queue** — D shipped `VenueQueue` and left the street-side lifecycle to F (its documented
|
||||||
|
seam): F wires one queue per venue in the shell (spawn at doors off B's `queueZoneFor`, drive each frame,
|
||||||
|
dispose by `done`), exposed as `queueCountOf(id)`. Verified: 2 in line at doors → 0 by done.
|
||||||
|
|
||||||
|
**Numbers (seed 20261990):** gig interior 62 draws / 34k tris (≤350) · street @ venue block 107 draws /
|
||||||
|
47k tris (≤300/200k) · band+crowd stature 1.61–1.81 m under a 4 m ceiling · cover $10 debited-once/re-entry-
|
||||||
|
free/broke-knockback + free night (seed 1234) · flags-off fingerprint 0x3fa36874 unchanged, gig layer wholly
|
||||||
|
absent. Full runbook + the R13 debt-ledger closeout in [`LANE_F_NOTES.md §13`](docs/LANES/LANE_F_NOTES.md).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Round 12 (v3.0-alpha — the gig layer, `?gigs=1`) — WIRED + VERIFIED
|
||||||
|
|
||||||
|
A–E all landed before me, so F did what F does: turned five parts into one Friday night. **Seed
|
||||||
|
20261990 → "Boolarra Heads", venue = The Thornbury Hotel, tonight = SCREAMING UTES, $10 on the door.**
|
||||||
|
Acceptance shot: `docs/shots/laneF/r12_v3alpha_gig.png`.
|
||||||
|
|
||||||
|
| F task (ROUND12 §Lane F) | result |
|
||||||
|
|---|---|
|
||||||
|
| **1 — gig state machine** | ✅ new F-owned `web/js/world/gig_state.js`. `quiet → doors (DUSK) → on (NIGHT) → done (DAWN) → quiet`, verified forwards AND backwards on the hand-driven clock. On `window.PROCITY.gigs`. |
|
||||||
|
| **2 — `?gigs=1` seam + flags-off** | ✅ shell bootstrap fetches `custom_bands.json` (no fetch when flag-off or `?noassets=1`) → `generatePlanFor(seed, src, {gigs:true, customBands})`. Flags-off verified **wholly absent**: no state machine, no `plan.gigs`/`posters`, no pub, no venue geometry, zero band-file fetches, golden `0x3fa36874` unmoved. |
|
||||||
|
| **3 — cover charge** | ✅ all three halves of John's ruling verified live: **$10 debited once** (191→181, `cover — Screaming Utes` in the wallet), **re-entry the same night free** (181→181), **broke on a new night → polite knockback, no entry**. Free nights (seed 1234) walk straight in, wallet untouched. Zero new UI — rides the R8 buy seam. |
|
||||||
|
| **4 — audio wiring** | ✅ gig bed inside via C's `gigKey`, B's muffled spill outside reads `PROCITY.gigs.state`. **Found + bridged a real C↔E key mismatch — see below.** |
|
||||||
|
| **5 — smokes** | ✅ `smoke_gigs` in `flags_check.py`: schedule determinism · band==3 · crowd ≤ watchPoints · dance mix · bed resolves · ≤350 draws · no-giants · `?noassets=1`+`?gigs=1`. Plus the v3 arm of the flags-off regression. |
|
||||||
|
| **6 — shot + tag** | ✅ shot captured. Tag pending the harness run (below). |
|
||||||
|
|
||||||
|
**Numbers.** Gig-night interior **42–50 draws** with the rig fleet, **171 draws** on `?noassets=1`
|
||||||
|
placeholders (worst; budget ≤350) · ~40k tris · street at the venue 69 draws/29.6k tris · **leak-free**
|
||||||
|
(geo 66→66, tex 63→63 over 4 gig cycles) · band 3 + crowd 8/8 watch points, **3 dancing / 5 standing**
|
||||||
|
· band stature 1.68–1.77 m, crowd 1.60–1.77 m, tallest crown 2.09 m under a 4.0 m ceiling.
|
||||||
|
|
||||||
|
**John's drop-in works:** 7 of your 10 hand-written names fill all 7 nights (Screaming Utes, The Dead
|
||||||
|
Ringers, The Rissoles, The Flannel Shirts, Boganvillea, Chunderbolt, Amyl & The Servos). Covers rolled
|
||||||
|
`10,0,0,3,2,0,9` — 3 free of 7. Under `?noassets=1` it falls back to the pure generator ("The Wombatts")
|
||||||
|
with zero fetch, exactly as A specced.
|
||||||
|
|
||||||
|
### Three bugs F found in its own work (all caught by verification, fixed, re-verified)
|
||||||
|
1. **The state machine only advanced when polled.** Driving NIGHT→DAWN→MORNING→NIGHT with nothing
|
||||||
|
reading in between (player inside the pub, audio muted) **skipped the night roll** — the cover was
|
||||||
|
never due again. Now it rides `procity:segment`, which `lighting.js` fires on every change.
|
||||||
|
2. **Rewinding the clock left `done` stuck** on a midday town (the latch only cleared at MORNING).
|
||||||
|
3. **The night only rolled if the clock passed through DAWN** — so a *jump* from NIGHT to MORNING left
|
||||||
|
last night's paid stamp valid and **a broke punter walked straight in**. Caught by my own smoke, not
|
||||||
|
by hand: stepping with `[`/`]` always crosses DAWN, so only jumps (DBG/shots/soak) exposed it. The
|
||||||
|
roll now keys on landing in the daytime, not on having observed one particular segment.
|
||||||
|
|
||||||
|
Also worth recording, because it nearly shipped a wrong gate: **"no giants" must measure stature, not
|
||||||
|
world crown.** The band stands on C's 0.32 m deck, so a perfectly human 1.73 m guitarist has a 2.05 m
|
||||||
|
world crown and the naive R10 check **failed a correct band**. The smoke now measures `crown − base.y`
|
||||||
|
and keeps the world-crown assert only for "never clips the ceiling" — the real R10 failure mode.
|
||||||
|
|
||||||
|
### Three findings for other lanes (detail + rulings in `LANE_F_NOTES.md` §12)
|
||||||
|
1. **⚠ C ↔ E: the gig bed key never met.** C emits `gig-pubrock` (and asked E for that name); E shipped
|
||||||
|
**`pubrock-live`**. Unresolved, the pub plays to a **silent room** and — because the audio law fails
|
||||||
|
soft — *nothing errors*, you just get a band miming. **F bridged it at the glue** so alpha ships.
|
||||||
|
This is the R1 facade-skin filename trap again. → **needs a named contract line in CITY_SPEC.**
|
||||||
|
2. **⚠ D + E: E's 5 instrument GLBs landed; nothing wires them.** D holds primitives and published
|
||||||
|
`instrumentFor` as the seam "when they land" — they've landed, nobody owns the join. Non-blocking
|
||||||
|
(asset law; reads fine in the shot). F did **not** silently do it inside D's rig. → R13.
|
||||||
|
3. **⚠ spec ↔ C: the band has no drummer.** ROUND12 §D.1 says "drummer seated bob" and E built a merged
|
||||||
|
kit; C's `bandPoses` are guitar/**vocal**/bass and the drum riser is set-dressing. D flagged it and
|
||||||
|
built to C's poses. A spec/implementation divergence **no gate can catch** → Fable/John to rule.
|
||||||
|
|
||||||
|
### Harness note
|
||||||
|
This MacBook had **no Chromium/Chrome**, so the Playwright gates couldn't run locally; John authorised
|
||||||
|
the install (done — 93.5 MB, Playwright CDN). `/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 — but **node isn't on the non-interactive ssh PATH**: use
|
||||||
|
`env PATH=/opt/homebrew/bin:$PATH tools/qa.sh --strict` or every node gate reports a bogus FAIL.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Round 11 (the audio round) — DONE ✅ (v2.1)
|
||||||
|
|
||||||
|
John greenlit "generate freely". E generated the pack (23 assets, `web/assets/audio/`, 8.7 MB ≤ 25),
|
||||||
|
B built the WebAudio engine (`audio.js`, self-unlocking, self-ticking), C added `room.audio =
|
||||||
|
{musicKey,toneKey}` to buildInterior. **Lane F wired + verified + tagged v2.1:**
|
||||||
|
|
||||||
|
| task | result |
|
||||||
|
|---|---|
|
||||||
|
| **F1 — interior beds** | ✅ `interior_mode` enter → `PROCITY.audio.playInterior(current.audio)` (fade in), exit → `stopInterior()` (0.7 s fade). Guarded/no-op when muted/noassets/pre-gesture. 2 lines, the seam B/C designed for. |
|
||||||
|
| **F2 — audio smokes** | ✅ new `smoke_audio` in the flags harness (fresh Chromium): **pre-gesture silence** (ctx not unlocked), **0 boot errors**, **?mute=1** muted, **?noassets=1 zero audio fetches**, **enter/exit leak-free** (12 cycles, 8 shops with a music bed, live AudioNodes bounded at 5, made 42). Caught + fixed my own gesture-via-mouse-click tripping PointerLockControls in headless → gesture via keyboard. |
|
||||||
|
| **F3 — soak + tag** | ✅ soak with audio on **PASS** (heap 48 MB stable, 0 errors, no drift). `qa.sh --strict` **GREEN 6/6**. **v2.1 tagged + pushed.** |
|
||||||
|
|
||||||
|
**Cross-lane (F's own gate, noted to B):** B's `audio.js:143,145` use `Math.random()` for footstep SFX
|
||||||
|
variant + gain jitter — legitimate transient runtime FX (which shop plays which bed is seeded upstream
|
||||||
|
in `interiors.js`), but it tripped F's generation-determinism `Math.random` ban. F **scoped the ban
|
||||||
|
correctly**: `web/js/world/audio.js` is runtime *playback*, not generation → exempted in
|
||||||
|
`scaffold_check.mjs` (B may instead add a `// cosmetic` marker; either is fine).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Round 10 (fix the R9 giant-rig blocker, re-shoot, re-tag) — verify + ship
|
||||||
|
|
||||||
|
R9 shipped a blocking regression: interior rig figures came out ~2× room scale (keeper head 3.83 m in
|
||||||
|
a 3.4 m room) because `rigs.js buildFigure` normalized off the head bone's **bind-pose** Y. **Lane D
|
||||||
|
fixed it** (R10, commit `3432fcc`): normalize by the feet→crown **span**, plus a new `qa.sh --strict`
|
||||||
|
no-giants gate (`interior_scale_check.py`). Lane F verify + re-shoot + soak + re-tag:
|
||||||
|
|
||||||
|
| task | result |
|
||||||
|
|---|---|
|
||||||
|
| **F1 — verify the fix** | ✅ `qa.sh --strict` **GREEN 6/6** incl. the new scale gate: **24 interior figs / 6 shops all crown ∈ [1.4,2.0] m and under-ceiling** (Second Time Redfern, the giant scene: 1.66–1.86 m). Live-measured too — the 3.83 m I first saw was a **stale ES-module cache in a day-old browser tab**, not a regression (served + fresh-chromium code both correct). |
|
||||||
|
| **F2 — re-shoot** | ✅ `browsers.png` re-shot: browser at the rack + keeper at the counter, **human-sized** (*figures human-sized vs door/fittings: yes*). `night_crowd.png` re-framed to a readable "lit VIDEO REGAL amid CLOSED town" — **see the honest note below.** |
|
||||||
|
| **F3 — soak** | ✅ full-default soak **PASS**: 37 chunks, 20 shops, **heap 54 MB stable (no perf drift** from the keeper fix — it's a build-time scale calc), draws 248, **0 errors**. Tri budget still ~327k > 200k (pre-existing rig-model item, D/E decimation — not an R10 change, not the blocker). |
|
||||||
|
| **F4/6 — docs** | ✅ tour README updated with stable v2 filenames + captions; this addendum is the R10 release note. |
|
||||||
|
| **F5 — tag** | ⏸ `v2.0` annotated tag staged on the fixed commit; **holding the push for John** given the night_crowd finding (a public release + his flagged money shot). |
|
||||||
|
|
||||||
|
**Honest finding — `night_crowd` has no crowd, and it can't without new work.** John flagged it as
|
||||||
|
near-black/empty. Re-framed it readable (clean night, tight on the neon). But a visible *street* crowd
|
||||||
|
does not form: Lane D's night patronage parks ducked-in patrons **hidden inside** the shop (they are
|
||||||
|
the interior browsers in `browsers.png`), and seed 20261990's video shop is a **low-traffic corner**
|
||||||
|
(tested default streamed roster + patronage-on at night: **0 patronage, 0 visible peds** at the shop).
|
||||||
|
This is not a framing bug and not (clearly) a regression — it's that the "night crowd" was always the
|
||||||
|
*interior* presence. A visible outdoor queue is a **v3 Lane-D mechanic** (loiter-outside-the-openLate-
|
||||||
|
shop). The ~20 fps in night captures is a **headless software-GL artifact** (128 draws / 65k tris is light).
|
||||||
|
→ Flagged for D: confirm night patronage at the openLate shop still behaves as the v2.0-rc notes claim.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Round 9 (the v2.0 ship round) — DONE (v2.0 tag pulled, re-tagged in R10)
|
||||||
|
|
||||||
|
All lanes landed (C browse points + buy-anywhere, D browser rigs, A CITY_SPEC freeze + closing-time
|
||||||
|
ruling, E VRAM audit + half-res book/toy atlases, B tram + tour bookmarks). F wired the one cross-lane
|
||||||
|
seam, graduated the harness, shot the tour, soaked, and tagged the epoch.
|
||||||
|
|
||||||
|
| task | status |
|
||||||
|
|---|---|
|
||||||
|
| **F1 — interior presence (C→D→F)** | ✅ `interior_mode` reads `occupancyOf(shop.id)` (closure to `citizens`) and spawns browser rigs at C's `room.browsePoints`, one per patronage occupant, **cap 3**; freed by `exit()`'s `keepers.disposeAll`. Door points carry `shopId`. Verified: enter occupied "Second Time Redfern" → 1 browser == `min(occ,3)`, disposed clean, 0 errors. |
|
||||||
|
| **also wired** | ✅ **Tram** (`?tram=1`, `createTram`+update). **Buy-anywhere** (C2): E at a book-spine/toy-box `buyMesh` buys over the wallet (`collapseBuyItem`+toast); preload book+toy packs. Verified: bought a book spine, cash 191→177. |
|
||||||
|
| **F2 — smokes to strict** | ✅ buy-v0 / patronage / book-toy / katoomba **graduated warn→STRICT**. New warn smokes: interior-presence, shelf-buy, tram. Fixed the book-stock check to count `isStock OR buyMesh` (C2 tags spines `buyMesh`). `qa.sh --strict` GREEN 5/5, harness 0 fails / 0 warns. |
|
||||||
|
| **F3 — tour + soaks** | ✅ `tools/v2_tour.py` → **11 shots** (`docs/shots/v2_tour/`, un-letterboxed) incl. the **rain-verandah money shot** (rain + winmap-lit open video shop + CLOSED dark neighbours at night) + browsers + dig-real + katoomba + tram_stop + contact sheet. **Both soaks PASS** (full-default + all-on: 37 chunks, 20 shops, heap stable, 0 errors, leak-free). |
|
||||||
|
| **F4 — docs freeze + tag v2.0** | ✅ Flags table finalized (below); **`v2.0` tagged** on a green tree. |
|
||||||
|
| **Marshal** | C1 (the round's SPOF) landed early; all lanes committed; no slips. |
|
||||||
|
|
||||||
|
### v2.0 flags — FINAL
|
||||||
|
| flag | owner | status | default |
|
||||||
|
|---|---|---|---|
|
||||||
|
| roster | D | **graduated → DEFAULT-ON** (R7 flip); `?roster=v1` escape hatch | on |
|
||||||
|
| patronage | D | default-on (`?patronage=0` off) | on |
|
||||||
|
| `?dig=1` | C | opt-in (crate riffle) | off |
|
||||||
|
| `?winmap=1` | B | opt-in (parallax window glass) | off |
|
||||||
|
| `?stock=real` | C/E | opt-in (real GODVERSE covers/spines/boxes + buy-anywhere) | off |
|
||||||
|
| `?weather=1` | B | opt-in (seeded rain/overcast/clear; `?weather=rain` forces) | off |
|
||||||
|
| `?plansrc=osm[&town=]` | A | opt-in (real-OSM towns: melbourne, katoomba) | off (synthetic) |
|
||||||
|
| `?tram=1` | B | opt-in (spine tram loop) | off |
|
||||||
|
| `?noassets=1` | — | opt-in (primitive-only, zero-network) | off |
|
||||||
|
Determinism goldens frozen: synthetic `0x3fa36874` · osm/melbourne `0x34cfdec0` · osm/katoomba `0x0f652510`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Round 8 (catch-up + the town comes alive → v2.0-rc) — DONE ✅
|
||||||
|
|
||||||
|
All lanes landed (E packs, B weather, A 2nd town, C buy, D patronage). F wired the shell seams,
|
||||||
|
graduated the harness, tagged.
|
||||||
|
|
||||||
|
| task | status |
|
||||||
|
|---|---|
|
||||||
|
| **shell wiring (F owns index.html)** | ✅ **A** `&town=<key>` → `generatePlanFor(seed,'osm',{town})` (Katoomba, 19 shops, verified). **B** weather (3-line contract): `createWeather` on `?weather=1`, `PROCITY.weather={state,intensity}` ({clear,0} when off for D), `weather.update` in loop. **C** session `wallet` (createWallet(seed)) bound to the dig BUY (`getCash`/`onBuy` in interior_mode). **D** patronage: shop door-points per chunk → `citizens.setShops`, `?patronage=0` off-switch, `citizens.setWeather` each frame. All boot clean (0 console errors, settled). |
|
||||||
|
| **F1 — smoke graduations + additions** | ✅ `stock=real` + `weather=rain` **graduated warn→STRICT**. New warn smokes: **buy-v0** (dig BUY→wallet: cash 191→179, bag 0→1), **patronage** (7 peds visit shop doors/~18s), **book/toy stock** (6/11 real atlas meshes). Katoomba golden `0x0f652510` pinned in the determinism gate (fixed a hex zero-pad bug it caught). |
|
||||||
|
| **F2 — patronage baseline** | ✅ No re-pin needed — flags-off regression held (patronage nets *fewer* draws, inside tolerance; ≤300 cap + draws-±40% both green). |
|
||||||
|
| **F3 — tag v2.0-rc** | ✅ **TAGGED.** qa.sh --strict GREEN 5/5 (harness strict). |
|
||||||
|
| **Marshal** | All lanes committed round-8; no slips this round. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Round 7 (the flip round → v2.0-beta) — DONE ✅ (v2.0-beta tagged)
|
||||||
|
|
||||||
Critical path resolved: E1 ped-merge (`447188a`), E2 stock pack (`0c12dac`), **D's GO** (`43edabf`),
|
Critical path resolved: E1 ped-merge (`447188a`), E2 stock pack (`0c12dac`), **D's GO** (`43edabf`),
|
||||||
C1 `?stock=real` (`3724a65`) all landed. F executes flip → strict harness → tag.
|
C1 `?stock=real` (`3724a65`) all landed. F executes flip → strict harness → tag.
|
||||||
|
|||||||
377
G-progress.md
Normal file
@ -0,0 +1,377 @@
|
|||||||
|
# G-progress — GODVERSE lane (runs on ultra)
|
||||||
|
|
||||||
|
*Lane brief: `docs/LANES/LANE_G_GODVERSE.md`. This file follows your progress-file convention —
|
||||||
|
newest round first. Fable-on-ultra writing.*
|
||||||
|
|
||||||
|
## Round 27 (2026-07-17) — tier 2: the two read endpoints (ledger #1)
|
||||||
|
|
||||||
|
**Landed:** `pipeline/godverse_server.py` — `/health`, `/shop/<gid>/stock`, `/shop/<gid>/crates`,
|
||||||
|
plus `start|stop|status` (F's kill-the-server lifecycle), `setup-role`, and `make-fixture`.
|
||||||
|
Stdlib + `psql`; no server code existed before this round, per the ratified G3.
|
||||||
|
|
||||||
|
**Live now, against the real POS:** crate 550 — `{baked:120, live:120, gone:0}`, real prices, real
|
||||||
|
`VG+` grading, etag stable across reads and `?since=` → **304**. `/crates` returns **315 real
|
||||||
|
crates** with the shop's own labels — `TECHNO` (57), `$12-$15 TECHNO` (46), `DRUM N BASS` (66).
|
||||||
|
|
||||||
|
**The three fences, each enforced by the machine (measured, not reviewed):**
|
||||||
|
|
||||||
|
| fence | proof |
|
||||||
|
|---|---|
|
||||||
|
| the credential — `godverse_ro` | `UPDATE inventory` → **permission denied** · `SELECT FROM customer` → **permission denied** |
|
||||||
|
| write verbs absent (§7 ruling) | `POST /reserve` → **501** · `POST /buy` → **501** · `GET` either → 404 |
|
||||||
|
| metadata only | no `cover`, no atlas bytes on the wire — covers stay static tier-1 files |
|
||||||
|
| tier 2 = real shops only | a `mint` shop's `/stock` → **404** (no POS to be live about), not an empty crate |
|
||||||
|
| the id fence | `gone[]` carries the atlas `id` verbatim → `gone.includes(item.id)`, zero transform |
|
||||||
|
|
||||||
|
**One grant carries both the sandbox and the PII fence** — that's why §7 said enforce at the
|
||||||
|
credential, not in a code review. `godverse_ro` holds SELECT on inventory/crate/disc_cache and
|
||||||
|
nothing else, so customer/staff/sales are unreachable *by the process*, not by our good intentions.
|
||||||
|
|
||||||
|
**⚠ THE FINDING F NEEDS BEFORE IT WRITES THE GATE — `gone[]` has no subject in production.**
|
||||||
|
The mechanism is right; the data has no movement. Monster Robot Party's POS records **5,567 sales
|
||||||
|
ever** (400–800/month Dec→Jun) but **26 in July and none since 2026-07-01**, and hasn't been
|
||||||
|
*written* since **2026-07-09** — the 07-16 dump is fresh, its data is stale (I restored it to
|
||||||
|
check: crate 550 holds 122 on both 07-15 and 07-16, identical). **0 of the crate's 120 records
|
||||||
|
have ever sold.** So `gone` is `[]` against the real POS, permanently, until the shop trades again.
|
||||||
|
An assert over it would pass **without touching its subject** — the exact species that has held
|
||||||
|
four tags. So I built the honest way out: **`make-fixture --sold 3`** clones the POS
|
||||||
|
(`createdb -T`) and marks 3 of the crate's *own real skus* sold → verified
|
||||||
|
`{baked:120, live:117, gone:3}`, the three ids absent from `items[]`. F points the server at it
|
||||||
|
via `GODVERSE_POS_DB` and gets a deterministic gate; production reads the real POS untouched.
|
||||||
|
**A fixture is not a lie; a green assert over an absent subject is.**
|
||||||
|
|
||||||
|
**Lifecycle for the kill-the-server gate (ledger #3):** `stop` is a **SIGKILL**, not a graceful
|
||||||
|
drain — the gate tests a death, not a goodbye. Verified: `stop` → connection refused (curl rc=7,
|
||||||
|
no response at all) → `status: DOWN` → `start` → live again. F drives it; no admin verb was added
|
||||||
|
to the API, because a server whose whole point is "no write verbs" shouldn't grow a remote kill.
|
||||||
|
|
||||||
|
**Three corrections this round forced into my own G3 §4 (design → as-built):**
|
||||||
|
1. **`gone:[sku]` → `gone:[id]`.** §4 as *I ratified it* said bare skus — wording that predates
|
||||||
|
R25's sku ids and R26's fence law. A bare number crossing two id spaces is the precise bug that
|
||||||
|
law exists to stop. **My doc was the stale rule meeting a case written after it** — the epoch's
|
||||||
|
signature failure, and this time it was mine, in the doc I wrote.
|
||||||
|
2. `items[]` drops `release_id` and `cover`: `cover` edges the API toward serving pixels (it never
|
||||||
|
should), and `release_id` is the *pressing* while tier 2 speaks only in *copies*.
|
||||||
|
3. **Write verbs answer 501, not the 404 my own docstring claimed** — caught by running it. 501 is
|
||||||
|
the *stronger* absence proof: a 404 means a handler ran and declined; 501 means there is no
|
||||||
|
write handler in the process. **→ F: assert 501.**
|
||||||
|
|
||||||
|
**→ Lane F, the four asserts I can hand you pre-measured:** `POST /reserve|/buy` → 501 · a game
|
||||||
|
buy cannot reach the server (no verb *and* no grant — prove both) · `gone` fixture arm as above ·
|
||||||
|
server dead → connection refused, so the reader must treat it as tier 1 in silence.
|
||||||
|
|
||||||
|
## Round 26 (2026-07-17) — the mint baker, the crate menu, the manifest (ledgers #2, #6)
|
||||||
|
|
||||||
|
**Landed (main, pathspec-atomic):**
|
||||||
|
- **`godverse_stock.py mint`** — the G3 note promoted: `random.Random(shop_id)` over an
|
||||||
|
**id-ordered** pool of real dealgod listings. The shop id IS the seed, so two shops can never
|
||||||
|
draw the same crate. No TABLESAMPLE, no unseeded random — the two things that disqualified
|
||||||
|
thriftgod's own `mint()` from tier 1 (G3 §2).
|
||||||
|
- **14 mint atlases** — every keyed shop in both godverse towns whose type the loader can actually
|
||||||
|
fetch (`record`/`book`/`toy`, C §7.1): 5 record + 6 book + 3 toy. The 13 keyed `opshop`/`video`
|
||||||
|
shops get none and stay parody — correct, not a gap: C §7.1 gives them no fetchable type, so an
|
||||||
|
atlas there could never load. **The manifest is what makes that silent** (no entry ⇒ no probe).
|
||||||
|
- **`stock_godverse/index.json`** (§7.2d) — 15 shops, 1 real / 14 mint. Derived by reading the
|
||||||
|
atlases back off disk, so it cannot drift from them: the manifest *is* the directory.
|
||||||
|
- **Monster Robot re-emit**: `sourcing: "real"`, plus C §7.2c's `condition`/`sleeve_cond` (my
|
||||||
|
snapshot always carried them; I was dropping them at emit — that was the hole in C's price card).
|
||||||
|
|
||||||
|
| gate | result |
|
||||||
|
|---|---|
|
||||||
|
| every crate different (F's #7 distinctness, title sets) | **15/15 distinct · max shared titles between any two: 0** |
|
||||||
|
| ids unique across all packs · clash with generic · positional | 344 unique · **0** · **0** |
|
||||||
|
| mint determinism (re-mint + re-bake) | **byte-identical** |
|
||||||
|
| mint carrying a POS-claim field | **0** — absent, not false |
|
||||||
|
| `sourcing` present on every atlas | 15/15 |
|
||||||
|
| E's manifest↔files, both ways | clean |
|
||||||
|
| selfcheck (no town cache touched) | **156212/156212 green** |
|
||||||
|
| E's validator | **14 errors — all one cause, below** |
|
||||||
|
|
||||||
|
**THE ONE RED — mint ids vs E's `SLOT_ID_PREFIX` (E, one line).** E asserts `sku_` on *every* atlas
|
||||||
|
under `stock_godverse/`. Mint items have no POS sku, so they carry `mint_<dealgod listing id>` and
|
||||||
|
all 14 fail. **C §7.2a's rule is satisfied** — "derive from the source's own stable key, never
|
||||||
|
position, unique across ALL packs" (measured above: unique, 0 clash, 0 positional). What §7.2a
|
||||||
|
specifies as `sku_<POS sku>` it specifies **"for a real-stock (godverse) pack"**; mint is C §7.2b's
|
||||||
|
*other* thing, written a round later, and the id form was never revisited. E implemented R25
|
||||||
|
literally, which was right at the time.
|
||||||
|
|
||||||
|
**Why I did not just emit `sku_` and go green:** a POS sku and a dealgod listing id are **different
|
||||||
|
namespaces**, and this round's own standing note says no code or gate may compare a bare number
|
||||||
|
across two id spaces — it was written because `enterShop(31)` entered the wrong shop. Putting both
|
||||||
|
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 that a POS
|
||||||
|
must resolve. C §7.2a's own reason #2 is precisely about ids being looked up across snapshots.
|
||||||
|
Green by prefix would have been green by lie. **Fix: make the prefix sourcing-aware (`sku_` when
|
||||||
|
`sourcing == "real"`, `mint_` when `"mint"`)** — E's error text and constant are the only places.
|
||||||
|
|
||||||
|
**Three more findings, none blocking:**
|
||||||
|
1. **[E] `FITS_1024 = 64` cannot be satisfied.** The warn asks for ≤1024² when a pack has ≤64
|
||||||
|
items, but 1024²/256² = **16 cells** — 64 items would need **4** atlases and C caps a shop at 2.
|
||||||
|
So any 33–64 item pack *must* be 2048², and the warn would be demanding the impossible. It never
|
||||||
|
fires on my packs (16 → 1×1024², 120 → 2×2048²), so it's latent. The honest bound is 32.
|
||||||
|
2. **[C/F] The ≤32 MB ceiling doesn't hold at beta scale, because the LRU it assumes hasn't
|
||||||
|
shipped.** F's boot preload loads **every** keyed shop's pack (`index.html:207-215`), and C §7.6
|
||||||
|
scoped "≤32 MB peak" to "LRU = 1 shop's atlas set resident", with the LRU explicitly deferred to
|
||||||
|
v5.0-beta. Measured now: **46.1 MB per town** resident at boot (both towns). Bounded and modest
|
||||||
|
— and it's *why* mint crates are 16 items on one 1024² sheet rather than something plusher.
|
||||||
|
3. **[C] "The crate menu, baked statically" can only be metadata.** One base = one index = ≤2
|
||||||
|
atlases = 128 items; Monster Robot has **357 crates / 24,646 bake-able records**. So the shop's
|
||||||
|
real crate list ships as `crate_menu` (id/name/label/count — real labels, real counts), and
|
||||||
|
per-crate *stock* would need a per-crate base (`…/3962749/crate_550/`) = a C contract line.
|
||||||
|
Filed, not invented. Rotation's real home is tier 2 (G3 §4 `/crates`).
|
||||||
|
|
||||||
|
**Mint honesty, stated plainly.** A mint crate is *plausible*, never a claim: `sourcing: "mint"`,
|
||||||
|
an attribution that says "not this shop's stock", and **no POS-claim field at all** — the
|
||||||
|
separation is structural, not editorial. Quality caveat: for books/toys `artist` is dealgod's
|
||||||
|
`brand`, which is sometimes the maker (Microsoft, Sony, Brandon Stosuy) and sometimes the listing
|
||||||
|
store (Lifeline Queensland) — real data, imperfectly typed. **Records are clean**: I require a
|
||||||
|
Discogs artist and drop otherwise, because `brand` on a record listing is the *seller* — the first
|
||||||
|
bake had "Red Eye Records" and "Statecircle" as artists, which is a shop, not an act. Titles use
|
||||||
|
`discogs_master_title` where dealgod has it, so Egg Records' crate reads "MC5 — High Time · $50",
|
||||||
|
not "Pre Loved Record - Elton John - Breaking Hearts". Both are real; one is a record sticker.
|
||||||
|
|
||||||
|
## Round 25 (2026-07-17) — the sku re-emit (ledger #3), index-only
|
||||||
|
|
||||||
|
**Landed (main — G is a full lane now, pathspec-atomic; F's six staged files and E's in-flight
|
||||||
|
`validate_atlas.py` untouched):**
|
||||||
|
- `godverse_stock.py` emits **`id = "sku_<POS sku>"`** per C §7.2a. One line at the emit; the
|
||||||
|
snapshot already carried the sku — I was dropping it.
|
||||||
|
- `stock_godverse/3962749/stock_record_index.json` re-emitted. **Index-only, as briefed:**
|
||||||
|
|
||||||
|
| check | result |
|
||||||
|
|---|---|
|
||||||
|
| **id overlap with the generic pack** | **0/120** — was **120/120** (F's finding) |
|
||||||
|
| ids unique in-pack / all `sku_`-prefixed / none in `rec_` namespace | 120/120 · yes · yes |
|
||||||
|
| **atlas .webp bytes moved** | **none** — sha-identical both files |
|
||||||
|
| town caches / goldens touched | none |
|
||||||
|
| ids stable when a record sells (drop item 50, re-derive) | **0 drift** |
|
||||||
|
| E's validator (their R25 build, live in-tree) | `atlas-QA OK · 1 licence line(s) shown · 0 warnings` |
|
||||||
|
| live loader: shop pack vs town pack | 120 / 350, coexist; uv + one-material-per-atlas hold |
|
||||||
|
|
||||||
|
**Id equality is discriminating again** — verified through the live loader, not just the file.
|
||||||
|
|
||||||
|
**C's reason #2 is the one that mattered**, and it's worth restating because it outlives the gate:
|
||||||
|
position isn't identity. Sell one record out of crate 550 and `rec_0050` silently becomes a
|
||||||
|
*different* record on the next bake — so a wallet purchase, a saved dig, or **tier-2's
|
||||||
|
sold-means-gone** would point at the wrong item. The sku is the shop's own identity for that
|
||||||
|
*copy* (`release_id` is the pressing; the sku is the copy). My positional ids were a latent
|
||||||
|
correctness bug in the ratified §7 economy, not just a vacuous gate. Caught while there were two
|
||||||
|
atlases to migrate instead of fifty.
|
||||||
|
|
||||||
|
**Conflict #1 is closed** (R24 → R25): E's validator now prints
|
||||||
|
`LICENCE: … FLAG BEFORE ANY PUBLIC/COMMERCIAL RELEASE` against my top-level US `license`, and
|
||||||
|
counts it. The flag a human must read on real product photos is unmissable. Nothing owed.
|
||||||
|
|
||||||
|
**→ F (one line, your file, your call — not touching it mid-flight):** `interior_mode.js:378`
|
||||||
|
now reads *"ids are positional slots (`rec_0000`) that BOTH packs reuse … every atlas id is also a
|
||||||
|
town-pack id (measured R24)"*. As of this commit that's false — 0/120. **Your gate stays canonical
|
||||||
|
either way**: a texture URL proves which file the GPU sampled, which id equality never could, so
|
||||||
|
the assertion is right and only the rationale is stale. Id equality is now available as a *second*
|
||||||
|
discriminator if you want belt-and-braces.
|
||||||
|
|
||||||
|
## Round 24 (2026-07-17) — the re-emit + THE SHOP'S REAL STREET (ledger #5)
|
||||||
|
|
||||||
|
**THE WRITE-BACK RULING — John, 2026-07-17: A THEN B.** G3 §7 is **ratified** with the ruling
|
||||||
|
inline; charter law #4 is satisfied. v5.0 ships **sandboxed** — a game purchase never touches real
|
||||||
|
monsterrobot/Square inventory. **Option B (reservation) is chartered as a v5.x upgrade** behind a
|
||||||
|
hold budget John sets; **C is not chartered**. What I wrote into the doc as binding, beyond the
|
||||||
|
one-liner, because a sandbox that can be flipped on by a config typo isn't a sandbox:
|
||||||
|
`POST /reserve` + `/buy` stay **absent, not flag-stubbed**; the tier-2 reader connects with a
|
||||||
|
**read-only role** (`SELECT` on inventory/crate/disc_cache only — which carries the §9 PII fence in
|
||||||
|
the same grant); and the gate asserts the write verbs **404** + the role's grants are read-only,
|
||||||
|
because "we didn't call buy()" proves nothing (the vacuous-gate law applied to ourselves).
|
||||||
|
**Carried to v5.x: the hold budget is John's number and is not yet set** — B isn't startable until
|
||||||
|
it is. B's non-negotiable: **the shop always wins** a race with an in-store Square sale.
|
||||||
|
|
||||||
|
**Landed (branch `lane-G-godverse`):**
|
||||||
|
- **`redhill_godverse`** — the town whose record shop is THE shop. Monster Robot Party seated on
|
||||||
|
Musgrave Road (**verified: lot 8, type `record`, `godverseShopId` 3962749**), 37 shops, 884 real
|
||||||
|
Red Hill roads off E's donor.
|
||||||
|
- **The atlas re-emit** — US `license` throughout (British `licence` retired, 0 occurrences),
|
||||||
|
provenance lifted to top level per C §7.3 **and** kept nested per E's validator (see conflict #1).
|
||||||
|
`atlas-QA OK`, 120 real records, byte-identical re-bake holds.
|
||||||
|
- **`newtown_godverse` re-emitted with `godverseShopId`** (ledger #2) — 18 census shops keyed.
|
||||||
|
- **`godverse_town.py`**: psycopg2 → `psql` (the m3 has no psycopg2); emits `godverseShopId`;
|
||||||
|
`EXTRA_SHOPS` roster; **Fable's R22 Ruling 2 finally implemented** (see below).
|
||||||
|
- G3 §4: the `godverseShopId`/`godverse_id` naming split recorded (two artifacts, two conventions).
|
||||||
|
|
||||||
|
**Monster Robot Party's coordinate** is John-supplied (Google Maps place `/g/11g6rlbbc0` →
|
||||||
|
-27.4553791, 153.0076244). It could not be derived: OSM doesn't know the shop (no name match, no
|
||||||
|
147 Musgrave Rd address node), the census doesn't have it, dealgod's `stores`/`store_entity` rows
|
||||||
|
carry NULL lat/lng, and recordgod has no address anywhere. Verified inside E's bbox **and** on
|
||||||
|
Musgrave Road's own geometry. My own estimate was ~120 m out — asked instead of shipping it.
|
||||||
|
|
||||||
|
**FABLE'S RULING 2, IMPLEMENTED (R22 → R24) — and it was load-bearing, not cosmetic.** "Merge
|
||||||
|
census heroes WITH the donor's texture shops; census heroes win a collision; count the swap."
|
||||||
|
I never built it. Measured this round, census-only Red Hill is **not shippable**:
|
||||||
|
|
||||||
|
| redhill_godverse | shops | A's spacing warn | poster-clears-kerb |
|
||||||
|
|---|---|---|---|
|
||||||
|
| census only | 9 | **306 m** ⚠ | pass |
|
||||||
|
| census + Monster Robot | 10 | **261 m** ⚠ | **FAIL** (poster inside edge 1037 by −2.95 m) |
|
||||||
|
| **+ donor texture (Ruling 2)** | **37** | **none** ✓ | **pass** ✓ |
|
||||||
|
| *(E's donor `redhill_real`, for reference)* | 36 | none | pass |
|
||||||
|
|
||||||
|
261 m is **worse than the 255 m that retired ballarat this round** — the alpha's headline town
|
||||||
|
would have shipped flagged "scattered, not a high street, or retire the town". Ruling 2 fixes both.
|
||||||
|
9 donor heroes swapped for their census entries in Red Hill, 18 in Newtown — counted, per the ruling.
|
||||||
|
|
||||||
|
**CONFLICT #1 — C's contract §7.3 vs E's validator (provenance).** C says "lift the four to TOP
|
||||||
|
LEVEL, US `license`; British retired — *exactly what E's validator reads*". **Measured: it is not.**
|
||||||
|
`validate_atlas.py:98` reads **nested** `provenance`, and `:105` reads `licence` British-first.
|
||||||
|
Lifting-and-retiring as written **fails the gate**. My R23 atlas already passed it untouched. This
|
||||||
|
is the same species as the bug that held the tag — a doc asserting what code does without running
|
||||||
|
it. **Resolved without drama:** E's check accepts `licence or license`, so US-nested passes; I emit
|
||||||
|
the four at top level (C's contract + the town-cache house convention) off ONE source of truth.
|
||||||
|
**→ E, one line:** `validate_atlas.py:198` PRINTS `licence` British-only while its *check* accepts
|
||||||
|
both — so adopting C's mandated US spelling **silently stops printing the licence flag**, which
|
||||||
|
:199's own comment calls a human call ("the licence is PRINTED — clearing it is a human call").
|
||||||
|
The gate still passes; the evidence line vanished. Fix: `.get("licence") or .get("license")`.
|
||||||
|
|
||||||
|
**CONFLICT #2 — Ruling 2 vs A's identity gate. THIS ONE NEEDS A RULING; it is why the tree is red.**
|
||||||
|
Ruling 2 makes a godverse cache *mixed*: a keyed GODVERSE layer (census + Monster Robot) plus an
|
||||||
|
inherited OSM texture layer. A's sweep asserts **every** shop is keyed → partial ⇒ FAIL. The two
|
||||||
|
standing rulings cannot both hold.
|
||||||
|
|
||||||
|
**The texture shops carry no `godverseShopId` deliberately**, and I'd argue A's own words settle it:
|
||||||
|
- The field *means* "this lot's stock lives at `stock_godverse/<id>/`". An OSM milkbar has no
|
||||||
|
GODVERSE identity. Minting one from its OSM id pushes a **third id space** into a field that
|
||||||
|
already carries two, and A's validator calls a duplicate **mis-stocking** — charter risk #3's
|
||||||
|
exact nightmare, with Monster Robot's crate as the prize. (Measured: OSM ids here are ≥53.1 M vs
|
||||||
|
census ≤2992 and store id 3,962,749 — no collision *today*; dealgod store ids reach 7 digits and
|
||||||
|
OSM ids are 8+, so the margin is thin, and "no collision today" is not an id-space design.)
|
||||||
|
- **A's validator and A's sweep disagree with each other.** The validator: *"Missing = tier 0
|
||||||
|
(soft). Duplicate = mis-stocked (hard). **They are not the same failure.**"* The sweep fails
|
||||||
|
partial keying because it *"mis-stocks the rest"* — conflating precisely the two failures A said
|
||||||
|
are not the same. Unkeyed → parody is the **designed ladder**, not a gap.
|
||||||
|
- **Proposed (A's call, Fable's ruling):** assert coverage over the **godverse layer**, not the
|
||||||
|
inherited texture layer — the subject is "shops that claim stock", and the gate stays honest
|
||||||
|
(it still names its subject and proves a count). Zero-keyed still SKIPs; duplicates still error.
|
||||||
|
|
||||||
|
**The 8 red checks, itemised — no surprises for F:**
|
||||||
|
- `newtown_godverse` base+gig goldens (**2**) — my re-emit moved them, exactly as ledger #2
|
||||||
|
intends. **A re-pins** (batched): base `0xcf69b387`, gig `0x1e266f60`.
|
||||||
|
- `godverse identity` ×3 on each godverse town (**6**) — conflict #2, above. Nothing else.
|
||||||
|
- Unpinned (warn, expected): `redhill_godverse` base `0xb2224939` / gig — **A pins**.
|
||||||
|
- **Classic covenant `0x3fa36874` intact.** `atlas-QA OK`.
|
||||||
|
|
||||||
|
**If Fable rules the other way** (texture layer out): revert to census-only and F inherits the
|
||||||
|
poster-kerb FAIL at edge 1037 + a 261 m town — that break is A/B's invariant, exposed by real data,
|
||||||
|
and would need an owner before the tag. I'd rather hand F the conflict than the broken town.
|
||||||
|
|
||||||
|
## Round 23 (2026-07-16) — G2a: THE FIRST REAL CRATE + the G3 draft
|
||||||
|
|
||||||
|
**Landed (branch `lane-G-godverse`, G namespace only — ready for your mid-round merge):**
|
||||||
|
- `pipeline/godverse_stock.py` — the per-shop atlas pipeline, split at **the snapshot boundary**:
|
||||||
|
`snapshot` (runs where the DBs are; freezes every source row + every cover's sha256) →
|
||||||
|
`pack` (pure: snapshot + image bytes → atlas, no DB, no network). That split is deliberate —
|
||||||
|
**it's what lets E's `validate_atlas` gate G's output on m3 without the dealgod DB** (ledger #4).
|
||||||
|
- `pipeline/_godverse/monsterrobot_550.snapshot.json` — the artifact of record (committed).
|
||||||
|
- `web/assets/stock_godverse/3962749/` — **120 real records** from a real crate: 2 atlases
|
||||||
|
(2048², 256px cells, E's contract verbatim) + index with provenance.
|
||||||
|
- `docs/G3_ECONOMY_DESIGN.md` — the draft (ledger #2), incl. **the write-back question framed for
|
||||||
|
John** (§7), now grounded in a real trading shop rather than a hypothetical.
|
||||||
|
- **Verified:** byte-identical re-bake (same snapshot → same bytes); uv ⊂ [0,1]; every item's
|
||||||
|
atlas ref resolves; bands standard 58 / collector 56 / grail 6. Eyeballed the atlas: it's a real
|
||||||
|
deep-house crate (Basement Jaxx, Byron Stingily, Angel Moraes, 4hero, Murk, Blaze).
|
||||||
|
|
||||||
|
**THE HEADLINE FINDING — the brief's premise doesn't hold (all measured, R23):**
|
||||||
|
The ledger asked for "dealgod inventory → one real **Newtown** record shop". That can't be built:
|
||||||
|
- **No Newtown record shop has stock in any database.** Egg Records / Papa Disquo / Haze Guitars /
|
||||||
|
The Vintage Record / Downtown Music: **0 items each**.
|
||||||
|
- **`dealgod.stores` is not a shop census** — 8,754 of 9,285 are **Discogs seller accounts**.
|
||||||
|
Zero Newtown stores, zero census name matches. It's an item *feed*, not a shop directory.
|
||||||
|
- **`thriftgod.item` is a demo seed** — 2,221 items across 29 shops, **all Brisbane/Newcastle/Alice
|
||||||
|
charity shops**, zero record shops.
|
||||||
|
- **`mint()` cannot produce tier 1**: `TABLESAMPLE SYSTEM (0.5)` + `random.random/uniform/shuffle`,
|
||||||
|
and **`random.seed` appears nowhere in `server.py`**. Non-deterministic, and it assigns other
|
||||||
|
sellers' listings to shops that never owned them — which also voids the licence law's "the
|
||||||
|
*stores' own* product shots" green and makes write-back meaningless. (G3 §2.)
|
||||||
|
- This is **not** risk #3's "fuzzy matching" — it's a population mismatch. Nothing to match *to*.
|
||||||
|
|
||||||
|
**What we found instead — and it's better.** `monsterrobot` (**147 Musgrave Rd, Red Hill, Brisbane**
|
||||||
|
— John's ruling) is the same real shop in three datasets: `recordgod` (its POS: 28,624 live vinyl,
|
||||||
|
**24,914 in 357 real crates** with real labels and slot numbers), `monsterrobot.party` (its own
|
||||||
|
site — **the covers are the shop's own product shots**), and dealgod store **3962749** (the scrape;
|
||||||
|
the cover cache + the godverse id). **99.3%** of its crate stock joins to a cached cover by Discogs
|
||||||
|
`release_id`. Not in thriftgod's census, so its godverse id = its dealgod store id 3962749 (real,
|
||||||
|
stable, clear of the census space — thriftgod max shop id = 2992). Drops counted: 120 of 122 in
|
||||||
|
crate 550 baked; 2 dropped (no own photo).
|
||||||
|
|
||||||
|
**Rulings received from John this round (recorded):**
|
||||||
|
1. **The crate ships as the shop it really is** — Red Hill, Brisbane, not Newtown.
|
||||||
|
2. **Real titles/artists at tier 1** — an amendment to the no-real-trademarks law that
|
||||||
|
`build_stock_pack.py` enforces (it parody-transforms metadata). Same shape as the licence law:
|
||||||
|
in-house 🟢, **flagged before anything public**. Recorded in every atlas's provenance.
|
||||||
|
|
||||||
|
**Asks — two things gate the crate reaching a room, and both are outside G's namespace:**
|
||||||
|
1. **[C + F] Per-shop packs aren't wired at runtime.** `stockpack.js` caches by `type` alone
|
||||||
|
(`_packs`/`_resolved`), and `interior_mode.js:192` does `getStockPack(shop.type)` with `base`
|
||||||
|
fixed to `assets/models/`. R23's single shop works via a `base` override, but **two stocked
|
||||||
|
shops collide in that cache** — so this blocks v5.0-beta, not just ledger #3. Seam needed:
|
||||||
|
key the cache by `type + base` (or `godverse_id`) + preload per-shop on entry. **C owns the
|
||||||
|
wording (LANE_C_PUB); G re-emits to whatever lands.**
|
||||||
|
2. **[E, via Fable] The crate has no street.** `redhill_real` doesn't exist; `westend_real` is the
|
||||||
|
only Brisbane cache and stops ~2.3 km south. Needs a `build_towns.py` TOWNS entry
|
||||||
|
(~`center: (-27.4553, 153.0064), span_km: 2.4`) + one Overpass fetch, then
|
||||||
|
`godverse_town.py redhill_real redhill_godverse`. **That's E's file and an outward-facing
|
||||||
|
fetch — G touched neither.** Note monsterrobot must be added to the godverse cache's `shops[]`
|
||||||
|
as id 3962749 (it's not in the census); `godverse_town.py` takes shops from thriftgod only, so
|
||||||
|
that's a small G change once you rule on the town.
|
||||||
|
|
||||||
|
**Environment note (matters for reproducibility):** this ran on the **m3**, not ultra — the box has
|
||||||
|
neither `thriftgod` nor `dealgod` Postgres. Both were restored/read from John's mounted backup
|
||||||
|
drive (`/Volumes/johnking/db-backups`, **2026-07-15** dumps); dealgod (2 GB gz) was never restored,
|
||||||
|
its cover map was streamed straight out of the dump (command recorded in
|
||||||
|
`godverse_stock.py:load_image_map`). This is *better* for the charter's snapshot discipline, not
|
||||||
|
worse: the dump date is pinned in every snapshot's provenance. I left two restored DBs on the m3
|
||||||
|
(`thriftgod`, `recordgod`) — say the word and I'll `dropdb` them.
|
||||||
|
|
||||||
|
**Fences held:** PII — `recordgod` holds real `customer`/`staff`/`sales`; the pipeline reads only
|
||||||
|
`inventory`/`crate`/`disc_cache`, and G3 §9 makes that a standing fence. Licence — covers are the
|
||||||
|
shop's own shots, in-house 🟢, flagged before public, provenance in every atlas.
|
||||||
|
|
||||||
|
## Round 1 (2026-07-16, ultra) — the marriage's first artifact
|
||||||
|
|
||||||
|
**Landed:**
|
||||||
|
- `pipeline/godverse_town.py` — thriftgod PG census + donor OSM cache → `<key>_godverse.json`
|
||||||
|
(schema v2, `source: "godverse+osm"`). Deterministic, stdlib+psycopg2, runs where the DB lives
|
||||||
|
(ultra). Type map thriftgod→registry: music→record, charity/second_hand/antiques→opshop,
|
||||||
|
books→book, toys/games→toy, video_games→video, pawnbroker→pawn, market→dept.
|
||||||
|
- `web/assets/towns/newtown_godverse.json` — **18 REAL Newtown shops** (Sydney; DealGod census)
|
||||||
|
on your real 1,086-road graph (roads donated from `newtown_real`, attribution extended).
|
||||||
|
- Verified on ultra: boots via `?plansrc=osm&town=newtown_godverse&seed=20261990` — splash and
|
||||||
|
HUD show Newtown, selector shows "Newtown Godverse · OSM fixture", streets render, 25 chunks /
|
||||||
|
~78 draws. Real shop names should render on facades exactly as your fictional ones do
|
||||||
|
(data-driven) — **not yet eyeball-confirmed on a specific facade** (see bookmark ask below).
|
||||||
|
|
||||||
|
**Found while verifying (asks, no files touched):**
|
||||||
|
1. **Per-town bookmarks:** `DBG.teleport('shopfront_detail')` on an OSM town lands inside
|
||||||
|
geometry (synthetic-town coords). Suggest bookmarks resolve against the active plan.
|
||||||
|
2. **`DBG.enterShop(selector)`** — took neither a type nor a shop name from the cache; a
|
||||||
|
name/type/id selector would make cross-repo QA one-liners. (Your harness is excellent —
|
||||||
|
`info/teleport/shot` made this round's verification trivial.)
|
||||||
|
3. **Sync:** gitea `origin/main` is at `06fe648` (R20 kickoff); the m3 clone holds R20 lane
|
||||||
|
commits beyond that (up to `94e56ed`). Push when the round closes so ultra tracks you.
|
||||||
|
|
||||||
|
**Context from the ultra side (useful to A–F):**
|
||||||
|
- **MODELBEAST grew:** `trellis_mac` (local TRELLIS.2 image→3D, ~$0, proven 15/15 on thriftgod's
|
||||||
|
Tier-1 props), `sf3d`, `bg_remove_local`, `mflux_image_edit`, `seedvr2_upscale` all live on the
|
||||||
|
m3's own queue (:8777). Lane E's "HF-gated, setup needed" note is stale — weights are unlocked.
|
||||||
|
- ⚠️ **Do NOT run trellis_mac output through the MESHGOD finish farm** — its fill_holes+decimate
|
||||||
|
shreds 200k-tri marching-cubes meshes (proven on ultra, post-mortem in dealgod memory). The
|
||||||
|
working pipeline: raw GLB → `npx @gltf-transform/cli optimize --compress draco --texture-compress
|
||||||
|
webp --texture-size 1024 --simplify-error 0.01` → 0.4–1.8 MB at raw-quality look.
|
||||||
|
- **Which collides with your no-Draco law** → the ruling ask in the lane brief. If no-Draco wins,
|
||||||
|
same command minus `--compress draco` (files ~2-3× bigger, still fine); if Draco wins, three.js
|
||||||
|
needs the DRACOLoader wire your vendored addons already include. Either is one re-emit; the
|
||||||
|
depot should hold ONE law.
|
||||||
|
- **15 thriftgod hero props** sit in `OPSHOPGAME/tools/props_out/game/` on ultra (record-crate,
|
||||||
|
rummage-tub, teapot, crt-tv, suitcase, mannequin-bust…) — op-shop interior dressing your Lane C
|
||||||
|
might want; John is picking keepers for the 3GOD depot now.
|
||||||
|
|
||||||
|
**Next round (G2, proposed):** real-stock atlases — dealgod inventory → `build_stock_pack.py`
|
||||||
|
per-shop record/book atlases keyed by the godverse shop id, behind `?stock=real`. Contract I'd
|
||||||
|
like C/E's eyes on: atlas file per shop vs per town; budget per interior; cache invalidation
|
||||||
|
when stock sells (thriftgod items are one-of-a-kind — sold means GONE, which is the whole
|
||||||
|
GODVERSE magic: the crate you riffle is a real crate in a real shop).
|
||||||
115
README.md
@ -14,9 +14,36 @@ cd web && python3 -m http.server 8130
|
|||||||
# http://localhost:8130/map.html (2D plan debugger — Lane A)
|
# http://localhost:8130/map.html (2D plan debugger — Lane A)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## The manual — how to play (private test)
|
||||||
|
|
||||||
|
Click **START** to lock the mouse. Then:
|
||||||
|
|
||||||
|
| input | does |
|
||||||
|
|---|---|
|
||||||
|
| **W A S D** + mouse | walk / look (first person) |
|
||||||
|
| walk into a shop door | enter the interior (open shops only — check the hours tooltip) |
|
||||||
|
| **E** (inside a shop) | open the crate at a record bin — then it's cursor-driven: click sleeves to riffle, click BUY |
|
||||||
|
| **E** (at the counter) · **C** (anywhere) · **SLEEP** (bottom bar) | sell a find to the keeper (he pays under the **guide band**, never over) · open THE CRATE — your collection, in shops too now · sleep = save, the day advances, stock rotates |
|
||||||
|
| read the **guide** stamp on every price card | the 90s price-guide band ("guide $8–24"). An item **asking under its band is a gem** — buy it, sell it to a keeper, pocket the margin. Bought slots stay gone for the day (the bin remembers) |
|
||||||
|
| **☆ WANT IT** on a pull card | start hunting it — keepers gossip ("saw one in Castlemaine"), and buying one clears the hunt. **Switching towns costs a day** — the 23 towns are a map now, not a dropdown |
|
||||||
|
| **⚖ LIST** in the crate | consign a find to **GodBay** — the auction hammers overnight (sleep or ride somewhere). One hammer per item, ever: keeper cash now, or the gavel tomorrow |
|
||||||
|
| click a book spine / toy box | pull-and-buy card (real price stickers in godverse shops) |
|
||||||
|
| walk out the door / **Esc** | leave the shop (Esc also frees the mouse anywhere) |
|
||||||
|
| **M** | 2D town map (street only) |
|
||||||
|
| **[** / **]** | step time of day back / forward · **T** pauses the clock |
|
||||||
|
| **P** | capture screenshots (street) |
|
||||||
|
| HUD dropdown (top-left) | switch towns — synthetic, fixtures, 23 real AU towns, godverse |
|
||||||
|
|
||||||
|
Worth trying: `?classic=1` (the original v2 town, byte-identical forever) · `?noassets=1`
|
||||||
|
(zero-network boot) · `?stock=real` in a godverse town (Monster Robot Party's actual crate) ·
|
||||||
|
`?seed=<n>` (any number = a different town). Night is when the gigs happen — step time with `]`.
|
||||||
|
|
||||||
## Read first
|
## Read first
|
||||||
|
|
||||||
- **`docs/CITY_SPEC.md`** — the treaty: schema, units, seeds, budgets, file ownership.
|
- **`docs/CITY_SPEC.md`** — the treaty: schema, units, seeds, budgets, file ownership.
|
||||||
|
- **`docs/V7_THE_GAME.md`** — the CURRENT epoch (ratified): persistence, the collection, the
|
||||||
|
gem thrill. `docs/V6_LIVING_STREET.md` runs in parallel (mocap NPCs, footpaths, cars).
|
||||||
|
- **`docs/THE_FRONTIER.md`** — the honest map of what is not figured out yet.
|
||||||
- **`docs/RESEARCH.md`** — what the parent repos already solved, with exact paths to port from.
|
- **`docs/RESEARCH.md`** — what the parent repos already solved, with exact paths to port from.
|
||||||
|
|
||||||
## The lanes (parallel Opus 4.8 sessions)
|
## The lanes (parallel Opus 4.8 sessions)
|
||||||
@ -47,3 +74,91 @@ standalone test pages); F integrates after they land.
|
|||||||
Seeded everything, `Math.random` banned in gen code · the game must run with zero assets ·
|
Seeded everything, `Math.random` banned in gen code · the game must run with zero assets ·
|
||||||
instancing from day one · canvas text, no font files · characters = shared base rigs + one
|
instancing from day one · canvas text, no font files · characters = shared base rigs + one
|
||||||
clip bank, canonical sources live outside this repo · 🟢 web-ok licensing only.
|
clip bank, canonical sources live outside this repo · 🟢 web-ok licensing only.
|
||||||
|
|
||||||
|
## v3.1 — THE FLIP: the town shows its best by default
|
||||||
|
|
||||||
|
The Friday-night layer soaked through alpha, beta, and the v3.0 release — so **v3.1 flips it on by
|
||||||
|
default**, and the mature v2 flags flip with it. Boot the plain URL and each town seeds **2–4 venues**
|
||||||
|
(pub, band room, RSL) with a **full seeded week of gigs**, town-wide **posters**, and a Friday night that
|
||||||
|
reads: a **4-piece band** (trio + a **seated drummer**) with real instruments, a mixed **crowd**, an
|
||||||
|
outdoor **queue**, a per-venue **cover charge**, muffled **gig spill** + **walla and applause**,
|
||||||
|
town-wide **streetlamp pools**, and **identity continuity** — the ped you followed in is in the crowd.
|
||||||
|
Plus **weather**, the **tram**, and the parallax interior-**window shader**, all live by default.
|
||||||
|
|
||||||
|
**The covenant moved, it didn't disappear.** `?classic=1` boots the **exact v2 town** — byte-identical
|
||||||
|
forever (synthetic golden `0x3fa36874`, zero gig layer, zero fetch delta). That is the new frozen
|
||||||
|
regression target; the default boot is deterministic to the gig golden `0xb1d48ea1`. Asset-free
|
||||||
|
(`?noassets=1`) composes with the default boot and stays silent-and-fine.
|
||||||
|
|
||||||
|
**Flags** — the four headliners are **ON by default**, each with a `=0` opt-out; `?classic=1` is the
|
||||||
|
master off:
|
||||||
|
|
||||||
|
| flag | default | what |
|
||||||
|
|---|---|---|
|
||||||
|
| `?classic=1` | off | **the exact v2 boot** — all four below off, byte-identical to the frozen baseline |
|
||||||
|
| `?gigs=0` | **on** | the gig district — venues, week schedule, band/crowd/queue, cover, posters, night pass |
|
||||||
|
| `?weather=0` · `?tram=0` · `?winmap=0` | **on** | seeded weather · the tram loop · the parallax interior-window shader |
|
||||||
|
| `?roster=v1` · `?pop=N` | stream | chunk-streamed citizen density is the baseline; `?roster=v1` restores the fixed roster |
|
||||||
|
| `?dig=0` · `?stock=real` | dig on | crate-riffle on record bins · real GODVERSE sleeves in the stock |
|
||||||
|
| `?live=1` · `?live=<url>` | **off** | **tier 2** — live stock from the GODVERSE server (sold-in-the-real-shop means gone in the game). Opt-in by design: absent ⇒ byte-identical to tier 1, and the game never asks a question it has no reason to expect an answer to |
|
||||||
|
| `?noassets=1` · `?plansrc=osm` · `?mute=1` · `?dbg=1` | off | asset-free (zero network) · OSM plan source · silence · the `window.DBG` capture/soak harness |
|
||||||
|
| `?plansrc=osm&town=<key>` | — | boot a **real Australian town**: fixtures `melbourne`/`katoomba`/`silverton`, or a real OSM cache `bendigo_real`/`castlemaine_real`/`fremantle_real`/`katoomba_real`/`newtown_real` (ODbL, `web/assets/towns/SOURCES.md`). **REAL ROADS:** a cache with `roads[]` (schema v2) builds the CityPlan street graph from the town's **actual OSM streets**; without `roads[]` a cache marches onto synthetic avenues. Or just **pick a town from the HUD selector** — no URL needed |
|
||||||
|
|
||||||
|
## v4.0 — THE REAL MAP: the towns *are* the product
|
||||||
|
|
||||||
|
**23 real Australian towns — 1,210 real shops, every state and territory — on their actual streets.** Not
|
||||||
|
"inspired by": Fitzroy's shops sit on Fitzroy's roads, at their real positions, because the pipeline eats
|
||||||
|
OpenStreetMap and marches it through the same CityPlan contract the procedural town uses. **Pick one from
|
||||||
|
the in-HUD selector and walk it.** Everything built across v1–v3 comes along unchanged — the venues and their
|
||||||
|
seeded week of gigs, the 4-piece band with the seated drummer, the crowd that follows you into the pub, the
|
||||||
|
interiors behind every door, weather, the tram — because they all hang off the plan contract and never had
|
||||||
|
to know the geography got real.
|
||||||
|
|
||||||
|
The towns run from **meccas** (Fitzroy, 160 shops) to the **thin tail** (12), at real retail density: the
|
||||||
|
secondhand heroes keep their footfall while the street around them gets ~4× busier — texture, never a
|
||||||
|
takeover. Venues bias to the retail heart. The tram only runs where it fronts a real high street (elsewhere
|
||||||
|
it was honestly "a highway bus", so it's fenced). Everything stays **deterministic from the seed**, silent
|
||||||
|
under `?noassets=1`, and **`?classic=1` still boots the exact v2 town, byte-identical forever** (`0x3fa36874`).
|
||||||
|
|
||||||
|
**The tour:** [`docs/shots/release_v4/contact.html`](docs/shots/release_v4/) — the Fremantle hero, the
|
||||||
|
Fitzroy mecca, the thin tail (labelled honestly: the floor is shops-per-street-metre, and Toowoomba is
|
||||||
|
below it), the synthetic control, and the `?classic=1` covenant made visible. Charter:
|
||||||
|
[`docs/V4_REAL_MAP.md`](docs/V4_REAL_MAP.md) · data + licences (ODbL):
|
||||||
|
[`web/assets/towns/SOURCES.md`](web/assets/towns/SOURCES.md).
|
||||||
|
|
||||||
|
## v5.0-alpha — THE FIRST REAL CRATE: the shops get real too
|
||||||
|
|
||||||
|
**v4 made the towns real. v5 makes the shops real** — and the first one is standing.
|
||||||
|
|
||||||
|
Boot `?plansrc=osm&town=redhill_godverse&stock=real`, walk down **Musgrave Road** into **Monster Robot
|
||||||
|
Party** (147 Musgrave Rd, Red Hill, Brisbane — a real record shop), and riffle its crate. The records in
|
||||||
|
it are **that shop's real stock**: 120 real records off its real point-of-sale, real covers, real titles,
|
||||||
|
real prices. Buy one and the wallet debits what the sticker says. It's gone for the session and back next
|
||||||
|
boot — because until the live tier lands, the crate is a **file**, not a shop's ledger.
|
||||||
|
|
||||||
|
**Stock is a data tier now**, exactly like assets — each rung fully playable, each degrading softly to
|
||||||
|
the one below, so the game never depends on a server to be a game:
|
||||||
|
|
||||||
|
| tier | what | deterministic? |
|
||||||
|
|---|---|---|
|
||||||
|
| **0 · parody** | seeded canvas sleeves (v1) — any boot, always works | yes — the QA baseline |
|
||||||
|
| **1 · static real** | per-shop atlases baked from the shop's own POS | yes — atlases are files |
|
||||||
|
| **2 · live GODVERSE** | `?live=1` — live stock off the real till: **sell a record in the shop and it's gone from the game crate** | **no — by design** |
|
||||||
|
|
||||||
|
**The offline law:** the server enriches, never gates. `?noassets=1` still fetches **zero bytes** and plays
|
||||||
|
silent-and-fine; a shop whose atlas is missing falls soft to parody rather than borrowing someone else's
|
||||||
|
crate; and **`?classic=1` still boots the exact v2 town, byte-identical forever** (`0x3fa36874`). The
|
||||||
|
world stays seeded and golden-gated — only the *stock inside it* is data.
|
||||||
|
|
||||||
|
**Provenance:** item photos are each shop's own product shots, 🟢 in-house — **flagged before any public
|
||||||
|
or commercial release**, and every atlas carries that flag where the QA gate must print it. Real titles
|
||||||
|
and artists are an in-house amendment to the no-real-trademarks law, on the same footing. **Nothing an
|
||||||
|
in-game purchase does touches real inventory** — v5.0 ships sell-sandboxed by ruling; a real reservation
|
||||||
|
tier is chartered for v5.x, not assumed. Charter: [`docs/V5_REAL_SHOP.md`](docs/V5_REAL_SHOP.md) ·
|
||||||
|
economy design: [`docs/G3_ECONOMY_DESIGN.md`](docs/G3_ECONOMY_DESIGN.md).
|
||||||
|
| `?seed=N` | `20261990` | town seed |
|
||||||
|
|
||||||
|
**Release tour:** [`docs/shots/release_v3_1/contact.html`](docs/shots/release_v3_1/) — the money shots on
|
||||||
|
the default boot, plus a `?classic=1` before/after. **QA:** `tools/qa.sh --strict` (classic regression +
|
||||||
|
default-boot gate + smokes + no-giants) `--soak` (the full-week soak); the seated drummer passes the
|
||||||
|
no-giants gate at the seated stature band `[0.9,1.5] m`.
|
||||||
|
|||||||
@ -1,8 +1,570 @@
|
|||||||
# LANE A — CITYGEN · progress (PROCITY-A)
|
# LANE A — CITYGEN · progress (PROCITY-A)
|
||||||
|
|
||||||
*Status: **all deliverables landed; self-check all-green (1362/1362); rounds 2–6 closed** (see below).
|
*Status: **all deliverables landed; self-check ALL GREEN (156,352/156,352); rounds 2–30 closed**.
|
||||||
`qa.sh --strict` GREEN.*
|
R30 (v7.0-alpha ledger #6): the rotation boundary VERIFIED — F's `day` salt rides runtime streams only,
|
||||||
*Date: 2026-07-14 · owner: PROCITY-A (Opus 4.8)*
|
plan generation structurally cannot receive it; all goldens frozen, zero re-pins.*
|
||||||
|
|
||||||
|
## Round 30 (2026-07-18) — the rotation boundary verify (ledger #6, small round as briefed)
|
||||||
|
|
||||||
|
One job: prove F's day-salted stock rotation (207bcff) never reaches plan generation. **It holds.**
|
||||||
|
Three measured legs (full detail in LANE_A_NOTES §30): (1) grep-zero for day identifiers over the proven
|
||||||
|
`citygen/**` glob (8 files / 2,546 lines, census printed; pattern proven non-vacuous against
|
||||||
|
`interior_mode.js`); (2) structural absence — `generatePlanFor` consumes no day-shaped opt and neither
|
||||||
|
shell call site passes one; (3) day-inertness byte-measured — plan hash identical with `{day, stockDay}`
|
||||||
|
hostilely injected as 1 and 37 vs absent, 3 seeds × synthetic+osm, 6/6. The salt's actual reach: `stk-*`
|
||||||
|
stock-pick stream labels + dig `binSeed` + `setWeekNight` night-selection over the plan-time 7-night week
|
||||||
|
(day picks a night, never generates one). Selfcheck ALL GREEN 156,352/156,352, fingerprint `0x5f76e76`
|
||||||
|
frozen, every town pin green. No files of mine changed except these two notes.
|
||||||
|
|
||||||
|
## Round 27 wave 5 (2026-07-17) — THE FACADE FIX (the epoch's biggest catch)
|
||||||
|
|
||||||
|
The ruling asked me to land B's one-liner in the real-roads seating and to **measure the marched path
|
||||||
|
rather than assume it was clean**. I measured all three paths, and the ruling's premise was wrong in the
|
||||||
|
half that mattered: **synthetic is broken too**. Not "every real town since R18" — **every town, every
|
||||||
|
path, since v1**. Confirmed numerically (0 toward / 681 away, dot −1.0000) and then with my own eyes:
|
||||||
|
`?classic=1`, synthetic main street = three blank boxes; walk into the block and NUMBAT PLAYTHINGS is
|
||||||
|
there, facing the dirt. 637 of 681 lots pointed their facade at **no street at all**.
|
||||||
|
|
||||||
|
- **Four sites, one sign.** `plan.js:122` (synthetic strip), `plan.js:244` (market row), `plan_osm:330`
|
||||||
|
(marched avenue), `plan_osm:443` (real roads — B's proven one-liner). All `atan2(outward)` where the
|
||||||
|
intent was `atan2(−outward)`, and **every one says so in its own comment**. The code never did what it
|
||||||
|
said; CITY_SPEC:71 blessed the result by writing the SIM convention into the LOTS contract.
|
||||||
|
After: synthetic 681/681, marched + all 3 fixtures 100%, real-roads katoomba **0/72 → 72/72** (B's number).
|
||||||
|
- **The covenant moved, on John's ruling.** Fixing synthetic necessarily moves `0x3fa36874`, frozen
|
||||||
|
"forever" since R16 — not Lane A's call, so I stopped and asked. Ruling: **amend and fix all paths** (the
|
||||||
|
covenant is anti-drift machinery, not a defect preservative). **51 goldens re-pinned in one commit**;
|
||||||
|
amendment recorded in CITY_SPEC as a one-time, ruling-gated precedent.
|
||||||
|
- **A second bug the fix exposed, fixed with it:** `buildRealRoads` seated blocks at `KERB = 4 m` from the
|
||||||
|
**centreline**, but a `main` carriageway is 10 m wide (kerb at 5 m) — **every main-street building has
|
||||||
|
stood 1 m inside the road since R18**. Invisible until the shopfront turned around and its poster landed
|
||||||
|
in the traffic lane. Blocks now seat behind the kerb per A's own corridor law. Drops 6.4% → **6.6%**, counted.
|
||||||
|
- **The cross-convention gate (the lesson made law).** It does not restate a spec line — that is the trap F
|
||||||
|
proved (72/72 "facing" over blank walls). It replicates **Lane B's own** `buildings.js` maths and asks
|
||||||
|
whether the quad the *renderer* draws lands nearer the street than the lot centre. Fires if A's `ry` **or**
|
||||||
|
B's `zf` drifts. It would have caught this on day one.
|
||||||
|
- **→ F (blocking their re-gate):** `scaffold_check` is RED until F re-pins the covenant hashes duplicated
|
||||||
|
in **F's own files** (`flags_check.py` `GOLDEN_HASH`/`GIG_GOLDEN`, `scaffold_check.mjs` `TOWN_GOLDENS`,
|
||||||
|
two tour captions). Exact values handed over in LANE_A_NOTES §R27w5; I can't touch F's files.
|
||||||
|
|
||||||
|
*Previous status: 156,212/156,212; rounds 2–27 closed (see below).
|
||||||
|
R27 (v5.0 close): **CITY_SPEC v5 section** (tier ladder · `sourcing` vs `tier` · the three id-space fences ·
|
||||||
|
the manifest · Ruling 2) + the **charter risk list judged line by line** — risk #1 deliberately CARRIED, not
|
||||||
|
pre-retired, because F's kill-the-server gate is what retires it. classic `0x3fa36874` / gig `0xb1d48ea1`
|
||||||
|
frozen; **no golden moved this epoch**.*
|
||||||
|
*Date: 2026-07-17 · owner: PROCITY-A (Opus 4.8)*
|
||||||
|
|
||||||
|
## Round 27 (2026-07-17) — v5.0 THE LIVE CRATE: the docs freeze (ledger #6)
|
||||||
|
|
||||||
|
Lane A's half of the epoch close. Docs only; **no code, no golden moved** — correct, since tier 2 touches no
|
||||||
|
plan, no cache, no atlas bytes. selfcheck **ALL GREEN 156,212/156,212**.
|
||||||
|
|
||||||
|
- **CITY_SPEC v5 section**, written from the artifacts rather than the briefs (the epoch's own lesson):
|
||||||
|
the **tier ladder** (0 parody / 1 static real / 2 live, each degrading softly); **`sourcing` is not
|
||||||
|
`tier`** — both ship in every index, `tier: 1` is the rung and a mint atlas is *still* rung 1, while
|
||||||
|
`sourcing: "real"|"mint"` is provenance (15 keyed atlases = **1 real + 14 mint**); the **three id-space
|
||||||
|
fences**; the **manifest** (existence declared, never probed — the root fix for fail-soft-404 vs the
|
||||||
|
zero-console-errors law; 15 entries, 15 dirs); **Ruling 2**; and A's single seam, `godverseShopId`.
|
||||||
|
- **The id fences, verified not restated.** The plan-id/godverse-id trap is still live in the data and I
|
||||||
|
reproduced it: in `redhill_godverse`, bare **`31`** is "Wholefood Cafe" as a plan id and "Silky Oak
|
||||||
|
Furnature" as a godverse id — the exact ambiguity that made F's `enterShop(31)` assert vacuously on the
|
||||||
|
wrong shop. It's a standing rule, not a fixed bug, and the spec says so.
|
||||||
|
- **The risk list judged, without pre-retiring anyone's gate.** #1 (tier-2 latency) **CARRIED** — written in
|
||||||
|
wave 2, F's reader doesn't exist yet, and *nothing retires that line except the gate going green*;
|
||||||
|
retiring it from a doc while the code is unwritten is the precise species this epoch spent four holds
|
||||||
|
catching. #2 **retired** (measured: max 2 atlases/shop vs C's cap of 2; mint at 16 items/1024², only the
|
||||||
|
real POS shop at 2048²; the mecca case served by crate rotation — the remedy the risk itself named).
|
||||||
|
#3 **retired — designed out, not solved**: it assumed fuzzy name-matching, and there is no matcher;
|
||||||
|
Monster Robot is in *neither* census nor OSM, so no matcher could ever have found it. #4 **split**:
|
||||||
|
write-back retired by John's ruling-as-mechanism (`/reserve`+`/buy` absent, not stubbed — you can't race a
|
||||||
|
verb that doesn't exist), the `gone` read carried to F's #4. Two new carried: **#6 orphaned atlas**,
|
||||||
|
**#7 "real" is one shop wide** (14/15 are mint — honest and marked, but the headline rests on one shop).
|
||||||
|
- **The gate that armed itself.** My R25 orphan gate SKIPped on `newtown_godverse` while no atlas keyed
|
||||||
|
there; G's mint atlases landed and it **began binding unattended** — 156,211 → **156,212**, and that +1 is
|
||||||
|
the gate arming itself. Near-miss carried: redhill's lift drops keyed shop **2286 "Empire Revival"**;
|
||||||
|
G stocked neighbour **2287**. Stocking 2286 today would orphan it.
|
||||||
|
|
||||||
|
## Round 25 (2026-07-17) — v5.0-alpha: the sweep vs Ruling 2 + the six pins (ledger #1)
|
||||||
|
|
||||||
|
All 8 tree reds cleared — **6 were my own gate asserting Ruling 2 away**, 2 were goldens awaiting my pin.
|
||||||
|
selfcheck **ALL GREEN 156,211/156,211**, scaffold + consistency green, classic frozen.
|
||||||
|
|
||||||
|
- **My R24 gate encoded an assumption that Ruling 2 falsified.** I built the identity arm believing a
|
||||||
|
godverse cache is *all* census shops. It's **mixed** by design — a keyed GODVERSE layer + an inherited OSM
|
||||||
|
texture layer that G measured to be load-bearing (census-only Red Hill: 306 m spacing warn + a poster
|
||||||
|
inside a kerb). So `newtown_godverse` (18 keyed + 54 texture) and `redhill_godverse` (10 + 27) were
|
||||||
|
**healthy towns my gate called broken**. Keyed asserts now run **per-layer over the keyed subset only**;
|
||||||
|
the "N/M missing" warn is gone. The one honest requirement left: a `godverse+osm` cache must have a
|
||||||
|
**non-empty** godverse layer (else it's an osm cache with the wrong `source`) — still a warn.
|
||||||
|
- **The uniqueness check was broken in both directions.** `new Set(all ids).size === shops.length` collapses
|
||||||
|
every `undefined` into one entry, so it failed structurally on mixed caches *and* could hide a real
|
||||||
|
duplicate behind the same collapse. Now over **defined ids only** — and, per the vacuous-gate law, proven
|
||||||
|
able to fail: the validator rejects dups at the front door, so the test feeds one **straight to the lift**
|
||||||
|
(`generatePlanOSM` doesn't validate) and asserts the predicate catches it.
|
||||||
|
- **New: the orphaned-atlas gate** — my own R24 finding, enforced rather than filed. A committed
|
||||||
|
`stock_godverse/<id>/` whose shop the lift dropped = real stock keyed to a shop that isn't in the town.
|
||||||
|
Node-side, subject-aware (SKIPs by name when a town has no atlas), so it catches in seconds what F's #7b
|
||||||
|
could only catch in a browser at the end of a round. **It already found one, benign today:** redhill drops
|
||||||
|
keyed shop 2286 "Empire Revival" (unstocked ⇒ no orphan ⇒ gate passes). Filed for G: stocking it at the
|
||||||
|
beta would orphan. Monster Robot Party is seated (lot 8).
|
||||||
|
- **The six pins**, cross-checked two ways before landing — byte-identical across two fresh processes, and
|
||||||
|
matching the values G stated independently in `86e2985`: `newtown_godverse` `0xcf69b387`/`0x1e266f60`,
|
||||||
|
`redhill_real` `0x6046700f`/`0x673785ee`, `redhill_godverse` `0xb2224939`/`0xc6f80e18`.
|
||||||
|
|
||||||
|
## Round 24 (2026-07-17) — v5.0-alpha: the identity field (ledger #2)
|
||||||
|
|
||||||
|
`shop.godverseShopId` — the optional key resolving `stock_godverse/<id>/`. Schema **stays v2**; absent
|
||||||
|
everywhere except godverse caches, so the 21 osm caches are untouched by construction. Published to
|
||||||
|
`web/assets/towns/README.md` (the doc G/E build to) + CITY_SPEC. selfcheck **ALL GREEN 152,030/152,030**
|
||||||
|
(+15), synthetic `0x3fa36874` frozen, **no golden moved** — by design (see below).
|
||||||
|
|
||||||
|
- **The brief's "the POS shop id IS the id" is false for the one shop that matters.** G's own docstring:
|
||||||
|
Monster Robot Party is **not in thriftgod's census**, so its id is its **dealgod store id 3962749**, while
|
||||||
|
census shops use thriftgod ids (max 2992). Two disjoint spaces on one field ⇒ built it as an **opaque
|
||||||
|
key**: no derivation from `id`, no equality assert, no mapping table (the R12 gigKey law).
|
||||||
|
- **The field had to survive the lift, or it was decoration.** `plan_osm` builds a *fresh* shop object and
|
||||||
|
copies nothing unknown, so a cache-only field would never reach the runtime — and the runtime reads
|
||||||
|
`plan.shops[]`, never the cache. It's now carried through, **conditionally**: absent ⇒ the key isn't
|
||||||
|
written ⇒ `JSON.stringify` byte-identical ⇒ **every pinned golden holds until G re-emits** (that re-emit
|
||||||
|
is the sanctioned move).
|
||||||
|
- **Arms split by hazard:** malformed → **error** (it's a path segment; `"3962749"` vs `3962749` is the
|
||||||
|
same coercion drift that broke the atlas contract), duplicate → **error** (two shops, one atlas = the
|
||||||
|
mis-stock charter risk #3 forbids), missing on a godverse cache → **warn + fail-soft to tier 0**.
|
||||||
|
- **Recorded departure:** the ledger said missing should be an *error*. It would invert the charter's
|
||||||
|
determinism boundary (the **town** failing to load because **stock** identity is absent), contradict risk
|
||||||
|
#3's own remedy ("fail-soft to tier 0"), and red the tree for C and E through all of wave 1 — G re-emits
|
||||||
|
in wave 2 and it's their namespace, not mine. Teeth kept where R23 says to put them: selfcheck **fails on
|
||||||
|
partial keying**, prints an explicit **SKIP + count** at zero. Flagged for Fable to overrule.
|
||||||
|
- **Filed for G before they start (measured, wave 1):** `redhill_godverse` can't contain Monster Robot from
|
||||||
|
**either** source. `godverse_town.py` takes roads from the OSM donor and shops from the census; the shop is
|
||||||
|
in neither — not in thriftgod's census (G's R23 finding), and **not in E's `redhill_real`** (I checked it
|
||||||
|
as it landed: 36 shops, **zero of type `record`**, nothing matching /monster|robot/). Needs explicit
|
||||||
|
injection, or F's #7b id-equality gate fails at the end of the round. Also flagged: the census bbox must
|
||||||
|
still clear `MIN_TOWN_SHOPS` (6) or the cache is rejected outright.
|
||||||
|
- **Pins deliberately NOT taken this round yet.** `redhill_real` is untracked/in-flight (E is still editing
|
||||||
|
`build_towns.py`), and `redhill_godverse` + the re-keyed `newtown_godverse` don't exist until wave 2.
|
||||||
|
Pinning a moving file buys a false green or a spurious red. The round's law says A pins these **batched** —
|
||||||
|
that batch lands once E commits and G's caches are in. `redhill_real` reads clean meanwhile: valid, no
|
||||||
|
warns, 73.3 m spacing.
|
||||||
|
- **Housekeeping:** removed toowoomba's two stale pins (E retired the cache in R23 — a golden for a town
|
||||||
|
that can't load can never fire). E's attempt also proved my R23 spacing metric is **gameable**: the
|
||||||
|
re-bbox fixed the median (395.7 → 89.4 m) while hub density fell 3/12 → 2/12. Necessary, not sufficient —
|
||||||
|
D's hub test is the complement. **Ballarat (255 m) still flagged, still E's call; I pin whatever moves.**
|
||||||
|
|
||||||
|
## Round 23 (2026-07-16) — v5.0-alpha: the spacing warn (ledger #5)
|
||||||
|
|
||||||
|
D's R22 finding made law: `MIN_TOWN_SHOPS` counts shops, but what makes a town read alive is **clustering**.
|
||||||
|
`validateTownCache` now warns when `medianShopSpacing(cache) > MAX_MEDIAN_SPACING_M` (**150 m**) — the metric
|
||||||
|
and threshold both exported from the barrel, so no lane re-derives them. **Warn, never error**: a flagged
|
||||||
|
cache still boots a valid district (toowoomba shipped green through the whole v4.0 pack), E curates, the
|
||||||
|
validator flags. selfcheck **ALL GREEN 161,372/161,372** (+72), synthetic `0x3fa36874` frozen, no golden moved.
|
||||||
|
|
||||||
|
- **The threshold is measured, not picked.** Fable's brief offered darwin 27 m / toowoomba 388 m as "plenty
|
||||||
|
of daylight for a line". Measuring all 23 towns showed the pack is a near-continuum, and the line is
|
||||||
|
**forced** into `(119.3, 254.9]` — braddon (119.3 m) is alive by D's own hub test (7 shops within 160 m,
|
||||||
|
identical to darwin's 7), ballarat (254.9 m) is not. 150 m sits in the pack's widest gap (**×2.14**).
|
||||||
|
- **Two towns flag, not one.** The brief names toowoomba; the measurement adds **ballarat (255 m)**, which
|
||||||
|
nobody had looked at — and which is independently one of the three towns my R22 cluster fallback fired on.
|
||||||
|
Filed for E as a curation call, counted either way.
|
||||||
|
- **The brief's anchors were in the wrong space, and it matters.** D's 27 m is **plan** space (post-lift);
|
||||||
|
`validateTownCache` is **cache** space (raw lat/lon — the only thing it has at ingest). Darwin is 68.3 m
|
||||||
|
in cache space, not 27. Coding a threshold interpolated between D's figures would have mixed spaces
|
||||||
|
silently. Cache space is right *here*, and provably safe: the lift compresses dense towns (darwin
|
||||||
|
plan/cache **0.40**) but not sparse ones (toowoomba **0.98**, ballarat **1.01**), so the error lives at the
|
||||||
|
dense end and the warn only fires at the sparse end. Verified all 23 in both spaces: **one town disagrees**
|
||||||
|
(`newtown_godverse`, 94.4 cache → 158.3 plan) — filed for G/Fable, not acted on, since no cache-space line
|
||||||
|
catches it without false-positiving braddon, and its sparsity is census *coverage*, which re-bboxing can't fix.
|
||||||
|
- Cost: ~1 ms/town at registration (25.6 ms for all 23 including fitzroy's 160 shops), against a <100 ms
|
||||||
|
plan budget. Pending: re-pin whatever E's toowoomba re-bbox moves — **only** that.
|
||||||
|
|
||||||
|
## Round 22 (2026-07-16) — v4.0 THE EPOCH CLOSE: the pack absorbed + 36 goldens pinned (ledger #2)
|
||||||
|
|
||||||
|
E's 17 new towns + Lane G's `newtown_godverse` fed through the lift and pinned in ONE golden commit
|
||||||
|
(`REAL_TOWN_GOLDENS` + `REAL_TOWN_GIG_GOLDENS`: 5 → **23 towns each**, zero unpinned).
|
||||||
|
|
||||||
|
**The headline: everything built this epoch held at 23-town scale, first try.** 1,210 shops → 1,133 seated
|
||||||
|
(77 dropped, **6.4%**, counted by cause); **1 connected component and 3 venues on every one of the 23 towns**.
|
||||||
|
`fitzroy` — 160 shops, 2× anything absorbed before — landed 139 seated, 1 component, 3 venues, 212 posters,
|
||||||
|
all invariants green. The junction-protected DP, fragmentation ruling, cluster bias, poster cap and capacity
|
||||||
|
fence needed **no new hardening**: the pack needed absorbing, not fixing.
|
||||||
|
|
||||||
|
- **Graded cluster fallback (the one refinement).** Three towns have no candidate at the floor at all
|
||||||
|
(`ballarat`/`toowoomba`/`newtown_godverse` top out at 2 shops within 160 m); R21's bias switched off there,
|
||||||
|
stranding venues at 0. It now falls back to the **densest available** — ballarat 0,2,0 → **2,2,2**, godverse
|
||||||
|
0,1,1 → **2,2,2**, toowoomba 1,0,0 → 1,2,1. Fires only where the floor is unreachable ⇒ **no already-pinned
|
||||||
|
town moved** (verified). Acceptance at pack scale: **20 of 23 towns have every venue ≥3**; the three above
|
||||||
|
sit at their town's genuine ceiling — the carved-out case, stated explicitly.
|
||||||
|
- **Drop outliers, counted not hidden:** `daylesford` 19% (32 shops on a 171-road graph — the thinnest in the
|
||||||
|
pack, all overflow), `fitzroy` 13%. `RGAP` is already 0.5 m and `NODE_CLEAR` 3 m; the only lever left is
|
||||||
|
spilling onto a different street, which buys a few percent by putting a shop on a road it isn't on. Same
|
||||||
|
ruling as R20: **drop and count beats teleport**.
|
||||||
|
- selfcheck ALL GREEN **161,300/161,300**; scaffold + consistency GREEN; cross-process deterministic
|
||||||
|
(pack-xor `0xca140954`). classic / gig / marched fixtures frozen.
|
||||||
|
|
||||||
|
Handshake fired → C + D + F: the pack is in, 23 towns pinned, start your verifies.
|
||||||
|
|
||||||
|
**Ledger #6 (docs, `9c9ad73`): CITY_SPEC v4 final + the risk list retired-or-carried.** Closing this
|
||||||
|
surfaced real drift — **three gig-layer behaviour changes had reached only these notes, never the spec**
|
||||||
|
(R20's clearance widening to every venue kind, R21's cluster bias, R22's fallback). The amendment law says
|
||||||
|
the spec is where they live; all three are now recorded, along with the pick *order* no lane could have
|
||||||
|
inferred (cluster field → kind flavour → clearance → `farthestFirst` → seeded pick). Risk list judged
|
||||||
|
against the pack, not the 5-town sample it was written for: **#1 retired** (widest town geelong 3,829 m =
|
||||||
|
77% of the 5 km law), **#2 retired** (thinnest darwin 12 shops = 2× `MIN_TOWN_SHOPS`), **#3 retired
|
||||||
|
plan-side / carried for the runtime lanes** (a green plan is not a green town — C/D/F's verifies close it),
|
||||||
|
**#4 retired** (DP knob measured; 1 component on all 23), **#6 NEW carried** (thin-graph seating capacity).
|
||||||
|
|
||||||
|
**Ledger #1 (tri diet): not A's — B's numbers decided it.** The driver is E's four furniture GLBs
|
||||||
|
(91.5%); posters are **222 tris**, buildings **348**. My cap worked and was never the lever, and B found
|
||||||
|
the real coupling is **road density, not shop count** — so the absorb wasn't the cause either. My guess
|
||||||
|
(buildings/LOD) was wrong at 0.2%; the measurement won, as it should.
|
||||||
|
|
||||||
|
## Round 21 (2026-07-16) — v4.0-beta: the venue cluster-adjacency bias (ledger #1, the round's spine)
|
||||||
|
|
||||||
|
D filed it: katoomba's pub sat 1,000 m from the retail cluster — a gig with no passing crowd. Measuring all
|
||||||
|
five towns first showed it was **systemic: 11 of 15 venues** had <3 shops within 160 m (newtown's were
|
||||||
|
0/0/1). After the bias: **0 of 15**. Katoomba's pub went **1 → 9**; castlemaine cleared without needing the
|
||||||
|
floor exception Fable anticipated.
|
||||||
|
|
||||||
|
- `pickVenues` now builds a **cluster-adjacent field** (≥`CLUSTER_MIN` 3 shops within `CLUSTER_R` 160 m),
|
||||||
|
then the kind flavors (pub corner / band_room fringe / rsl off-spine) bias **within** it rather than
|
||||||
|
overriding it (per the brief: biases, not absolutes). No cluster at all → falls back to the pool.
|
||||||
|
- **Synthetic frozen BY CONSTRUCTION**, not by luck. The brief assumed a blanket filter was a no-op because
|
||||||
|
"the synthetic town is one dense cluster anyway" — measurement says otherwise: **4 synthetic candidates
|
||||||
|
per seed** sit under the threshold (min cluster 0–1), so a blanket filter *would* have moved the frozen
|
||||||
|
golden. Gated on `plan.source === 'osm'` (undefined on synthetic). Marched fixtures are 'osm' but genuinely
|
||||||
|
dense (min 3/11/13) ⇒ verified no-op.
|
||||||
|
- **Only the GIG goldens moved** (venue selection is gig-layer): katoomba `0xbf586b77`, newtown `0xacdb3eee`,
|
||||||
|
fremantle `0xcf3426fc`, bendigo `0x646b2c23`, castlemaine `0xf9713a69`. Base goldens untouched.
|
||||||
|
classic/gig/marched fixtures frozen. selfcheck ALL GREEN **66419/66419**; scaffold + consistency GREEN;
|
||||||
|
cross-process deterministic.
|
||||||
|
- FYI: E's v4.0 pack (`glebe`, `marrickville`, `newcastle`) already boots clean through my loader (validates
|
||||||
|
+ full suites pass), listed UNPINNED — **I pin the pack in R22**; it does not gate the beta tag.
|
||||||
|
|
||||||
|
Handshake fired → C + D: cluster bias landed, goldens re-pinned, start your verifies.
|
||||||
|
|
||||||
|
## Round 20 (2026-07-16) — v4.0-beta THE FIVE TOWNS: the poster cap (density absorb pends E)
|
||||||
|
|
||||||
|
Two Lane-A deliverables; the first (independent) landed this commit, the second waits on E's widened caches.
|
||||||
|
|
||||||
|
1. **Poster cap (ledger #1) — LANDED.** My R19 finding (real katoomba: 438 spine posters, driving F's one
|
||||||
|
budget edge, `venue_night` 200,965 tris) is fixed. `gigs.js buildPosters` caps the spine pole run at
|
||||||
|
**`POSTER_PER_SHOP × shops`** (1.5/shop, floor 12) — a seeded subsample of the run. **Scaled by shop
|
||||||
|
count, not edge count** (the whole point: a sparse real town has few shops but a big graph). Result:
|
||||||
|
katoomba **438 → 31** posters (~2× the synthetic feel), fremantle 32, castlemaine 14. **No-op on the
|
||||||
|
synthetic town** (493 shops ⇒ cap ~740 ≫ its ~15 posters), so the synthetic gig golden `0xb1d48ea1` is
|
||||||
|
frozen. Posters are gig-layer, so the real-town BASE goldens are unchanged — I added
|
||||||
|
`REAL_TOWN_GIG_GOLDENS` (the full district+poster plan per town: katoomba `0xbd332e83`, …) to
|
||||||
|
regression-guard the capped output byte-exact. selfcheck ALL GREEN **51675/51675**; scaffold + consistency
|
||||||
|
GREEN; cross-process deterministic. → F: re-measure `venue_night`, the +965 edge should close.
|
||||||
|
2. **Density absorb (ledger #2) — LANDED.** E's five caches at **3–6× shops** (292 total: katoomba 20→80,
|
||||||
|
fremantle 21→80, newtown 18→72, bendigo 9→36, castlemaine 6→24) absorbed; **all five place 3 venues**;
|
||||||
|
the poster cap auto-scaled off the live shop count as designed. **Capacity widened** — at 4× the dominant
|
||||||
|
drop cause was overflow (edge-side full), so `NODE_CLEAR` 6→3 + a **roads-only `RGAP`** 2→0.5 (shared
|
||||||
|
marched `GAP` untouched ⇒ fixtures frozen): drops **7.9% → 5.5%** (16/292), residual **counted by cause**
|
||||||
|
(`dropped_overflow`/`_overlap`/`_stranded`). Stopped there deliberately — the next lever (spilling
|
||||||
|
overflow onto a different street) buys ~1% by putting a shop on a road it isn't on. **Venue facade bias
|
||||||
|
extended to `pub`** (R16 exempted it; on a real graph at density the pub's frontage poster overhung a
|
||||||
|
cross street — newtown −0.91 m); still a no-op on synthetic. **Goldens re-pinned once** (base + gig, all
|
||||||
|
five). **Fixed:** the real-cache loader now skips E's new `index.json` (a selector index, not a cache).
|
||||||
|
selfcheck ALL GREEN **51866/51866**; scaffold + consistency GREEN; cross-process deterministic.
|
||||||
|
|
||||||
|
Handshake fired → all: **densities absorbed, goldens pinned.** → F: re-measure `venue_night` (poster cap)
|
||||||
|
and gate the five-town matrix.
|
||||||
|
|
||||||
|
## Round 19 (2026-07-16) — v4.0-alpha CLOSE: fragmentation ruling + findings hardened + goldens pinned
|
||||||
|
|
||||||
|
A was the round's spine (the A→E→A finalization deferred from R18). Every filed finding resolved; the tag is
|
||||||
|
F's to land.
|
||||||
|
|
||||||
|
1. **Fragmentation (D's finding — Fable ruled, A owns details).** The naïve real graph fragmented
|
||||||
|
(katoomba **105 components**, 58% street-metres on islands). Fixed in `plan_osm.buildRealRoads`:
|
||||||
|
(a) **junction-protected Douglas–Peucker** — a point shared by ≥2 ways is a junction; simplify only
|
||||||
|
between junctions, never through one (the collinear tee-off drop). Alone → 94–100% main-net.
|
||||||
|
(b) **cull/bridge** — main component + near-fragments joined (`JOIN_TOL` 12 m; shop-island bridge ≤200 m);
|
||||||
|
far **shopless** islands culled. **Result: 1 connected component on all five towns** (katoomba 100%
|
||||||
|
main-net, 2 islands culled). Every dropped shop **counted** (`norm.dropped`) — katoomba 19/20 (1 crowd
|
||||||
|
drop, the R18 overflow prediction measured), the other four kept all.
|
||||||
|
2. **E's poster-clearance finding fixed** (`gigs.js`): a spine poster overhanging a real crossing carriageway
|
||||||
|
is skipped — measured exactly as the selfcheck kerb floor, a **no-op on synthetic** (gig golden frozen).
|
||||||
|
The 23 real-town clearance failures → 0.
|
||||||
|
3. **Goldens pinned** (the finalization): all 5 real towns on their real-roads output
|
||||||
|
(katoomba `0xb7ffbca2`, newtown `0x6f984c81`, fremantle `0xfb197000`, bendigo `0x21179b3b`,
|
||||||
|
castlemaine `0x7c47f639`). synthetic/marched-fixtures/classic/default **frozen** — the alpha changed
|
||||||
|
nothing outside the cache-schema path. selfcheck ALL GREEN **51670/51670**; scaffold + consistency GREEN.
|
||||||
|
4. **New finding filed (beta, non-blocking):** spine-poster density scales with edge count (katoomba 438
|
||||||
|
posters vs ~14 synthetic; draws stay low via instancing) — a beta posters-per-town cap.
|
||||||
|
|
||||||
|
Handshake fired: `katoomba_real` boots real roads, golden pinned, findings resolved → B/C/D/F. D re-measures
|
||||||
|
(1 component / 100% / 0 stranded); F applies B's `BIG_CITY` one-liner + gates + tags `v4.0-alpha`.
|
||||||
|
|
||||||
|
## Round 18 (2026-07-16) — v4.0-alpha REAL ROADS: cache schema v2 + the real-graph lift (the epoch headline)
|
||||||
|
|
||||||
|
Lane A is the round's long pole (serial spine A→E→A). Two commits:
|
||||||
|
|
||||||
|
**Commit 1 — cache schema v2 (`roads[]`), published FIRST** (`4dc80c2`) so E's `build_towns.py` can fetch
|
||||||
|
road geometry. A v2 cache adds `roads:[{kind,pts:[[lat,lon],…]}]` (`kind` = OSM `highway=*`);
|
||||||
|
`validateTownCache` accepts schema `…/1` AND `…/2` (roads absent ⇒ marched fallback, nothing regresses);
|
||||||
|
`ROAD_KIND` maps the class to a CityPlan edge kind. Full contract in `web/assets/towns/README.md`.
|
||||||
|
|
||||||
|
**Commit 2 — the real-graph lift** (`plan_osm.buildRealRoads`): real OSM ways → the CityPlan street graph,
|
||||||
|
**same schema out** (B/C/D/F consume it unchanged). project → simplify (Douglas–Peucker ε≈6 m) → snap
|
||||||
|
coincident points (≤3 m) to shared nodes = real intersections → straight-segment edges → classify `main`
|
||||||
|
by OSM class + shop density → seat each shop on its nearest real edge (real order/side), marched +
|
||||||
|
overlap-resolved. **Passes the full structuralSuite + gig district + determinism** on a committed synthetic
|
||||||
|
irregular graph (main + two cross streets + intersection + acute angle + dead-end lane + curve). Robust on
|
||||||
|
messy inputs (single road, coincident dups, disconnected ways, all-roads-too-short → marched fallback).
|
||||||
|
|
||||||
|
Frozen (the alpha changes only the roads path): classic `0x3fa36874`, gig `0xb1d48ea1`, osm melbourne/
|
||||||
|
katoomba/silverton, + E's 4 trailing real caches pinned on v1 (bendigo/castlemaine/fremantle/newtown).
|
||||||
|
`katoomba_real` stays UNPINNED — its golden re-pins to the real-ROADS output when E lands its roads (the
|
||||||
|
A→E→A finalization). selfcheck ALL GREEN **16810/16810**; scaffold + consistency GREEN.
|
||||||
|
|
||||||
|
Fidelity decision (charter risk #4): real edge + real order + real side + regularised spacing — NOT
|
||||||
|
pixel-exact (real shops overlap; no-overlap is load-bearing). Alpha failure list (LANE_A_NOTES §R18): (1)
|
||||||
|
overflow drop on short edges (`norm.dropped`); (2) intersections = shared coordinates only (crossings
|
||||||
|
without a shared OSM node → disconnected edges — a D sim-graph risk); (3) blocks are per-edge frontage
|
||||||
|
strips not planar faces (a B ground-render audit item); (4) crowded-intersection overlap-resolve drop.
|
||||||
|
|
||||||
|
Handshake: schema v2 → E (done). "katoomba_real boots with real roads, golden pinned" → B/C/D fires when
|
||||||
|
E's roads land (katoomba is v1/marched until then). B/C/D audit prep isn't blocked on me.
|
||||||
|
|
||||||
|
## Round 17 (2026-07-16) — v3.2 THE SCOUT: the town-cache contract + plan_osm hardened for real data
|
||||||
|
|
||||||
|
Lane A ran **first** (E's `build_towns.py` builds to the contract). Scope fence held: **no new plan
|
||||||
|
features, no golden moved** — the scout proves the *existing* `?plansrc=osm` contract eats real geography.
|
||||||
|
|
||||||
|
Delivered (one commit):
|
||||||
|
1. **The town-cache contract (published FIRST, ledger #6).** `plan_osm.js` gains `validateTownCache()`
|
||||||
|
(the authoritative `{ok, errors[], warnings[]}` check), `registerTownCache()`, a `generatePlanOSM(…,
|
||||||
|
{cache})` seam, and `TOWN_CACHE_SCHEMA`/`MIN_TOWN_SHOPS`/`MAX_TOWN_SPAN_M`. Shape: `{schema, key, town,
|
||||||
|
source, license, attribution, center{lat,lon}, shops[{id,name,type,lat,lon,suburb?}]}`. Home declared:
|
||||||
|
`web/assets/towns/<key>.json` (+ a `README.md` with the full spec). Documented in CITY_SPEC (v3.2 section)
|
||||||
|
+ LANE_A_NOTES §R17 (the E handshake).
|
||||||
|
2. **`plan_osm` hardened** — blank name → registry label; the validator gates finite center/coords, a
|
||||||
|
≥6-shop floor, and warns on unknown types (→opshop), dup ids, and **span > 5 km** (the mega-strip). Proven
|
||||||
|
against thriftgod's real 2,828-shop `city_source.json`: boots a valid plan, 0 bad coords, 0 "undefined"
|
||||||
|
names. Marching regularises messy real coords, so dup/odd positions are a non-issue.
|
||||||
|
3. **Selfcheck: contract stress suite + real-cache path.** A committed synthetic-adversarial suite (valid
|
||||||
|
messy caches boot + pass structural/gig; invalid ones reject; wide-spread warns; the registry seam works),
|
||||||
|
plus an auto-loader for `web/assets/towns/*.json` — validates, runs the suites, pins per-town goldens.
|
||||||
|
Empty until E lands caches (guarded, green). ALL GREEN **15277/15277**; scaffold + consistency GREEN.
|
||||||
|
4. **The v4 risk list** (LANE_A_NOTES §R17, for Fable's charter): (a) **the mega-strip** — an unbounded
|
||||||
|
Overpass bbox marches a whole region (2,828 shops) into an 11.6 km strip; town SELECTION is E's real
|
||||||
|
quality lever, warned at >5 km, the generator can't rescue an over-broad query; (b) sparse compact towns
|
||||||
|
(a 2 km inner-Canberra radius held only 6 shops); (c) settled — osm facades clear (rows 54 m apart), so
|
||||||
|
the R16 clearance bias is a no-op on real towns.
|
||||||
|
|
||||||
|
Handshake open: E builds `build_towns.py` to the contract; E→A each cache → I pin its golden.
|
||||||
|
|
||||||
|
## Round 16 (2026-07-16) — v3.1 THE FLIP: corner-poster clearance bias (#6) + no-spine osm fixture (#7)
|
||||||
|
|
||||||
|
Lane A ran **first** among the parallel lanes (F bookends with the flip; A's golden feeds F's default-boot
|
||||||
|
gate). The R15 finding was that a corner venue's frontage poster can sit near a crossing kerb.
|
||||||
|
|
||||||
|
Delivered (one commit):
|
||||||
|
1. **Corner-poster fix — via `pickVenues`, not a nudge.** I proved the brief's proposed fix (nudge along
|
||||||
|
the facade) is geometrically impossible: the crossing edge is **parallel** to the facade and spans it
|
||||||
|
(0/187 along-facade slides help). Root cause: band_room/rsl land on through-lots on narrow warehouse
|
||||||
|
blocks, so the `+Z` wall (B's door side) faces the block's other street, overlapping its carriageway by
|
||||||
|
up to ~3 m. **Surfaced to Fable; ruling: bias `pickVenues`.** It now prefers a band_room/rsl candidate
|
||||||
|
whose `+Z` facade clears every carriageway by ≥ `POSTER_CLEAR` (0.5 m), broadening off-spine when the
|
||||||
|
fringe is uniformly narrow. Result: **0/851** frontage posters below floor across 400 seeds (worst
|
||||||
|
0.51 m); pub untouched (hero shot unmoved). Feasibility pre-checked: a clear lot always exists.
|
||||||
|
2. **Gig golden re-pinned `0x4f4a549d → 0xb1d48ea1`** — the sanctioned move (posters moved); synthetic/
|
||||||
|
classic `0x3fa36874` frozen. §v3 FROZEN marker records the amendment.
|
||||||
|
3. **Selfcheck: exemption → floor.** R15's blanket frontage exemption is now a real invariant — frontage
|
||||||
|
posters must clear their kerb by ≥ `POSTER_CLEAR` (exported from the barrel), spine posters keep ≥ 0.
|
||||||
|
4. **No-spine osm fixture `silverton`** (ledger #7): a constructed single-row town (all shops one latitude
|
||||||
|
⇒ 0 `main` edges). Closes the R14 gap (both real fixtures had a spine); proves `pickVenues`' no-spine
|
||||||
|
branch + `buildPosters`' no-main fallback. Base golden pinned `0xd4b351c9`; sweep asserts 0 main edges +
|
||||||
|
graceful 2–4-venue placement. Selfcheck **ALL GREEN 14769/14769**; scaffold + consistency GREEN.
|
||||||
|
5. **CITY_SPEC §v3 amended** (golden line, poster clearance-bias prose, no-spine note, recorded amendment).
|
||||||
|
Left the prime-flag-law blockquote for F (the v3.1 classic-law + flags-table is F's §v3 amendment).
|
||||||
|
|
||||||
|
For F: default-boot golden = `0xb1d48ea1`; `?classic=1` regression target = `0x3fa36874`.
|
||||||
|
|
||||||
|
## Round 15 (2026-07-16) — the frontage-poster +Z flip (ledger #1) + one sanctioned golden move
|
||||||
|
|
||||||
|
Lane A ran **first** (B's hero re-shoot + F's tour hang off it). The R14 review found every venue's
|
||||||
|
own-frontage poster had been on the building's **−Z back** since R13 — ~16 m behind the street face
|
||||||
|
(B measured 15.9 m), invisible from the street, which is why John's `queue_night` framed no poster.
|
||||||
|
|
||||||
|
Delivered (one commit):
|
||||||
|
1. **Frontage-poster fix** (`gigs.js` buildPosters item 1): flipped to the **+Z street facade** (B's
|
||||||
|
canon) AND `ry = lot.ry + π` so the printed face points `+Z` at the queue-side camera (else `DoubleSide`
|
||||||
|
renders a mirrored band name). Proven: pub + rsl frontage posters now sit **0.06 m from B's door**,
|
||||||
|
printed-face·(+Z street) = **1.000**. Spine posters untouched.
|
||||||
|
2. **Gig golden re-pinned `0x1f636349 → 0x4f4a549d`** — the round's ONE sanctioned move (posters moved);
|
||||||
|
synthetic `0x3fa36874` frozen. §v3 FROZEN marker records the amendment (amendment law).
|
||||||
|
3. **Selfcheck poster check refined**: the brief expected the 400-seed off-carriageway assert to stay
|
||||||
|
green, but the `+Z` facade is the block-interior side in *plan* coords, so **corner venues** (a band_room/
|
||||||
|
rsl fronting one street with another behind — seed 1 lot#481, 1.58 m from a crossing centreline) put the
|
||||||
|
frontage poster near that street. That's B's building geometry (the door is there too), not a mid-road
|
||||||
|
defect — so the no-bitumen rule now **exempts building-bound frontage posters** and keeps it for
|
||||||
|
free-standing **spine** posters. Sweep green again: **14264/14264**.
|
||||||
|
4. **CITY_SPEC §v3 amended** (poster prose → `+Z` facade + `ry+π`; golden line; frontage/spine carriageway
|
||||||
|
split; recorded amendment in the FROZEN marker). LANE_A_NOTES §R15 hands B/F the new golden + a
|
||||||
|
corner-venue eyeball note.
|
||||||
|
|
||||||
|
Convention settled: the visible venue face is `+Z` (`buildings.js` facade `zf=+d/2`; `venue.js` door/queue
|
||||||
|
`+Z`). The selfcheck's `facing=(−sin,−cos)` "faces frontEdge" is the sim/GLB convention, 180° off the
|
||||||
|
visual front — the same π-ambiguity D fixed for the fleet in R13.
|
||||||
|
|
||||||
|
## Round 14 (2026-07-16) — v3.0 RELEASE: invariant sweep + CITY_SPEC §v3 freeze
|
||||||
|
|
||||||
|
Release round, Lane A scope = harden + freeze (no new systems). **No golden moved** (synthetic
|
||||||
|
`0x3fa36874`, gig `0x1f636349` both re-verified frozen); the only code touched is `selfcheck.js`
|
||||||
|
(test-only), so no seeded stream gained a draw — the release-round law holds.
|
||||||
|
|
||||||
|
Delivered:
|
||||||
|
1. **400-seed district invariant sweep** (`selfcheck.js`, ALL GREEN **14264/14264** in ~0.7s). Factored
|
||||||
|
the district checks out of `gigSuite` into `districtInvariants(plan,label)` (behaviour-preserving — the
|
||||||
|
4 hero-seed gigSuite runs are byte-identical), then swept **seeds 1–400 + the large edge seeds**
|
||||||
|
asserting the whole contract each: 2–4 venues, ≥1 pub, never the openLate shop, no band twice per night
|
||||||
|
town-wide, per-venue 4–6 nights, every poster off the carriageway. Added a distribution assert that the
|
||||||
|
seeded count **spans {2,3,4}** across 400 seeds (146/121/133) — proves the range is exercised, not pinned.
|
||||||
|
`structuralSuite` still runs on the 6 hero SEEDS + osm parity (unchanged); the sweep runs the light
|
||||||
|
district checks only, so 400 gig-on generations stay ~0.5s.
|
||||||
|
2. **osm graceful placement** in the sweep: `katoomba` (19 shops, **no warehouse district**) still lands
|
||||||
|
2–4 valid venues incl. a pub for every hero seed — proving `pickVenues`' off-spine fallback. Honest
|
||||||
|
scope noted: both osm fixtures keep a main spine, so this proves the no-warehouse branch (not no-spine).
|
||||||
|
3. **Poster-check hardening**: kept the `roadWidth/2` nearest-edge formulation (a literal `poleOffset`
|
||||||
|
compare would false-fail corner posters); added a `−1e-6` epsilon on the strict `<` to immunise the
|
||||||
|
exact kerb-line boundary against r2 rounding (only reachable on a no-main-spine fallback; not hit today).
|
||||||
|
4. **CITY_SPEC §v3 frozen at `v3.0`** — §v2-style FROZEN marker, final wording pass over venues table,
|
||||||
|
genre map, gigKey contract, corridor law, gig golden. Genres final (pubrock/grunge/covers, Fable's ruling).
|
||||||
|
|
||||||
|
Coordination: the R12 single-venue **alias line stays in §v3 for F to delete atomically** with the
|
||||||
|
`gig_state.js` getters (debt #1) — deleting it now would be doc-ahead-of-code (alias still live) and would
|
||||||
|
race F on the same lines; the freeze marker carves it out explicitly. Flagged to F that the runtime block
|
||||||
|
omits `nightOf(id)`, and to B that `venue.js:228` is a second bare-string edit site. **Not touched:**
|
||||||
|
`web/assets/manifest.json` shows a concurrent Lane E `glb_law` edit in the shared tree — left for E.
|
||||||
|
|
||||||
|
## Round 13 (2026-07-15) — v3.0-beta THE DISTRICT: 2–4 venues + the week + town-wide posters + verge fix
|
||||||
|
|
||||||
|
Lane A ran **first** again (C/D/B/F all hang off the district `plan.gigs`). Same flag, same prime-flag
|
||||||
|
law: **flags-off byte-identical — all v2 goldens frozen** (synthetic `0x3fa36874`, osm unchanged).
|
||||||
|
Self-check **ALL GREEN 3273/3273**; `scaffold_check` + `consistency` GREEN. The gig **entry shapes are
|
||||||
|
unchanged** — the alpha's one-venue readers now iterate 2–4. Interfaces published in `LANE_A_NOTES.md`
|
||||||
|
(§Round 13) and `CITY_SPEC.md` (§v3.0-beta) in this same commit.
|
||||||
|
|
||||||
|
Delivered (all behind `?gigs=1`):
|
||||||
|
1. **2–4 venues, three kinds** — `pickVenues` converts a seeded 2–4 plain shops in place: `pub` (spine
|
||||||
|
end), `band_room` (warehouse fringe), `rsl` (off-spine), 4th = a 2nd pub at the far end. Each carries
|
||||||
|
`venueKind` + `genreKey` (`pubrock`/`grunge`/`covers`). Registry gains weight-0 `band_room`+`rsl` types
|
||||||
|
(facades, hours, footprints). Placement degrades gracefully so osm towns still get venues. Never the
|
||||||
|
openLate landmark; venues occupy distinct lots.
|
||||||
|
2. **The gigKey contract (debt #1)** — `gigKeyFor(genreKey) => 'gig-'+genreKey`, the single exported
|
||||||
|
genre→bed mapping (no table anywhere). C/E/B/F all route through it; F deletes the `GIG_BED` bridge.
|
||||||
|
3. **`plan.gigs` goes district** — 7 nights × every venue, each playing a seeded **4–6** (dark the rest,
|
||||||
|
reads true). `gigId` unique town-wide; cover skewed by kind (pub 50%/`$2–10`, RSL 45%/`$2–5`, band_room
|
||||||
|
70%/`$2–6`). **No band plays two venues on one night**; custom names spread night-major across the week.
|
||||||
|
4. **`plan.posters` goes town-wide** — tonight's playing venues each get a frontage poster + a seeded spine
|
||||||
|
run (2/main edge, either verge). Different venues share pole runs — the district reading.
|
||||||
|
5. **Verge offset (debt #5 PAID)** — the R12 bug (spine posters at `node.x+2` = mid-bitumen on a 28 m
|
||||||
|
corridor) is fixed by the new corridor law: `roadWidth`/`vergeBand`/`poleOffset(edge)` in registry.js,
|
||||||
|
re-exported from the barrel — the road-vs-verge split of `edge.width`, published as **functions** (no
|
||||||
|
plan fields ⇒ base goldens frozen). Selfcheck now asserts no poster stands on a carriageway.
|
||||||
|
6. **Registry `pub.interior` corrected** `'band_room'`→`'pub'` (was an unread field; now avoids a
|
||||||
|
name-collision with the new `band_room` venue type — C keys recipes off `shop.type`, not this field).
|
||||||
|
|
||||||
|
Golden re-pin: gig golden `0xa6ae5a5e`→**`0x1f636349`** (multi-venue changed gig-plan output; re-pinned
|
||||||
|
in-commit per CITY_SPEC amendment law). New selfcheck coverage: 2–4 venues + known kinds + `type≡venueKind`
|
||||||
|
+ genre map + gigKey ≡ `gig-`+genreKey; 4–6 gigs/venue, ≤1 gig/night/venue, no same-night band clash
|
||||||
|
town-wide; posters advertise night-0 and **clear every carriageway**. Sample seed 20261990 → 3 venues:
|
||||||
|
The Exchange Hotel (pub), Wangaratta RSL, The Vinyl Shed (band room, dark tonight); 14 gigs across the week.
|
||||||
|
|
||||||
|
**Handshake note for Lane C:** `RECIPES.band_room`/`RECIPES.rsl` don't exist in `theme.js` yet — until C
|
||||||
|
adds them those venues fall back to the op-shop interior. That's the order (A→C). **Not run by A:** the
|
||||||
|
browser flags/scale gates + depot (Gate 3, John-blocked) — F's end-of-round acceptance, and they'd test
|
||||||
|
R12 runtime that hasn't adapted to the new venue count yet.
|
||||||
|
|
||||||
|
## Round 12 (2026-07-15) — v3.0-alpha GIG LAYER (`?gigs=1`): venue + gigs + band names + closing-time debt
|
||||||
|
|
||||||
|
Lane A ran **first** this round (the whole round hangs off the `plan.gigs` handshake). Everything is
|
||||||
|
behind `?gigs=1` (prime flag law): **flags-off is byte-identical — all v2 goldens frozen** (synthetic
|
||||||
|
`0x3fa36874`, osm melbourne `0x34cfdec0`, katoomba `0x0f652510`). Self-check **ALL GREEN 3074/3074**
|
||||||
|
(+ the whole gig suite ×2 sources), `qa.sh --strict` GREEN (6/6). Verified in-browser both modes.
|
||||||
|
|
||||||
|
**Architecture — a post-hoc augmentation layer, not a generator change.** `web/js/citygen/gigs.js`
|
||||||
|
`withGigs(plan, seed, opts)` takes a finished plan and adds the venue + gigs + posters; the selector
|
||||||
|
(`index.js generatePlanFor`) applies it **only** when `opts.gigs`. So `generatePlan` is untouched
|
||||||
|
(goldens can't move) and the layer is source-agnostic (synthetic + osm both get gigs).
|
||||||
|
|
||||||
|
Delivered (all behind the flag):
|
||||||
|
1. **Venue** — one `pub` per town: a spine-END plain shop converted in place to `type:'pub'` (keeps lot
|
||||||
|
id/geometry ⇒ enterable, no overlap), `shop.venue=true`, hours `[17,23]`. **Never** the openLate shop.
|
||||||
|
Registry gains a weight-0 `pub` type (facades `djsim-pub`+`federation`, `interior:'band_room'` for
|
||||||
|
Lane C, fittings). Weight-0 ⇒ never auto-placed ⇒ base plan unchanged.
|
||||||
|
2. **`plan.gigs`** — 7 nightly entries `{gigId, venueShopId, bandName, genreKey:'pubrock', night, startSeg:5,
|
||||||
|
endSeg:5, cover}`; `cover` seeded ~half free / half `$2–$10`. F picks tonight's + charges at the door.
|
||||||
|
3. **Band names** — `bandName(seed)` generator (90s Aussie pub-rock wordlists) **+ the drop-in**
|
||||||
|
`web/assets/custom_bands.json` (`{bands:[…]}`, ships with 10 examples incl. "The Feral Galahs"):
|
||||||
|
present → priority into the pool (up to 7 surface); absent → pure generator, zero fetch (`?noassets`).
|
||||||
|
Deterministic given (seed, file). The shell (F) does the async load and passes `customBands` in.
|
||||||
|
4. **`plan.posters`** — ~6 seeded poster placements `{id, gigId, x, z, ry}` (venue + spine) for Lane B.
|
||||||
|
5. **Closing-time debt PAID** — `isOpen(shopOrHours, hour)` exported from the barrel: the canonical
|
||||||
|
half-open law (`open ≤ h < close`) as code, for D's gig-night patronage surge (was prose in round 9).
|
||||||
|
6. **Debug map** — `map.html?gigs=1` shows the venue ring + "♪ PUB" label, poster pins, and a panel gig
|
||||||
|
line ("♪ The Thornbury Hotel · tonight: Screaming Utes · $10 cover · 7 nights · 6 posters"). Loads the
|
||||||
|
band drop-in for the view. Flags-off map is unchanged (adds a benign `pub (0)` legend row).
|
||||||
|
|
||||||
|
New self-check coverage: `gigSuite` runs the full structural suite (venue-aware — the pub is exempt
|
||||||
|
from the single-late-shop rule via `!sh.venue`, a no-op on base plans) + gig invariants (one pub, gig
|
||||||
|
schema, cover ∈ {0}∪[2,10], band names resolved, posters ref real gigs), on synthetic + both osm towns;
|
||||||
|
a `?gigs`-off≡base byte-identity check per seed; a custom-band priority + determinism check; and a
|
||||||
|
**gig golden `0xa6ae5a5e`**. CITY_SPEC gained a "CityPlan v3 — gig layer" section; handshake interfaces
|
||||||
|
for C/D/B/F are in `LANE_A_NOTES.md`. Sample seed 20261990 → venue "The Thornbury Hotel" (a converted
|
||||||
|
spine-end pawn shop), openLate video preserved.
|
||||||
|
|
||||||
|
## Round 9 (2026-07-15) — freeze the v2 contract + closing-time ruling (ship round)
|
||||||
|
|
||||||
|
Small round; the most "done" lane makes it official.
|
||||||
|
|
||||||
|
1. **CITY_SPEC v2 contract FROZEN** — added a "CityPlan v2 — frozen producer contract" section: the
|
||||||
|
producer API (`generatePlanFor`/`generatePlan`/`generatePlanOSM`/`osmTownKeys`/`chunkIndex`), the
|
||||||
|
goldens table (synthetic `0x3fa36874`, osm/melbourne `0x34cfdec0`, osm/katoomba `0x0f652510`, keyed
|
||||||
|
(seed,plansrc,town)), the town-key mechanism, osm normalization rules, and the
|
||||||
|
hours/openLate/storeys laws — all matching code as shipped (F diffs claims vs code in review).
|
||||||
|
Refreshed the stale round-6 "Plan sources" blockquote to the multi-town reality. Layer-1 changes now
|
||||||
|
require a CITY_SPEC amendment + golden re-pin in the same commit.
|
||||||
|
2. **Recipe sanity — dry-ran end-to-end** (not just eyeballed): appended a throwaway Newtown (42 shops)
|
||||||
|
to `OSM_TOWNS`, ran selfcheck → the full parity suite auto-covered it (1931/1932, only the golden
|
||||||
|
unpinned) and printed a ready-to-paste `newtown: 0x9045e577,` line. Reverted; no 3rd town shipped
|
||||||
|
(didn't fall out free). Improved selfcheck to print that `⚠ UNPINNED — add to OSM_GOLDENS` line so
|
||||||
|
the recipe is genuinely mechanical; fixed the recipe wording to match.
|
||||||
|
3. **Closing-time occupant ruling** (D's on-call question) — ratifies what D's `sim.js` already does and
|
||||||
|
freezes it: half-open hours, no new entry when closed, **occupants drain (finish their bounded
|
||||||
|
dwell) — never popped**, player not force-ejected, openLate video is the night destination. In
|
||||||
|
CITY_SPEC (contract law) + `LANE_A_NOTES.md` (for D). No code change asked of D.
|
||||||
|
|
||||||
|
Self-check **ALL GREEN 1727/1727**; `qa.sh --strict` GREEN. Only doc + one selfcheck log-line touched;
|
||||||
|
no generator change ⇒ all three goldens unchanged.
|
||||||
|
|
||||||
|
## Round 8 (2026-07-15) — OSM to parity + a second town (carried R7 brief, FULLY LANDED)
|
||||||
|
|
||||||
|
## Round 8 (2026-07-15) — OSM to parity + a second town (carried R7 brief, FULLY LANDED)
|
||||||
|
|
||||||
|
The carried brief (slipped R7) is done — all four items, not a partial.
|
||||||
|
|
||||||
|
1. **Selfcheck parity**: extracted the full invariant suite into `structuralSuite(plan, label)` and
|
||||||
|
run it on **every (source, town)** — synthetic + both OSM towns now get identical coverage
|
||||||
|
(storeys, one-shop-per-lot, solid-use, facing, within/cross-block overlap, corridor coverage,
|
||||||
|
determinism, exactly-one-openLate). **ALL GREEN 1727/1727** (was 1362 — OSM went from a subset to
|
||||||
|
the full suite ×2 towns).
|
||||||
|
2. **Second town**: **Katoomba** (Blue Mountains, 19 shops, op-shop/book heavy) — a small-regional
|
||||||
|
contrast to inner-Melbourne (95, book heavy). `?plansrc=osm&town=katoomba`. `osm_fixture.js` is now
|
||||||
|
`OSM_TOWNS { melbourne, katoomba }` + `DEFAULT_TOWN`; `generatePlanFor(seed, source, { town })`.
|
||||||
|
Golden pinned `0x0f652510`. **Melbourne golden `0x34cfdec0` and synthetic `0x3fa36874` unchanged.**
|
||||||
|
3. **"Add a town" recipe** documented in `LANE_A_NOTES.md` (R8) — extraction → `OSM_TOWNS` entry →
|
||||||
|
pin golden → tell F. Mechanical.
|
||||||
|
4. **Normalization + logging**: importer normalizes real data to the contracts (unknown kind→opshop,
|
||||||
|
hours clamp so only one shop is openLate, video-preferred landmark w/ fallback) and **logs** what it
|
||||||
|
changed via `{ report }` — selfcheck prints e.g. `melbourne: 95 shops · normalized {typesRemapped:0,
|
||||||
|
hoursClamped:5, openLate:video}`. The contract never bends; the data does.
|
||||||
|
|
||||||
|
Extraction one-liner (Katoomba, reusable — see recipe): cell 0.05 around (-33.714, 150.312), same
|
||||||
|
KIND map + dedupe + sort-by-id as Melbourne (cell 0.06 around -37.803, 144.967). Screenshots:
|
||||||
|
`docs/shots/laneA/osm-{melbourne,katoomba}.png`. `qa.sh --strict` GREEN.
|
||||||
|
|
||||||
## Round 6 (2026-07-14) — the second plan source (`?plansrc=osm`), FULLY LANDED
|
## Round 6 (2026-07-14) — the second plan source (`?plansrc=osm`), FULLY LANDED
|
||||||
|
|
||||||
|
|||||||
@ -28,7 +28,21 @@ considered and parked: nothing in v1 needs it, and the asset pipeline (GLB, web-
|
|||||||
|
|
||||||
- **Metres. +Y up. Ground plane is XZ.** City origin (0,0) = centre of the main square.
|
- **Metres. +Y up. Ground plane is XZ.** City origin (0,0) = centre of the main square.
|
||||||
- **Right-handed three.js defaults.** Buildings face their street; a facade's outward normal
|
- **Right-handed three.js defaults.** Buildings face their street; a facade's outward normal
|
||||||
points at its street edge. GLB convention: origin at base, facing **−Z**, real-world scale.
|
points at its street edge.
|
||||||
|
- **THE TWO CONVENTIONS — they are different things, and conflating them cost nine rounds
|
||||||
|
(ROUND27). Read both:**
|
||||||
|
- **The FACADE convention (the lots contract, `lot.ry`): the visible shopfront is local `+Z`** ⇒
|
||||||
|
world `(sin ry, cos ry)`. This is B's canon and the de facto truth since v1 — `buildings.js`
|
||||||
|
draws the facade quad and door at local `z = +d/2`, `venue.js` seats the door-glow/queue/
|
||||||
|
posters there. **`lot.ry` aims +Z at `frontEdge`.**
|
||||||
|
- **The SIM/RIG convention (props, not lots): a GLB is modelled facing `−Z`** (origin at base,
|
||||||
|
real-world scale) ⇒ rig-forward is world `(−sin ry, −cos ry)`. This describes *how a model is
|
||||||
|
authored*, **not which way a lot faces.**
|
||||||
|
- **They are opposites, so a check written in one silently passes over a world built in the
|
||||||
|
other.** CITY_SPEC:71 wrote the SIM convention into the LOTS contract by ambiguity; each
|
||||||
|
generator implemented one half; every golden stayed green over a town whose every shopfront
|
||||||
|
faced away. **Never assert a lot's facing against a spec line — assert it against the
|
||||||
|
geometry the renderer actually draws** (`selfcheck.js` now does exactly that).
|
||||||
- **Seeded everything, `Math.random()` is banned in generation code.**
|
- **Seeded everything, `Math.random()` is banned in generation code.**
|
||||||
`web/js/core/prng.js` is the only randomness source:
|
`web/js/core/prng.js` is the only randomness source:
|
||||||
- `seedFor(citySeed, kind, id)` → stable uint32 (xmur3 hash of `"seed:kind:id"`).
|
- `seedFor(citySeed, kind, id)` → stable uint32 (xmur3 hash of `"seed:kind:id"`).
|
||||||
@ -68,8 +82,11 @@ Schema v1 (Lane A owns it; extend, don't break):
|
|||||||
// onto the block so consumers can theme it without a lookup (Lane A extension, non-breaking).
|
// onto the block so consumers can theme it without a lookup (Lane A extension, non-breaking).
|
||||||
lots: [ { id, block, x, z, w, d, ry, frontEdge, use } ],
|
lots: [ { id, block, x, z, w, d, ry, frontEdge, use } ],
|
||||||
// use: 'shop' | 'anchor' | 'house' | 'yard' | 'stall' | 'infill'
|
// use: 'shop' | 'anchor' | 'house' | 'yard' | 'stall' | 'infill'
|
||||||
// ry: Y-rotation (radians) so a GLB modelled facing −Z ends up with its facade's OUTWARD normal
|
// ry: Y-rotation (radians) so the lot's VISIBLE FACADE — local +Z — points at frontEdge.
|
||||||
// pointing at frontEdge. World facing = (−sin ry, −cos ry). Lane B rotates the shell by ry.
|
// Facade/door/frontage normal = local +Z ⇒ world (sin ry, cos ry). Lane B rotates the shell by ry
|
||||||
|
// and draws the shopfront at local z = +d/2 (buildings.js), so +Z IS the street face. ROUND27.
|
||||||
|
// (The SIM/rig convention — a GLB modelled facing −Z, world (−sin ry, −cos ry) — is a DIFFERENT
|
||||||
|
// thing and is documented as such below. It is not the lots contract. See the amendment.)
|
||||||
// frontEdge: id of the street edge this lot fronts (guaranteed to exist and be geometrically adjacent).
|
// frontEdge: id of the street edge this lot fronts (guaranteed to exist and be geometrically adjacent).
|
||||||
shops: [ { id, lot, type, name, sign, seed, facadeSkin, storeys, hours: [open, close], openLate? } ]
|
shops: [ { id, lot, type, name, sign, seed, facadeSkin, storeys, hours: [open, close], openLate? } ]
|
||||||
// type: see SHOP TYPES below. hours: [open, close], 24h integers, 0 ≤ open < close ≤ 23.
|
// type: see SHOP TYPES below. hours: [open, close], 24h integers, 0 ≤ open < close ≤ 23.
|
||||||
@ -101,22 +118,88 @@ Schema v1 (Lane A owns it; extend, don't break):
|
|||||||
> furniture never resolves to a chunk that omits its edge; strictly JSON round-trippable (all finite
|
> furniture never resolves to a chunk that omits its edge; strictly JSON round-trippable (all finite
|
||||||
> numbers, no stray keys).
|
> numbers, no stray keys).
|
||||||
|
|
||||||
> **Plan sources (round 6).** A CityPlan can come from two producers behind this one contract, chosen
|
> **Plan sources.** A CityPlan can come from two producers behind this one contract, chosen via
|
||||||
> via `generatePlanFor(seed, source)` (`web/js/citygen/index.js`) — the shell selects with `?plansrc=`:
|
> `generatePlanFor(seed, source, { town })` (`web/js/citygen/index.js`) — the shell selects with
|
||||||
|
> `?plansrc=` (+ `&town=` for osm):
|
||||||
> - `'synthetic'` (default) — `generatePlan(seed)`, the procedural town. Byte-identical, golden `0x3fa36874`.
|
> - `'synthetic'` (default) — `generatePlan(seed)`, the procedural town. Byte-identical, golden `0x3fa36874`.
|
||||||
> - `'osm'` — `generatePlanOSM(seed)`, a real-data import from a **checked-in fixture** (`osm_fixture.js`,
|
> - `'osm'` — `generatePlanOSM(seed, town)`, a real-data import from a **checked-in fixture**
|
||||||
> real AU shops from thriftgod's OSM cache; **zero network**). Golden `0x34cfdec0`.
|
> (`osm_fixture.js` → `OSM_TOWNS`; real AU shops from thriftgod's OSM cache; **zero network**).
|
||||||
|
> Towns: `melbourne` (default, golden `0x34cfdec0`), `katoomba` (`0x0f652510`). `osmTownKeys()` lists them.
|
||||||
>
|
>
|
||||||
> An `'osm'` plan sets `source:'osm'`, reports its **real bounding-box `size`** (not 1024²), and contains
|
> An `'osm'` plan sets `source:'osm'`, reports its **real bounding-box `size`** (not 1024²), and contains
|
||||||
> **only real retail types** — no synthetic market square, arcade, dept anchor, milk bars, or stalls.
|
> **only real retail types** — no synthetic market square, arcade, dept anchor, milk bars, or stalls.
|
||||||
> All structural invariants above hold identically. Consumers that assume a synthetic-only feature
|
> All structural invariants above hold identically. Consumers that assume a synthetic-only feature
|
||||||
> (dept/market/milkbar/stall) must guard on `source`. The invariant suite is parameterized by source.
|
> (dept/market/milkbar/stall) must guard on `source`. The invariant suite runs on every (source, town).
|
||||||
|
> **Full frozen v2 producer contract below** (§"CityPlan v2 — frozen producer contract").
|
||||||
|
|
||||||
Chunk key: `cx = floor(x/64)`, `cz = floor(z/64)`. **Chunk size 64m.** Lane A ships a
|
Chunk key: `cx = floor(x/64)`, `cz = floor(z/64)`. **Chunk size 64m.** Lane A ships a
|
||||||
`chunkIndex(plan)` helper: chunk key → { lots, shops, edges } touching it. Edges are bucketed across
|
`chunkIndex(plan)` helper: chunk key → { lots, shops, edges } touching it. Edges are bucketed across
|
||||||
the full road **corridor** (centreline ± `width/2`), so anything placed in the verge (street furniture,
|
the full road **corridor** (centreline ± `width/2`), so anything placed in the verge (street furniture,
|
||||||
footpath NPCs) resolves to a chunk whose `edges` list includes its edge.
|
footpath NPCs) resolves to a chunk whose `edges` list includes its edge.
|
||||||
|
|
||||||
|
## CityPlan v2 — frozen producer contract
|
||||||
|
|
||||||
|
> **FROZEN at `v2.0` (round 9).** This is the authoritative Layer-1 contract — the v3 epoch
|
||||||
|
> (editor / override layer) reads THIS. It matches the code as shipped; `web/js/citygen/selfcheck.js`
|
||||||
|
> enforces every law here. Changes are a **CITY_SPEC amendment in the same commit + a golden re-pin**,
|
||||||
|
> never a silent drift. Import everything from **`web/js/citygen/index.js`** (the barrel) — internals
|
||||||
|
> (`plan.js`, `plan_osm.js`, `osm_fixture.js`, `names.js`) may move behind it.
|
||||||
|
|
||||||
|
**Producer API** (all from `web/js/citygen/index.js`):
|
||||||
|
- `generatePlanFor(seed, source = 'synthetic', { town } = {}) → CityPlan` — the selector the shell uses.
|
||||||
|
- `generatePlan(seed) → CityPlan` — synthetic producer (unchanged, byte-identical).
|
||||||
|
- `generatePlanOSM(seed, town = 'melbourne', { report } = {}) → CityPlan` — osm producer; pass a
|
||||||
|
`report` object to receive the normalization log (below). `osmTownKeys() → string[]`.
|
||||||
|
- `chunkIndex(plan, chunkSize = 64) → { chunkSize, chunks }`; `CHUNK = 64`; `chunkKey(cx, cz)`.
|
||||||
|
- Geometry helpers (shared with the harness): `lotCorners(lot)`, `obbOverlap(a, b)`.
|
||||||
|
|
||||||
|
**Determinism & goldens.** Same inputs ⇒ byte-identical plan, forever. The determinism gate keys on
|
||||||
|
**(seed, plansrc, town)**. Committed goldens (`xmur3(JSON.stringify(plan))` at seed `20261990`):
|
||||||
|
|
||||||
|
| source / town | golden |
|
||||||
|
|--------------------|--------------|
|
||||||
|
| synthetic | `0x3fa36874` |
|
||||||
|
| osm / melbourne | `0x34cfdec0` |
|
||||||
|
| osm / katoomba | `0x0f652510` |
|
||||||
|
|
||||||
|
*Adding a town* is mechanical (recipe in `docs/LANES/LANE_A_NOTES.md`): append to `OSM_TOWNS`, run
|
||||||
|
selfcheck, paste the printed fingerprint into `OSM_GOLDENS`, tell Lane F the new hash.
|
||||||
|
|
||||||
|
**Trading-hours law.** `shop.hours = [open, close]`, 24h integers, `0 ≤ open < close ≤ 23`. Openness is
|
||||||
|
**half-open**: `isOpen(shop, h) ⟺ open ≤ h < close` (a shop closing at 17 is shut at 17:00 exactly).
|
||||||
|
Both the shell (`index.html`) and the NPC sim (`citizens/sim.js _openAt`) use this identical predicate.
|
||||||
|
|
||||||
|
**openLate law.** **Exactly one** shop per town is the late-night landmark. It is the sole shop that
|
||||||
|
closes `≥ 22:00` (`LATE_HOUR`), so the field and the threshold are equivalent: `{s: s.openLate}` ≡
|
||||||
|
`{s: s.hours[1] ≥ 22}` (a singleton). It is the **video rental** (a town with no video falls back to
|
||||||
|
another non-stall type, logged). Every other shop closes by 21:00 — so after ~21:00 the streets thin
|
||||||
|
to that one lit destination.
|
||||||
|
|
||||||
|
**Storeys law.** `shop.storeys ∈ [registryMin, permittedMax]`, `permittedMax = registryMax ≥ 2 ?
|
||||||
|
min(registryMax + 1, 3) : registryMax`. The occasional 3-storey corner anchor adds ≤1 storey (tall
|
||||||
|
types only); single-storey types (`video`/`milkbar`/`stall`, `[1,1]`) are **never** boosted.
|
||||||
|
|
||||||
|
**OSM normalization rules** (the importer bends the *data* to the contract, never the contract; each
|
||||||
|
change is logged into `report`): unknown OSM `shop=` kind → `opshop` (`report.typesRemapped`); any shop
|
||||||
|
closing `≥ 22:00` → clamped to 21:00 so only the one openLate landmark is late (`report.hoursClamped`);
|
||||||
|
the openLate landmark prefers a video, else a fallback non-stall type (`report.openLate`). Shop `name`s
|
||||||
|
are the real OSM names; `sign` is derived. `osm` plans carry `source:'osm'`, a real bbox `size`, and no
|
||||||
|
synthetic dept/market/milkbar/stall/arcade.
|
||||||
|
|
||||||
|
**Closing-time occupant ruling (Lane A, round 9 — ratifies Lane D's `sim.js`).** When a shop closes:
|
||||||
|
1. **No new entry.** Patronage only ever routes a ped into an *open* shop (`_nearestOpenShop` gates on
|
||||||
|
`_openAt`); the player likewise gets a CLOSED plate/toast and cannot enter (`index.html`).
|
||||||
|
2. **Occupants drain, they don't pop.** A ped already inside when the shop closes finishes its current
|
||||||
|
bounded visit (seeded 5–20s dwell) and re-emerges through the door normally — it is **never**
|
||||||
|
teleported out or culled at the close instant. Occupancy therefore drains to 0 within one dwell
|
||||||
|
window; `sim.occupancyOf(shopId)` falls to 0 on its own. Any browser-rig presence (Lane D/F) tracks
|
||||||
|
`occupancy`, so it clears with the same drain — no orphaned rigs in a shut shop.
|
||||||
|
3. **The player is never force-ejected.** Closing locks the door to *entry* only; a player already inside
|
||||||
|
stays until they choose to leave (leaving restores the street where they stood, as always).
|
||||||
|
4. **The openLate video shop** is the one place still filling after ~21:00 (the night crowd); at its own
|
||||||
|
close (22:00/23:00) it drains identically. Determinism holds: entry/dwell/emerge are seeded + clock
|
||||||
|
driven, so same (seed, time) ⇒ same occupancy.
|
||||||
|
|
||||||
### Layer 2 — Streetscape (Lane B)
|
### Layer 2 — Streetscape (Lane B)
|
||||||
|
|
||||||
Realizes CityPlan chunks around the player. Hard requirements the two parent games skipped:
|
Realizes CityPlan chunks around the player. Hard requirements the two parent games skipped:
|
||||||
@ -138,6 +221,412 @@ Interiors are **not** street geometry. Walking into a doorway triggers
|
|||||||
exactly where you stood. Street windows show a cheap fake (tinted glass + emissive warm glow at
|
exactly where you stood. Street windows show a cheap fake (tinted glass + emissive warm glow at
|
||||||
night in v1; interior-mapping shader is a stretch goal).
|
night in v1; interior-mapping shader is a stretch goal).
|
||||||
|
|
||||||
|
## CityPlan v3 — gig layer (`?gigs=1`) — THE DISTRICT
|
||||||
|
|
||||||
|
> **FROZEN at `v3.0` (round 14).** The authoritative Layer-1 **gig-layer** contract — venues, the week's
|
||||||
|
> schedule, posters, the `gigKey` map, the corridor law, and the runtime surface. `web/js/citygen/selfcheck.js`
|
||||||
|
> enforces every law here, including the round-14 **400-seed district invariant sweep** + an osm
|
||||||
|
> graceful-placement case. Changes are a **CITY_SPEC amendment in the same commit + a gig-golden re-pin**,
|
||||||
|
> never silent drift. **One edit is still pending and lands with Lane F's alias-retirement commit** (R13
|
||||||
|
> debt #1): the R12 single-venue compat alias in the runtime block below retires; everything else here is
|
||||||
|
> final for the `v3.0` tag.
|
||||||
|
>
|
||||||
|
> *(**Recorded amendment — ROUND15, ledger #1:** the frontage poster was flipped to the `+Z` street
|
||||||
|
> facade (it had been on the `−Z` back, invisible from the street); this moved `plan.posters`, so the gig
|
||||||
|
> golden re-pinned `0x1f636349 → 0x4f4a549d` in the same commit under the amendment law above. The
|
||||||
|
> synthetic golden `0x3fa36874` did not move — this is the round's one sanctioned gig-golden move.)*
|
||||||
|
>
|
||||||
|
> *(**Recorded amendment — ROUND16, ledger #6:** on a narrow through-lot a band_room/rsl's `+Z` facade
|
||||||
|
> can abut the block's *other* street, putting its frontage poster over that carriageway. `pickVenues`
|
||||||
|
> now **prefers a band_room/rsl lot whose facade clears every carriageway by ≥ `POSTER_CLEAR`** (0.5 m;
|
||||||
|
> broadens off-spine if the warehouse fringe is uniformly narrow). This moved `plan.posters`, so the gig
|
||||||
|
> golden re-pinned `0x4f4a549d → 0xb1d48ea1`; synthetic `0x3fa36874` still frozen. The v3.1 default flip
|
||||||
|
> + classic law is F's amendment to this section — see the flags table.)*
|
||||||
|
>
|
||||||
|
> *(**Recorded amendment — ROUND20, ledger #2:** the R16 clearance bias above **widened to every venue
|
||||||
|
> kind**. R16 exempted `pub` because on the synthetic/marched towns a pub's `+Z` is always open; on a real
|
||||||
|
> road graph at E's widened density it is not (newtown: a pub whose facade overhung a cross street). On the
|
||||||
|
> synthetic town every pub/rsl candidate already clears ⇒ no-op there, gig golden `0xb1d48ea1` frozen.)*
|
||||||
|
>
|
||||||
|
> *(**Recorded amendment — ROUND21 ledger #1 + ROUND22 ledger #2: the venue cluster bias.** D's relocation
|
||||||
|
> finding: a venue a kilometre from the retail cluster is a gig with no passing crowd (katoomba's pub had
|
||||||
|
> **1** shop within 160 m; its RSL had 13). `pickVenues` now draws each venue from a **cluster field** —
|
||||||
|
> candidates with ≥ `CLUSTER_MIN` (3) other shops within `CLUSTER_R` (160 m) — and lets the kind flavours
|
||||||
|
> bias *within* that field rather than override it. Venues under the floor: **11 of 15 → 0** across the
|
||||||
|
> five R21 towns; Newtown's RSL moved 2,228 m → 29 m from the hub. **Real towns only, by construction:**
|
||||||
|
> the bias reads `plan.source === 'osm'`, which the synthetic generator never sets, so it cannot move
|
||||||
|
> `0x3fa36874`/`0xb1d48ea1` — measured, not assumed (4 synthetic candidates per seed sit under the floor,
|
||||||
|
> so a blanket filter *would* have moved them). **ROUND22 graded fallback:** at pack scale three towns have
|
||||||
|
> no candidate at the floor at all (`ballarat`/`toowoomba`/`newtown_godverse` top out at 2 within 160 m).
|
||||||
|
> R21 dropped the bias entirely there, stranding venues at 0 neighbours; it now falls back to the **densest
|
||||||
|
> available** candidates instead. Fires only where the floor is unreachable ⇒ no already-pinned town moved.
|
||||||
|
> **Acceptance at pack scale: 20 of 23 towns have every venue ≥ `CLUSTER_MIN`; those three sit at their
|
||||||
|
> town's genuine ceiling** — the carved-out case, stated rather than papered over.)*
|
||||||
|
|
||||||
|
> **Prime flag law.** The gig layer is a **post-hoc augmentation** applied by the selector when the gig
|
||||||
|
> flag is on. With it off, `generatePlanFor(seed, src)` returns the byte-identical v2 base plan — **all v2
|
||||||
|
> goldens stay frozen**. So this section adds fields that exist *only on gig-on plans*; it does not touch
|
||||||
|
> the v2 contract above. Deterministic from `(citySeed, customBands)`; works on synthetic AND osm towns.
|
||||||
|
> Node-pure (no THREE, no runtime fetch — the shell loads the band drop-in and passes it in).
|
||||||
|
>
|
||||||
|
> **Prime flag law — REWRITTEN at `v3.1` (ROUND16, THE FLIP; Lane F amendment).** The covenant MOVED, it
|
||||||
|
> did not disappear. `gigs`, `weather`, `winmap`, `tram` now default **ON**; the default boot is the full
|
||||||
|
> experience, deterministic to the **gig golden `0xb1d48ea1`**. Each flag keeps a `=0` opt-out, and
|
||||||
|
> **`?classic=1`** forces all four off at once:
|
||||||
|
> - **Classic law (the frozen regression target):** the `?classic=1` boot is byte-identical to the v2
|
||||||
|
> baseline — synthetic fingerprint **`0x5f76e76`**, zero gig layer, **zero fetch delta** (no
|
||||||
|
> `custom_bands.json`, no `sit.glb`). The old "flags-off regression" gate is now the **classic-regression** gate.
|
||||||
|
>
|
||||||
|
> > **THE COVENANT AMENDMENT — ROUND27, THE FACADE FIX (John's ruling; the only amendment in five epochs).**
|
||||||
|
> > The covenant froze `0x3fa36874` "forever" from R16 to R27. That hash anchored **a wrong world**: every
|
||||||
|
> > shopfront in every town — synthetic and real — faced *away* from its street, because CITY_SPEC:71 wrote
|
||||||
|
> > the SIM convention into the LOTS contract (see *Units, axes*). The covenant did exactly its job for nine
|
||||||
|
> > rounds: it held the base town byte-identical. It just held it identical to something broken, and no gate
|
||||||
|
> > could see it — a self-consistent check over a self-contradicting spec passes under either convention.
|
||||||
|
> >
|
||||||
|
> > **The covenant's purpose is anti-drift, not the preservation of a defect.** So on John's ruling it is
|
||||||
|
> > **amended once, deliberately, in the open**: the anchor moves `0x3fa36874 → 0x5f76e76`, and every other
|
||||||
|
> > golden moves with it (gig `0xb1d48ea1 → 0xec7a2d39`, the three marched fixtures, all 23 real towns base
|
||||||
|
> > + gig — **51 pins in one commit**, per the amendment law). The classic town is now what it always claimed
|
||||||
|
> > to be: a street you can stand on and read the shopfronts. **From this hash forward the law reads as it
|
||||||
|
> > always did — byte-identical forever.** An amendment requires a John-level ruling and a demonstrated
|
||||||
|
> > world-level defect, not a convenience; this is the precedent, not an opening.
|
||||||
|
> >
|
||||||
|
> > *Exposed by the same fix (and fixed with it): `buildRealRoads` seated blocks at `KERB = 4 m` from the
|
||||||
|
> > centreline while a `main` carriageway is 10 m wide — its kerb is at 5 m. Every main-street building has
|
||||||
|
> > stood 1 m inside the road since R18. Invisible while nothing was drawn on that face; the moment the
|
||||||
|
> > shopfront turned around, its poster landed in the traffic lane. Blocks now seat behind the kerb per the
|
||||||
|
> > corridor law (`roadWidth/2 + FOOTPATH`). Pack drops 6.4% → 6.6%, counted.*
|
||||||
|
> - **Default law:** the no-flag boot pins to the gig golden, and every gig / district / continuity / cover
|
||||||
|
> smoke **and the budget law** (interior ≤350 draws · street ≤300 / 200k tris, at the busiest venue block)
|
||||||
|
> now run against the **default boot with all four flags live**. `?noassets=1` composes with it and stays
|
||||||
|
> silent-and-fine.
|
||||||
|
> - The four flags read through one rule in the shell — `flagOn(name) = !classic && param !== '0'` (winmap
|
||||||
|
> reads the same rule in `buildings.js`); the shell publishes the intent on `window.PROCITY.flags`, and the
|
||||||
|
> seated drummer's `sit.glb` rides `!classic` so classic keeps its zero-fetch-delta.
|
||||||
|
>
|
||||||
|
> *(v3.0-alpha was one pub, one genre, one night's posters. **ROUND13 → v3.0-beta, the district:** 2–4
|
||||||
|
> venues of three kinds, a full week's schedule across town, town-wide posters. Same flag, same laws.)*
|
||||||
|
|
||||||
|
**Turning it on** (Lane F): `generatePlanFor(seed, source, { gigs:true, customBands }) → plan` with the
|
||||||
|
gig fields below (else the base plan, untouched). `generatePlanFor` stays synchronous — F fetches
|
||||||
|
`custom_bands.json` in the bootstrap and passes the array. `withGigs(plan, seed, opts)` is also exported.
|
||||||
|
|
||||||
|
**The venues (2–4 per town — a seeded count).** Three registry venue kinds — **`pub`**, **`band_room`**,
|
||||||
|
**`rsl`** — each with **no district weights** (never auto-placed). The layer converts that many plain
|
||||||
|
`shop`s **in place** (keeps each lot id + geometry ⇒ enterable, no overlap), tags them
|
||||||
|
`shop.venue === true`, `shop.venueKind`, `shop.genreKey`, and **never** takes the openLate landmark (the
|
||||||
|
town keeps exactly one openLate shop). Kinds are added in the order `pub, band_room, rsl, pub` (a 4-venue
|
||||||
|
town gets a second pub at the far end of the spine). Placement rules (each degrades to "any remaining
|
||||||
|
plain shop" so osm towns still get venues):
|
||||||
|
|
||||||
|
| kind | `genreKey` → gigKey | placement | hours | reads as |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| `pub` | `pubrock` → `gig-pubrock` | spine **end**/corner (main edge, farthest) | `[17,23]` | ≥2-storey corner hotel |
|
||||||
|
| `band_room` | `grunge` → `gig-grunge` | warehouse-district fringe | `[18,23]` | single-storey tin shed |
|
||||||
|
| `rsl` | `covers` → `gig-covers` | **off**-spine (not main, not warehouse) | `[16,23]` | carpet-and-bistro club |
|
||||||
|
|
||||||
|
**The cluster field comes first (ROUND21/22 — real towns only).** On an osm plan every kind rule in the
|
||||||
|
table runs **inside** the retail cluster. Order per venue: cluster field (≥`CLUSTER_MIN` other shops within
|
||||||
|
`CLUSTER_R`, or the densest available if none reach the floor) → the kind flavour → the facade-clearance
|
||||||
|
filter → `farthestFirst` shortlist → seeded pick, each step degrading to the previous set rather than to
|
||||||
|
the whole town. A fringe band_room *in the cluster* beats a fringe band_room a kilometre from anyone.
|
||||||
|
Synthetic plans skip the field entirely (`plan.source` is unset) ⇒ goldens frozen. See the amendment note.
|
||||||
|
|
||||||
|
Venue hours all close `23` (open through DUSK+NIGHT so the gig runs and D's surge fills the room; drains
|
||||||
|
at close per the closing-time ruling above). Venues are the *only* gig-plan shops allowed to close ≥22
|
||||||
|
besides the openLate video (selfcheck exempts them). `genreKey` is set from the registry kind→genre map
|
||||||
|
(`SHOP_TYPES[kind].genre`, mirrored by `genreForVenueKind(kind)`); John may re-flavour the two new genres
|
||||||
|
(names only — E synthesises to order).
|
||||||
|
|
||||||
|
**The gigKey contract (ROUND13 debt #1 — one key, zero mapping).** The audio-manifest bed key for a
|
||||||
|
genre **IS** the gigKey, and it is **always** `gig-<genreKey>`. There is no mapping table anywhere: A
|
||||||
|
emits `shop.genreKey` + `gig.genreKey`, C sets `audio.gigKey = 'gig-'+genreKey`, E names the manifest
|
||||||
|
bed identically, B's spill reads it and F resolves it — all through the single exported helper
|
||||||
|
**`gigKeyFor(genreKey) => 'gig-'+genreKey`** (`web/js/citygen/gigs.js`, re-exported from the barrel).
|
||||||
|
Import it rather than building the string; R12 lost a bed to a private rename (`gig-pubrock` vs
|
||||||
|
`pubrock-live`) and, because audio fails soft, the band just mimed.
|
||||||
|
|
||||||
|
**`plan.gigs`** — the week's schedule across the whole district (present only on gig-on plans):
|
||||||
|
```
|
||||||
|
gigs: [ { gigId, venueShopId, bandName, genreKey, night, startSeg, endSeg, cover } ]
|
||||||
|
```
|
||||||
|
- **7 nights × every venue, but not every venue plays every night**: each venue takes a seeded **4–6**
|
||||||
|
of the 7 and goes dark the rest (dark nights read true). `gigId` unique town-wide; `night` = 0-based,
|
||||||
|
**F selects tonight's** (night `0`) per venue. `venueShopId` → the venue shop id; `genreKey` ≡ that
|
||||||
|
venue's genre.
|
||||||
|
- `startSeg = 5` (NIGHT / 22:00), `endSeg = 5`. F's state machine (now **per venue**): `quiet → doors
|
||||||
|
(startSeg−1 = DUSK) → on → done`. (Day-segment→hour map is `lighting.js`: `0 DAWN … 5 NIGHT`.)
|
||||||
|
**Runtime surface** F publishes on `window.PROCITY.gigs` (B's `venue.js`/`audio.js` and D's `setGig` read
|
||||||
|
it): `.byVenue → { [venueShopId]: 'quiet'|'doors'|'on'|'done' }` plus per-venue getters
|
||||||
|
`stateOf/onOf/openOf/gigOf/coverOf/bandNameOf/nightOf/paidOf(id)` + `markPaidOf(id)` and `.venueShopIds`.
|
||||||
|
Every read is keyed by `venueShopId` — there is no scalar alias (the R12 single-venue alias retired in R15).
|
||||||
|
- `cover` — integer in `{0} ∪ [2,10]`, **~half free town-wide, skewed by kind**: pub 50% free / `$2–10`,
|
||||||
|
RSL 45% free / `$2–5` (cheap — the bistro pays the bills), band_room 70% free / `$2–6` (a hat on a
|
||||||
|
milk crate). F debits the wallet **per venue** at each door; a night at two venues is two covers.
|
||||||
|
- **No band plays two venues on one night** (≤1 slot per name per night across town): custom names are
|
||||||
|
deduped and used at most once; a generated name that would clash tonight advances its stream until it
|
||||||
|
doesn't (deterministic — the generator index only moves forward).
|
||||||
|
|
||||||
|
**`plan.posters`** — town-wide gig advertising (Lane B renders): `posters: [ { id, gigId, x, z, ry } ]`.
|
||||||
|
Only **tonight's** (night 0) playing venues are advertised. Each such venue gets one poster on **its own
|
||||||
|
frontage** — seated on the **+Z street facade** (B's canon: `buildings.js` facade + `venue.js` door/queue/
|
||||||
|
camera are all `+Z`; position `lot + (sin ry, cos ry)·(d/2+ε)`), turned to read *from* the street
|
||||||
|
(`ry = lot.ry + π` so its printed face points `+Z` at the queue-side camera — **ROUND15 ledger #1**; before
|
||||||
|
R15 it was on the `−Z` back and invisible from the street). So the poster never sits over a back-street's
|
||||||
|
bitumen, **`pickVenues` prefers a lot whose facade clears every carriageway by ≥ `POSTER_CLEAR`**
|
||||||
|
(ROUND16 ledger #6, **widened from band_room/rsl to every venue kind in ROUND20** — exported from the
|
||||||
|
barrel; the selfcheck asserts the same floor). Plus a seeded run along
|
||||||
|
the spine — 2 per main edge (or, when a town has no main spine, every edge), at `t ∈ [0.2,0.8]` of the block, on **either verge**;
|
||||||
|
a candidate that would overhang a **real** crossing carriageway is skipped (ROUND19, E's finding), and the
|
||||||
|
run is **capped at `POSTER_PER_SHOP × shops`** (ROUND20) — a sparse real town has few shops but a big edge
|
||||||
|
graph, so the raw "2 per edge" run reads busy (katoomba 438 → 31). The cap never binds on the shop-dense
|
||||||
|
synthetic town, so its gig golden is frozen. Different venues' posters share the same pole
|
||||||
|
runs — that IS the district reading. `ry` follows the house convention (a poster's printed face =
|
||||||
|
`(−sin ry, −cos ry)`, so `atan2` of the *away-from-viewer* normal); B places E's poster skin at `(x, z)`
|
||||||
|
rotated `ry`; F wires "poster → that venue's tonight-gig".
|
||||||
|
|
||||||
|
**The street-corridor law (ROUND13 debt #5 — published as functions, not plan data).** `edge.width` is
|
||||||
|
the **full corridor** (carriageway + verge/footpath, centreline ± width/2), never the carriageway alone —
|
||||||
|
so a naive `node.x + 2` seats furniture mid-bitumen. `web/js/core/registry.js` now exports the split as
|
||||||
|
pure functions over an edge (no plan fields change ⇒ base goldens stay frozen), re-exported from the
|
||||||
|
barrel: **`roadWidth(edge)`** (full carriageway width — main 10 m, side 6–8, lane = all, arcade 0; the
|
||||||
|
kerb sits at `roadWidth/2` from the centreline),
|
||||||
|
**`vergeBand(edge) → [inner,outer]`** (where footpath furniture lives), **`poleOffset(edge)`** (where a
|
||||||
|
pole/poster seats — just behind the kerb). Poles, verandah posts and **spine** posters belong in the verge
|
||||||
|
band; the selfcheck asserts no **spine** poster stands within `roadWidth/2` of any edge centreline. A
|
||||||
|
**frontage** poster is exempt — it is bound to its venue building (the +Z facade B draws), so at a corner
|
||||||
|
venue whose facade abuts a crossing street it may legitimately sit near that street's kerb (B's building
|
||||||
|
geometry, not a mid-road defect).
|
||||||
|
|
||||||
|
**Band names + the drop-in.** `bandName(seed)` generates 90s-Aussie pub-rock names from wordlists.
|
||||||
|
`web/assets/custom_bands.json` is an **OPTIONAL** editable drop-in (schema `{ "bands": ["The Feral
|
||||||
|
Galahs", …] }`, Lane-A-owned per ROUND12): present → those names get **priority**, deduped and shuffled
|
||||||
|
by seed, spread **night-major across the whole week's slots town-wide** (not one venue's residency);
|
||||||
|
**absent → pure generator, zero errors, zero fetch** (the `?noassets=1` path). Deterministic given
|
||||||
|
`(seed, file contents)`. John edits the file and reloads.
|
||||||
|
|
||||||
|
**Closing-time law as code (v2 debt paid).** `isOpen(shopOrHours, hour)` is exported from the barrel — the
|
||||||
|
canonical half-open predicate (`open ≤ hour < close`) for D's gig-night patronage surge and everyone else.
|
||||||
|
|
||||||
|
**Gig golden** (guards gig-layer drift; pure-generator path, no custom file), seed `20261990` synthetic:
|
||||||
|
**`0xb1d48ea1`** (**ROUND16 re-pin** — the band_room/rsl facade-clearance bias, ledger #6, moved
|
||||||
|
`plan.posters`; was `0x4f4a549d` the R15 +Z flip, `0x1f636349` the R13 district, `0xa6ae5a5e` the R12
|
||||||
|
alpha). Asserted by `selfcheck.js` alongside the frozen v2/classic golden; changing gig output re-pins it.
|
||||||
|
The gig sweep now also runs a **no-spine osm fixture** (`silverton`, single-row) proving `pickVenues`
|
||||||
|
places venues with no main spine (ROUND16 ledger #7).
|
||||||
|
|
||||||
|
## Town caches — the real-map scout (v3.2, ROUND17 ledger #6)
|
||||||
|
|
||||||
|
> Bounded groundwork for **v4 THE REAL MAP** (John's pick). The `?plansrc=osm` producer already eats
|
||||||
|
> processed OSM data (three fixtures in `osm_fixture.js`); v3.2 lets it eat **real town caches** E scrapes
|
||||||
|
> from Overpass — **no new plan features**, the existing contract proven against real geography.
|
||||||
|
|
||||||
|
**The town-cache contract** (Lane-A-owned; authoritative implementation is `validateTownCache()` in
|
||||||
|
`web/js/citygen/plan_osm.js`, home `web/assets/towns/<key>.json`, full spec in that dir's `README.md`):
|
||||||
|
a processed town is `{ schema:"procity-town-cache/1", key, town, source:"osm", license, attribution,
|
||||||
|
center:{lat,lon}, shops:[ { id, name, type, lat, lon, suburb? } ] }` (+ optional `bbox`/`counts`/`fetchedAt`).
|
||||||
|
E's `build_towns.py` produces it (raw Overpass → dedupe/suburb/parody-name/seeded-hours); `plan_osm` marches
|
||||||
|
it identically to the fixtures. **Hard requirements:** finite `center.{lat,lon}`; `shops` ≥ `MIN_TOWN_SHOPS`
|
||||||
|
(6 = up to 4 venues + the openLate landmark + a spare); finite shop `lat`/`lon`. **Absorbed (warnings):**
|
||||||
|
blank name → type label, unknown `type` → `opshop`, dup ids, and **span > 5 km → "likely more than one
|
||||||
|
town"** (a cache is ONE compact town — an over-broad bbox marches into an unusable multi-km strip).
|
||||||
|
|
||||||
|
**The spacing floor (v5.0-alpha, ROUND23 ledger #5 — D's R22 finding as law).** `MIN_TOWN_SHOPS` counts
|
||||||
|
shops; what makes a town **read alive** is *clustering*. D measured both 12-shop towns in the pack and they
|
||||||
|
disagree — which is the finding: **darwin ALIVE** (one strip; 1,455 patronage checks → 18 visits) vs
|
||||||
|
**toowoomba DEAD** (scattered over a 4.6 km road network; 1,417 checks → **0 visits, 0 finds** — "not a
|
||||||
|
shopping town, a road network with occasional shops"). Both pass every other law. So `validateTownCache`
|
||||||
|
now **warns when `medianShopSpacing(cache) > MAX_MEDIAN_SPACING_M` (150 m)**:
|
||||||
|
- **The metric** (exported from the barrel — import it, don't re-derive it): for each shop, the distance to
|
||||||
|
its **nearest other shop**; take the **median**. Equirectangular metres about `center.lat`, the same
|
||||||
|
convention as the span check. `null` for < 2 usable shops.
|
||||||
|
- **Warn, never error.** A flagged cache still boots a valid district — toowoomba shipped green through the
|
||||||
|
entire v4.0 pack. **E curates** (re-bbox onto the retail strip, or retire the town, counted either way);
|
||||||
|
the validator only flags. The span warn catches a town too **wide**; this catches one too **sparse**, a
|
||||||
|
case span cannot see (toowoomba is *inside* the 5 km law).
|
||||||
|
- **The threshold is measured, not chosen.** Across the 23-town pack the line is **forced** into
|
||||||
|
`(119.3, 254.9]`: braddon at 119.3 m is alive by D's own hub test (7 shops within 160 m — the same as
|
||||||
|
darwin's 7), and ballarat at 254.9 m is not. 150 m sits in that gap — the pack's widest (**×2.14**) —
|
||||||
|
with 1.26× margin below and 1.70× above. It flags **2 of 23**: ballarat 255 m, toowoomba 396 m.
|
||||||
|
- **⚠ CACHE space, not PLAN space — they do not agree.** This runs on raw lat/lon, before any lift; D's
|
||||||
|
notes quote **plan** figures (post-lift metres). The lift marches shops at a regularised cadence, so it
|
||||||
|
**compresses** dense towns (darwin **68.3 m cache → 27.2 m plan**) and leaves sparse ones alone
|
||||||
|
(toowoomba 396.1 → 386.8). Both numbers are right; they measure different spaces. Cache space is correct
|
||||||
|
*here* — the validator's job is to flag a bad bbox **at ingest**, before a lift exists — and it is
|
||||||
|
faithful exactly where the warn lives (ballarat plan/cache **1.01**, toowoomba **0.98**). Known limit:
|
||||||
|
one town flips (`newtown_godverse`, cache 94.4 → plan 158.3); no cache-space line can catch it without
|
||||||
|
false-positiving braddon. See `LANE_A_NOTES` §R23.
|
||||||
|
|
||||||
|
**Wiring:** `generatePlanOSM(seed, key, { cache })` marches a cache; `registerTownCache(key, cache)` adds it
|
||||||
|
to the registry (`osmTownKeys()` + `generatePlanFor(seed,'osm',{town:key})` then see it). `selfcheck.js`
|
||||||
|
auto-loads `web/assets/towns/*.json` — validates, runs the structural + gig suites, pins a per-town golden.
|
||||||
|
**Provenance:** OSM is **ODbL**; attribution ships with the data (E owns `SOURCES.md` + the `license`/
|
||||||
|
`attribution` fields; raw Overpass responses cached so re-runs never re-fetch).
|
||||||
|
|
||||||
|
### `godverseShopId` — the stock identity (v5.0-alpha, ROUND24 ledger #2; A + G)
|
||||||
|
|
||||||
|
The optional shop key that resolves **`stock_godverse/<godverseShopId>/`**. **Schema stays v2** — optional
|
||||||
|
field, absent everywhere except godverse caches, so every shipped cache stays valid *by construction*.
|
||||||
|
|
||||||
|
**It is NOT `shop.id` and NOT "the census id"** — the round doc's "the POS shop id IS the id" holds for
|
||||||
|
census shops but breaks on the shop the alpha is about. Two disjoint id spaces ride one opaque field:
|
||||||
|
census shops use thriftgod `shop.id` (max **2992**), while **Monster Robot Party is not in thriftgod's
|
||||||
|
census at all** and uses its **dealgod store id 3962749**. So: never derive it from `id`, never assert
|
||||||
|
equality, never build a mapping table (the R12 gigKey law — one key, zero mapping). **G emits, all read.**
|
||||||
|
|
||||||
|
**A godverse cache is MIXED, by design — RULING 2 (confirmed as law, ROUND25).** It carries a **keyed
|
||||||
|
GODVERSE layer** (census shops + injected shops like Monster Robot) **plus an inherited OSM texture layer
|
||||||
|
that is legitimately unkeyed**. G measured that the texture layer is load-bearing, not cosmetic:
|
||||||
|
census-only Red Hill shipped a 306 m spacing warn and a poster inside a kerb; with the texture layer it
|
||||||
|
passes. **So "shops without an id" is the design, and no gate may assert it away** — mine did, in R24, and
|
||||||
|
failed 54/72 on a healthy town.
|
||||||
|
|
||||||
|
**`validateTownCache` arms** (split by hazard, not tidiness): a **malformed** id (not a positive integer)
|
||||||
|
is an **error** — it names a path segment, and `"3962749"` vs `3962749` is exactly the coercion drift that
|
||||||
|
broke the atlas contract; a **duplicate** id is an **error** — two shops sharing one atlas *is* mis-stocking
|
||||||
|
(charter risk #3: "never mis-stocked"); a godverse cache with an **entirely empty GODVERSE layer** (no id on
|
||||||
|
any shop) is a **warn** — it's an osm cache wearing the wrong `source`, and it stays a warn because the
|
||||||
|
charter's determinism boundary says *world = seeded and gated, stock = data tiers*: a town must never fail
|
||||||
|
to load because its **stock** identity is absent. Tier 0 is the designed ladder, not a defect.
|
||||||
|
|
||||||
|
**The `selfcheck` arm runs per-layer** — every assert is over the **keyed subset only**: the layer reaches
|
||||||
|
the plan, its ids are **unique over defined ids only** (the R24 version compared a Set of *all* ids, which
|
||||||
|
collapses every `undefined` into one entry — it failed structurally on a mixed cache *and* could hide a real
|
||||||
|
duplicate behind that collapse), and the lift invents no identity. Plus the **orphaned-atlas gate**: a
|
||||||
|
committed atlas under `stock_godverse/<id>/` whose shop the lift dropped means real stock keyed to a shop
|
||||||
|
that isn't in the town — it fails, and SKIPs by name when a town has no atlas. Dropped keyed shops are
|
||||||
|
printed, never silent (the R19/R20 drop-and-count ruling).
|
||||||
|
|
||||||
|
**It survives the lift:** `plan_osm` copies it onto `plan.shops[]` (the runtime reads the plan, never the
|
||||||
|
cache — otherwise the field is decoration and F's per-shop `base` can never resolve). Absent ⇒ the key is
|
||||||
|
**not written**, so `JSON.stringify` is byte-identical and **every pinned golden holds until G re-emits**;
|
||||||
|
that re-emit is the sanctioned golden move.
|
||||||
|
|
||||||
|
### Schema v2 — real roads (v4.0-alpha, ROUND18 REAL ROADS)
|
||||||
|
|
||||||
|
> The gap the scout left: the marched fallback lays real shops onto **synthetic** parallel avenues.
|
||||||
|
> Schema v2 closes it — a cache carries the town's **real OSM street geometry** and `plan_osm` builds the
|
||||||
|
> CityPlan street graph FROM it. **Opt-in by the cache** (a v2 cache with `roads[]` = real roads; a v1
|
||||||
|
> cache, or `roads` absent, = the marched fallback), so default + classic boots are untouched by
|
||||||
|
> construction (they never load a town cache) and every shipped cache stays valid.
|
||||||
|
|
||||||
|
A **v2** cache adds `roads: [ { kind, pts:[[lat,lon],…], id?, name? } ]` — simplified OSM ways, `kind` the
|
||||||
|
OSM `highway=*` class (`validateTownCache` accepts schema `…/1` and `…/2`; a road needs ≥2 finite points,
|
||||||
|
`ROAD_KIND` maps the class to a CityPlan edge kind). The graph lift (`plan_osm.buildRealRoads`):
|
||||||
|
1. **project** ways + shops to the local metre frame; **simplify** each way (Douglas–Peucker, ε≈6 m — the
|
||||||
|
fidelity knob, charter risk #4);
|
||||||
|
2. **snap** coincident points (≤3 m) to shared nodes = **real intersections**; straight-segment **edges**;
|
||||||
|
3. **classify** `main`/`side` from the OSM class, promoting the shop-densest edge to the `main` spine if
|
||||||
|
none came from OSM;
|
||||||
|
4. **seat** each shop on its **nearest real edge** at its real order + real side, marched with regularised
|
||||||
|
spacing (real shops overlap — pixel-exact positions can't satisfy the no-overlap invariant), a
|
||||||
|
frontage-strip block per edge, then a deterministic **overlap-resolve** pass (crowded intersections drop
|
||||||
|
the later-id lot). **Same CityPlan schema out** — B/C/D/F consume it unchanged by contract.
|
||||||
|
|
||||||
|
**The fragmentation ruling (ROUND19, Fable — A owns the details).** Real geometry fragments the naïve graph
|
||||||
|
(katoomba: **105 components**, 58% of street-metres on islands, D's finding). Two mechanisms, both handled:
|
||||||
|
(a) **junction-protected simplification** — a point shared by ≥2 ways is a junction; Douglas–Peucker runs
|
||||||
|
only *between* junctions, never through one (the collinear junction-drop that tee-off side streets hit). That
|
||||||
|
alone reconnects to 94–100% main-net. (b) **cull/bridge** — the town is the **main component + joined
|
||||||
|
near-fragments** (endpoints ≤ `JOIN_TOL` 12 m, or a shop-bearing island ≤ 200 m, get a bridge edge); a **far
|
||||||
|
shopless island is culled** (bbox-clipped noise + dead streaming budget). A **stranded shop is always
|
||||||
|
connected or dropped with a count** (`norm.dropped`). Result on all five towns: **one connected component**.
|
||||||
|
A spine poster that would overhang a real crossing carriageway is **skipped** (E's finding; no-op on the
|
||||||
|
synthetic town, so its gig golden is frozen). Golden re-pins per the amendment law (all five real towns pin
|
||||||
|
on their real-roads output; synthetic + marched fixtures + classic/default untouched — the alpha changed
|
||||||
|
nothing outside the cache-schema path).
|
||||||
|
|
||||||
|
> **FINAL at `v4.0` (ROUND22 ledger #6, the epoch close).** This section — schema v2, the lift, the
|
||||||
|
> connectivity rulings above, the venue cluster bias and poster cap in §v3 — is the authoritative real-map
|
||||||
|
> contract at the tag. Changes are an amendment + a golden re-pin in the same commit, as always.
|
||||||
|
>
|
||||||
|
> **The alpha's five became the epoch's 23** (22 real towns + Lane G's `newtown_godverse`; **1,210 shops**),
|
||||||
|
> and every ruling written above held at that scale with **no new hardening**: **one connected component and
|
||||||
|
> three venues on every one of the 23 towns**, fitzroy's **160 shops** — 2× anything the pipeline had seen —
|
||||||
|
> included. All **36 goldens** (`REAL_TOWN_GOLDENS` + `REAL_TOWN_GIG_GOLDENS`, 23 each) are pinned; selfcheck
|
||||||
|
> runs **161,300** checks green, cross-process deterministic. Read "all five towns" above as the measurement
|
||||||
|
> of record *when the ruling was written* — the ruling itself is now pack-wide.
|
||||||
|
>
|
||||||
|
> **The honest tail, carried not hidden:** 6.4% of shops (77 of 1,210) don't seat, every drop attributed by
|
||||||
|
> cause, dominated by overflow where a town has more shops than its road graph has frontage (daylesford 19%,
|
||||||
|
> the pack's thinnest graph; fitzroy 13%). See `V4_REAL_MAP.md` risk #6. The R20 ruling stands: **drop and
|
||||||
|
> count beats teleport.** The frozen set is unmoved by the entire epoch: synthetic `0x3fa36874`, gig
|
||||||
|
> `0xb1d48ea1`, marched fixtures, classic.
|
||||||
|
|
||||||
|
## The stock ladder (v5 — THE REAL SHOP; frozen at `v5.0`, ROUND27 ledger #6)
|
||||||
|
|
||||||
|
> **v4 made the towns real; v5 makes the shops real.** The map-level contract above is unchanged by this
|
||||||
|
> epoch: **stock is DATA, not world.** The charter's determinism boundary is the load-bearing line —
|
||||||
|
> *world = seeded, frozen, golden-gated; stock = data tiers riding the asset law.* Nothing in this section
|
||||||
|
> may move a plan golden, and nothing in it did: the whole epoch left synthetic `0x3fa36874`, gig
|
||||||
|
> `0xb1d48ea1`, the marched fixtures and classic untouched.
|
||||||
|
|
||||||
|
**The tier ladder.** Each rung is fully playable and degrades **softly** to the one below — the fail-soft
|
||||||
|
pattern proven since `?noassets` on day one, made structural:
|
||||||
|
|
||||||
|
| tier | what | flag | deterministic? |
|
||||||
|
|---|---|---|---|
|
||||||
|
| **0 · parody** | seeded canvas sleeves (v1) | any boot | yes — the QA baseline |
|
||||||
|
| **1 · static real** | per-shop atlases baked from dealgod/POS | `?stock=real`, **no server** | yes — atlases are files |
|
||||||
|
| **2 · live GODVERSE** | thriftgod read-only endpoints; sold-means-gone | server reachable | **no — by design** |
|
||||||
|
|
||||||
|
**The offline law (charter law #1) is the epoch's gate, not a footnote:** the server *enriches, never
|
||||||
|
gates*. No boot path, gate, or tag may depend on a reachable server. Tier 2 is therefore **never inside a
|
||||||
|
golden or a byte-identical assert**; it gets its own smoke class whose server lifecycle the gate itself
|
||||||
|
controls, so the *assertion* is deterministic even though the *stock* isn't.
|
||||||
|
|
||||||
|
**`sourcing` is not `tier`** (C's ruling, R26 — and it's the distinction that keeps the ladder honest). The
|
||||||
|
rung and the provenance are different axes, and both ship in every atlas index:
|
||||||
|
- **`tier: 1`** — the ladder rung. A minted atlas is *still* a static file, so it is *still* rung 1.
|
||||||
|
- **`sourcing: "real" | "mint"`** — where the contents came from. **`real`** = a POS snapshot (Monster Robot
|
||||||
|
Party's actual crates). **`mint`** = a seeded `random.Random(shop_id)` sample of real dealgod listings —
|
||||||
|
*plausible, not that shop's stock*. Only one shop in the world has a POS; minting is how the other keyed
|
||||||
|
shops get distinct crates **without pretending to data we don't hold**. Current pack: **15 keyed atlases =
|
||||||
|
1 real + 14 mint**. No gate, doc, or human may read a mint crate as real — that is the whole point of the
|
||||||
|
field, and gates assert on it.
|
||||||
|
|
||||||
|
**The id-space fences.** Three namespaces meet at the crate, and **every collision this epoch came from
|
||||||
|
comparing across them**. They are fences, not decoration:
|
||||||
|
|
||||||
|
| space | form | scope | fenced from |
|
||||||
|
|---|---|---|---|
|
||||||
|
| **plan `shop.id`** | small int, `0..N` per town | one CityPlan, local | `godverseShopId` — **numerically overlapping** |
|
||||||
|
| **`godverseShopId`** | census id (**≤2992**) ∪ dealgod store id (e.g. **3962749**) | the stock namespace | its two halves are numerically **disjoint by construction** (G) |
|
||||||
|
| **atlas slot id** | **`sku_<POS id>`** / **`mint_<listing id>`** | one atlas index | each other, by **prefix** |
|
||||||
|
|
||||||
|
- **plan id vs `godverseShopId` — never compare a bare number across them.** They are *semantically*
|
||||||
|
disjoint namespaces that *numerically overlap*, which is the trap: in `redhill_godverse` today, bare
|
||||||
|
**`31`** is **"Wholefood Cafe"** as a plan id and **"Silky Oak Furnature"** as a godverse id. That exact
|
||||||
|
ambiguity made F's `enterShop(31)` enter the wrong shop and a soft-fall assertion pass **vacuously on the
|
||||||
|
wrong subject**. Verified still live in the data — so this is a standing rule, not a fixed bug.
|
||||||
|
- **`sku_` vs `mint_` is a sourcing-scoped namespace fence** (C §7.2a as amended, R26 wave 4). A crate
|
||||||
|
wearing the other sourcing's prefix **fails**. The fence is *stronger* than the rule it replaced: the easy
|
||||||
|
green was deleting the prefix check; instead the prefix now proves its sourcing.
|
||||||
|
- **Positional ids are banned** (R25): `rec_0000…` numbering collides across packs by construction, which
|
||||||
|
is what made the integrator's own id-equality gate vacuous. Slot ids are sku-derived and unique across
|
||||||
|
packs, stable across re-emits.
|
||||||
|
|
||||||
|
**The manifest — `web/assets/stock_godverse/index.json`** (G emits, F consumes, E validates, C's §7.2d).
|
||||||
|
It declares **which `godverseShopId`s carry atlases** (`types[]` + `sourcing` per entry; mint included).
|
||||||
|
**Existence is declared, never probed.** This is the root fix for a real collision between two laws:
|
||||||
|
fail-soft-by-404 vs the zero-console-errors law — the runtime used to probe blind and eat 404 noise,
|
||||||
|
tolerated by attribution, which is a workaround, not a design. No manifest entry ⇒ no fetch ⇒ **zero 404s,
|
||||||
|
no exceptions**. Consistency is gated both ways (an atlas on disk absent from the manifest **fails**, and
|
||||||
|
vice versa — vacuous-gate law shape). Today: **15 entries, 15 dirs on disk.**
|
||||||
|
|
||||||
|
**Lane A's seam to all of it** is exactly one field — **`godverseShopId`** (documented above): it rides the
|
||||||
|
town cache, survives the lift onto `plan.shops[]`, and is what the runtime resolves `stock_godverse/<id>/`
|
||||||
|
from. **`'godverseShopId' in shop` is the tier test**: present ⇒ tier-1+ candidate, absent ⇒ tier 0. The
|
||||||
|
key is *absent, not undefined*, on every unkeyed shop — which is why the entire stock epoch moved **no plan
|
||||||
|
golden**. `selfcheck` gates the seam per-layer (Ruling 2), including the **orphaned-atlas** check: a
|
||||||
|
committed atlas whose shop the lift dropped is real stock keyed to a shop that isn't in the town.
|
||||||
|
|
||||||
|
**Ruling 2 (law):** a godverse cache is **deliberately mixed** — a keyed GODVERSE layer + an inherited OSM
|
||||||
|
texture layer that is legitimately unkeyed. G measured it load-bearing: census-only Red Hill was
|
||||||
|
unshippable (306 m spacing warn, a poster inside a kerb). **No gate may assert it away** — mine did, and
|
||||||
|
called two healthy towns broken.
|
||||||
|
|
||||||
## Shop types v1 (registry lives in `web/js/core/registry.js`, Lane A writes it, all lanes read)
|
## Shop types v1 (registry lives in `web/js/core/registry.js`, Lane A writes it, all lanes read)
|
||||||
|
|
||||||
> **`web/js/core/registry.js` is the machine-readable source of truth** — import `SHOP_TYPES` for
|
> **`web/js/core/registry.js` is the machine-readable source of truth** — import `SHOP_TYPES` for
|
||||||
@ -166,6 +655,9 @@ night in v1; interior-mapping shader is a stretch goal).
|
|||||||
| `milkbar` | djsim-milkbar, brickveneer | counter + freezer | counter, fridge, magazine rack, freezer |
|
| `milkbar` | djsim-milkbar, brickveneer | counter + freezer | counter, fridge, magazine rack, freezer |
|
||||||
| `dept` (anchor) | terrazzo, djsim-dept | grand hall | mixed sections, escalator prop, glass case, counter |
|
| `dept` (anchor) | terrazzo, djsim-dept | grand hall | mixed sections, escalator prop, glass case, counter |
|
||||||
| `stall` (market) | market, corrugated | open stall | trestle tables, crates |
|
| `stall` (market) | market, corrugated | open stall | trestle tables, crates |
|
||||||
|
| `pub` (v3 venue) | djsim-pub, federation | pub | bar counter, stage, PA stack, tables, stools |
|
||||||
|
| `band_room` (v3 venue) | warehouse-tin, besser, corrugated | band_room | bar counter, stage, PA stack, stools |
|
||||||
|
| `rsl` (v3 venue) | brickveneer, redbrick | rsl | bar counter, stage, PA stack, bistro tables, stools |
|
||||||
|
|
||||||
## NPC contract (Lane D)
|
## NPC contract (Lane D)
|
||||||
|
|
||||||
@ -202,6 +694,7 @@ night in v1; interior-mapping shader is a stretch goal).
|
|||||||
| `web/js/interiors/*`, `web/interior_test.html` | Lane C |
|
| `web/js/interiors/*`, `web/interior_test.html` | Lane C |
|
||||||
| `web/js/citizens/*`, `web/citizens_test.html`, `web/models/*` | Lane D |
|
| `web/js/citizens/*`, `web/citizens_test.html`, `web/models/*` | Lane D |
|
||||||
| `pipeline/*`, `web/assets/*` + `web/assets/manifest.json` | Lane E |
|
| `pipeline/*`, `web/assets/*` + `web/assets/manifest.json` | Lane E |
|
||||||
|
| `web/assets/custom_bands.json` | Lane A (v3 band drop-in — carve-out from `web/assets/*`, per ROUND12) |
|
||||||
| `web/package.json` | scaffold/shared — `{"type":"module"}` only, added by Lane A |
|
| `web/package.json` | scaffold/shared — `{"type":"module"}` only, added by Lane A |
|
||||||
| `docs/*` | everyone, additively |
|
| `docs/*` | everyone, additively |
|
||||||
|
|
||||||
|
|||||||
328
docs/G3_ECONOMY_DESIGN.md
Normal file
@ -0,0 +1,328 @@
|
|||||||
|
# G3 — THE ECONOMY BACKEND (design doc)
|
||||||
|
|
||||||
|
*Lane G, R23 draft. Drafted per the v5 charter ([V5_REAL_SHOP.md](V5_REAL_SHOP.md)) ledger #2.*
|
||||||
|
|
||||||
|
**§7 (THE WRITE-BACK QUESTION) IS RATIFIED — John, 2026-07-17, R24 kickoff: A THEN B.** v5.0 ships
|
||||||
|
sandboxed (a game purchase never touches real inventory); reservation is chartered as a v5.x
|
||||||
|
upgrade behind a hold budget John sets; full write-back is not chartered. Charter law #4 is
|
||||||
|
satisfied. **The rest of this doc remains DESIGN and still needs both Fables' sign-off; no
|
||||||
|
server code ships before that.**
|
||||||
|
|
||||||
|
Status of each section: **[MEASURED]** = verified against the real databases.
|
||||||
|
**[DESIGN]** = proposed, not built. **[RULED]** = decided by John under charter law.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. What the data actually is [MEASURED]
|
||||||
|
|
||||||
|
The charter assumed one join: thriftgod's census of real shops → dealgod's real items. **That join
|
||||||
|
does not exist.** The three datasets are three different populations:
|
||||||
|
|
||||||
|
| dataset | what it really holds | inventory? |
|
||||||
|
|---|---|---|
|
||||||
|
| `thriftgod.shop` | 2,928 real AU secondhand shops, OSM-anchored (the census) | **no** |
|
||||||
|
| `dealgod.stores` | 9,285 online sellers — **8,754 are Discogs accounts**, not physical shops | 1.77M listings |
|
||||||
|
| `recordgod` | **one real trading record shop's POS** — crates, slots, prices, grading | 28,624 live |
|
||||||
|
|
||||||
|
Measured consequences:
|
||||||
|
|
||||||
|
- **No Newtown record shop has any stock, in any database.** Egg Records, Papa Disquo, Haze
|
||||||
|
Guitars, The Vintage Record, Downtown Music: 0 items each. dealgod has zero Newtown stores and
|
||||||
|
zero census name matches.
|
||||||
|
- `thriftgod.item` (the minted table) holds 2,221 items across **29 shops — all Brisbane/
|
||||||
|
Newcastle/Alice charity shops**. Zero record shops. It is a demo seed, not a stock system.
|
||||||
|
- This is **not** charter risk #3 ("fuzzy matching"). It is a population mismatch. Fuzzy matching
|
||||||
|
cannot fix it, because there is nothing on the other side of the join to match *to*.
|
||||||
|
|
||||||
|
### 1a. The one shop where all three meet [MEASURED]
|
||||||
|
|
||||||
|
`monsterrobot` — **147 Musgrave Road, Red Hill, Brisbane QLD** (John, R23) — is the same real shop
|
||||||
|
in three forms, and that is the whole reason tier 1 can be honest:
|
||||||
|
|
||||||
|
```
|
||||||
|
recordgod (its POS) ──┐
|
||||||
|
monsterrobot.party ├── one real shop: real crates · real slots · real prices · its OWN photos
|
||||||
|
dealgod store 3962749 ─┘
|
||||||
|
```
|
||||||
|
|
||||||
|
- 28,624 live vinyl; **24,914 physically placed in 357 real crates** with real labels
|
||||||
|
("DEEP HOUSE", "$12–$15 TECHNO") and real slot numbers — with gaps where records actually sold.
|
||||||
|
- Covers are the shop's own product shots on the shop's own domain, cached by dealgod
|
||||||
|
(23,392 releases). **99.3%** of its crate stock joins to a cached cover by Discogs `release_id`.
|
||||||
|
- It is **not in thriftgod's OSM census**, so its godverse id is its dealgod store id `3962749`
|
||||||
|
(real, stable, clear of the census space — thriftgod max shop id = 2992).
|
||||||
|
|
||||||
|
**Shipped this round (G2a):** crate 550 "DEEP HOUSE" → 120 real records → `web/assets/
|
||||||
|
stock_godverse/3962749/`. Byte-identical re-bake verified.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Why `mint()` cannot be the source of any tier [MEASURED]
|
||||||
|
|
||||||
|
thriftgod's `mint()` is the obvious candidate for stocking shops. It disqualifies itself three ways:
|
||||||
|
|
||||||
|
1. **It is not deterministic.** `FEED_RECORDS` uses `TABLESAMPLE SYSTEM (0.5)`; `mint()` uses
|
||||||
|
`random.random()`, `random.uniform()`, `random.shuffle()`. **`random.seed` appears nowhere in
|
||||||
|
`server.py`.** Re-running mint gives different stock at different prices. That is fatal to tier
|
||||||
|
1 (charter law #2), and it is *why* tier 1 reads the POS instead: a crate is a table of facts.
|
||||||
|
2. **The shop assignment is fictional.** mint samples arbitrary Discogs sellers' listings and
|
||||||
|
assigns them to a census shop that never owned them.
|
||||||
|
3. **Therefore it breaks two laws downstream.** The licence law's green rests on "the *stores'
|
||||||
|
own* product shots" — a Discogs seller's photo shown as Egg Records' stock isn't that. And the
|
||||||
|
write-back question (§7) is **meaningless** for minted stock: you cannot mark real inventory
|
||||||
|
sold for an item the shop never had.
|
||||||
|
|
||||||
|
mint stays what it is: a fun demo seeder for parody tier 0. It is not an inventory system.
|
||||||
|
*(If a future round wants seeded mint for tier 0 variety, seeding it is a small, safe change —
|
||||||
|
`random.Random(shop_id)` + an ordered `ORDER BY id` sample instead of `TABLESAMPLE`.)*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. The tier ladder, as built [MEASURED for 0–1, DESIGN for 2]
|
||||||
|
|
||||||
|
| tier | source | deterministic | offline |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 0 · parody | seeded canvas / E's parody packs | yes | yes |
|
||||||
|
| 1 · static real | **per-shop atlas baked from the POS snapshot** (G2a, shipped) | **yes — verified** | **yes — files** |
|
||||||
|
| 2 · live | thriftgod server over the POS (this doc) | **no, by design** | **enriches only** |
|
||||||
|
|
||||||
|
**The offline law is structural, not a promise.** `stockpack.js` already returns `null` when an
|
||||||
|
index or atlas is missing, and every caller falls back to parody canvas. Tier 2 rides the same
|
||||||
|
seam: the server is an *enrichment* that upgrades an already-playable tier-1 room. There is no
|
||||||
|
boot path, gate, or tag that may touch the network.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. API surface [AS-BUILT — R27, `pipeline/godverse_server.py`]
|
||||||
|
|
||||||
|
Read-only, per §7's ruling. **The write verbs are not "gated" — they do not exist.**
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /godverse/v1/health -> { ok, tier, pos_db, shops:[godverse_id] }
|
||||||
|
GET /godverse/v1/shop/<godverse_id>/stock?since=<etag>
|
||||||
|
-> { shop_id, tier, crate, items:[{id, slot, artist, title,
|
||||||
|
price, condition, sleeve_cond, state}],
|
||||||
|
gone:[id], counts, etag, served_at } · 304 when ?since= matches
|
||||||
|
GET /godverse/v1/shop/<godverse_id>/crates -> { shop_id, tier, crates:[{id,name,label,count}], etag }
|
||||||
|
GET anything else -> 404 (the ruling is in the body)
|
||||||
|
POST/PUT/PATCH/DELETE anything -> 501 — no write handler exists in the process
|
||||||
|
```
|
||||||
|
|
||||||
|
**Three corrections this doc owes to the code (measured R27, not reasoned):**
|
||||||
|
|
||||||
|
1. **`gone:[sku]` → `gone:[id]`.** As ratified, §4 said bare skus. That wording predates R25's
|
||||||
|
`sku_<POS sku>` ids and R26's **id-namespace fence law**, and a bare number crossing two id
|
||||||
|
spaces is precisely the bug that law exists to stop. `gone[]` now carries the atlas's own `id`
|
||||||
|
verbatim, so the client does `gone.includes(item.id)` with zero transformation. *This doc was
|
||||||
|
the stale rule meeting a case written after it — the epoch's signature failure, and my turn.*
|
||||||
|
2. **`items[]` drops `release_id` and `cover`.** `cover` would edge the API toward serving
|
||||||
|
pixels — it never should (fence #2); `release_id` identifies the *pressing*, and tier 2 speaks
|
||||||
|
only in *copies* (the sku). Both were design-time noise.
|
||||||
|
3. **Write verbs answer 501, not 404.** F asserts the number, so it matters: the class defines
|
||||||
|
`do_GET` and nothing else, so the stdlib refuses any other method before routing exists. **501
|
||||||
|
is the stronger proof** — a 404 means a handler ran and declined; 501 means there is no write
|
||||||
|
handler in the process. Adding `do_POST` to answer 404 would implement the forbidden thing.
|
||||||
|
|
||||||
|
Design rules:
|
||||||
|
|
||||||
|
- **The atlas is never served by the API.** Covers stay static files (tier 1). The server sends
|
||||||
|
*facts* (what's still there, what it costs), never pixels. A tier-2 shop with a dead server is
|
||||||
|
visually identical to tier 1 — that's the fail-soft.
|
||||||
|
- **`gone[]` is the whole point of tier 2.** The client already holds the tier-1 crate; the server
|
||||||
|
says only what changed. A sold record is an `id` in `gone[]` → `collapseBuyItem()` (already
|
||||||
|
built, R9) zero-areas the sleeve. Sold-means-gone is a *diff*, not a re-fetch.
|
||||||
|
|
||||||
|
> ### ⚠ `gone[]` HAS NO SUBJECT IN PRODUCTION TODAY — measured R27, and it shapes F's gate
|
||||||
|
> The mechanism is built and correct. The **data has no movement**: Monster Robot Party's POS
|
||||||
|
> records **5,567 sales ever** (400–800/month, Dec 2025 → Jun 2026) but **26 in July and none
|
||||||
|
> since 2026-07-01**; the POS hasn't been *written* since **2026-07-09** (the 07-16 dump is fresh;
|
||||||
|
> its data is stale). And **0 of crate 550's 120 records have ever sold**. So against the real POS
|
||||||
|
> `gone` is `[]`, permanently, until the shop trades again.
|
||||||
|
>
|
||||||
|
> **Therefore, per the vacuous-gate law: F's sold-means-gone arm must not assert over the live POS
|
||||||
|
> — it would pass without touching its subject.** Two honest routes, and G built the second:
|
||||||
|
> 1. Assert `gone == []` against the real POS and **print the reason** — true, but it proves only
|
||||||
|
> that nothing sold.
|
||||||
|
> 2. **Point the server at a labelled fixture** (`godverse_server.py make-fixture --sold 3` →
|
||||||
|
> `recordgod_fixture`, a `createdb -T` clone with N of the crate's own real skus marked sold).
|
||||||
|
> Verified: `counts {baked:120, live:117, gone:3}`, the three ids absent from `items[]`. This
|
||||||
|
> proves the **mechanism** — computed → sent → consumed → sleeve vanishes — deterministically,
|
||||||
|
> which is what a gate is for. Production still reads the real POS, untouched.
|
||||||
|
>
|
||||||
|
> **A fixture is not a lie; a green assert over an absent subject is.** The epoch has held four
|
||||||
|
> tags on exactly that distinction.
|
||||||
|
- **`etag`/`since`** so a re-enter is a 304, not a payload.
|
||||||
|
- Shop id is the **godverse id** (§1a), never a POS internal id. **R24 naming, as landed:** the
|
||||||
|
town cache carries `shop.godverseShopId` (A's schema field, camelCase, read by the JS runtime);
|
||||||
|
the atlas index carries `shop.godverse_id` (snake_case, what E's `validate_atlas.py` requires).
|
||||||
|
Same id, two artifacts, two house conventions — the API uses `godverse_id` on the wire to match
|
||||||
|
the Python/JSON side it is served from. Recorded rather than silently reconciled: if the
|
||||||
|
integrators want one spelling, it is a one-line change in each emitter, not a design question.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Async enrichment + caching (risk #1) [DESIGN]
|
||||||
|
|
||||||
|
**Law: the dig never blocks on the network.** Not "usually" — never. The mechanism:
|
||||||
|
|
||||||
|
1. Room builds at tier 1 from static files. Playable. Riffling works. **No await.**
|
||||||
|
2. The stock fetch is fired in parallel, never awaited by any build path, with a **hard ~400 ms
|
||||||
|
budget** and `AbortController`.
|
||||||
|
3. If it lands in time: apply `gone[]` + price deltas before the player reaches the crate.
|
||||||
|
4. If it lands late: apply on arrival (a record vanishing mid-riffle is *correct* — that's a real
|
||||||
|
shop). If it never lands: the room stays tier 1, silently. **Zero console noise** — a dead
|
||||||
|
server is an expected state, not an error.
|
||||||
|
5. Cache the response per `(shop, crate)` in memory + `sessionStorage`, keyed by `etag`. One
|
||||||
|
fetch per shop per session, not per crate entry.
|
||||||
|
6. **One flight at a time per shop**; re-entering a shop reuses the cache. No polling loop.
|
||||||
|
|
||||||
|
The v1 machinery makes this cheap: the pack is a shared material + per-item UVs, so applying
|
||||||
|
`gone[]` is a vertex collapse on an existing merged mesh — no rebuild, no re-upload, no stutter.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Identity: census ↔ lot ↔ POS (risk #3) [DESIGN, informed by §1]
|
||||||
|
|
||||||
|
The R22 contract said "atlas per shop keyed by godverse shop id". §1 shows the id must come from
|
||||||
|
whichever dataset actually *has* the shop, and mismatches must fail soft, never mis-stock:
|
||||||
|
|
||||||
|
- **A shop is stocked only by explicit, recorded identity** — a `godverse_id` that resolves to a
|
||||||
|
real POS. There is no fuzzy name matching, ever. A shop we cannot identify with certainty gets
|
||||||
|
tier 0. **Mis-stocking is worse than no stock**: it puts one shop's records in another's crate,
|
||||||
|
which is a lie the whole epoch exists to avoid.
|
||||||
|
- **Every unmatched census shop is counted, not guessed** (the drops law). R23's count: 2,928
|
||||||
|
census shops, 1 identified real POS.
|
||||||
|
- **The census is not a stock oracle.** Being in the census means the shop exists, not that we
|
||||||
|
know what's in it. v5.0-beta's "every hero shop stocked" is therefore bounded by *real POS
|
||||||
|
access*, not by census coverage — see §9.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. THE WRITE-BACK QUESTION — **RULED: A THEN B** (John, 2026-07-17, R24 kickoff)
|
||||||
|
|
||||||
|
> ### THE RULING
|
||||||
|
> **v5.0 ships on Option A (sandbox).** An in-game purchase **never** touches real
|
||||||
|
> monsterrobot/Square inventory. Sold-means-gone is per-session, tier-1 semantics.
|
||||||
|
> **Option B (reservation) is CHARTERED as a v5.x upgrade**, behind a hold budget John sets.
|
||||||
|
> **Option C is not chartered** — it happens only as a deliberate business launch, if ever.
|
||||||
|
>
|
||||||
|
> Charter law #4 is hereby satisfied: the write-back question has an explicit, John-ratified
|
||||||
|
> answer, and until B ships the standing state is **read-live, sell-sandboxed**.
|
||||||
|
|
||||||
|
**What this makes law for v5.0 (binding on the server work, when it starts):**
|
||||||
|
- The API's `POST /reserve` and `POST /buy` (§4) stay **unbuilt and unrouted**. Not stubbed
|
||||||
|
behind a flag — absent. A sandbox that can be flipped on by a config typo is not a sandbox.
|
||||||
|
- The game's buy path terminates in the client's wallet. **No code path from a game purchase to
|
||||||
|
`inventory.in_stock`, `sold_date`, `hold_expires_at`, `qty`, or Square exists.** The tier-2
|
||||||
|
reader connects to the POS **read-only** — enforce it at the credential, not in a code review:
|
||||||
|
a role with `SELECT` on `inventory`/`crate`/`disc_cache` and nothing else (this also carries the
|
||||||
|
§9 PII fence in the same grant).
|
||||||
|
- A gate worth its name here (the vacuous-gate law): assert the write verbs **404**, and that the
|
||||||
|
tier-2 role's grants are read-only. A test that "we didn't call buy()" proves nothing.
|
||||||
|
|
||||||
|
**Option B's charter (v5.x — the preconditions, so it can't slide in quietly):**
|
||||||
|
1. **The hold budget — John's number, not yet set.** Max concurrent game-held SKUs, and max hold
|
||||||
|
duration. Until both are set, B is not startable. *(Open item, not a blocker for v5.0.)*
|
||||||
|
2. **The shop always wins.** If a held record sells in-store via Square, the game loses the hold,
|
||||||
|
silently and immediately, and the player is told the truth ("someone bought it in the shop") —
|
||||||
|
the one race policy that is never up for negotiation.
|
||||||
|
3. **Staff-visible reason code** on every hold (`held: GODVERSE`), so a human at the counter can
|
||||||
|
see why a record is out of the crate and clear it by hand.
|
||||||
|
4. **Soak first.** B rides plumbing that has soaked, not plumbing that tags a round — Fable's
|
||||||
|
framing, and the reason A ships first.
|
||||||
|
|
||||||
|
*The options as framed for the ruling are preserved below, since the trade-offs are the record of
|
||||||
|
why A-then-B was chosen.*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**The question as put:** does an in-game purchase ever touch real thriftgod/recordgod inventory?
|
||||||
|
Live, not hypothetical: `monsterrobot` is a **real trading shop** whose POS syncs to **Square**,
|
||||||
|
with real customers buying these exact records off the real shelf.
|
||||||
|
|
||||||
|
Relevant measured facts: the POS already has `in_stock`, `sold_date`, **`hold_expires_at`**, `qty`
|
||||||
|
and `square_item_id`. A reservation primitive therefore already exists — the question is whether
|
||||||
|
the game is allowed to pull it.
|
||||||
|
|
||||||
|
### Option A — Sandbox (read-live, sell-local)
|
||||||
|
Game reads real stock; a purchase never leaves the game. Sold-means-gone is per-session.
|
||||||
|
- **For:** zero real-world risk; no payment, tax, or fulfilment surface; a bug can't cost real
|
||||||
|
stock; ships fastest.
|
||||||
|
- **Against:** the one-of-a-kind thrill is fiction — the real crate never changes, and two players
|
||||||
|
(or the same player, next boot) buy the same record. Tier 2 becomes "live prices" more than
|
||||||
|
"live stock".
|
||||||
|
|
||||||
|
### Option B — Reservation (a real, expiring hold)
|
||||||
|
A game purchase places a real hold on the real SKU via the existing `hold_expires_at`. The record
|
||||||
|
genuinely leaves the real crate for N hours; unclaimed, it returns.
|
||||||
|
- **For:** genuinely one-of-a-kind, and **reversible** — the strongest truth-per-unit-risk; uses a
|
||||||
|
primitive the POS already has.
|
||||||
|
- **Against:** a game action removes real sellable stock from real in-store customers. Needs a
|
||||||
|
hold cap/budget, a staff-visible reason code ("held: GODVERSE"), and a race policy against
|
||||||
|
Square in-store sales. Someone must decide what happens when a held record sells in the shop
|
||||||
|
anyway (the game must lose, gracefully).
|
||||||
|
|
||||||
|
### Option C — Full write-back (game sale = real sale)
|
||||||
|
A purchase marks the real SKU sold and syncs to Square.
|
||||||
|
- **For:** total truth — buy it in the game and it is gone from the real shop, forever.
|
||||||
|
- **Against:** irreversible real-world inventory and revenue consequences; the game becomes a
|
||||||
|
sales channel (real payment, tax, postage, fulfilment, refunds, chargebacks); a bug destroys
|
||||||
|
real stock; a game wallet is not money. This is a business decision, not an engineering one.
|
||||||
|
|
||||||
|
*(Lane G asked nothing and recommended nothing here — the options above are the record as put to
|
||||||
|
John. **Ruled A-then-B; the binding terms are at the top of this section.**)*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Failure modes [DESIGN]
|
||||||
|
|
||||||
|
| failure | behaviour | gate class |
|
||||||
|
|---|---|---|
|
||||||
|
| server unreachable / DNS dead | tier 1, silent, zero console noise | tier-2 smoke |
|
||||||
|
| server dies **mid-session** | room keeps last-good state; no re-fetch storm; no stutter | tier-2 smoke |
|
||||||
|
| server slow (> budget) | request aborted; tier 1 stands; late arrival applies or is dropped | tier-2 smoke |
|
||||||
|
| 5xx / malformed JSON | treated as unreachable — never a partial apply | tier-2 smoke |
|
||||||
|
| atlas missing but server up | **tier 0** — no stock without covers; never text-only sleeves | tier-1 gate |
|
||||||
|
| `gone[]` names an unknown sku | ignored + counted, never an error | tier-2 smoke |
|
||||||
|
| clock skew on holds | server time is authoritative; client never computes expiry | tier-2 smoke |
|
||||||
|
|
||||||
|
**No tier-2 failure may fail a gate or block a tag** (charter law #1). Tier 2 gets its own smoke
|
||||||
|
class: reachability, fail-soft, latency. Kill the server mid-session and the game must not stutter
|
||||||
|
— that is v5.0's release gate, and it is a *client* test, not a server test.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Fences (non-negotiable, both already load-bearing)
|
||||||
|
|
||||||
|
- **PII.** `recordgod` holds real `customer`, `customer_session`, `mailing_list`, `staff`,
|
||||||
|
`sales` — real private-individual data. The pipeline and the server read **only**
|
||||||
|
`inventory` / `crate` / `disc_cache`. No exceptions, no joins, no "just for debugging".
|
||||||
|
Charter law #3: no private-individual data, ever.
|
||||||
|
- **Licence.** Covers are the shop's own product shots → in-house 🟢, **flagged before any
|
||||||
|
public/commercial release**. Real titles/artists ship at tier 1 per John's R23 amendment to the
|
||||||
|
no-real-trademarks law, under the same flag. Every atlas carries its provenance.
|
||||||
|
- **Scope reality check for v5.0-beta.** "Every hero shop stocked" needs a real POS per shop. We
|
||||||
|
have exactly one (John's own). Stocking a *second* real shop means a second real shopkeeper's
|
||||||
|
data and consent — a licence and relationship question, not a pipeline question. Beta should be
|
||||||
|
scoped to **monsterrobot's 357 crates** (24,646 real records — the whole shop, crate rotation
|
||||||
|
per risk #2) rather than to more shops, unless John says otherwise.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Open items for the reviewers
|
||||||
|
|
||||||
|
1. **[Fable-m3 / C]** Per-shop pack resolution is not wired: `stockpack.js` caches by `type`
|
||||||
|
alone (`_packs`/`_resolved`), and `interior_mode.js:192` resolves `getStockPack(shop.type)`
|
||||||
|
with `base` fixed at `assets/models/`. R23's single shop works via a `base` override; **two
|
||||||
|
stocked shops will collide in that cache.** The seam C/F need: key the cache by
|
||||||
|
`type + base` (or `godverse_id`), and preload per-shop on shop entry. G re-emits to whatever
|
||||||
|
wording C lands (LANE_C_PUB).
|
||||||
|
2. **[Fable-m3 / E]** `redhill_real` doesn't exist — the only Brisbane cache (`westend_real`)
|
||||||
|
stops ~2.3 km south of Red Hill. The crate has no street until E's `build_towns.py` gets a
|
||||||
|
Red Hill entry (~`center: (-27.4553, 153.0064), span_km: 2.4`) and one Overpass fetch. That's
|
||||||
|
E's file and an outward call — **G has not touched either.**
|
||||||
|
3. ~~**[John, R24]** §7, the write-back question.~~ **RULED 2026-07-17: A then B** (§7). Carried
|
||||||
|
forward: **the hold budget is John's number and is not yet set** — Option B is not startable
|
||||||
|
until it is, which is a v5.x item, not a v5.0 blocker.
|
||||||
|
4. **[Both Fables]** §9's beta scoping — depth (one real shop, 357 crates) vs breadth (more
|
||||||
|
shops, which needs other people's data).
|
||||||
87
docs/LANES/FABLE_TO_LANE_G.md
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
# Fable (m3, PROCITY integrator) → Lane G — welcome, rulings, and the G2/G3 sequence
|
||||||
|
|
||||||
|
Date: 2026-07-16 · Re: `lane-G-godverse` (`223fbca`), LANE_G_GODVERSE.md, G-progress.md R1.
|
||||||
|
|
||||||
|
Reviewed: the brief, the progress file, `godverse_town.py` (line-by-line), and the
|
||||||
|
`newtown_godverse` cache shape. Verdict: **clean work, fully in-convention** — deterministic
|
||||||
|
adapter (ordered query, no RNG), schema v2 honoured, attribution extended, registry vocabulary
|
||||||
|
respected, and you found real bugs with our own harness. Welcome to the family, Lane G. 🤝
|
||||||
|
|
||||||
|
## Merge policy (how G lands on main)
|
||||||
|
|
||||||
|
Your branch touches only G-owned files — good. **Merges happen at round boundaries**: the m3 is
|
||||||
|
mid-round-21 (the v4.0-beta close; A is re-pinning goldens right now), and dropping a new town
|
||||||
|
cache onto main mid-round would inject an unpinned town into A's live selfcheck sweep. I'll merge
|
||||||
|
`lane-G-godverse` right after `v4.0-beta` tags, and A pins `newtown_godverse`'s golden in R22.
|
||||||
|
Standing rule: G commits to the branch freely; Fable-m3 merges at each boundary; anything
|
||||||
|
touching A–F files needs a pre-ruling (as you correctly assumed).
|
||||||
|
|
||||||
|
## Ruling 1 — Draco: **NO-DRACO WINS. The depot speaks uncompressed GLB.**
|
||||||
|
|
||||||
|
Not house pride — load-bearing: the depot (`digalot.fyi/3god`) serves the game **at runtime**,
|
||||||
|
and PROCITY's loader is decoder-free by design (the asset law is fail-simple: no decoder wasm, no
|
||||||
|
loader config drift, `?noassets` must stay zero-fetch). All 39 published assets are already
|
||||||
|
uncompressed; flipping the law means shipping DRACOLoader + wasm to every boot to save disk on a
|
||||||
|
depot that isn't disk-bound. Your side is one re-emit: the same gltf-transform command **minus
|
||||||
|
`--compress draco`** (`--texture-compress webp --texture-size 1024 --simplify-error 0.01` all
|
||||||
|
stay — WebP ≤1024 is already our law). Formally: **depot law = GLB · no Draco · WebP ok ·
|
||||||
|
tex ≤1024 · footprint/origin per the manifest conventions (Lane E's docs).**
|
||||||
|
|
||||||
|
Your ⚠️ trellis-farm warning (fill_holes+decimate shreds marching-cubes meshes) is precious —
|
||||||
|
Lane E will fold it into the pipeline docs verbatim, credited to ultra's post-mortem.
|
||||||
|
|
||||||
|
## Ruling 2 — godverse density: **inherit the donor's texture layer.**
|
||||||
|
|
||||||
|
An 18-hero census town is sparse by our round-20 standard, and the fix is already built: the
|
||||||
|
donor cache carries R20's texture shops (cafes/bakeries/etc., SUBTLE-capped). G2's adapter should
|
||||||
|
**merge census heroes WITH the donor's texture shops** — census heroes always win a collision
|
||||||
|
(dedupe by proximity + name against the donor's hero entries; drop the donor hero, keep yours,
|
||||||
|
count the swap). Result: real census shops on a street that also feels alive. bbox-cropping to
|
||||||
|
the retail spine (King St) is a fine *secondary* knob, your call. A/E will advise on the dedupe
|
||||||
|
if you want eyes — ask in G-progress and I'll route it.
|
||||||
|
|
||||||
|
## Your bug reports — both accepted, both queued for R22
|
||||||
|
|
||||||
|
1. **Per-town bookmarks** (synthetic coords teleport into OSM geometry) → **Lane B**, R22:
|
||||||
|
bookmarks resolve against the active plan (B already did this for `poseForVenue`/
|
||||||
|
`queue_night` — the older bookmark set gets the same treatment).
|
||||||
|
2. **`DBG.enterShop(selector)`** (name/type/id) → **Lane F**, R22: the harness grows a selector.
|
||||||
|
Glad the harness earned its keep — `info/teleport/shot` being useful cross-repo is exactly
|
||||||
|
why it exists.
|
||||||
|
|
||||||
|
Also queued: **Lane E refreshes the stale MODELBEAST note** (trellis_mac proven 15/15, the new
|
||||||
|
:8777 queue inventory) — thank you for the correction.
|
||||||
|
|
||||||
|
## The 15 hero props — accepted in principle
|
||||||
|
|
||||||
|
Route: John picks keepers (in progress, per your note) → re-emit no-Draco → through Lane E's
|
||||||
|
provenance/depot pipeline (licence zone recorded per asset — flag anything scraped vs generated)
|
||||||
|
→ Lane C decides op-shop dressing slots. Don't push to the depot before the E provenance pass;
|
||||||
|
that table is our licensing spine and it stays complete.
|
||||||
|
|
||||||
|
## G2 (real stock) — green-lit in HALF, sequenced
|
||||||
|
|
||||||
|
- **G2a — GREEN NOW (post-beta):** static per-shop atlases behind `?stock=real`, keyed by
|
||||||
|
godverse shop id, built by `build_stock_pack.py` from dealgod inventory. Contract review with
|
||||||
|
C/E at R22 kickoff — my priors: **atlas per shop** (a real shop's crate is that shop's), budget
|
||||||
|
= the existing pack budgets (C owns interior draw/tex ceilings), **fail-soft to parody canvas**
|
||||||
|
when the atlas is absent (the existing law — a godverse town under `?noassets` must stay
|
||||||
|
silent-and-fine).
|
||||||
|
- **G2b — sold-means-gone: NOT YET.** Live invalidation is a server dependency in disguise —
|
||||||
|
it's G3 wearing a hat. It waits for the G3 design.
|
||||||
|
|
||||||
|
## G3 (economy backend) — design doc first, and one non-negotiable
|
||||||
|
|
||||||
|
The vision is right (thriftgod = brain and bank; PROCITY = body), and it's the biggest
|
||||||
|
architecture change since the flip, so: **design doc → both integrators → John**, nothing before
|
||||||
|
`v4.0` ships. The non-negotiable law going in: **the server enriches, never gates** — PROCITY
|
||||||
|
must boot, play, and dig fully offline (static packs as the fallback tier); thriftgod presence
|
||||||
|
upgrades stock to live/one-of-a-kind. Sketch against that and we'll find the seams.
|
||||||
|
|
||||||
|
## Sync
|
||||||
|
|
||||||
|
Your nudge is satisfied: m3 pushed R20 (8 commits), the R21 instructions, and this doc;
|
||||||
|
`origin/main` is current. `v4.0-beta` tags at R21 close, then your branch merges. Watch
|
||||||
|
G-progress conventions for the R22 kickoff — you'll get a section like every other lane.
|
||||||
|
|
||||||
|
— Fable (m3)
|
||||||
@ -2,6 +2,867 @@
|
|||||||
|
|
||||||
Durable answers to questions other lanes raised against Lane A. Full status in `A-progress.md`.
|
Durable answers to questions other lanes raised against Lane A. Full status in `A-progress.md`.
|
||||||
|
|
||||||
|
## Round 30 (2026-07-18) — THE ROTATION BOUNDARY VERIFY (ledger #6): **HOLDS**
|
||||||
|
|
||||||
|
### → Fable / F: `day` rides RUNTIME streams only — plan generation cannot see it. Measured, not read.
|
||||||
|
|
||||||
|
**Verdict: the seeded-rotation boundary HOLDS.** F's `day` salt (207bcff) touches exactly three runtime
|
||||||
|
seams and nothing upstream: (1) `interiors.js:178` `ctx.stockSalt` from `opts.stockDay`, consumed ONLY in
|
||||||
|
`layout.js:417` as a suffix on the `stk-*` stock-pick stream labels (room/fittings/audio streams untouched
|
||||||
|
— `stockSalt` has exactly those two references in the tree); (2) the dig `binSeed` key `@d` tag in
|
||||||
|
`interior_mode.js` (same null-gating: no game / day 1 / real-sourced ⇒ absent); (3) `gig_state.setWeekNight`,
|
||||||
|
which only **selects** a night from `plan.gigs` — the whole 7-night week is generated at plan time
|
||||||
|
(`gigs.js:227` `for night < NIGHTS`) from citySeed streams; day picks, never feeds.
|
||||||
|
|
||||||
|
**How measured (three falsifiable legs):**
|
||||||
|
1. **Grep-zero over the proven glob:** `citygen/**` = exactly 8 files, no subdirs, 2,546 lines (census
|
||||||
|
printed, ls-verified). `grep -riE 'day|stockDay|stockSalt'` → zero identifier hits (only lexical noise:
|
||||||
|
fixture name "Heydays", shop name "Sunday Stall", "today" in comments, `daylesford_real` golden keys).
|
||||||
|
Non-vacuous: the same pattern hits `stockDay` 4× in `world/interior_mode.js`.
|
||||||
|
2. **Structural absence at the entry:** `generatePlanFor(seed, source, opts)` consumes `opts.{town, gigs,
|
||||||
|
customBands, cache, report}` only (grepped every `opts.` in citygen); both shell call sites
|
||||||
|
(`index.html:127`, `map.html:315`) pass `{town, gigs, customBands}` — no day exists to pass.
|
||||||
|
3. **Day-inertness, byte-measured:** hostilely injected `{day, stockDay}` into `generatePlanFor` anyway —
|
||||||
|
plan JSON hash identical for day-absent / day 1 / day 37 across 3 seeds × both sources (6/6 legs,
|
||||||
|
e.g. synthetic 20261990 `0xec7a2d39` all three ways). Even a rogue caller can't leak day into a plan.
|
||||||
|
|
||||||
|
**Goldens frozen:** selfcheck **ALL GREEN 156,352/156,352**, fingerprint `0x5f76e76` (the covenant),
|
||||||
|
gig golden `0xec7a2d39`, all town pins (melbourne/katoomba/silverton + real caches) green, no re-pins.
|
||||||
|
Pre-existing informational ⚠ (redhill "Empire Revival" lift-drop) unchanged, not mine, not new.
|
||||||
|
|
||||||
|
## Round 27 wave 5 (2026-07-17) — THE FACADE FIX: it was every town, not just the real ones
|
||||||
|
|
||||||
|
### → F: **your files hold the old covenant hashes — they are now wrong, and only you can fix them**
|
||||||
|
This is the one thing blocking your re-gate. `scaffold_check` is **RED** until you re-pin, and I can't
|
||||||
|
touch your files. Exact values (verified byte-identical across two fresh processes):
|
||||||
|
|
||||||
|
| your file | constant | old | **new** |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `tools/flags_check.py:30` | `GOLDEN_HASH` | `0x3fa36874` | **`0x5f76e76`** |
|
||||||
|
| `tools/flags_check.py:31` | `GIG_GOLDEN` | `0xb1d48ea1` | **`0xec7a2d39`** |
|
||||||
|
| `tools/qa/scaffold_check.mjs:231` | `TOWN_GOLDENS.melbourne` | `0x34cfdec0` | **`0x9c7e76b3`** |
|
||||||
|
| `tools/qa/scaffold_check.mjs:231` | `TOWN_GOLDENS.katoomba` | `0x0f652510` | **`0xf3aafec8`** |
|
||||||
|
| `tools/qa/scaffold_check.mjs:225` | the comment quoting all three | — | same three |
|
||||||
|
| `tools/qa/tour_v5.py:42`, `tour_shots.py:10` | captions quoting `0x3fa36874` | — | **`0x5f76e76`** |
|
||||||
|
|
||||||
|
Also: `flags_check.py:148` greps stdout for the literal `'3fa36874'` — that will silently stop matching.
|
||||||
|
|
||||||
|
### The finding: the ruling's premise was wrong — **synthetic was broken too**
|
||||||
|
The wave-5 ruling says *"synthetic follows B's canon so every golden has been green over a wrong product
|
||||||
|
since R18"*. Half right, and the wrong half is load-bearing. **Synthetic does not follow B's canon** — it
|
||||||
|
is broken identically, and so is the marched path, and so are the three checked-in fixtures. It is not
|
||||||
|
"every real town since R18": it is **every town, every path, since v1**. Measured in B's canon before
|
||||||
|
touching anything, then confirmed with my own eyes in the browser:
|
||||||
|
|
||||||
|
| path | before | after |
|
||||||
|
|---|---|---|
|
||||||
|
| synthetic (`plan.js`) — **the classic covenant** | **0 toward / 681 away** | 681 / 0 |
|
||||||
|
| marched-osm + the 3 fixtures | 0 / 80, 0 / 95, 0 / 19, 0 / 12 | all toward |
|
||||||
|
| real-roads (B's finding) | 0 / 72 katoomba | **72 / 72** — matches B exactly |
|
||||||
|
|
||||||
|
**The screenshot is the proof:** standing on the synthetic main street at `?classic=1`, lot 0 — three blank
|
||||||
|
grey boxes. Walk *behind* them, into the block, and there is NUMBAT PLAYTHINGS: sign, door, windows, awning,
|
||||||
|
facing the dirt. Same camera after the fix: CROWN TOYS & GAMES · MARBLES & KITES · NUMBAT PLAYTHINGS, a
|
||||||
|
strip of shopfronts facing the road. **637 of 681 synthetic lots had their facade pointing at no street at
|
||||||
|
all.**
|
||||||
|
|
||||||
|
**Why it survived**: all four sites are the same one-sign error — `atan2(outward)` where the intent was
|
||||||
|
`atan2(−outward)` — and *every one of them says so in its own comment* (`plan.js:122` "facade faces back
|
||||||
|
down the outward normal, to the street"). The code never did what its comment said, and CITY_SPEC:71 blessed
|
||||||
|
the result. Nobody was wrong; the spec was.
|
||||||
|
|
||||||
|
### The covenant moved — John ruled it
|
||||||
|
Fixing synthetic necessarily moves `0x3fa36874`, frozen "forever" since R16. That was not mine to decide.
|
||||||
|
**John's ruling: amend the covenant, fix all paths** — the covenant is anti-drift machinery, not a defect
|
||||||
|
preservative. **51 goldens re-pinned in one commit** (classic `0x3fa36874 → 0x5f76e76`, gig
|
||||||
|
`0xb1d48ea1 → 0xec7a2d39`, 3 fixtures, 23 towns × base+gig). The amendment is recorded in CITY_SPEC as a
|
||||||
|
one-time, ruling-gated precedent — from this hash forward the law reads as it always did.
|
||||||
|
|
||||||
|
### A second bug the fix exposed (fixed here, counted)
|
||||||
|
`buildRealRoads` seated blocks at `KERB = 4 m` **from the centreline** — but a `main` carriageway is 10 m
|
||||||
|
wide, so its kerb is at 5 m. **Every main-street building has stood 1 m inside the road since R18.** The
|
||||||
|
footprint never moved; only which face is the front — so it was invisible until the shopfront turned around
|
||||||
|
and its poster landed in the traffic lane (−1.07 m). Blocks now seat behind the kerb per A's own corridor
|
||||||
|
law (`roadWidth/2 + FOOTPATH`). Pack drops **6.4% → 6.6%**, counted, not hidden.
|
||||||
|
|
||||||
|
### The cross-convention gate — built to F's lesson, not to a spec line
|
||||||
|
F proved that measuring lots against CITY_SPEC:71's own convention is **self-agreement**: 72/72 "facing" over
|
||||||
|
a town of blank walls. So the new gate does not restate a spec line. It **replicates Lane B's own placement
|
||||||
|
maths** from `buildings.js` (`toWorld(lot, 0, y, d/2 + 0.02)`) and asks a physical question neither spec line
|
||||||
|
can fudge: *does the quad the renderer actually draws land nearer the street than the lot centre?* It fires
|
||||||
|
if A's `ry` sign drifts **or** if B's `zf` sign drifts. It would have caught this on day one of v1.
|
||||||
|
|
||||||
|
## Round 27 (2026-07-17) — v5.0: the docs freeze — CITY_SPEC v5 + the risk list judged (ledger #6)
|
||||||
|
|
||||||
|
### → F + Fable: **A's docs half is done and the tree is green; nothing of mine gates your tag**
|
||||||
|
selfcheck **ALL GREEN 156,212/156,212**, classic `0x3fa36874` / gig `0xb1d48ea1` frozen, **no golden moved**
|
||||||
|
(none should — tier 2 touches no plan, no cache, no atlas bytes). CITY_SPEC has its **v5 section** (the tier
|
||||||
|
ladder · `sourcing` vs `tier` · the three id-space fences · the manifest · Ruling 2 · A's one seam), and the
|
||||||
|
charter risk list is judged line by line in `V5_REAL_SHOP.md`.
|
||||||
|
|
||||||
|
### The risk list: I did NOT pre-retire your gate
|
||||||
|
Written in wave 2, so **risk #1 (latency/stutter at tier 2) is CARRIED, not retired** — your reader and
|
||||||
|
kill-the-server gate don't exist yet, and **nothing retires that line except the gate going green**. Retiring
|
||||||
|
it from a doc while the code is unwritten would be the exact species this epoch spent four holds catching: a
|
||||||
|
rule meeting a case that doesn't exist yet. #2 retired (measured), #3 retired (designed out), #4 **split** —
|
||||||
|
write-back retired by John's ruling-as-mechanism, the `gone` read carried to your #4. Two new carried lines
|
||||||
|
(#6 orphaned atlas, #7 "real" is one shop wide).
|
||||||
|
|
||||||
|
### The gate that armed itself — worth seeing, since it's the law working unattended
|
||||||
|
My R25 orphaned-atlas gate **SKIPped** on `newtown_godverse` all through R25 ("no committed atlas keys to
|
||||||
|
this town" — subject absent, so it said so instead of passing). The moment G's 14 mint atlases landed, it
|
||||||
|
**started binding on its own** and passed: selfcheck went 156,211 → **156,212**, and that +1 is the gate
|
||||||
|
arming itself. Nobody edited it. That's what the vacuous-gate law buys — a gate that waits, in the open, for
|
||||||
|
its subject.
|
||||||
|
|
||||||
|
**Live near-miss on the books → G:** `redhill_godverse`'s lift drops keyed shop **2286 "Empire Revival"**;
|
||||||
|
you stocked its numeric neighbour **2287**. Nothing is orphaned and the gate is green — but **stocking 2286
|
||||||
|
would orphan it today**, and the hazard grows with every shop stocked. Carried as risk #6.
|
||||||
|
|
||||||
|
### One correction to the round doc's framing, for the record
|
||||||
|
The brief lists "the manifest" and "sourcing" as if `tier` were superseded. The artifacts say otherwise, and
|
||||||
|
**C's ruling is the precise one**: every atlas index carries **both** — `tier: 1` (the ladder rung; a mint
|
||||||
|
atlas is *still* a static file, so still rung 1) **and** `sourcing: "real"|"mint"` (where the contents came
|
||||||
|
from). They are different axes. The spec documents both, because collapsing them is exactly how a mint crate
|
||||||
|
would come to read as real.
|
||||||
|
|
||||||
|
## Round 25 (2026-07-17) — v5.0-alpha: the sweep reconciled with Ruling 2, six goldens pinned (ledger #1)
|
||||||
|
|
||||||
|
### → F: **the tree is green — all 8 reds are gone, nothing of mine is pending** (the handshake)
|
||||||
|
selfcheck **ALL GREEN 156,211/156,211**; scaffold + consistency green; classic `0x3fa36874` and gig
|
||||||
|
`0xb1d48ea1` frozen. The six goldens are pinned: `newtown_godverse` base `0xcf69b387` / gig `0x1e266f60`,
|
||||||
|
`redhill_real` base `0x6046700f` / gig `0x673785ee`, `redhill_godverse` base `0xb2224939` / gig
|
||||||
|
`0xc6f80e18`. Cross-checked two ways before pinning — **byte-identical across two fresh processes**, and
|
||||||
|
the three values G stated independently in `86e2985` match mine exactly. **Your third attempt is unblocked
|
||||||
|
from my side.**
|
||||||
|
|
||||||
|
Two lines still print and **both are deliberate, neither is pending**:
|
||||||
|
- `⊘ SKIP real/newtown_godverse: orphaned-atlas check` — correct: the only committed atlas (3962749) keys
|
||||||
|
to Red Hill, so newtown has no subject. The vacuous-gate law says say so rather than pass quietly.
|
||||||
|
- `⚠ real/redhill_godverse: 1 keyed shop(s) dropped by the lift — 2286 "Empire Revival"` — an honest
|
||||||
|
disclosure, not a defect (see below).
|
||||||
|
|
||||||
|
### My R24 gate asserted Ruling 2 away — that's the whole of my 6 reds, and it was my error
|
||||||
|
I built the identity gate on an assumption that was true when I wrote it and false the moment G shipped:
|
||||||
|
that a godverse cache is *all* census shops. Ruling 2 makes it **mixed** — a keyed GODVERSE layer plus an
|
||||||
|
inherited texture layer — so `newtown_godverse` (18 keyed + 54 texture) and `redhill_godverse` (10 + 27)
|
||||||
|
are **healthy towns my gate called broken**. Every keyed assert now runs **per-layer, over the keyed subset
|
||||||
|
only**; unkeyed texture shops raise nothing. The validator's "N/M missing" warn is gone with it — the only
|
||||||
|
honest requirement left at that layer is that a `godverse+osm` cache have a **non-empty** godverse layer at
|
||||||
|
all (else it's an osm cache wearing the wrong `source`), which stays a warn for the R24 charter reasons.
|
||||||
|
|
||||||
|
### The uniqueness check was broken in both directions — rebuilt so it can actually fail
|
||||||
|
The R24 version was `new Set(all ids).size === shops.length`. Every `undefined` collapses into **one** Set
|
||||||
|
entry, so it (a) failed structurally on any mixed cache — 54 texture shops became one entry — and (b) could
|
||||||
|
**hide a real duplicate behind that same collapse**. It now runs over **defined ids only**. And per the
|
||||||
|
vacuous-gate law I made it prove it bites: `validateTownCache` rejects duplicates at the front door, so a
|
||||||
|
dup can never reach the plan that way — the test feeds one **straight to the lift** (`generatePlanOSM` does
|
||||||
|
not validate) and asserts the predicate catches it, plus a healthy mixed cache passes the same predicate.
|
||||||
|
A check nobody can watch fail is not a check.
|
||||||
|
|
||||||
|
### New — the orphaned-atlas gate (my R24 finding, now enforced instead of just filed)
|
||||||
|
The lift legitimately drops shops (overlap-resolve, counted since R19). Dropping a shop that **has a
|
||||||
|
committed atlas** means real stock keyed to a shop that isn't in the town — exactly the failure I filed in
|
||||||
|
R24, and one F's #7b would only catch at the *end* of a round, in a browser. It's now a node gate: for each
|
||||||
|
godverse town, every `stock_godverse/<id>/` on disk whose id belongs to that cache **must be seated**, else
|
||||||
|
FAIL. Subject-aware — SKIPs by name when a town has no atlas.
|
||||||
|
|
||||||
|
**It already found something real, benign today:** redhill's lift drops keyed shop **2286 "Empire Revival"**
|
||||||
|
(10 keyed in cache → 9 seated). It has no atlas, so nothing is orphaned and the gate passes. **→ G: if you
|
||||||
|
stock Empire Revival at the beta, it will orphan** — the shop isn't on the plan. Monster Robot Party is
|
||||||
|
safely seated (lot 8), which is the one that matters this round.
|
||||||
|
|
||||||
|
## Round 24 (2026-07-17) — v5.0-alpha: the identity field (ledger #2)
|
||||||
|
|
||||||
|
### → G: **`godverseShopId` is live — emit it, and mind the id-space trap** (the handshake for your #5)
|
||||||
|
The field is published and the lift carries it: put `godverseShopId` on shop entries in a
|
||||||
|
`source: "godverse+osm"` cache and it lands on `plan.shops[]`, which is what F's per-shop `base` reads.
|
||||||
|
Contract in `web/assets/towns/README.md`; `validateTownCache` enforces it; selfcheck gates it.
|
||||||
|
|
||||||
|
**The round doc says "the POS shop id IS the id — no mapping table". That's true for census shops and
|
||||||
|
false for the shop this whole alpha is about** — and your own docstring is what told me:
|
||||||
|
|
||||||
|
| shop | id source | equals `shop.id`? |
|
||||||
|
|---|---|---|
|
||||||
|
| census shops (`newtown_godverse`) | thriftgod `shop.id` (max 2992) | yes, incidentally |
|
||||||
|
| **Monster Robot Party** | **dealgod store 3962749** — not in thriftgod's census | **no** |
|
||||||
|
|
||||||
|
So I built it as an **opaque key**, not a census id: no equality assert, no derivation from `id`, no mapping
|
||||||
|
table. Both spaces ride it, and your disjointness (2992 vs 3962749) is what makes that safe. **Uniqueness is
|
||||||
|
an error**, because two shops keyed to one atlas is mis-stocking (charter risk #3).
|
||||||
|
|
||||||
|
**⚠ The thing that will bite you in wave 2 — `redhill_godverse` cannot contain Monster Robot from either
|
||||||
|
source. Measured, not inferred.** `godverse_town.py` takes **roads from the OSM donor** and **shops from
|
||||||
|
thriftgod's census**. The shop is in neither:
|
||||||
|
- **Not in the census** — your own R23 finding (that's *why* its id is a dealgod store id).
|
||||||
|
- **Not in E's `redhill_real` donor** — I checked the cache the moment it landed: **36 shops, zero of type
|
||||||
|
`record`, nothing matching /monster|robot/**. OSM's Red Hill doesn't know the shop exists.
|
||||||
|
|
||||||
|
So the adapter as written produces a Red Hill of census charity shops, and the atlas at
|
||||||
|
`stock_godverse/3962749/` keys to a shop that **isn't in the town** — F's #7b id-equality assertion would
|
||||||
|
fail, correctly, at the end of the round. **Inject it explicitly**: name, `type: "record"`, its real lat/lon
|
||||||
|
(147 Musgrave Rd), `godverseShopId: 3962749`. The schema is ready for it — that's exactly the case the
|
||||||
|
opaque-key design exists to serve.
|
||||||
|
|
||||||
|
**Second, cheaper trap on the same path:** `redhill_godverse` still has to clear **`MIN_TOWN_SHOPS` (6)**
|
||||||
|
from the census bbox alone. Newtown yielded 18; Red Hill is a Brisbane suburb and thriftgod's census is
|
||||||
|
thin there. If the bbox returns < 6, the cache is **rejected outright** (hard error, not a warn) and the
|
||||||
|
town won't exist. I can't measure that from here — no census DB on m3 — but you'll see it instantly. If it
|
||||||
|
bites, the count includes any shop you inject.
|
||||||
|
|
||||||
|
**E's donor is otherwise good for you:** valid, no warnings, **73.3 m median spacing** — half the warn floor,
|
||||||
|
a proper high street to hang the shop on.
|
||||||
|
|
||||||
|
### → F: the seam you need for #6a
|
||||||
|
`plan.shops[].godverseShopId` is the per-shop `base` key — read it off the **plan**, not the cache. It's
|
||||||
|
absent (not `undefined`) on every non-godverse shop, so `'godverseShopId' in shop` is a clean tier test:
|
||||||
|
present ⇒ tier 1 candidate, absent ⇒ tier 0 parody. That absence is also why **no golden moved this round**.
|
||||||
|
|
||||||
|
### → Fable: one recorded departure from the ledger, and why
|
||||||
|
Ledger #2 says `validateTownCache` **requires** the field on godverse shops. I implemented **missing → warn**
|
||||||
|
(malformed and duplicate → hard errors). Three reasons, and it's yours to overrule:
|
||||||
|
1. **The charter's determinism boundary** — *world = seeded, frozen, gated; stock = data tiers*. An error
|
||||||
|
inverts it: the **town** would fail to load because its **stock** identity is absent. Tier 0 must boot.
|
||||||
|
2. **Charter risk #3 names the remedy** — identity gaps "fail-soft to tier 0, never mis-stocked". Missing
|
||||||
|
*is* the soft case; duplicate is the hard one. They're different failures and now get different arms.
|
||||||
|
3. **Sequencing** — `newtown_godverse` carries 0 ids today, it's G's to re-emit in wave 2, and I can't do
|
||||||
|
it here (their namespace; no census DB on m3). A hard error would red the tree for C and E through all
|
||||||
|
of wave 1 to say what the warn already says.
|
||||||
|
The requirement still has teeth, in the place R23 taught us to put them: selfcheck **fails on partial
|
||||||
|
keying** and prints an explicit **SKIP with the count** at zero — it cannot pass vacuously.
|
||||||
|
|
||||||
|
### Also: E's toowoomba retirement showed my spacing metric is gameable (worth knowing)
|
||||||
|
E tried the re-bbox before retiring and found it **fixed my metric while making the town worse** — median NN
|
||||||
|
395.7 → 89.4 m ("passes") but hub density 3/12 → 2/12, the pack's worst. My warn is **necessary, not
|
||||||
|
sufficient**: it catches scatter, and it can be satisfied by tightening the box around a scatter. D's hub
|
||||||
|
test is the complement, and E used both. Retired pins removed from both golden maps (a golden for a deleted
|
||||||
|
town can never fire — the vacuous-gate law one layer down). **Ballarat (255 m) is still flagged and still
|
||||||
|
E's call this round; if it moves, I pin it.**
|
||||||
|
|
||||||
|
## Round 23 (2026-07-16) — v5.0-alpha: the spacing warn (D's thin-tail finding as law)
|
||||||
|
|
||||||
|
### → E: **the warn is live — and it flags TWO towns, not one** (the handshake for your #4)
|
||||||
|
`validateTownCache` now warns when **`medianShopSpacing(cache) > 150 m`**. Import the metric from the
|
||||||
|
barrel (`medianShopSpacing`, `MAX_MEDIAN_SPACING_M`) rather than re-deriving it — and if you re-implement
|
||||||
|
in Python, the definition is: *per shop, distance to the nearest OTHER shop; take the median*, in
|
||||||
|
equirectangular metres about `center.lat`. Warn, never error: **you curate, I only flag.**
|
||||||
|
|
||||||
|
**Use the warn as your acceptance test.** Re-bbox toowoomba, re-run the validator; when the warn goes
|
||||||
|
silent, the town has a high street. That's the whole loop, no judgement call needed.
|
||||||
|
|
||||||
|
| town | median spacing | verdict |
|
||||||
|
|---|---|---|
|
||||||
|
| `toowoomba_real` | **396 m** | your ledger #4 — D proved it dead (1,417 patronage checks → 0 visits) |
|
||||||
|
| `ballarat_real` | **255 m** | **nobody has looked at this one.** Same class, 1.7× inside the line |
|
||||||
|
| braddon (worst passing) | 119 m | alive by D's hub test — 7 shops within 160 m, same as darwin |
|
||||||
|
|
||||||
|
**Ballarat is the finding beyond the brief.** The round doc names only toowoomba, because D only spot-checked
|
||||||
|
the two 12-shop towns. Measuring all 23 says ballarat is the same shape — and it's one of the three towns my
|
||||||
|
R22 graded cluster fallback fired on (no venue candidate at the ≥3-shops/160 m floor), so two independent
|
||||||
|
signals agree. Your call whether it re-bboxes or ships as a known thin-tail; counted either way. **If your
|
||||||
|
fix moves toowoomba's (or ballarat's) cache, ping me — the goldens are mine to re-pin, and only what you move.**
|
||||||
|
|
||||||
|
### ⚠ The number in my validator will NOT match the number in D's notes — both are right
|
||||||
|
D's notes say darwin **27 m**; my validator says darwin **68.3 m**. Neither is wrong: **D measured PLAN
|
||||||
|
space** (post-lift metres), **the validator measures CACHE space** (raw lat/lon, before any lift exists —
|
||||||
|
it has to, that's the layer it runs at). The lift marches shops at a regularised cadence, so it *compresses*
|
||||||
|
dense towns and leaves sparse ones alone:
|
||||||
|
|
||||||
|
| town | cache | plan | plan/cache | |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| darwin | 68.3 | 27.2 | **0.40** | dense ⇒ the lift compresses hard |
|
||||||
|
| toowoomba | 396.1 | 386.8 | **0.98** | sparse ⇒ the lift barely moves it |
|
||||||
|
| ballarat | 254.9 | 257.7 | **1.01** | sparse ⇒ ditto |
|
||||||
|
|
||||||
|
That asymmetry is *why cache space is safe here*: the error is concentrated at the dense end, and the warn
|
||||||
|
only ever fires at the sparse end, where the two spaces agree to within 2%. I verified all 23 towns in both
|
||||||
|
spaces — **exactly one disagrees.**
|
||||||
|
|
||||||
|
### → G + Fable: `newtown_godverse` is the one town that flips (filed, not acted on)
|
||||||
|
Godverse is the single cache where the two spaces straddle the line: **94.4 m cache (passes) → 158.3 m plan
|
||||||
|
(would warn)**. My warn does **not** flag it, and I think that's correct on both counts: (a) no cache-space
|
||||||
|
threshold can catch it without also flagging braddon, which D's own test says is alive; (b) godverse is a
|
||||||
|
**census subset** — 18 thriftgod shops over the same Newtown footprint that carries 72 OSM shops — so its
|
||||||
|
sparsity is *coverage*, not a bad bbox, and re-bboxing (the warn's whole remedy) cannot fix it.
|
||||||
|
|
||||||
|
**But it's thin by two independent signals** — plan-space 158 m, and it's the third town my R22 cluster
|
||||||
|
fallback fired on. Since R23 stocks a real record shop *in this town*, and the beta asks "fitzroy_godverse
|
||||||
|
if the census is dense there", that's worth knowing now rather than at beta. Not my call — filing it.
|
||||||
|
|
||||||
|
## Round 22 (2026-07-16) — v4.0: the pack absorbed, 36 goldens pinned (the epoch close)
|
||||||
|
|
||||||
|
### → C + D + F: **the pack is in — 23 towns, all pinned, start your verifies** (the handshake)
|
||||||
|
E's 17 new towns + Lane G's `newtown_godverse` fed through the lift. **All 36 waiting goldens pinned in
|
||||||
|
this one commit** (`REAL_TOWN_GOLDENS` + `REAL_TOWN_GIG_GOLDENS` now hold **23 towns each**, zero unpinned).
|
||||||
|
selfcheck **ALL GREEN 161,300/161,300**; scaffold + consistency GREEN; cross-process deterministic (pack-xor
|
||||||
|
`0xca140954`). classic `0x3fa36874` / gig `0xb1d48ea1` / marched fixtures **frozen**.
|
||||||
|
|
||||||
|
### The pack absorbed — everything built this epoch held at 23-town scale, first try
|
||||||
|
| | |
|
||||||
|
|---|---|
|
||||||
|
| towns | **23** (22 real + `newtown_godverse`) |
|
||||||
|
| shops | 1,210 → **1,133 seated** (77 dropped, **6.4%**, all counted by cause) |
|
||||||
|
| graph | **1 connected component on every town** (junction protection + cull/bridge) |
|
||||||
|
| district | **3 venues on every town** (cluster bias + facade clearance) |
|
||||||
|
|
||||||
|
**fitzroy (160 shops — 2× anything absorbed before)**: 139 seated, 1 component, 3 venues, 212 posters, all
|
||||||
|
invariants green. The junction pipeline, fragmentation ruling, cluster bias, poster cap and capacity fence
|
||||||
|
all ran at mecca scale with **no new hardening needed** — the pack needed absorbing, not fixing.
|
||||||
|
|
||||||
|
### One refinement this round: a graded cluster fallback for genuinely thin towns
|
||||||
|
At pack scale three towns have **no candidate at the floor at all** — `ballarat`, `toowoomba`,
|
||||||
|
`newtown_godverse` top out at **2** shops within 160 m. R21's bias switched off entirely there, stranding
|
||||||
|
venues at 0. It now falls back to the **densest available**, so the venue still lands on what little crowd
|
||||||
|
the town has: ballarat **0,2,0 → 2,2,2**, godverse **0,1,1 → 2,2,2**, toowoomba **1,0,0 → 1,2,1**.
|
||||||
|
It only fires where the floor is unreachable ⇒ **no already-pinned town moved** (verified).
|
||||||
|
|
||||||
|
**Acceptance (R21's, at 23-town scale): 20 of 23 towns have every venue ≥3 shops/160 m.** The three above
|
||||||
|
sit at their town's genuine ceiling — the "no better candidate exists" case the acceptance carved out.
|
||||||
|
**Saying so explicitly, as asked.**
|
||||||
|
|
||||||
|
### Drop outliers (counted, not hidden)
|
||||||
|
`daylesford` **19%** (6/32, all overflow — 32 shops on a 171-road graph, the thinnest graph in the pack) and
|
||||||
|
`fitzroy` **13%** (21/160, 17 overflow). Both are the R20 capacity fence doing its job: `RGAP` is already
|
||||||
|
0.5 m and `NODE_CLEAR` 3 m, so the only lever left is spilling overflow onto a *different* street — which
|
||||||
|
buys a few percent by putting a shop on a road it isn't on. Same call as R20: **drop and count beats
|
||||||
|
teleport**. Everything ≤6.4% pack-wide.
|
||||||
|
|
||||||
|
### → B / F (the tri diet, ledger #1): my side of the diagnosis
|
||||||
|
My poster cap scales off **shop count**, so it auto-tracked the widening rather than fighting it (fitzroy
|
||||||
|
160 shops → 212 posters; katoomba 80 → 111). If B's layer-by-layer names **plan geometry** (lot or poster
|
||||||
|
counts) as the `venue_night` driver, I own the fix and will take it. My read from the plan side: the growth
|
||||||
|
is *more shops ⇒ more night building geometry at the venue block* — E's density landing as B's render cost —
|
||||||
|
so I'd expect the driver to be buildings/LOD rather than posters. B's numbers decide it, not my expectation.
|
||||||
|
|
||||||
|
**CLOSED — B's diagnosis landed (`0c4dad5`): the fix is not A's.** The driver is E's four furniture GLBs at
|
||||||
|
**149,161 of 163,015 tris (91.5%)** ⇒ **asset tris ⇒ Lane E owns it** by the ledger's own rule. Plan geometry
|
||||||
|
is nowhere near the bill: **posters 222 tris** (B: the cap "already worked and was never the lever"),
|
||||||
|
**buildings 348** (the R3 atlas/instancing holding), venue pools 0. **A is clear on ledger #1 — F, don't wait
|
||||||
|
on me for the re-measure.** Worth recording that B's numbers corrected my expectation too: I guessed
|
||||||
|
buildings/LOD, and B measured it at 0.2%. B also found the real coupling — **shop count doesn't drive this,
|
||||||
|
ROAD density does** (the bench/bin/shelter cadence runs on a 966-edge graph), which means my absorb of E's
|
||||||
|
widening was never the cause either. The brief is a hypothesis; the measurement wins — mine included.
|
||||||
|
|
||||||
|
## Round 21 (2026-07-16) — the venue cluster bias (D's relocation finding) + goldens re-pinned
|
||||||
|
|
||||||
|
### → C + D: **cluster bias landed, goldens re-pinned — start your verifies** (the handshake)
|
||||||
|
**Only the GIG goldens moved** (venue selection is gig-layer; the base town goldens are untouched):
|
||||||
|
katoomba `0xbf586b77`, newtown `0xacdb3eee`, fremantle `0xcf3426fc`, bendigo `0x646b2c23`,
|
||||||
|
castlemaine `0xf9713a69`. Base unchanged (katoomba `0x420f677d`, …). selfcheck **ALL GREEN 66419/66419**;
|
||||||
|
classic `0x3fa36874` / gig `0xb1d48ea1` / marched fixtures **frozen**.
|
||||||
|
|
||||||
|
### → D: your relocation finding is resolved — and it was systemic
|
||||||
|
You found katoomba's pub at 1 shop within 160 m. Measuring all five, **11 of 15 venues** sat under the
|
||||||
|
threshold (newtown's were 0/0/1 — every venue stranded). After the bias, **0 of 15**:
|
||||||
|
|
||||||
|
| town | before (shops within 160 m) | after |
|
||||||
|
|---|---|---|
|
||||||
|
| katoomba | pub **1**, rsl 10, band_room 7 | band_room 10, rsl 12, **pub 9** |
|
||||||
|
| newtown | band_room 0, pub 0, rsl 1 | band_room 7, rsl 11, pub 5 |
|
||||||
|
| fremantle | pub 1, rsl 2, band_room 2 | band_room 8, pub 3, rsl 3 |
|
||||||
|
| bendigo | pub 1, rsl 0, band_room 0 | rsl 10, band_room 10, pub 5 |
|
||||||
|
| castlemaine | band_room 6, rsl 4, pub 1 | pub 5, band_room 6, rsl 4 |
|
||||||
|
|
||||||
|
Castlemaine cleared without needing the floor exception Fable flagged. Your venue-win % and the "gig with
|
||||||
|
no passing crowd" observation should both improve — the pub is now in the retail heart on every town.
|
||||||
|
|
||||||
|
### How it works (and why synthetic is frozen BY CONSTRUCTION, not by luck)
|
||||||
|
`pickVenues` builds a **cluster-adjacent field** first (candidates with ≥ `CLUSTER_MIN` 3 shops within
|
||||||
|
`CLUSTER_R` 160 m), then the kind flavors (pub corner / band_room fringe / rsl off-spine) bias **within**
|
||||||
|
that field rather than overriding it — a fringe band_room in the cluster beats a fringe band_room a
|
||||||
|
kilometre from anyone. A town with no cluster falls back to the whole pool.
|
||||||
|
**Gated on `plan.source === 'osm'`** — real-data towns only. Fable's brief assumed a blanket filter would
|
||||||
|
be a no-op because "the synthetic town is one dense cluster anyway". **It isn't:** I measured **4 synthetic
|
||||||
|
candidates per seed** sitting under the threshold (min cluster 0–1), so a blanket filter *would* have moved
|
||||||
|
the frozen synthetic golden. The `source` gate makes it impossible instead of unlikely. The marched fixtures
|
||||||
|
are 'osm' too but genuinely dense (min cluster 3/11/13) ⇒ verified no-op.
|
||||||
|
|
||||||
|
### FYI: E's v4.0 town pack is already booting clean
|
||||||
|
`glebe_real`, `marrickville_real`, `newcastle_real` landed and my loader auto-validates + boots them through
|
||||||
|
the full structural + gig suites — **all pass**, listed UNPINNED (a non-fatal warn). That's per the brief:
|
||||||
|
**I pin the pack in R22**, and it must not gate the beta tag. Early signal: the pack needs no new hardening.
|
||||||
|
|
||||||
|
## Round 20 (2026-07-16) — v4.0-beta: the poster cap (density absorb pending E's widened caches)
|
||||||
|
|
||||||
|
### → F: **poster cap landed — re-measure `venue_night`** (your +965-tri edge should close)
|
||||||
|
My R19 finding (438 posters on katoomba) is fixed. `gigs.js` now **caps the spine pole run at
|
||||||
|
`POSTER_PER_SHOP × shops` (1.5/shop, floor 12)** — a seeded subsample. Real katoomba **438 → 31 posters**
|
||||||
|
(~2× the synthetic feel), fremantle 32, castlemaine 14. Scaled by **shop count, not edge count**, so the
|
||||||
|
`venue_night` stress view (was 200,965 tris, +0.5% over 200k) should drop well under. Re-measure and close it.
|
||||||
|
|
||||||
|
### Why no base-golden re-pin (and what I DID pin)
|
||||||
|
Posters live in the **gig layer** (`withGigs`), not the base `plan_osm` output — so the pinned real-town
|
||||||
|
**base** goldens are unchanged by the cap (correctly green). To regression-guard the capped output byte-exact
|
||||||
|
(not just via `districtInvariants`), I added a **`REAL_TOWN_GIG_GOLDENS`** map (the full gig plan per town:
|
||||||
|
district + capped posters) — katoomba `0xbd332e83`, etc. Synthetic gig golden `0xb1d48ea1` **frozen** — the
|
||||||
|
cap never binds on the shop-dense synthetic town (493 shops ⇒ cap ~740 ≫ its ~15 posters).
|
||||||
|
|
||||||
|
### → all: **densities absorbed, goldens pinned** (ledger #2 — E's widened caches landed)
|
||||||
|
E's five caches at **3–6× shops** (katoomba 20→80, fremantle 21→80, newtown 18→72, bendigo 9→36,
|
||||||
|
castlemaine 6→24 = 292 total) are absorbed. **All five place 3 venues**; the poster cap auto-scaled off the
|
||||||
|
live shop count exactly as designed (katoomba 111 posters at 80 shops). selfcheck **ALL GREEN 51866/51866**.
|
||||||
|
- **Capacity widened** (the R18 overflow fence, load-tested). At 4× the dominant drop cause was **overflow**
|
||||||
|
(an edge-side holds more shops than fit) — katoomba 10 of 11 drops. Tightened the run: `NODE_CLEAR` 6→3
|
||||||
|
and a **roads-only `RGAP` 2→0.5** (the shared marched `GAP` is untouched ⇒ marched fixtures stay frozen).
|
||||||
|
Drops **7.9% → 5.5%** (16/292). 0.5 m also reads truer — a real AU main street is continuous terrace
|
||||||
|
shopfronts. Residual drops stay **counted, now by cause** (`dropped_overflow` / `_overlap` / `_stranded`).
|
||||||
|
I stopped there deliberately: the next lever is spilling overflow onto a *different* street, which buys
|
||||||
|
~1% at the cost of putting a shop on a road it isn't on. Dropping a crowded shop is the honest trade.
|
||||||
|
- **Venue facade bias now covers `pub`.** R16 exempted pub ("spine-fronted, +Z always open") — true on
|
||||||
|
synthetic/marched, but on a REAL graph at density the pub landed where its frontage poster overhung a
|
||||||
|
cross street (newtown, −0.91 m). The `POSTER_CLEAR` filter now covers every venue kind; still a **no-op on
|
||||||
|
synthetic** (every pub candidate already clears) so the gig golden `0xb1d48ea1` is frozen.
|
||||||
|
- **Goldens re-pinned once** (base + gig, all five): katoomba `0x420f677d` / `0x2115732a`, newtown
|
||||||
|
`0x741c18ec` / `0x17ab31bb`, fremantle `0x73d385df` / `0x82c27397`, bendigo `0xc175194e` / `0xf5ce58c4`,
|
||||||
|
castlemaine `0x26c128c9` / `0x48272da9`. classic/gig/marched fixtures **frozen**.
|
||||||
|
- **Fixed:** my real-cache loader globbed `*.json` and tried to validate E's new `index.json` as a town
|
||||||
|
cache. It now skips it (it's B's selector index, not a cache).
|
||||||
|
|
||||||
|
## Round 19 (2026-07-16) — the alpha close: fragmentation resolved + findings hardened + goldens pinned
|
||||||
|
|
||||||
|
### → B / C / D / F: **`katoomba_real` boots on real roads, golden pinned, findings resolved** (the handshake)
|
||||||
|
The A→E→A finalization landed. `katoomba_real` golden = **`0xb7ffbca2`** (pinned); the other four real towns
|
||||||
|
pinned too (newtown `0x6f984c81`, fremantle `0xfb197000`, bendigo `0x21179b3b`, castlemaine `0x7c47f639`).
|
||||||
|
selfcheck **ALL GREEN 51670/51670**; classic/gig/marched-fixtures frozen (`0x3fa36874` / `0xb1d48ea1` / …).
|
||||||
|
|
||||||
|
### The fragmentation fix (D's finding — Fable's ruling, my details)
|
||||||
|
**katoomba: 105 components → 1; 58% → 100% of street-metres in the main net.** Two mechanisms:
|
||||||
|
1. **Junction-protected Douglas–Peucker** (the root cause). A point shared by ≥2 ways is a junction;
|
||||||
|
simplify only *between* junctions, never through one — the collinear junction-drop where a side street
|
||||||
|
tees into a straight main road. This alone took every town to 94–100% main-net.
|
||||||
|
2. **Cull/bridge** (`JOIN_TOL` 12 m, shop-island bridge ≤ 200 m): main component + joined near-fragments;
|
||||||
|
far **shopless** islands culled (bbox-clipped noise + dead streaming budget = half of B's tris problem).
|
||||||
|
Per-town: katoomba 2 islands culled/0 bridges, newtown 3/4, fremantle 4/0, bendigo 3/1, castlemaine 0/1.
|
||||||
|
**Every dropped shop is counted** in `norm.dropped` (ruling): katoomba kept 19/20 (1 overflow/overlap
|
||||||
|
crowd drop — the R18 overflow prediction, now measured + counted), the rest kept all.
|
||||||
|
|
||||||
|
### → D: your re-measure inputs (post-fix)
|
||||||
|
Component count → **1** (all towns). Street-metres in main net → **100%**. Stranded shops → **0** (all
|
||||||
|
connected or counted-dropped). Near-crowd on fragments should collapse to ~0 (one component). The crafted
|
||||||
|
irregular-town scaffold still runs; the real katoomba golden is pinned so your reruns are stable.
|
||||||
|
|
||||||
|
### → F: E's poster finding resolved
|
||||||
|
Spine posters that overhang a **real crossing carriageway** are now skipped (measured exactly as the
|
||||||
|
selfcheck kerb floor). No-op on synthetic (gig golden frozen); the 23 real-town clearance failures → 0.
|
||||||
|
|
||||||
|
### New finding (v4.0-beta, non-blocking): spine-poster density scales with edges
|
||||||
|
Real katoomba emits **438 posters** (2 per main edge × a dense real graph) vs ~14 synthetic. Draws stay low
|
||||||
|
(instanced per gigId, B measured ~1 draw), and every poster clears its kerb — but 438 pole-posters town-wide
|
||||||
|
reads busy. **Beta candidate:** cap posters-per-town (seeded subsample of the spine run). Not fixed this
|
||||||
|
round — invariants hold, it's density polish.
|
||||||
|
|
||||||
|
## Round 18 (2026-07-16) — v4.0-alpha REAL ROADS: cache schema v2 (roads[]) + the real-graph lift
|
||||||
|
|
||||||
|
### → E: **schema v2 is live (committed first) — fetch roads to it**
|
||||||
|
A v2 cache adds `roads: [ { kind, pts:[[lat,lon],…], id?, name? } ]` (`kind` = OSM `highway=*`). Full
|
||||||
|
contract in `web/assets/towns/README.md`; `validateTownCache` enforces it (schema `…/1` still valid →
|
||||||
|
marched). **`katoomba_real` first** (the alpha gate). Bound the `highway=*` query to the same per-town bbox
|
||||||
|
as the shops. Snap tolerance is 3 m, so ship intersections as **shared coordinates** where ways meet (OSM
|
||||||
|
already does) — that's how the lift finds real intersections. Drop each cache in; the selfcheck auto-builds
|
||||||
|
+ validates the real graph and prints the golden. Ping me → I pin `katoomba_real`'s real-roads golden.
|
||||||
|
|
||||||
|
### The graph lift (`plan_osm.buildRealRoads`) — implemented + proven on a synthetic irregular graph
|
||||||
|
Real OSM ways → the CityPlan street graph, **same schema out** (B/C/D/F unchanged by contract). Pipeline:
|
||||||
|
project → simplify (Douglas–Peucker ε≈6 m) → snap coincident points (≤3 m) to shared nodes = real
|
||||||
|
intersections → straight-segment edges → classify `main` by OSM class + shop density → seat each shop on
|
||||||
|
its **nearest real edge** (real order, real side), marched, overlap-resolved. Passes the **full
|
||||||
|
structuralSuite + gig district + determinism** on a synthetic town with a main street, two cross streets
|
||||||
|
(a real intersection + an acute angle), a dead-end lane and a curve. `roads` absent ⇒ the v1 marched
|
||||||
|
fallback — **all osm goldens frozen** (melbourne/katoomba/silverton), classic/gig frozen. NOT YET run
|
||||||
|
against real katoomba geometry — that's the A→E→A finalization (E fetches, I pin + harden).
|
||||||
|
|
||||||
|
### Fidelity decision (charter risk #4 — A owns the knob) + the alpha failure list
|
||||||
|
I chose **real edge + real order + real side, regularised spacing** — NOT pixel-exact shop positions,
|
||||||
|
because real shops overlap and the no-overlap invariant is load-bearing (venues, chunk streaming, sim all
|
||||||
|
assume it). Written down for the alpha (fixed or fenced):
|
||||||
|
1. **Overflow drop.** A short real edge can't seat all its shops (marched at footprint+gap); overflow shops
|
||||||
|
drop (real order kept). `norm.dropped` counts them. Katoomba (20 shops, real street lengths) should keep
|
||||||
|
all — verify when E's roads land. *Fence if it bites: widen edge capacity or split long shop runs.*
|
||||||
|
2. **Intersection topology = shared coordinates only.** Two ways that cross WITHOUT a shared OSM node
|
||||||
|
become topologically-disconnected edges (visually crossing, not graph-connected). **→ D:** the sim graph
|
||||||
|
may not route across such crossings; if Katoomba has any, that's the risk to measure. E ships shared
|
||||||
|
coords at junctions (OSM convention) so most are fine.
|
||||||
|
3. **Blocks are per-edge frontage strips**, not planar faces. **→ B:** your ground/pavement render reads
|
||||||
|
these polys; a per-edge strip may look thin/overlapping at a tight corner — audit + file with a
|
||||||
|
measurement (it's my lot geometry to fix, not yours).
|
||||||
|
4. **Overlap-resolve drops crowded-intersection lots** (deterministic, by shop id). Rare; counted in
|
||||||
|
`norm.dropped`.
|
||||||
|
|
||||||
|
### → B/C/D: handshake state
|
||||||
|
`katoomba_real` still MARCHES today (it's v1 — E hasn't fetched roads yet), so I can't yet say "boots with
|
||||||
|
real roads, golden pinned." I'll fire that handshake the moment E's katoomba roads land and I pin the
|
||||||
|
golden. Per Fable's note, your audit prep (scaffolding, assertion lists) isn't blocked on me — the graph
|
||||||
|
lift + the failure list above tell you exactly what irregular geometry to point your audits at.
|
||||||
|
|
||||||
|
## Round 17 (2026-07-16) — the town-cache contract (published FIRST) + plan_osm hardened for real data
|
||||||
|
|
||||||
|
### → E: **the town-cache contract is live — build `build_towns.py` to it** (half-day handshake)
|
||||||
|
Your pipeline outputs one JSON per real town in the processed shape; `plan_osm` marches it like the
|
||||||
|
fixtures. **Home: `web/assets/towns/<key>.json`** (full spec in that dir's `README.md`). Shape:
|
||||||
|
```
|
||||||
|
{ schema:"procity-town-cache/1", key, town, source:"osm", license, attribution,
|
||||||
|
center:{lat,lon}, shops:[ {id, name, type, lat, lon, suburb?} ] } // + optional bbox/counts/fetchedAt
|
||||||
|
```
|
||||||
|
- **Authoritative validator:** `validateTownCache(cache)` in `plan_osm.js` — `{ok, errors[], warnings[]}`.
|
||||||
|
Import it; don't hand-roll the check. **Hard errors:** finite `center.{lat,lon}`, `shops` ≥ **6**
|
||||||
|
(`MIN_TOWN_SHOPS`), finite shop `lat`/`lon`. **Warnings (absorbed):** blank name → type label, unknown
|
||||||
|
`type` → `opshop`, dup ids, **span > 5 km**.
|
||||||
|
- `type` should be a registry `SHOP_TYPE`; map OSM `shop=*` tags to those (unknowns → `opshop`).
|
||||||
|
- **Katoomba-real:** use a DISTINCT key (`katoomba_real`, not `katoomba`) so it sits alongside the
|
||||||
|
hand-made fixture for the comparison — `osmTownKeys()` dedupes but same-key would shadow the fixture.
|
||||||
|
- Drop each cache in and the selfcheck auto-validates + boots + prints its golden to pin; ping me
|
||||||
|
(E→A handshake) and I'll pin `REAL_TOWN_GOLDENS` + it enters the sweep.
|
||||||
|
- **ODbL:** ship `SOURCES.md` + the `license`/`attribution` fields; cache raw Overpass so re-runs don't refetch.
|
||||||
|
|
||||||
|
### plan_osm hardening — verified against real data (2,828 real Canberra shops)
|
||||||
|
I ran thriftgod's real `city_source.json` through the hardened `plan_osm`: **it boots a valid plan** (0
|
||||||
|
bad-coord lots, 0 "undefined" names). The marching regularises messy real coords, so dup/odd positions are
|
||||||
|
a non-issue. Added: blank-name → registry-label default; the contract validator gates the rest.
|
||||||
|
|
||||||
|
### → Fable / v4 charter — the ugly-real-geometry RISK LIST (the scout's real finding)
|
||||||
|
1. **The mega-strip (the big one).** An unbounded Overpass bbox = a whole region. All 2,828 Canberra shops
|
||||||
|
marched into an **11.6 km** single strip (350 shops/avenue) — a valid plan, an unusable "town". **Mitigation
|
||||||
|
shipped:** `validateTownCache` warns at span > 5 km; the contract says a cache is ONE compact town. **v4
|
||||||
|
risk:** town SELECTION (bbox per town) is E's pipeline's job and the real quality lever — the generator
|
||||||
|
can't rescue an over-broad query. Charter needs a per-town bbox/centre discipline.
|
||||||
|
2. **Sparse compact towns.** A tight 2 km radius of inner Canberra held only 6 secondhand shops — real small
|
||||||
|
towns can fall below a usable density. `MIN_TOWN_SHOPS` is 6 (the functional floor for the district); E
|
||||||
|
should curate towns with real shopping density (Katoomba 19, Melbourne 95 clear it easily).
|
||||||
|
3. **Not a risk (settled):** osm facades face `−Z`/south with rows a fixed 54 m apart, so the R16 `+Z`
|
||||||
|
clearance bias is a no-op on osm towns (facades always clear) — real towns won't trip the frontage floor.
|
||||||
|
|
||||||
|
### Scope held: no new plan features, no golden moved
|
||||||
|
Classic `0x3fa36874`, gig `0xb1d48ea1`, all three osm fixtures frozen. selfcheck **15277/15277** (added the
|
||||||
|
contract stress suite + the ready-and-empty real-cache path). The scout proved the *existing* contract eats
|
||||||
|
real data — nothing more, per the fence.
|
||||||
|
|
||||||
|
## Round 16 (2026-07-16) — corner-poster fix via pickVenues clearance bias (ledger #6) + no-spine osm fixture (#7)
|
||||||
|
|
||||||
|
### → B + F: **new gig golden `0x4f4a549d → 0xb1d48ea1`** (classic/synthetic `0x3fa36874` unchanged)
|
||||||
|
The round's one sanctioned move. **F**: the default-boot gate pins to `0xb1d48ea1` (not the R15 value the
|
||||||
|
brief cited). **B**: re-shoot against this. Amendment law; §v3 marker records it.
|
||||||
|
|
||||||
|
### The corner-poster fix (NOT a nudge — Fable's brief premise was wrong)
|
||||||
|
The R15 finding was that a corner venue's frontage poster sits near a crossing kerb. The brief proposed
|
||||||
|
nudging the poster ALONG the facade away from the crossing-street *end* — but I proved that's geometrically
|
||||||
|
impossible: the crossing edge is **parallel** to the facade and spans it (0/187 along-facade slides recover
|
||||||
|
any clearance). Root cause: band_room/rsl land on **through-lots on narrow warehouse-fringe blocks**, so the
|
||||||
|
`+Z` wall (B's door side, where the poster seats) faces the block's *other* street, overlapping its
|
||||||
|
carriageway by up to ~3 m. **Fable's ruling (asked mid-round): bias `pickVenues`.** So:
|
||||||
|
- `gigs.js pickVenues` now prefers a band_room/rsl candidate whose `+Z` facade clears every carriageway by
|
||||||
|
**≥ `POSTER_CLEAR` (0.5 m)**; if the warehouse fringe is uniformly narrow it broadens to any off-spine
|
||||||
|
clear lot before falling back. Proven: **0 of 851** frontage posters across 400 seeds now sit below the
|
||||||
|
floor (worst clearance 0.51 m). Pub is untouched (spine-fronted, `+Z` open — the hero shot doesn't move).
|
||||||
|
- The R15 blanket frontage *exemption* in the selfcheck is now a real **floor**: frontage posters must clear
|
||||||
|
their kerb by ≥ `POSTER_CLEAR`; spine posters keep the ≥ 0 no-bitumen floor. `POSTER_CLEAR` is exported
|
||||||
|
from the barrel — import it, don't re-guess. (`−0.02` in the assert absorbs r2 poster-coord rounding.)
|
||||||
|
|
||||||
|
### The no-spine osm fixture (ledger #7): `silverton`
|
||||||
|
`osm_fixture.js` gains **`silverton`** — a *constructed* single-row town (all shops at one latitude ⇒
|
||||||
|
plan_osm bins them into one avenue ⇒ **0 `main` edges**). Closes the R14 sweep gap (both real fixtures had a
|
||||||
|
spine): it proves `pickVenues`' no-spine branch (pub's `onMain` filter is empty) and `buildPosters`' no-main
|
||||||
|
fallback (spine run over every edge). Base golden pinned `0xd4b351c9`; the sweep asserts 0 main edges +
|
||||||
|
graceful 2–4-venue placement. `osmTownKeys()` now returns 3 towns — anything iterating it (shell selector,
|
||||||
|
your gates) picks it up; it's a valid town, `?plansrc=osm&town=silverton`.
|
||||||
|
|
||||||
|
### → F: the classic-law / flags-table §v3 amendment is yours
|
||||||
|
I amended §v3 for the golden + poster bias only (localized, recorded in the FROZEN marker). The v3.1 flip
|
||||||
|
(default-on, `?classic=1` covenant, the flags table) is your §v3 amendment — I left the prime-flag-law
|
||||||
|
blockquote untouched to avoid racing you on the same lines. My classic golden (`0x3fa36874`) is your
|
||||||
|
`?classic=1` regression target; my gig golden (`0xb1d48ea1`) is your default-boot target.
|
||||||
|
|
||||||
|
## Round 15 (2026-07-16) — the frontage-poster +Z flip (ledger #1) + the one sanctioned golden move
|
||||||
|
|
||||||
|
### → B + F: **new gig golden `0x1f636349 → 0x4f4a549d`** (synthetic `0x3fa36874` unchanged)
|
||||||
|
The frontage-poster fix moved `plan.posters`, so the gig golden re-pinned — the round's ONE sanctioned
|
||||||
|
move (amendment law; §v3 marker records it). B: re-shoot `queue_night` against this. F: the smoke's gig
|
||||||
|
golden is now `0x4f4a549d`.
|
||||||
|
|
||||||
|
### The fix (gigs.js buildPosters item 1)
|
||||||
|
Each venue's own-frontage poster sat at local **−Z** — the *back* of the building (~16 m behind, matching
|
||||||
|
B's 15.9 m), invisible from the street, which is why `queue_night` framed no poster. Flipped to the **+Z
|
||||||
|
street facade** AND turned it to read from the street: `ry = lot.ry + π` so its printed face points `+Z`
|
||||||
|
at the queue-side camera (else `DoubleSide` shows a mirrored band name). Proven: pub + rsl frontage posters
|
||||||
|
now sit **0.06 m from B's door**, printed-face·(+Z street) = **1.000**. Only item 1; spine posters untouched.
|
||||||
|
|
||||||
|
### The −Z/+Z convention, settled (worth recording — it's the R13 RY_FLIP π-ambiguity again)
|
||||||
|
The **visible** venue face is `+Z`: `buildings.js` builds the facade/door at `zf = +d/2` ("front face at
|
||||||
|
local +Z"), and `venue.js` seats the door-glow/queue/poster-camera on `(sin ry, cos ry)` = `+Z`. The
|
||||||
|
selfcheck's `facing = (−sin ry,−cos ry)` "every lot faces its frontEdge" is the **sim/GLB** convention,
|
||||||
|
180° off from the visual front (same ambiguity D fixed at the source for the fleet in R13). A poster faces
|
||||||
|
the *viewer* when its printed face = `(−sin ry,−cos ry)` points at them, i.e. `ry = atan2(away-from-viewer)`.
|
||||||
|
|
||||||
|
### → B + F (eyeball in the tour): corner-venue frontage posters can sit near a crossing street
|
||||||
|
The `+Z` facade is the block-interior side in *plan* coordinates, so for a **corner venue** (a band_room/
|
||||||
|
rsl fronting one street with another behind — e.g. seed 1 band_room lot#481, 1.58 m from a side-street
|
||||||
|
centreline) the frontage poster lands near that crossing street's kerb. That's B's building geometry (the
|
||||||
|
door is there too), not a mid-road defect — so the selfcheck's no-bitumen assert now **exempts frontage
|
||||||
|
posters** (they're building-bound) and keeps it for free-standing **spine** posters. The pub hero is clean
|
||||||
|
(29.9 m of open ground on `+Z`). If a corner poster reads as floating over a road in the tour, that's a
|
||||||
|
v3.1 venue-placement tweak, not a v3.0 blocker.
|
||||||
|
|
||||||
|
## Round 14 (2026-07-16) — v3.0 RELEASE: invariant sweep + CITY_SPEC §v3 frozen
|
||||||
|
|
||||||
|
Release round — no new systems, no golden moves. **Synthetic `0x3fa36874` and gig `0x1f636349` are
|
||||||
|
unchanged**; the only code I touched is `selfcheck.js` (test-only), so no seeded stream gained a draw.
|
||||||
|
|
||||||
|
### The 400-seed district sweep (`selfcheck.js`, ALL GREEN 14264/14264 in ~0.7s)
|
||||||
|
- Factored the district-contract checks out of `gigSuite` into `districtInvariants(plan, label)` so the
|
||||||
|
sweep asserts them at SCALE without re-running the heavy `structuralSuite` (that still runs on the 6
|
||||||
|
hero `SEEDS` + osm parity). Behaviour-preserving — the 4 hero-seed `gigSuite` runs are byte-identical.
|
||||||
|
- Sweeps **seeds 1–400** synthetic + the large edge seeds (2e9, 8675309) asserting the full district
|
||||||
|
contract each: 2–4 venues, ≥1 pub, never the openLate shop, no band twice in one night town-wide,
|
||||||
|
per-venue 4–6 nights, every poster off the carriageway. Also asserts the seeded count **spans {2,3,4}**
|
||||||
|
across the range (measured 146/121/133) — proves the 2–4 range is genuinely exercised, not pinned.
|
||||||
|
- **osm graceful placement**: `katoomba` (smallest fixture, 19 shops, **no warehouse district**) still
|
||||||
|
lands 2–4 valid venues incl. a pub for every hero seed — proving `pickVenues`' off-spine fallback.
|
||||||
|
Honest scope: both osm fixtures keep a main spine, so this proves the *no-warehouse* branch, not
|
||||||
|
*no-spine* (a genuine no-spine town would need a single-row fixture that doesn't exist yet — v3.1).
|
||||||
|
- Poster check kept as "≥ `roadWidth/2` from the nearest edge centreline" (not a literal `poleOffset`
|
||||||
|
compare, which would false-fail legit corner posters seated behind their placement edge's `poleOffset`
|
||||||
|
but inside a crossing edge's clearance band). Added a `−1e-6` epsilon on the strict `<` to immunise the
|
||||||
|
exact kerb-line boundary against r2 rounding (only reachable on a no-main-spine fallback; not hit today).
|
||||||
|
|
||||||
|
### CITY_SPEC §v3 frozen at v3.0
|
||||||
|
Final wording pass; marked **FROZEN at `v3.0` (round 14)** with a §v2-style marker. Venues table, genre
|
||||||
|
map, gigKey contract, corridor law, and gig golden `0x1f636349` are all final (genres are pubrock/grunge/
|
||||||
|
covers — Fable's R14 ruling, John did not re-flavour).
|
||||||
|
|
||||||
|
### → Lane F: the alias-retirement handshake (R13 debt #1) — I deliberately did NOT touch it
|
||||||
|
The R12 single-venue compat alias is **still live** (`gig_state.js:141-151`, read by `audio.js`/`venue.js`),
|
||||||
|
so I left the §v3 alias sentences (`CITY_SPEC.md` ~lines 271–272) **intact** — deleting them at freeze
|
||||||
|
time would be doc-ahead-of-code and would race you on the same lines. The freeze marker explicitly carves
|
||||||
|
this out as your pending edit. In your alias-retirement commit (after B drops its readers), delete both
|
||||||
|
`gig_state.js:141-151` and those two spec sentences **together** — one atomic step, one deleter, no race.
|
||||||
|
- While you're in that runtime block: it enumerates `stateOf/onOf/openOf/gigOf/coverOf/bandNameOf/paidOf`
|
||||||
|
+ `markPaidOf` + `.venueShopIds`, but **omits `nightOf(id)`** (`gig_state.js:131`). Add it when you edit.
|
||||||
|
|
||||||
|
### → Lane B: second edit site for the bare-string arm
|
||||||
|
When you remove the `venue.update()` bare-string alias arm, note `venue.js:228` also self-calls
|
||||||
|
`update('quiet')` at construction through that same arm — fix both or it falls through (harmless today:
|
||||||
|
still yields `'quiet'`, but it's a live second site).
|
||||||
|
|
||||||
|
## Round 13 (2026-07-15) — v3.0-beta THE DISTRICT (published FIRST; C/D/B/F hang off this)
|
||||||
|
|
||||||
|
Supersedes the R12 alpha interface below. Same flag (`?gigs=1`), same prime-flag law: flag OFF ⇒ plan
|
||||||
|
byte-identical to v2 (synthetic golden still `0x3fa36874`, osm unchanged). What changed: **one venue → 2–4;
|
||||||
|
one genre → three (by kind); one night's posters → town-wide.** The gig **entry shapes are unchanged** —
|
||||||
|
downstream code that read one venue now iterates many. Verified green: `selfcheck.js` 3273/3273,
|
||||||
|
`scaffold_check` + `consistency` GREEN.
|
||||||
|
|
||||||
|
**Gig golden re-pinned: `0xa6ae5a5e` → `0x1f636349`** (the district changed gig-plan output; re-pinned in
|
||||||
|
the same commit as the change per CITY_SPEC amendment law). Turn-on is unchanged:
|
||||||
|
`generatePlanFor(seed, src, { town, gigs:true, customBands })`.
|
||||||
|
|
||||||
|
### The venues (NEW: 2–4 per town, three kinds) — `plan.shops.filter(s => s.venue)`
|
||||||
|
Each converted-in-place venue shop carries three new fields:
|
||||||
|
```
|
||||||
|
s.venue === true · s.venueKind ∈ {'pub','band_room','rsl'} · s.genreKey ∈ {'pubrock','grunge','covers'}
|
||||||
|
s.type === s.venueKind // C keys its interior recipe off shop.type — the type IS the kind
|
||||||
|
```
|
||||||
|
| kind | genreKey | placement | hours | reads as |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| `pub` | `pubrock` | spine end/corner | `[17,23]` | ≥2-storey corner hotel |
|
||||||
|
| `band_room` | `grunge` | warehouse fringe | `[18,23]` | single-storey tin shed |
|
||||||
|
| `rsl` | `covers` | off-spine | `[16,23]` | carpet-and-bistro club |
|
||||||
|
- Seeded count 2–4; a 4th venue is a **second pub** at the far spine end. A town always has ≥1 pub.
|
||||||
|
- **Never** the openLate landmark (still exactly one openLate shop). Placement degrades gracefully so osm
|
||||||
|
towns (no warehouse district, maybe no spine) still get their venues.
|
||||||
|
- `genreKey` comes from the registry kind→genre map: `genreForVenueKind(kind)` / `SHOP_TYPES[kind].genre`.
|
||||||
|
|
||||||
|
### THE gigKey CONTRACT (debt #1 — import it, don't build the string)
|
||||||
|
The audio-manifest bed key **IS** the gigKey and is **always** `gig-<genreKey>`. No mapping table anywhere:
|
||||||
|
```js
|
||||||
|
import { gigKeyFor } from './citygen/index.js'; // re-exported from the barrel
|
||||||
|
gigKeyFor('pubrock') === 'gig-pubrock' // C sets audio.gigKey = gigKeyFor(genreKey); E names the bed the same; B reads it; F resolves it
|
||||||
|
```
|
||||||
|
R12 lost a bed to a private rename (`gig-pubrock` vs `pubrock-live`) — audio fails soft, so the band just
|
||||||
|
mimed. One key, zero mapping. E renames `pubrock-live → gig-pubrock` this round; F deletes the `GIG_BED` bridge.
|
||||||
|
|
||||||
|
### `plan.gigs` — the week across the whole district (entry shape UNCHANGED)
|
||||||
|
```
|
||||||
|
plan.gigs = [ { gigId, venueShopId, bandName, genreKey, night, startSeg, endSeg, cover }, … ]
|
||||||
|
```
|
||||||
|
- **7 nights × every venue, but not every venue plays every night**: each venue plays a seeded **4–6** of
|
||||||
|
the 7, dark the rest (reads true). `gigId` unique **town-wide**; `night ∈ [0,7)`; `genreKey ≡` its venue's.
|
||||||
|
- **Pick tonight per venue** (each venue has its own state now): `plan.gigs.filter(g => g.venueShopId === v.id && g.night === 0)[0]` — **may be undefined** (venue dark tonight; F/B skip it, no poster).
|
||||||
|
- `startSeg=5 endSeg=5`. Same `quiet → doors(seg4) → on(seg5) → done` machine — F runs one **per venue**.
|
||||||
|
- `cover` ∈ `{0}∪[2,10]`, skewed by kind: pub 50% free/`$2–10`, RSL 45% free/`$2–5`, band_room 70% free/`$2–6`.
|
||||||
|
F debits **per venue** — a night at two venues is two covers.
|
||||||
|
- **No band plays two venues on one night** (guaranteed by A). Custom names spread night-major across the
|
||||||
|
whole week town-wide (deduped, priority-seeded), the rest generated.
|
||||||
|
|
||||||
|
### `plan.posters` — town-wide now (shape UNCHANGED; **Lane B** renders)
|
||||||
|
```
|
||||||
|
plan.posters = [ { id, gigId, x, z, ry }, … ] // only TONIGHT's (night 0) playing venues advertised
|
||||||
|
```
|
||||||
|
- One on each playing venue's **own frontage** (on the facade plane) + a seeded spine run (2 per main edge).
|
||||||
|
- Different venues' posters share the same pole runs — that IS the district reading. `p.gigId` tells B/F
|
||||||
|
which venue's gig each poster sells (`plan.gigs.find(g=>g.gigId===p.gigId)`).
|
||||||
|
- **Debt #5 fixed:** posters no longer land mid-road. See the corridor law ↓.
|
||||||
|
|
||||||
|
### The street-corridor law (debt #5) — NEW helpers, use for ANY street furniture
|
||||||
|
`edge.width` is the **full corridor** (carriageway + verge, centreline ± width/2), NOT the carriageway.
|
||||||
|
`node.x + 2` seats furniture on the bitumen. Registry now exports the split (re-exported from the barrel):
|
||||||
|
```js
|
||||||
|
import { roadWidth, vergeBand, poleOffset } from './citygen/index.js';
|
||||||
|
roadWidth(edge) // carriageway half-corridor (main 10m, side 6–8, lane=all, arcade 0)
|
||||||
|
vergeBand(edge) // [inner,outer] — where footpath furniture belongs
|
||||||
|
poleOffset(edge) // where a pole/poster/post seats: just behind the kerb
|
||||||
|
```
|
||||||
|
Selfcheck now **asserts** no poster stands within `roadWidth/2` of any edge centreline.
|
||||||
|
|
||||||
|
### → Lane C: two recipes to add + the gigKey + a corrected field
|
||||||
|
- `getRecipe(shop.type)` resolves off `RECIPES[type]` — `RECIPES.pub` exists; **`RECIPES.band_room` and
|
||||||
|
`RECIPES.rsl` do NOT yet.** Until you add them, those venues fall back to the op-shop interior. That's
|
||||||
|
the handshake — you're next in order.
|
||||||
|
- Set `room.audio.gigKey = gigKeyFor(shop.genreKey)` (import from the barrel) — never hand-build `'gig-…'`.
|
||||||
|
- Registry `pub.interior` was `'band_room'` (harmless, unread — you key off `shop.type` via `canonicalType`);
|
||||||
|
I corrected it to `'pub'` so it isn't a name-collision with the new `band_room` venue type. No behaviour change.
|
||||||
|
|
||||||
|
### → Lane D: iterate the venues, one setGig each
|
||||||
|
`const venues = plan.shops.filter(s => s.venue)` — call `setGig` **per venue** (multiple concurrent). Tonight's
|
||||||
|
gig per venue as above (skip dark ones). `isOpen(shopOrHours, hour)` unchanged (half-open law). Queue zone
|
||||||
|
comes from B (`venue.queueZone`) — consume if present, else a straight line off the door.
|
||||||
|
|
||||||
|
### → Lane F: state machine goes per-venue
|
||||||
|
One latch **per venue keyed by `venueShopId`**, off the same `procity:segment` spine. `gigKeyFor(genreKey)`
|
||||||
|
resolves all three genres (the debt-#1 assert now covers 3). Delete `GIG_BED` once E's rename lands. Cover
|
||||||
|
charge is per venue (per-venue paid stamps). Keep the alpha `window.PROCITY.gigs` shape as a compat alias
|
||||||
|
for one round (B reads it mid-round).
|
||||||
|
|
||||||
|
## Round 12 (2026-07-15) — v3.0-alpha GIG INTERFACE (published; C/D/B/F build off this)
|
||||||
|
|
||||||
|
Everything is behind `?gigs=1` (prime flag law): with the flag OFF the plan is **byte-identical** to
|
||||||
|
v2 (all goldens frozen — synthetic `0x3fa36874`, osm unchanged). The gig layer is a post-hoc
|
||||||
|
augmentation applied by the selector, so it works on synthetic AND osm towns.
|
||||||
|
|
||||||
|
### How the gig layer turns on (Lane F wires this)
|
||||||
|
```js
|
||||||
|
// F's shell bootstrap, when ?gigs=1:
|
||||||
|
const customBands = await loadCustomBands(); // see drop-in below; [] under ?noassets or if absent
|
||||||
|
const plan = citygen.generatePlanFor(seed, src, { town, gigs: true, customBands });
|
||||||
|
```
|
||||||
|
`generatePlanFor(seed, source, { gigs:true, customBands })` → base plan + `withGigs`. Without
|
||||||
|
`gigs:true`, the base plan is returned untouched. `generatePlanFor` stays **synchronous** — F does the
|
||||||
|
async `custom_bands.json` fetch in the bootstrap and passes the array in.
|
||||||
|
|
||||||
|
### `plan.gigs` — the nightly schedule (NEW, present only when gigs on)
|
||||||
|
```
|
||||||
|
plan.gigs = [ { gigId, venueShopId, bandName, genreKey, night, startSeg, endSeg, cover }, … ]
|
||||||
|
```
|
||||||
|
- 7 nightly entries at the one venue. `night` = 0-based index; **F picks tonight's** (alpha: `gigs[0]`).
|
||||||
|
- `venueShopId` = the pub shop's id. `genreKey` = `'pubrock'` (alpha's one genre; E ships that bed).
|
||||||
|
- `startSeg=5` (NIGHT, 22:00) `endSeg=5`. F's state machine: `quiet → doors (startSeg−1 = DUSK seg4)
|
||||||
|
→ on (seg5) → done`. Segment→hour map is lighting.js (`0 DAWN … 5 NIGHT`).
|
||||||
|
- `cover`: integer. **~half are 0 (free, walk in); the rest $2–$10** (F debits the wallet at the door).
|
||||||
|
|
||||||
|
### The venue (one `pub` per town)
|
||||||
|
- A spine-END plain shop is converted in place to `type:'pub'` (keeps its lot id + geometry ⇒ no
|
||||||
|
overlap, enterable like any shop). Tagged **`shop.venue === true`**; also `plan.shops.find(s=>s.type==='pub')`.
|
||||||
|
- **Never** the openLate landmark (the video survives — the town still has exactly one openLate shop).
|
||||||
|
- Registry `pub` type added: facades, `interior:'band_room'` (→ **Lane C** builds the pub archetype off
|
||||||
|
this), fittings hints, hours `[17,23]`. It has **no district weights** so it's never auto-placed.
|
||||||
|
- Venue hours `[17,23]` are open through DUSK+NIGHT so the gig runs and **D's patronage surge** fills it.
|
||||||
|
|
||||||
|
### `plan.posters` — gig advertising (NEW; **Lane B** renders)
|
||||||
|
```
|
||||||
|
plan.posters = [ { id, gigId, x, z, ry }, … ] // ~6, advertising tonight's gig (gigs[0])
|
||||||
|
```
|
||||||
|
One on the venue frontage + a seeded few along the spine, on the poles/walls B already streams. B
|
||||||
|
places E's poster skin at `(x,z)` rotated `ry`. F wires "poster → tonight's-gig discoverability".
|
||||||
|
|
||||||
|
### → Lane D: the closing-time debt is PAID — consume `isOpen`
|
||||||
|
`citygen.isOpen(shopOrHours, hour)` is the canonical **half-open** law (`open ≤ hour < close`) — the
|
||||||
|
frozen closing-time ruling as code. Use it for the gig-night patronage surge: the venue is open through
|
||||||
|
the gig, patrons enter while open, and at close they **drain** (finish their dwell) — never popped, and
|
||||||
|
the player is never force-ejected. Your `sim.js _openAt` already matches; switch to `isOpen` so there's
|
||||||
|
one source of truth (or keep yours — they're identical).
|
||||||
|
|
||||||
|
### → John: the band-name drop-in (`web/assets/custom_bands.json`)
|
||||||
|
Edit the file, reload — your names get **priority** on the town's posters. Schema:
|
||||||
|
`{ "bands": ["The Feral Galahs", …] }`. **Delete it and the generator takes over** (no errors, no fetch
|
||||||
|
under `?noassets=1`). Deterministic given (seed, file contents). An example ships with 10 names; a town
|
||||||
|
has 7 nightly slots, so up to 7 of yours surface (shuffled by seed).
|
||||||
|
|
||||||
|
## Round 9 (2026-07-15)
|
||||||
|
|
||||||
|
### → Lane D/F: closing-time occupant ruling (you already built it right — this ratifies + freezes it)
|
||||||
|
|
||||||
|
Question: what happens to a ped browsing inside a shop when it hits closing time? **Ruling** (now a
|
||||||
|
frozen contract law — CITY_SPEC §"CityPlan v2 → Closing-time occupant ruling"):
|
||||||
|
1. **No new entry when closed.** Openness is half-open: `open ≤ h < close` (a shop closing at 17 is shut
|
||||||
|
at 17:00). Patronage only routes into an *open* shop; the player gets the CLOSED plate. ✓ your
|
||||||
|
`sim.js _openAt` + `_nearestOpenShop` already do exactly this.
|
||||||
|
2. **Occupants drain, they don't pop.** A ped inside at close finishes its bounded 5–20s dwell and
|
||||||
|
re-emerges normally — **never** teleported/culled at the close instant. `occupancyOf(shopId)` falls
|
||||||
|
to 0 within one dwell window on its own, so browser rigs (the C→D→F seam) clear with the same drain.
|
||||||
|
✓ your `patronTimer`/`_emerge` already do this — do NOT add a hard close-time eviction.
|
||||||
|
3. **Player is never force-ejected** — closing locks *entry* only; a player inside stays until they leave.
|
||||||
|
4. **The openLate video shop** is the one place still filling after ~21:00; at its own close it drains
|
||||||
|
identically. All seeded + clock-driven ⇒ deterministic occupancy.
|
||||||
|
No code change requested of you — this pins the behavior so it can't drift, and documents it for v3.
|
||||||
|
|
||||||
|
### → everyone / F: the CityPlan v2 producer contract is FROZEN
|
||||||
|
|
||||||
|
CITY_SPEC now has a **"CityPlan v2 — frozen producer contract"** section: producer API, the goldens
|
||||||
|
table (synthetic `0x3fa36874`, osm/melbourne `0x34cfdec0`, osm/katoomba `0x0f652510`), the town-key
|
||||||
|
mechanism, osm normalization rules, and the hours/openLate/storeys/closing laws — all matching code
|
||||||
|
(F: diff away). Changes to Layer-1 now require a CITY_SPEC amendment + golden re-pin in the same commit.
|
||||||
|
|
||||||
|
## Round 8 (2026-07-15)
|
||||||
|
|
||||||
|
### → Lane F: OSM is at parity + there's a SECOND town. Three goldens to pin, one new selector arg.
|
||||||
|
|
||||||
|
- **Selfcheck parity done**: the full invariant suite (`structuralSuite`) now runs on **every
|
||||||
|
(source, town)** — synthetic and both OSM towns get identical coverage (storeys, one-shop-per-lot,
|
||||||
|
facing, within/cross-block overlap, corridor coverage, determinism, exactly-one-openLate, …).
|
||||||
|
`node selfcheck.js` → **ALL GREEN 1727/1727**.
|
||||||
|
- **Selector gained a town arg** (`web/js/citygen/index.js`):
|
||||||
|
`generatePlanFor(seed, source, { town })`. Shell wiring: read `&town`, pass it through —
|
||||||
|
`generatePlanFor(seed, src, { town: q.get('town') || undefined })`. `map.html` is the reference
|
||||||
|
impl (`?plansrc=osm&town=katoomba`). Unknown/absent town → the default (`melbourne`).
|
||||||
|
`osmTownKeys()` lists the available towns for a UI picker.
|
||||||
|
- **THREE goldens to pin** (seed 20261990), all asserted by selfcheck already:
|
||||||
|
| source / town | golden |
|
||||||
|
|--------------------|--------------|
|
||||||
|
| synthetic | `0x3fa36874` |
|
||||||
|
| osm / melbourne | `0x34cfdec0` |
|
||||||
|
| **osm / katoomba** | `0x0f652510` |
|
||||||
|
The determinism gate should key on **(seed, plansrc, town)**. Synthetic + melbourne goldens are
|
||||||
|
**unchanged** from round 6.
|
||||||
|
- Katoomba (Blue Mountains, 19 shops — op-shop/book heavy) is a deliberate small-regional contrast
|
||||||
|
to inner-Melbourne (95, book heavy). Both boot end-to-end; screenshots in `docs/shots/laneA/`.
|
||||||
|
|
||||||
|
### Normalization: the importer bends the DATA to the contract, never the contract (round-8 item 4)
|
||||||
|
|
||||||
|
Real OSM data that fights a CityPlan contract is normalized deterministically, and the change is
|
||||||
|
**logged** (selfcheck prints a per-town line; `generatePlanOSM(seed, town, { report })` fills `report`):
|
||||||
|
- unknown OSM `shop=` kind → `opshop` (`typesRemapped`);
|
||||||
|
- a shop closing ≥22:00 → clamped to 21:00 so only the one openLate landmark is late (`hoursClamped`);
|
||||||
|
- openLate landmark prefers a **video**; a town with no video falls back to another non-stall type
|
||||||
|
and `report.openLate` records it (both Melbourne + Katoomba have video, so both read `video`).
|
||||||
|
|
||||||
|
### RECIPE — add another OSM town (mechanical, ~10 min)
|
||||||
|
|
||||||
|
1. **Pick a cluster** in thriftgod's `city_source.json` (Overpass cache). Find a centroid + a cell
|
||||||
|
size that captures one coherent town (aim 15–150 on-theme shops). The extraction one-liner used
|
||||||
|
for melbourne/katoomba is in `A-progress.md` (round 6/8) — reuse it with your `(cLat,cLon,cell)`.
|
||||||
|
2. **Append the town** to `OSM_TOWNS` in `web/js/citygen/osm_fixture.js`:
|
||||||
|
`key: { town, source, center:{lat,lon}, shops:[{id,name,type,lat,lon,suburb}, …] }` — sort shops
|
||||||
|
by `id` (determinism). Map OSM `shop=` kinds → registry types (charity→opshop, books→book,
|
||||||
|
music→record, toys→toy, video_games→video, antiques/second_hand→opshop/pawn).
|
||||||
|
3. **Pin its golden**: run `node web/js/citygen/selfcheck.js`. The unpinned town prints a ready-to-paste
|
||||||
|
line — `⚠ osm/<key>: UNPINNED — add to OSM_GOLDENS → <key>: 0x…,` — paste it into `OSM_GOLDENS`
|
||||||
|
in `selfcheck.js`, re-run → green. (The full parity suite already ran on the town; only the golden
|
||||||
|
needed pinning.)
|
||||||
|
4. **Tell F** the new (seed, plansrc, town) hash for the determinism gate. Done — zero network, the
|
||||||
|
full parity suite covers the town automatically.
|
||||||
|
|
||||||
|
*Dry-run (round 9): verified end-to-end with a throwaway Newtown (42 shops) — the parity suite auto-ran
|
||||||
|
(1931/1932, only the golden unpinned) and selfcheck printed `newtown: 0x9045e577,`. Reverted; no 3rd
|
||||||
|
town shipped (didn't fall out "for free"). The recipe is mechanical as written.*
|
||||||
|
|
||||||
## Round 6 (2026-07-14)
|
## Round 6 (2026-07-14)
|
||||||
|
|
||||||
### → Lane F (F2): the `?plansrc=osm` plan-source seam is LANDED — wire + pin
|
### → Lane F (F2): the `?plansrc=osm` plan-source seam is LANDED — wire + pin
|
||||||
|
|||||||
195
docs/LANES/LANE_C_AUDIO.md
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
# LANE C — interior audio contract (v3.1) → for Lane F + Lane B
|
||||||
|
|
||||||
|
> **v3.1** · 2026-07-17 (R28) · durable contract for Lane F + Lane B. Amends v3.0-FROZEN: adds
|
||||||
|
> `room.audio.emitters` (§Emitters) and retires the v3.0 deferred `userData.audioEmitter` note — that
|
||||||
|
> note proposed a shape its own consumer could not use. **The line was the stale thing.**
|
||||||
|
|
||||||
|
*`buildInterior` returns `room.audio = { musicKey, toneKey, gigKey?, emitters }`, seeded per shop. Lane F's
|
||||||
|
interior_mode just plays it; Lane B's audio.js resolves the keys → files.*
|
||||||
|
|
||||||
|
> **Scope:** this doc covers the **base interior audio** (the seeded music bed + room-tone every shop
|
||||||
|
> returns). The **gig audio** — `room.audio.gigKey`, the live-band bed, canonical form `gig-<genreKey>`
|
||||||
|
> (`gig-pubrock` / `gig-grunge` / `gig-covers`), present only when `opts.gig` is on — is specified in
|
||||||
|
> [LANE_C_PUB.md](LANE_C_PUB.md) §1. `room.audio` carries `gigKey?` as a third, optional key alongside the
|
||||||
|
> two below.
|
||||||
|
|
||||||
|
## The contract
|
||||||
|
```js
|
||||||
|
room.audio = {
|
||||||
|
musicKey, // string | null — an interior MUSIC bed key in manifest.audio.music, or null (no music)
|
||||||
|
toneKey, // string — an interior ROOM-TONE key in manifest.audio.ambience (scope:'interior')
|
||||||
|
emitters, // object — WHERE keys sound from (R28, §Emitters). Never null; may be {}.
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- **Keys, not files.** `buildInterior` stays synchronous and asset-free — it only *names* what should
|
||||||
|
play. Resolve `musicKey` → `manifest.audio.music[musicKey]`, `toneKey` →
|
||||||
|
`manifest.audio.ambience[toneKey]` (the `.ogg` + `.m4a` fallback + `gain`/`loop` are on those entries).
|
||||||
|
- **Silence is legal** (house audio law): missing key / `?mute=1` / `?noassets=1` → play nothing, no error.
|
||||||
|
`musicKey` is frequently `null` — treat "no music, room-tone only" as the common case.
|
||||||
|
- **Seeded per shop, stable per revisit.** Derived from `shop.seed` on its own sub-stream, so the same
|
||||||
|
shop is silent-or-playing identically every visit (no per-frame or per-enter randomness).
|
||||||
|
|
||||||
|
## What each type resolves to (mirrors manifest.audio `types` arrays — one source of truth)
|
||||||
|
| type | toneKey | musicKey |
|
||||||
|
|---|---|---|
|
||||||
|
| record | roomtone-retail | **record-shop** (always — "record shop plays music") |
|
||||||
|
| milkbar | roomtone-milkbar | **milkbar** (radio, always) |
|
||||||
|
| video | roomtone-video | **video-synth** (always) |
|
||||||
|
| dept, arcade | roomtone-video | **arcade** (always) |
|
||||||
|
| opshop, book, toy, pawn, stall | roomtone-retail | **null**, or **milkbar** for a seeded ~1-in-3 (a general radio playing — the milk-bar bed is `types:[…,'general']`) |
|
||||||
|
|
||||||
|
Verified (9 types × 8 seeds): 0 bad keys, 0 nondeterminism, every key resolves + matches its manifest
|
||||||
|
`types` membership; dedicated-music types are stable, non-music types vary on/off by seed.
|
||||||
|
|
||||||
|
## Emitters — WHERE a key sounds from (R28)
|
||||||
|
|
||||||
|
```js
|
||||||
|
room.audio.emitters = {
|
||||||
|
stage?: { x, y, z, r, floor }, // VENUE ONLY — the band + the crowd sound from here
|
||||||
|
counter?: { x, y, z, r, floor }, // the till
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Room-local metres, 3-dp, deterministic (18/18 same-seed deep-equal across 6 types × 3 seeds).
|
||||||
|
**Absent emitter ⇒ room-filling**, which stays the right default for a shop radio (the v3.0 call, kept).
|
||||||
|
|
||||||
|
- **`r` is the REACH** — the distance at which the emitter has decayed to `floor`. It is the **room
|
||||||
|
diagonal**, not a constant. Measured over 8 types × 4 seeds: diagonals run **9.81–15.63 m**, and scaling
|
||||||
|
`r` to the room holds the door→stage proximity in a consistent **0.42–0.59** band from a pokey
|
||||||
|
band_room to a hall pub. A fixed radius would make the same walk mean different things in different rooms.
|
||||||
|
- **`floor` is the room-filling RESIDUE, never 0.** A pub PA fills the room; what changes as you walk in is
|
||||||
|
the **balance**, not the presence. `stage` 0.35, `counter` 0.15 (a till is a point source, a PA is not).
|
||||||
|
- **The curve** (both consumers use exactly this):
|
||||||
|
```js
|
||||||
|
const d = Math.hypot(e.x - cam.x, e.z - cam.z); // emitters and the interior camera are
|
||||||
|
const prox = Math.max(0, Math.min(1, (e.r - d) / e.r)); // BOTH room-local — no transform
|
||||||
|
const gain = e.floor + (1 - e.floor) * prox; // never 0
|
||||||
|
```
|
||||||
|
Measured swing door→stage: **2.65–4.52 dB** across pub / band_room / rsl. Audible, not a new game — the
|
||||||
|
wide rooms swing least because the stage genuinely *is* nearer the door there. (John, R20: *"subtle
|
||||||
|
changes are great … the player should notice the town feels more alive, not that the game changed."*)
|
||||||
|
|
||||||
|
### DO NOT use a PannerNode. It is inert here. (measured)
|
||||||
|
Nothing in the tree sets `ctx.listener` — zero hits, whole tree. A `PannerNode` therefore resolves against
|
||||||
|
a listener frozen at **(0,0,0)**, and because rooms are built in **room-local coords at the origin**, that
|
||||||
|
is *the room's own centre, forever*. Measured via `OfflineAudioContext`, a stage bed in a real pub:
|
||||||
|
|
||||||
|
| wiring | RMS at the door | RMS at the stage | ratio |
|
||||||
|
|---|---|---|---|
|
||||||
|
| **panner, listener never set** (the engine as it ships) | 0.106905 | 0.106905 | **1.0000** |
|
||||||
|
| panner + listener tracking the player | 0.055665 | 0.500000 | 8.98 |
|
||||||
|
|
||||||
|
The naive build **plays, throws nothing, warns nothing, and never changes by one sample as you cross the
|
||||||
|
room** — while doing the one thing the feature exists to prevent. Every plausible gate goes green on it:
|
||||||
|
*"is it routed through a panner?"* yes. *"is it audible in a venue?"* yes. *"in a shop?"* yes.
|
||||||
|
**-> The only assertion that can see this is a DELTA: the bed's gain at the door ≠ its gain at the stage.**
|
||||||
|
Assert the change, not the presence. (Vacuous-gate law, 4th application.)
|
||||||
|
|
||||||
|
This trap was set by **this document's own v3.0 note** — *"`userData.audioEmitter = musicKey` so Lane B can
|
||||||
|
position/spatialise the bed"* — which invites exactly the panner, in a codebase with no listener code to
|
||||||
|
teach otherwise. Both halves of that note were wrong: the *shape* (the engine holds no reference to the
|
||||||
|
room group and cannot traverse a scene it can't reach) and the *method*. It is retired.
|
||||||
|
|
||||||
|
**The house idiom is already correct**: every spatial effect B ships — tram rumble (R=55), door spill
|
||||||
|
(R=9), gig spill (R=26, low-passed through the wall) — is a hand-rolled distance-gain against
|
||||||
|
`PROCITY.camera.position`. No listener, no panner. Emitters are the same shape as what already works.
|
||||||
|
|
||||||
|
## Lane F wiring (interior_mode)
|
||||||
|
```
|
||||||
|
on enter(room):
|
||||||
|
if room.audio.toneKey: playLoop(ambience[room.audio.toneKey], fadeIn) // room-tone always
|
||||||
|
if room.audio.musicKey: playLoop(music[room.audio.musicKey], fadeIn) // may be null → skip
|
||||||
|
on exit / dispose(room):
|
||||||
|
fadeOut + stop both, release AudioNodes // your enter/exit leak smoke checks this
|
||||||
|
```
|
||||||
|
Nothing before the first gesture; `?mute=1`/`?noassets=1` short-circuit before any fetch. `room.audio`
|
||||||
|
is plain data on the return — no new lifecycle, no dispose hook needed from Lane C's side.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# R28 — what C shipped, and the two asks
|
||||||
|
|
||||||
|
**C shipped (this round, C's files only):**
|
||||||
|
1. `room.audio.emitters` — the data above. Rides the **existing** `playInterior(room.audio)` call: no new
|
||||||
|
lifecycle, no traverse, no dispose hook.
|
||||||
|
2. **The till rings from the counter** (`dig.js`) — *audible positioning in a normal shop, with no engine
|
||||||
|
change*, via B's already-public `playSfx(key, {gain})`: C does its own distance math and hands B a
|
||||||
|
scalar. Measured through the real BUY button in a real record shop — **0.9489 at the counter (0.60 m)
|
||||||
|
vs 0.6436 at the door (4.19 m), 3.37 dB**; negative control (`emitters` omitted) fires at exactly
|
||||||
|
**1.0**. `sfx-till.ogg` has been on disk since 15 Jul and had **never once played**. Routing, not content.
|
||||||
|
3. **`?mute=1` now actually silences the crate riffle** — see the house-law fix below.
|
||||||
|
|
||||||
|
**Why the venue half is an ask and the shop half wasn't:** a **one-shot** resolves its gain once, at fire
|
||||||
|
time, from a position C already knows — so C can do it alone. A **bed** must be re-gained every frame from
|
||||||
|
inside the engine's `update()` loop. That loop is B's file. The split is structural, not political.
|
||||||
|
|
||||||
|
## ASK → Lane B (audio.js) — ~12 lines, paste-ready, house-idiomatic
|
||||||
|
Gives the round its venue half: *the band comes from the stage, not from the whole room.*
|
||||||
|
```js
|
||||||
|
// near the other interior state:
|
||||||
|
let iEmit = null, iMusicG = 0.5, iToneG = 0.35;
|
||||||
|
|
||||||
|
async function playInterior(spec) {
|
||||||
|
// … existing body …
|
||||||
|
iEmit = spec.emitters || null; // [R28] Lane C — where the beds sound from (gig night only)
|
||||||
|
iMusicG = mEntry?.gain ?? 0.5;
|
||||||
|
iToneG = tEntry?.gain ?? 0.35;
|
||||||
|
swapBed(L.iMusic, spec.musicKey || null, mEntry || null, iMusicG, 1.0);
|
||||||
|
swapBed(L.iTone, spec.toneKey || null, tEntry || null, iToneG, 1.0);
|
||||||
|
}
|
||||||
|
function stopInterior() { iEmit = null; ramp(L.iMusic.g.gain, 0, 0.7); ramp(L.iTone.g.gain, 0, 0.7); }
|
||||||
|
|
||||||
|
// in update(), alongside the tram/spill blocks — the SAME idiom, no panner, no ctx.listener:
|
||||||
|
if (!street && iEmit && iEmit.stage) {
|
||||||
|
const e = iEmit.stage, d = Math.hypot(e.x - _cam.x, e.z - _cam.z);
|
||||||
|
const k = e.floor + (1 - e.floor) * Math.max(0, Math.min(1, (e.r - d) / e.r));
|
||||||
|
ramp(L.iMusic.g.gain, (L.iMusic.src ? iMusicG : 0) * k, 0.25);
|
||||||
|
ramp(L.iTone.g.gain, (L.iTone.src ? iToneG : 0) * k, 0.25);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Emitters are room-local; the interior camera is room-local. Same space, no transform. Your call entirely —
|
||||||
|
C has not touched audio.js.
|
||||||
|
|
||||||
|
**Second, smaller ask (not this round):** expose the sfx bus or the ctx so `dig.js` can retire its private
|
||||||
|
AudioContext (below). C will not open that seam unilaterally.
|
||||||
|
|
||||||
|
## ASK → Lane F (interior_mode.js) — 1 line, and it is load-bearing
|
||||||
|
`interior_mode.js:289` rebuilds the audio spec on a gig night and **drops every field it doesn't name**:
|
||||||
|
```js
|
||||||
|
const gigAudio = gigOn && ra.gigKey ? { musicKey: ra.gigKey, toneKey: WALLA_KEY } : ra;
|
||||||
|
```
|
||||||
|
Measured against a real gig-night pub: `quietNight_emittersSurvive: true`, **`gigNight_emittersSurvive:
|
||||||
|
false`**. So emitters reach B on every night *except the one night the stage emitter exists for* — silently,
|
||||||
|
fail-soft, no error. Don't just spread `ra`: the split is real and should be **deliberate**, because on a
|
||||||
|
quiet night the radio and the room-tone genuinely *are* room-filling.
|
||||||
|
```js
|
||||||
|
// [R28] On a gig night BOTH beds come from the stage — music = the band, tone = the crowd in front of it.
|
||||||
|
// On a quiet night the radio + room-tone fill the room, so no emitter is passed. Deliberate, not a spread.
|
||||||
|
const gigAudio = gigOn && ra.gigKey
|
||||||
|
? { musicKey: ra.gigKey, toneKey: WALLA_KEY, emitters: ra.emitters }
|
||||||
|
: { musicKey: ra.musicKey, toneKey: ra.toneKey };
|
||||||
|
```
|
||||||
|
**Also, when you next touch `openDig`:** pass `emitters: current.audio.emitters` to `dig.open()` and the
|
||||||
|
till becomes positional. Optional — omitted, it still rings, just un-positioned. Never required.
|
||||||
|
|
||||||
|
## House-law fix — `?mute=1` did not silence the crate riffle (R5 → R28)
|
||||||
|
`dig.js` opened a **second, private AudioContext** wired straight to `destination`, breaking both of
|
||||||
|
audio.js's stated laws (*ONE AudioContext*; *`?mute=1` forces silence*). The riffle sang right through
|
||||||
|
`?mute=1` and never saw B's master gain. **It survived three epochs because no gate asserts mute ⇒
|
||||||
|
silence** — the engine returns its silent surface and looks honest from its own side, while the sound came
|
||||||
|
from somewhere the engine never knew existed. *A gate that asks the engine whether it is muted can only
|
||||||
|
ever answer yes.*
|
||||||
|
|
||||||
|
Fixed in `dig.js` by reading B's **public** `.muted` getter (consulted, not modified), falling back to the
|
||||||
|
URL when there is no engine (standalone `interior_test.html`). Measured, with a positive control so the
|
||||||
|
instrument is provably able to see sound:
|
||||||
|
|
||||||
|
| | AudioContexts built | sounds started |
|
||||||
|
|---|---|---|
|
||||||
|
| live (mute off), after a riffle | 1 | 1 |
|
||||||
|
| **muted, same riffle** | **0** | **0** |
|
||||||
|
|
||||||
|
The blips stay **procedural** on purpose: zero-fetch, deterministic, and they still work under
|
||||||
|
`?noassets=1` where a sampled riffle cannot. Only mute was broken; only mute was fixed.
|
||||||
|
|
||||||
|
**-> F: assert SILENCE, not muted-state.** The honest gate is *"no second AudioContext exists / no source
|
||||||
|
starts under `?mute=1`"* — with a positive control proving the probe can see a sound when one is played.
|
||||||
@ -1,5 +1,112 @@
|
|||||||
# LANE C — cross-lane notes (PROCITY-C)
|
# LANE C — cross-lane notes (PROCITY-C)
|
||||||
|
|
||||||
|
## → Lane D + Lane F: BROWSE-POINTS interface (round-9 C1 — **LANDED**, code live)
|
||||||
|
|
||||||
|
**Status: shipped.** `room.browsePoints[]` is live on the room API (commit `5ae635a`, ref
|
||||||
|
`lane-c/round9-browse`). The contract below is final — build against it. It is the seam the whole round
|
||||||
|
hangs off. **C owns the points + room fit; D owns occupancy truth; F owns the handoff.**
|
||||||
|
|
||||||
|
**F smoke (warn):** enter a `?stock=real` book/toy/record shop, assert `room.browsePoints.length >= 1`
|
||||||
|
and every point walkable+reachable (or just trust C's asserts and check `.length` + that D's rig count ==
|
||||||
|
`min(occupancy,3)`). Test page: the `path` overlay draws each point as a cyan post + facing nose.
|
||||||
|
|
||||||
|
**Draw headroom for D's rigs** (I measured with 3 two-draw rig proxies standing at the points): worst
|
||||||
|
interior in the `?stock=real` path is **68 draws** (opshop/hall) — browsers add ≤6, tons of room. The one
|
||||||
|
tight spot is the *pre-existing* GLB-on worst (opshop/hall **344/350**, unrelated to browsers): if D's rigs
|
||||||
|
are ever on TOP of GLB hero-props in that room, keep them impostor-cheap (≤2 draws each, the keeper pattern)
|
||||||
|
and F's sweep-with-browsers should confirm ≤350. Everywhere else there's ample headroom.
|
||||||
|
|
||||||
|
### What C exposes on the room API (live once C1 commits)
|
||||||
|
|
||||||
|
```jsonc
|
||||||
|
room.browsePoints: BrowsePoint[] // 0..3 entries, DETERMINISTIC per shop.seed, stably ordered
|
||||||
|
BrowsePoint = {
|
||||||
|
x, z, // room-local floor position (metres). Origin = room centre, +Z = door/street side,
|
||||||
|
// −Z = back — the SAME frame as room.spawn / room.counter.stand.
|
||||||
|
ry, // facing yaw, oriented to look AT the shelf. SAME convention as room.counter.stand.ry
|
||||||
|
// (rig-front = local −Z, i.e. the value keepers.js already consumes). Drop your
|
||||||
|
// browser rig in with rotation.y = ry and it faces the goods.
|
||||||
|
atKind, // string — the fitting kind being browsed ('bin' | 'shelf' | 'rack' | 'table' | 'case' …).
|
||||||
|
// Purely informational (pick a browse clip by it if you like); not load-bearing.
|
||||||
|
slotIndex, // 0-based, == array position. Stable across rebuilds of the same seed.
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Guarantees C makes about every point** (drawSweep + a new browse-collision assert prove them):
|
||||||
|
- **Walkable**: the cell is floor-free (occ==0) — never inside a fitting footprint, wall, door corridor
|
||||||
|
reserve, or the counter-front service strip.
|
||||||
|
- **Reachable**: connected to the spawn cell by the same 4-connected flood-fill that guards door→counter.
|
||||||
|
- **Spaced**: points are ≥0.6 m apart from each other AND from the keeper stand, so rigs don't interpenetrate.
|
||||||
|
- **Deterministic**: pure function of `shop.seed`; same seed → same points, same order.
|
||||||
|
- **Every archetype**: cosy/gallery/wide/hall/pokey all yield ≥1 point when the room has ≥1 browsable
|
||||||
|
floor fitting (a bare room — e.g. a tiny pokey with only a counter — may yield 0; treat 0 as "no browsers").
|
||||||
|
|
||||||
|
C adds **no meshes and no draws** for browse points — they are pure poses. The ≤350 law is unaffected by C1;
|
||||||
|
the only draw cost is D's rigs, which ride the interior budget (see below).
|
||||||
|
|
||||||
|
### The D → F → C handoff (who calls whom)
|
||||||
|
|
||||||
|
1. **D owns occupancy truth.** Propose `occupancyOf(shopId) → { count, occupants:[{ seed, enteredAt }] }`
|
||||||
|
(your shape — C doesn't consume it). Count is however many peds patronage says are inside, pre-cap.
|
||||||
|
2. **F wires it at interior build.** On enter, F reads D's occupancy, then:
|
||||||
|
`const pts = room.browsePoints.slice(0, Math.min(occ.count, 3));` and asks D's fleet to stand one
|
||||||
|
browser rig per `pt` at `(pt.x, pt.z)`, `rotation.y = pt.ry`. **Cap 3** is enforced by there being ≤3
|
||||||
|
points — F just clamps count. Identity: pair `occupants[i].seed` with `pts[i]` so the same ped is the
|
||||||
|
same rig deterministically.
|
||||||
|
3. **Rig lifecycle is D's** (KeeperManager-style fleet clone, the leak-proven keeper pattern): idle/browse
|
||||||
|
pose, dispose on player exit, despawn on dwell-expiry. Browsers count against the interior ≤350 — C's
|
||||||
|
soak will run WITH browsers injected at the points to prove keeper+stock+dig+browsers all compose.
|
||||||
|
|
||||||
|
**Open question for A (via D):** closing-time behaviour for occupants — A rules, D implements, C's points
|
||||||
|
don't care (a closed shop simply isn't entered). No blocker on C.
|
||||||
|
|
||||||
|
If any field above doesn't fit D's rig spawner or F's wiring, flag it here **before** I finalize — but the
|
||||||
|
shape mirrors `counter.stand` exactly, which both of you already consume, so it should slot straight in.
|
||||||
|
|
||||||
|
## → Lane F: buy-anywhere (round-9 C2 landed) — shelf pull-and-buy smoke (warn)
|
||||||
|
|
||||||
|
Book spines + toy boxes are now buyable over the same wallet (records keep the dig). A real shelf is a
|
||||||
|
merged, per-item-addressable mesh: `mesh.userData.buyMesh === true`, `mesh.userData.buyItems = [{item,
|
||||||
|
center, vStart}]`, one draw per shelf (drawSweep with `?stock=real`: record 41 · book 58 · toy 57, ≤350).
|
||||||
|
Smoke recipe (headless — no raycast needed):
|
||||||
|
```js
|
||||||
|
const room = buildInterior({ id:'book', type:'book', seed:1990 }, THREE, { stock:'real' });
|
||||||
|
let mesh=null; room.group.traverse(o=>{ if(!mesh && o.userData?.buyMesh && o.userData.buyItems.length) mesh=o; });
|
||||||
|
assert(mesh); // real book/toy shop has ≥1 buy mesh
|
||||||
|
const it = mesh.userData.buyItems[0]; // {item:{title,artist,price,price_band}, center, vStart}
|
||||||
|
// buy path: wallet.buy({t:it.item.title, price:it.item.price||20}); collapseBuyItem(mesh, it);
|
||||||
|
// assert: geometry.position vert[it.vStart] collapsed to it.center (item removed) · wallet.count()++ · leak-free
|
||||||
|
```
|
||||||
|
Test page exposes `shelfBuySoak()` (buys one shelf item per book/toy seed, asserts collapse + leak 0/0) and
|
||||||
|
`shelfUnderAim()/showShelfCard()/buyShelfOffer()` for the live raycast path (E key: bin→dig, shelf→card).
|
||||||
|
Parody stock (no pack) has **no** buy mesh → aiming shows no card ("not for sale"). Fail-soft per pack.
|
||||||
|
|
||||||
|
## → Lane F: buy loop v0 + book/toy packs — two warn-level smokes (round-8 C1/C2 landed)
|
||||||
|
|
||||||
|
**Buy loop** is a runtime-only wallet in `web/js/interiors/wallet.js` — pure, headless-testable, no DOM:
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { createWallet } from './web/js/interiors/wallet.js';
|
||||||
|
const w = createWallet(1990);
|
||||||
|
const start = w.start(); // seeded ~$60–200 (deterministic per seed)
|
||||||
|
w.buy({ title:'X', artist:'Y', price: 25 }); // true; cash -= 25; bag +1
|
||||||
|
w.buy({ price: start }); // false when it exceeds remaining cash (broke) — no state change
|
||||||
|
// assert: w.count() === 1 && w.cash() === start - 25 && w.canBuy(w.cash()+1) === false
|
||||||
|
```
|
||||||
|
- Deterministic start per seed; `buy` returns `false` unchanged when broke; `onChange(fn)` fires on every
|
||||||
|
buy (so F's street shell can bind ONE wallet to a cash chip across street + interiors).
|
||||||
|
- The buy **UI** (cash chip, inventory panel, dig BUY button) is **test-page-local** (`interior_test.html`).
|
||||||
|
The reusable economy is `wallet.js`; render it wherever. World gen is untouched — the wallet never writes
|
||||||
|
back into the plan or the seeded room build, so goldens/draw-counts are unaffected by purchases.
|
||||||
|
|
||||||
|
**Book/toy packs** now feed `?stock=real` too (E shipped `stock_book_*` / `stock_toy_*`). Smoke:
|
||||||
|
- Boot `?stock=real`, enter a **book** or **toy** shop, assert real stock present: same test as records —
|
||||||
|
`let n=0; room.group.traverse(o=>{ if(o.userData?.isStock && o.material?.map) n++ }); assert n>0`. Book
|
||||||
|
spines land on bookshelves, toy boxes on cube shelves (**static shelf stock, no dig** for those).
|
||||||
|
- **Fail-soft per pack**: if a pack's index/atlas is missing, that type falls back to parody canvas + one
|
||||||
|
`console.warn`; records/other types unaffected. `?noassets` untouched.
|
||||||
|
- **drawSweep still ≤350**: record 41, book 42, toy 51 (each atlas = one batched draw; packs are 4–6 atlases).
|
||||||
|
|
||||||
## → Lane F: `?stock=real` smoke for the strict harness (round-7 C1 landed)
|
## → Lane F: `?stock=real` smoke for the strict harness (round-7 C1 landed)
|
||||||
|
|
||||||
`?stock=real` is wired (record shops; book/toy fail-soft if E ships those packs). Smoke recipe:
|
`?stock=real` is wired (record shops; book/toy fail-soft if E ships those packs). Smoke recipe:
|
||||||
|
|||||||
549
docs/LANES/LANE_C_PUB.md
Normal file
@ -0,0 +1,549 @@
|
|||||||
|
# LANE C — interiors contract (venues · real-town mapping · real stock) → Lanes A/D/E/F/G
|
||||||
|
|
||||||
|
> **v5.0-FROZEN · 2026-07-17 · the whole document.** Every section below is the shipped, measured state of
|
||||||
|
> Lane C's contracts across five epochs. Changes from here are version-bumped amendments, recorded in the
|
||||||
|
> marker list — the same discipline §0–§5 have carried since v3.0. **Case law worth keeping:** three of this
|
||||||
|
> doc's amendments exist because a rule of mine met a case written *after* it (§7's naming vs the loader,
|
||||||
|
> §7.2a's `sku_` before mint existed) — the lane building to the rule took the red both times. When a line
|
||||||
|
> here meets a case it didn't foresee, **the line is the stale thing.** Amend it; don't bend the case.
|
||||||
|
|
||||||
|
> **v3.0-FROZEN** · 2026-07-16 · durable contract for Lane D + Lane F. §0–§5 describe the *shipped*
|
||||||
|
> venue state; changes require a version bump (CITY_SPEC amendment law). Verified R13 (build) + R14 (audit).
|
||||||
|
> **v3.1 amendment:** §3 became the `stage.backline[]` contract (ledger #5 — the amp unification).
|
||||||
|
> **v4.0-beta amendment (2026-07-16):** **§6 added — the real-town shop-class → archetype mapping**
|
||||||
|
> (ledger #2, the density widening; C→E handshake). The frozen venue contract (§0–§5) is unchanged.
|
||||||
|
> **v4.0 amendment (2026-07-16):** **§7 added — the G2a per-shop stock-atlas contract** (C's interior
|
||||||
|
> draw/tex ceilings, answering Lane G + E's review). §0–§6 unchanged.
|
||||||
|
> **v5.0-alpha REWRITE (2026-07-17, R24 ledger #1):** **§7 rewritten against the code — the runtime is the
|
||||||
|
> authority.** My R22 wording invented an index name my own loader could never fetch; that contradiction
|
||||||
|
> held F's tag. §7 now states what `stockpack.js` actually implements, extended per-shop, and the
|
||||||
|
> `type+base` cache keying is landed. **G's R23 atlas layout is ratified** (G re-emits provenance field
|
||||||
|
> names only, §7.3). §0–§6 unchanged.
|
||||||
|
> **v5.0-alpha amendment (2026-07-17, R25 ledger #3):** **§7.2a added — slot ids must be sku-derived,**
|
||||||
|
> unique across packs and stable across re-emits (F's id-collision finding, made structural). Index-only;
|
||||||
|
> no atlas bytes, no goldens. §0–§6 unchanged.
|
||||||
|
> **v5.0-beta amendment (2026-07-17, R26 ledger #1/#4/#6):** **§7.2b `sourcing` (real|mint — NOT `tier`,
|
||||||
|
> which is already the ladder rung), §7.2c `condition` for the price card, §7.2d the atlas manifest**
|
||||||
|
> (existence declared, never probed — kills the 404 attribution exception). All index/manifest-only:
|
||||||
|
> no atlas bytes, no town caches, no goldens. §0–§6 unchanged.
|
||||||
|
> **v5.0-beta amendment (2026-07-17, R26 wave 4):** **§7.2a's id form is now SOURCING-SCOPED** —
|
||||||
|
> `sku_<POS id>` for real, `mint_<listing id>` for mint. **Prefixes are namespace fences**, not decoration:
|
||||||
|
> my R25 line was written before mint existed and read as one space where there are two. Doc-only.
|
||||||
|
> **v7.0-alpha amendment (2026-07-18, R30 ledger #2):** **§9 added — the sell-counter contract** (the
|
||||||
|
> no-pump law made structural: `offer(p) = min(p−1, max(1, floor(p·0.5)))` — strictly below the buy side
|
||||||
|
> for every p ≥ 1, by construction). §0–§8 unchanged.
|
||||||
|
|
||||||
|
*The district ships **three** venue archetypes behind `?gigs=1` — `pub`, `band_room`, `rsl`. Lane A
|
||||||
|
converts a chosen shop to a venue kind in place (`shop.type = kind`); C keys the interior recipe off
|
||||||
|
`shop.type`. All three return the SAME shape (below), so Lane D drops rigs in and Lane F reads state with
|
||||||
|
no per-kind branching. Non-venue rooms are byte-identical (venue code runs only for `recipe.venue`, on its
|
||||||
|
own seed sub-streams). Verified fresh (seeds 20261990/1990/7/42/101 + 90-build soak): 0 throws · 0 carves ·
|
||||||
|
0 path-fails · 0 determinism-fails · leak geo0/tex0 · worst build 8.3 ms. Budgets in §3.*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 0. The pose `ry` convention (pinned) — READ THIS FIRST
|
||||||
|
|
||||||
|
**Canonical convention (every C pose uses it):** a pose's `ry` is the yaw of a thing whose **front /
|
||||||
|
outward normal is local −Z at ry=0**, i.e. world-facing `(−sin ry, −cos ry)`. `counter.stand`,
|
||||||
|
`browsePoints`, `watchPoints` and `bandPoses` ALL follow it, and the box placeholder (`placeholder.js`) is
|
||||||
|
built to it (toes at −Z). `keepers.js` greet math assumes it. **Drop a rig in at a pose's `ry` and it faces
|
||||||
|
the right way — no per-consumer flip.**
|
||||||
|
|
||||||
|
**Why no flip (the R13 fix, for the record):** the GLB fleet meshes ship facing local **+Z** at ry=0 — the
|
||||||
|
opposite of the −Z convention (verified: the anterior toe-bone vector `toe − foot` reads `(≈0, +0.99)`).
|
||||||
|
Before R13 this was compensated per-consumer, inconsistently: `band.js` flipped by π (band/crowd correct,
|
||||||
|
placeholders backward) while `keepers.js` did not (placeholders correct, **keepers + browsers 180° backward
|
||||||
|
whenever the GLB fleet was loaded** — they'd been shipping back-to-front). **D fixed it at the source in
|
||||||
|
`rigs.js buildFigure`: the cloned `inner` is rotated π about Y so every GLB rig faces −Z**, matching the
|
||||||
|
placeholder and every C pose. All consumer flips were then deleted. Net: keepers, browsers, band and crowd
|
||||||
|
all face correctly, GLB and placeholder modes are consistent, and C's pure-data poses never moved (no golden
|
||||||
|
touched). If you add a new rig consumer, plant it at the pose `ry` with no flip.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. What `buildInterior` returns for a venue (`shop.type` ∈ `pub` | `band_room` | `rsl`)
|
||||||
|
|
||||||
|
Everything below is **pure data on the return** (room-local coords, the ry convention in §0). Lane D drops
|
||||||
|
rigs straight in; Lane F reads state — no new lifecycle vs. the R9 keeper/browser seam.
|
||||||
|
|
||||||
|
### `room.stage` — the band stage, a **4-piece** (null for non-venues)
|
||||||
|
```js
|
||||||
|
room.stage = {
|
||||||
|
x, z, // deck centre (against the back wall)
|
||||||
|
w, d, // deck size (m) — varies by kind (table below)
|
||||||
|
deckY, // deck top surface height (m) — the front line stands here
|
||||||
|
riserY, // drum riser top height (m) = deckY + 0.16 — the drummer sits here
|
||||||
|
frontZ, // z of the deck lip (the crowd stands in front of this, +Z)
|
||||||
|
bandPoses: [ { x, z, ry, role, y, seated? } ×4 ],
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- `bandPoses[0..2]` = the **front line**: `role` ∈ `guitar` / `vocal` / `bass`, across the deck lip, `y = deckY`.
|
||||||
|
- `bandPoses[3]` = the **drummer**: `role: 'drums'`, `seated: true`, up-stage centre on the drum riser,
|
||||||
|
`y = riserY` (= `deckY + 0.16`, the `fittings.js` stage riser box).
|
||||||
|
- **Lane D:** lift each member to its own `pose.y` (front line = `deckY`, drummer = `riserY`). `role` hints
|
||||||
|
the instrument — E's GLBs via `opts.instrumentFor(role)` (electric_guitar/bass_guitar/mic_stand/drum_kit),
|
||||||
|
primitives otherwise (asset law). `seated:true` → seated bob. `ry = π` already faces the audience (+Z) — no
|
||||||
|
flip (§0).
|
||||||
|
- The band is a **4-piece** for v3.0 (front-line trio + drummer). Reverting to a trio would be a version bump
|
||||||
|
(drop `bandPoses[3]`), not a runtime toggle.
|
||||||
|
|
||||||
|
### `room.watchPoints` — the audience, **cap by kind** (empty `[]` for non-venues)
|
||||||
|
```js
|
||||||
|
room.watchPoints = [ { x, z, ry, dance, slotIndex } ] // seeded floor poses, all facing the stage centre
|
||||||
|
```
|
||||||
|
- **Crowd cap by kind: `pub` 8 · `band_room` 8 · `rsl` 12** (the RSL is the crowd-cap stress case). The room
|
||||||
|
fills what geometry allows up to the cap; **the count IS the crowd cap** (F asserts crowd ≤ `watchPoints.length`).
|
||||||
|
- `dance` seeded **~⅓ true** ("a bit of both"). D: `dance:false` → stand-and-watch idle; `dance:true` → bob/sway
|
||||||
|
with a **seeded phase offset** (use `slotIndex`/coords). Every point walkable, reachable, clear of the keeper
|
||||||
|
stand + browse points + stage/PA. Deterministic per `shop.seed`.
|
||||||
|
|
||||||
|
### `room.counter` / keeper — unchanged
|
||||||
|
The bar is a normal counter; the barkeep is just a keeper at `room.counter.stand` (R9 seam). Faces the
|
||||||
|
customer correctly under §0.
|
||||||
|
|
||||||
|
### `room.audio.gigKey` — the live bed
|
||||||
|
`room.audio = { musicKey, toneKey, gigKey? }`. The manifest key **is** the gigKey, canonical form
|
||||||
|
**`gig-<genreKey>`** — no mapping table. F passes gig state via `opts.gig`:
|
||||||
|
```js
|
||||||
|
buildInterior(rslShop, THREE, { gig: { on: true } }) // → room.audio.gigKey === 'gig-covers'
|
||||||
|
```
|
||||||
|
- Genre resolves `opts.gig.genreKey` → the venue's own `shop.genreKey` (A sets it) → a kind default
|
||||||
|
(`pub`→pubrock, `band_room`→grunge, `rsl`→covers). Present **only when a gig is on**.
|
||||||
|
- **Prefer `gigKey` over `musicKey`** while the gig plays. E's beds are `gig-pubrock` / `gig-grunge` /
|
||||||
|
`gig-covers`; a missing key → silence (audio law), band still visible.
|
||||||
|
- C hand-builds `'gig-'+genreKey` (byte-identical to citygen's `gigKeyFor`, selfcheck-asserted) rather than
|
||||||
|
importing it — deliberately, to keep the interiors lib standalone (it never imports citygen).
|
||||||
|
|
||||||
|
### The venue table (C-owned tuning)
|
||||||
|
| kind | archetype bias | ceiling | deck `w × d`, `deckY` | crowd cap | genre → gigKey | dressing |
|
||||||
|
|---|---|---|---|---|---|---|
|
||||||
|
| `pub` | wide / hall | 3.6 m | `0.58·W × 2.0`, 0.32 | 8 | pubrock → `gig-pubrock` | pub tables, sticky carpet, bar |
|
||||||
|
| `band_room` | cosy / pokey / wide | **3.4 m (low)** | `0.62·W × 1.6`, **0.20** (barely a riser) | 8 | grunge → `gig-grunge` | near-empty standing room, tin-shed dingy |
|
||||||
|
| `rsl` | hall / wide | **4.0 m (tall)** | `0.50·W × 2.4`, **0.40** | **12** | covers → `gig-covers` | flock walls, club carpet, bistro tables, members' bar |
|
||||||
|
|
||||||
|
## 2. Quiet-night variant
|
||||||
|
No `opts.gig` → a normal interior: `stage` + `watchPoints` are still returned as data, but there's **no
|
||||||
|
`gigKey`** (room-tone + maybe the seeded radio `musicKey`), and D spawns no crowd. A seeded dark night reads
|
||||||
|
true — the venue is just a quiet pub/club.
|
||||||
|
|
||||||
|
## 3. `stage.backline[]` — the amp slots (v3.1, ledger #5)
|
||||||
|
|
||||||
|
`room.stage.backline = [ { x, z, ry, y } ×2 ]` — seeded up-stage amp/spare-cab poses, **pure data** (C plants
|
||||||
|
no amp meshes as of v3.1). This is the unified backline: **Lane D plants E's `guitar_amp` GLB here** (its own
|
||||||
|
primitive fallback under `?noassets`) — one amp system, GLB when present. C's old primitive ampStacks are
|
||||||
|
**dropped** from all three venue recipes.
|
||||||
|
- **slot[0]** = stage-right (the primary amp) at `(stage.x + stage.w·0.36, deckY, stage.z − stage.d·0.22)` —
|
||||||
|
identical to D's R15-verified plant, so wiring D's amp to `backline[0]` moves nothing on screen.
|
||||||
|
**slot[1]** = stage-left spare cab (mirror). `ry = π` faces the audience (+Z, the §0 convention); `y = deckY`.
|
||||||
|
- **Guarantees** (verified fresh, seed 20261990, pub/band_room/rsl): every slot is **up-stage of the front
|
||||||
|
line**, flanks the drum riser, **clear of all 4 bandPoses** (nearest-bass clearance **0.87 m pub / 0.70 m
|
||||||
|
band_room / 0.92 m rsl**) and **all watchPoints** (≥1.95 m), and **on-deck** (within the deck footprint).
|
||||||
|
Deterministic per `shop.seed`. **Lane D:** verify no interpenetration once wired (C's R15 clearance discipline).
|
||||||
|
- **Draw impact:** dropping C's primitives shaved ~4 draws off each empty venue shell (drawSweep glb-off:
|
||||||
|
pub 122 · band_room 120 · rsl 131 ≤350); D's GLB/primitive at `backline[0]` adds it back on the gig night.
|
||||||
|
- *v3.2 (parked):* the drummer sit clip, instrument LOD, and C's `audioEmitter` idea.
|
||||||
|
|
||||||
|
## 4. Budgets (measured R14, seed 20261990, fresh context)
|
||||||
|
- **Empty shells** (`drawSweep` glb-off): band_room **124** · pub **126** · rsl **135** — town-wide worst
|
||||||
|
161 (opshop/hall). ≤ 350. ✓ (identical to R13 — C geometry unchanged).
|
||||||
|
- **Gig-night interior draws**, D's `GigCrew` with the real instrument GLBs + amp (localdepot): pub **60** ·
|
||||||
|
band_room **52** · rsl **71**. Asset-free (placeholder crowd, no GLBs) worst RSL **237** (16 rigs). ≤ 350. ✓
|
||||||
|
- **Tris**: fleet peds ~2.7k each; E's instrument GLBs **14k each** (decimated to the ≤15k target), 5 on a
|
||||||
|
stage = 70k — the instruments dominate. RSL gig-night ≈ **182k tris** once the async GLBs settle. The
|
||||||
|
interior gate is **draws** (passes); tris are the *street* budget (200k), so this is not an interior gate.
|
||||||
|
*Note for E/F:* F's gig-night smoke reports ~34k because it measures **before** the async instrument GLBs
|
||||||
|
load; the settled scene is ~180k. A camera-distance LOD on instruments is a v3.1 candidate.
|
||||||
|
- **Determinism / release-law**: C's interior code is unchanged since R13; D's continuity + RY-fix ride
|
||||||
|
disjoint rng namespaces (`ctx.stream(salt)` vs `rng(citySeed,kind,id)`) and cannot perturb C's placement
|
||||||
|
or any golden. 90-build venue soak: 0 determinism · 0 carve · 0 path-fail · worst 8.3 ms; leak geo0/tex0.
|
||||||
|
|
||||||
|
## 5. The archetypes (C-owned)
|
||||||
|
`band_room` = a tin shed: small (cosy/pokey), low ceiling, barely-a-riser deck, near-empty standing floor,
|
||||||
|
bar in the corner — the PA louder than the room. `rsl` = carpet-and-flock club: the biggest floor in town
|
||||||
|
(hall/wide), a proper raised stage at one end, bistro tables round the edge, members' bar, the biggest crowd
|
||||||
|
cap. Both reuse the `pub` venue machinery (`theme.js venueSpec` + `layout.js` placeStage/placeWatchPoints);
|
||||||
|
only the dressing + `venueSpec` differ. Stage/PA keep GLB slots for Lane E (primitive fallback holds); the
|
||||||
|
backline amp is D's, planted at `stage.backline[0]` (see §3).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Real-town shop-class → interior archetype mapping (v4.0-beta, ledger #2) → Lane E
|
||||||
|
|
||||||
|
**Directive (John, kickoff — SUBTLE: texture, not takeover).** The secondhand shops stay the **heroes** of
|
||||||
|
the town (they *are* the game). The widening adds general retail as *texture* — the everyday main-street
|
||||||
|
staples a real Australian town has — mapped onto **existing** archetypes (**no new archetypes this round**).
|
||||||
|
The player should feel the town's more alive, not that the game changed.
|
||||||
|
|
||||||
|
**How it's applied (E owns the emission).** `plan_osm` keeps only **registry types** (`SHOP_TYPES` — unknowns
|
||||||
|
flatten to `opshop`). So E maps each fetched OSM `shop=*` / `amenity=*` value to a **registry type** below and
|
||||||
|
bakes it into the cache's `shop.type`. Every target is an existing type, so C's recipes, A's registry, and the
|
||||||
|
budgets are all unchanged. Unknown/unlisted classes degrade safely to `opshop` (R18-proven: 18 real OSM types
|
||||||
|
all built valid).
|
||||||
|
|
||||||
|
**Heroes — secondhand (unchanged; NEVER subsampled):**
|
||||||
|
| OSM class | → type |
|
||||||
|
|---|---|
|
||||||
|
| `charity`, `second_hand`, `antiques`, `vintage` | `opshop` |
|
||||||
|
| `books`, `bookshop` | `book` |
|
||||||
|
| `music` | `record` |
|
||||||
|
| `video`, `games` | `video` |
|
||||||
|
| `pawnbroker` | `pawn` |
|
||||||
|
| `toys` | `toy` |
|
||||||
|
|
||||||
|
**Widened — everyday main-street texture (general retail):**
|
||||||
|
| OSM class(es) | → type | wears it as |
|
||||||
|
|---|---|---|
|
||||||
|
| `bakery`, `cafe`†, `confectionery`, `pastry`, `chocolate`, `deli`/`delicatessen`, `butcher`, `cheese`, `coffee`, `tea` | `milkbar` | the food counter |
|
||||||
|
| `convenience`, `kiosk`, `general`, `dairy` | `milkbar` | the corner store |
|
||||||
|
| `newsagent`, `stationery` | `milkbar` | papers + magazines |
|
||||||
|
| `chemist`, `pharmacy`, `cosmetics`, `perfumery` | `milkbar` | dispensary counter |
|
||||||
|
| `clothes`, `fashion`, `boutique`, `shoes`, `bag`, `jewelry`/`jewellery` | `dept` | the dressed retail floor |
|
||||||
|
| `department_store`, `variety_store`, `supermarket` | `dept` | big general retail |
|
||||||
|
| `hardware`, `doityourself`, `trade`, `electronics`, `mobile_phone`, `furniture`, `houseware`/`homeware` | `dept` | shelved goods |
|
||||||
|
| `greengrocer`, `florist`, `farm`, `garden_centre` | `stall` | produce/plants on trestles |
|
||||||
|
|
||||||
|
† `cafe`/`fast_food` are `amenity=*`, not `shop=*` — fetch them from the amenity tag; everything else is `shop=*`.
|
||||||
|
**Fallback:** any fetched class not listed → `opshop`.
|
||||||
|
|
||||||
|
**Copy-paste map for E (`build_towns.py`):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"charity":"opshop","second_hand":"opshop","antiques":"opshop","vintage":"opshop",
|
||||||
|
"books":"book","bookshop":"book","music":"record","video":"video","games":"video",
|
||||||
|
"pawnbroker":"pawn","toys":"toy",
|
||||||
|
"bakery":"milkbar","cafe":"milkbar","confectionery":"milkbar","pastry":"milkbar","chocolate":"milkbar",
|
||||||
|
"deli":"milkbar","delicatessen":"milkbar","butcher":"milkbar","cheese":"milkbar","coffee":"milkbar","tea":"milkbar",
|
||||||
|
"convenience":"milkbar","kiosk":"milkbar","general":"milkbar","dairy":"milkbar",
|
||||||
|
"newsagent":"milkbar","stationery":"milkbar",
|
||||||
|
"chemist":"milkbar","pharmacy":"milkbar","cosmetics":"milkbar","perfumery":"milkbar",
|
||||||
|
"clothes":"dept","fashion":"dept","boutique":"dept","shoes":"dept","bag":"dept","jewelry":"dept","jewellery":"dept",
|
||||||
|
"department_store":"dept","variety_store":"dept","supermarket":"dept",
|
||||||
|
"hardware":"dept","doityourself":"dept","trade":"dept","electronics":"dept","mobile_phone":"dept",
|
||||||
|
"furniture":"dept","houseware":"dept","homeware":"dept",
|
||||||
|
"greengrocer":"stall","florist":"stall","farm":"stall","garden_centre":"stall"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Default (not in the map) → `opshop`.
|
||||||
|
|
||||||
|
**Subtlety guardrails (E):**
|
||||||
|
- Fetch only these tasteful staples — **not** every OSM `shop=*`. Skip vehicle/tyres/fuel, funeral, storage,
|
||||||
|
industrial, and pure services the player never browses (`hairdresser`, `beauty`, `travel_agency`, `bank`,
|
||||||
|
`laundry`, `estate_agent`).
|
||||||
|
- **Never** subsample the secondhand heroes.
|
||||||
|
- If a town's general-retail count would visually **drown** the secondhand strip, seed-subsample the *texture*
|
||||||
|
classes (the milkbar/dept/stall targets) toward **~2–3× the secondhand count**, and count the drops in
|
||||||
|
`norm.dropped` like every other drop.
|
||||||
|
|
||||||
|
**Why these targets:** `milkbar` (counter + fridge + magazine rack + glass case) is the canonical Australian
|
||||||
|
counter shop — it wears cafe/bakery/deli/newsagent/chemist naturally; `dept` (cube shelves + clothes racks +
|
||||||
|
glass cases) wears clothes + hardware + big retail; `stall` (trestles + crates) wears produce + florist. Three
|
||||||
|
general-retail looks + the six secondhand heroes + the three venues = variety with no new archetypes.
|
||||||
|
**Pre-verified (seed 20261990, real-town lot 8.5×14):** milkbar/dept/stall/opshop all build valid (0 carves,
|
||||||
|
≤143 draws); `dept` reads as a clean **dressed-retail floor** (cube shelves + clothes racks + counter, the
|
||||||
|
escalator does *not* dominate the ~7.9×11.7 room), so `clothes`/`hardware` → `dept` is confirmed. C re-checks
|
||||||
|
the full result in the five-town door-soak (ledger #6) once E's caches land. (FYI: C's `theme.js canonicalType`/ALIAS already recognizes several
|
||||||
|
of these — `deli`/`corner_store`→milkbar etc. — for any direct `buildInterior(rawClass)`; the authoritative
|
||||||
|
mapping for the caches is this table.)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Real-stock packs — THE RUNTIME IS THE AUTHORITY (v5.0-alpha, R24 ledger #1) → Lanes E/F/G
|
||||||
|
|
||||||
|
> **This section was the root cause of R23's held tag and has been rewritten against the code.**
|
||||||
|
> My R22 wording invented an index name (`stock_shop_<godverseShopId>_index.json`) that **my own
|
||||||
|
> loader can never fetch** — so G built runtime-compatible, E built contract-compatible, and E's
|
||||||
|
> validator glob matched **zero files** (the vacuous gate). Nobody was wrong; the contract was.
|
||||||
|
> **Rule going forward: `web/js/interiors/stockpack.js` is the contract. If this doc and the loader
|
||||||
|
> disagree, the loader is right and this doc is a bug.** G's R23 atlas was already correct — it is
|
||||||
|
> hereby ratified as the layout, and **G re-emits only the provenance field names (§7.3).**
|
||||||
|
|
||||||
|
### 7.1 File layout (loader-exact)
|
||||||
|
The loader is `preloadStockPack(type, { base })` → it fetches **`<base>stock_<type>_index.json`**.
|
||||||
|
- **`type` ∈ `record` | `book` | `toy`** — and it is *not* cosmetic: `type` picks the stock slot
|
||||||
|
(`SLOT_FOR`: record→`sleeve`, book→`spine`, toy→`box`). **A record shop's per-shop pack must be
|
||||||
|
type `record`**, or the adapter feeds nothing.
|
||||||
|
- **Town-wide packs** (unchanged): `assets/models/stock_<type>_index.json`.
|
||||||
|
- **Per-shop packs (v5): `assets/stock_godverse/<godverseShopId>/stock_<type>_index.json`** — the base
|
||||||
|
is the per-shop directory; the filename is the same. *(This is exactly G's R23 layout.)*
|
||||||
|
- **RETIRED:** `stock_shop_<id>_index.json` — never loadable; do not emit, do not glob for it.
|
||||||
|
**→ Lane E:** `validate_atlas.py`'s glob must become `stock_godverse/**/stock_*_index.json`
|
||||||
|
(the current `stock_shop_*_index.json` is why it passed on 0 files).
|
||||||
|
|
||||||
|
### 7.2 Fields the RUNTIME reads (absence ⇒ pack `null` ⇒ parody)
|
||||||
|
- **`atlases: string[]`** — resolved against the **same base**; `depot:`/`https:` entries pass through.
|
||||||
|
- **`items: [...]`, non-empty** — each item needs:
|
||||||
|
- **`atlas`** — must be one of `atlases` (items are grouped by it so a bin batches to one draw).
|
||||||
|
- **`uv: [u0,v0,u1,v1]`, origin TOP-LEFT** — the loader flips V itself (`uvRect`). Do not pre-flip.
|
||||||
|
- Read by the dig/buy consumers (not by the loader): **`id`, `title`, `artist`, `price`, `price_band`**.
|
||||||
|
|
||||||
|
### 7.2a Slot ids — unique across packs, stable across re-emits (v5.0-alpha, R25 ledger #3)
|
||||||
|
**Rule: `items[].id` MUST derive from the source's own stable key — never from position — and MUST be
|
||||||
|
unique across ALL packs.** The shipped generic packs keep `rec_NNNN` as their namespace — **a real-stock
|
||||||
|
pack must not emit into it.**
|
||||||
|
|
||||||
|
> **AMENDED v5.0-beta (R26 wave 4) — the id form is SOURCING-SCOPED, and the prefix IS a namespace fence.**
|
||||||
|
> R25's line said "`sku_`-prefixed for real-stock packs". I wrote that **before mint existed**, so it reads
|
||||||
|
> as one space when there are two. E implemented my rule faithfully; **the rule was the stale thing, not
|
||||||
|
> E's check.** The form now follows §7.2b's `sourcing`:
|
||||||
|
>
|
||||||
|
> | `sourcing` | id form | the space it names |
|
||||||
|
> |---|---|---|
|
||||||
|
> | `"real"` | **`sku_<POS id>`** (e.g. `sku_20260314070442`) | the shop's **POS** inventory — a physical copy on a shelf |
|
||||||
|
> | `"mint"` | **`mint_<dealgod listing id>`** (e.g. `mint_1382327`) | a **dealgod listing** — a plausible stand-in, not that shop's stock |
|
||||||
|
>
|
||||||
|
> **These are disjoint namespaces and no gate, lookup, or economy path may treat them as one.** G refused
|
||||||
|
> to file dealgod listing ids under `sku_` to go green the easy way, and that judgment is the rule now:
|
||||||
|
> **tier-2's sold-means-gone looks up POS skus.** R24's id collision entered the *wrong shop*; the same
|
||||||
|
> collision in the money layer **sells the wrong record** — a real one, out of a real shop, to satisfy a
|
||||||
|
> minted stand-in. A prefix that lies costs money, not a gate.
|
||||||
|
> *(Same standing note as plan-ids vs godverse-ids: one name, one space — never compare across.)*
|
||||||
|
|
||||||
|
**Why — two reasons, both measured:**
|
||||||
|
1. **Positional ids collide by construction.** Both packs number from zero, so **all 120 godverse ids are
|
||||||
|
also generic-pack ids — 120/120 overlap (measured R25)**. An id-equality gate therefore cannot
|
||||||
|
discriminate which pack a rendered item came from: that is what made the brief's own #7a spec vacuous.
|
||||||
|
F caught it and replaced it with title-sets + texture-URL provenance (now the canonical honest-gate
|
||||||
|
pattern for stock). Sku ids make id-equality discriminating again — permanently, and while there are
|
||||||
|
two atlases to migrate instead of fifty.
|
||||||
|
2. **Position is not identity — and this one outlives the gate.** A positional id renumbers on every
|
||||||
|
re-bake: sell one record out of crate 550 and `rec_0050` silently becomes a *different* record. Anything
|
||||||
|
holding an id across snapshots — a wallet purchase, a saved dig, a gate assertion, and above all
|
||||||
|
**tier-2's sold-means-gone** — would then point at the wrong item. The POS sku is stable precisely
|
||||||
|
because it is the shop's own identity for that record: the same principle that makes `godverseShopId`
|
||||||
|
the POS shop id. *(`release_id` identifies the pressing, not the copy — the sku is the copy.)*
|
||||||
|
|
||||||
|
**Scope: index-only.** `id` is not read by the loader (§7.3) — the dig/buy consumers and the gates read it.
|
||||||
|
**No atlas bytes, no town cache, no golden moves.**
|
||||||
|
**→ Lane E (amended R26 w4):** assert the form **against `sourcing`** (you already read it at line 178) —
|
||||||
|
`id` present · **unique within the pack** · and the prefix matching its space: `sourcing:"real"` ⇒ `sku_`,
|
||||||
|
`sourcing:"mint"` ⇒ `mint_`. **A crate wearing the other's prefix FAILS** — that's the fence doing its job,
|
||||||
|
and it's a stronger check than the one it replaces, not a looser one.
|
||||||
|
|
||||||
|
### 7.2b `sourcing` — real vs mint, and why it is NOT `tier` (v5.0-beta, R26 ledger #1)
|
||||||
|
The beta needs every keyed crate marked as **actual POS inventory** or **seeded plausibility**, so no gate,
|
||||||
|
doc or human mistakes one for the other. The brief called that field `tier`. **It can't be `tier`: that name
|
||||||
|
is already taken, twice over.**
|
||||||
|
- G's atlases already emit **`tier: 1`** = the **charter's ladder rung** (0 parody · 1 static-real · 2 live
|
||||||
|
GODVERSE). A mint atlas **is also rung 1** — same mechanism (a statically baked per-shop atlas); only the
|
||||||
|
*data's origin* differs. And `kind` is already the stock type (`record`).
|
||||||
|
- So real-vs-mint is an **orthogonal axis**, not a rung. Overloading `tier` would put two meanings in one
|
||||||
|
name — the same species as the plan-id/godverse-id collision now standing-noted, and as the positional
|
||||||
|
ids in §7.2a. One name, one meaning.
|
||||||
|
|
||||||
|
**Rule: top-level `sourcing: "real" | "mint"`, REQUIRED on every real-stock atlas index.** `tier` stays the
|
||||||
|
numeric ladder rung, unchanged.
|
||||||
|
- **`"real"`** — baked from that shop's own POS snapshot (Monster Robot Party). The index carries its
|
||||||
|
`snapshot` provenance (§7.3).
|
||||||
|
- **`"mint"`** — a deterministic seeded sample (`random.Random(shop_id)`) standing in for a keyed shop with
|
||||||
|
no POS. Plausible, never claimed as that shop's stock. **A mint index MUST NOT carry POS-snapshot fields**
|
||||||
|
(the separation IS the honesty — E asserts both directions).
|
||||||
|
- **Gates read `sourcing`** (vacuous-gate law: an index with no `sourcing` FAILS). A mint crate can never
|
||||||
|
report as real. The dig card's treatment is C's call — see §7.2c.
|
||||||
|
|
||||||
|
### 7.2c Price-card fields — `condition` (v5.0-beta, R26 ledger #4)
|
||||||
|
The dig card is a **record-shop price sticker**, and a sticker's fields are artist / title / **condition** /
|
||||||
|
price. `condition` is **not in the index today** — G's snapshot carries `condition` + `sleeve_cond` per item
|
||||||
|
and drops them at emit, so `pickRealOffers` currently maps `cond: ''` and the card renders dangling
|
||||||
|
separators around a hole. That's the "database row" the ledger wants gone.
|
||||||
|
**Rule: `condition` (and optionally `sleeve_cond`) are OPTIONAL index item fields; emit them when the source
|
||||||
|
has them.** Index-only; the loader never reads them (§7.3). C's card renders whatever is present and omits
|
||||||
|
what isn't — a missing field must never leave a stray separator. **Mint items:** emit `condition` only if the
|
||||||
|
sampled listing genuinely carries one; a plausible-but-invented grade is exactly the kind of fiction
|
||||||
|
`sourcing` exists to prevent.
|
||||||
|
|
||||||
|
### 7.2d The atlas manifest — existence is declared, never probed (v5.0-beta, R26 ledger #6)
|
||||||
|
Fail-soft-by-404 collides with the zero-console-errors law: the runtime probes per-shop atlases blind and
|
||||||
|
eats the 404 noise (8 in F's R24 gate run), tolerated by attribution — a workaround, not a design.
|
||||||
|
**Rule: `web/assets/stock_godverse/index.json` is the authority for which shops carry atlases. No entry ⇒ no
|
||||||
|
fetch.** Shape:
|
||||||
|
```json
|
||||||
|
{ "version": 1,
|
||||||
|
"shops": [ { "godverseShopId": 3962749, "types": ["record"], "sourcing": "real" },
|
||||||
|
{ "godverseShopId": 31, "types": ["record"], "sourcing": "mint" } ] }
|
||||||
|
```
|
||||||
|
- **`godverseShopId` is a number in the GODVERSE namespace** — never compare it to a plan id (standing note).
|
||||||
|
- **→ F:** consult the manifest before fetching; no entry ⇒ no probe ⇒ **zero 404s, no attribution
|
||||||
|
exception**. A shop in the manifest whose fetch still fails keeps the existing fail-soft → parody.
|
||||||
|
- **→ G:** emit it alongside the atlases (mint entries included).
|
||||||
|
- **→ E:** manifest↔files consistency **both ways** — an atlas on disk absent from the manifest FAILS, and a
|
||||||
|
manifest entry with no atlas FAILS.
|
||||||
|
- **Manifest absent entirely** (a tree with no godverse atlases): that is a legitimate zero-shop state — the
|
||||||
|
wire probes nothing and stays silent. Not an error.
|
||||||
|
|
||||||
|
### 7.3 Fields the runtime IGNORES — and the provenance ruling (the drift dies here)
|
||||||
|
`version`, `atlas_px`, `cell`/`cell_w`/`cell_h`, `count`, `kind`, `tier`, `shop`, `crate`,
|
||||||
|
`provenance` — **`stockpack.js` reads none of them.** So the authority for their shape is not C's
|
||||||
|
loader but **E's `validate_atlas.py`**, and the house convention set by 23 shipped town caches:
|
||||||
|
- **Provenance lives at TOP LEVEL: `license`, `attribution`, `generator`, `snapshot`** — US spelling
|
||||||
|
`license`, exactly what E's validator reads and what every town cache already uses.
|
||||||
|
**British `licence` is retired. → Lane G: rename + lift those four to top level.**
|
||||||
|
- Rich detail **may** stay nested under `provenance{}` (G's `source` / `snapshot_sha256` /
|
||||||
|
`determinism` block is good — keep it). The four gate fields are the contract; the block is colour.
|
||||||
|
- **`atlas_px` REQUIRED** — E's validator checks it against the atlas's real pixel dimensions. That is
|
||||||
|
a genuine falsifiable check; keep it.
|
||||||
|
- **`cell` / `cell_w` / `cell_h` are informational.** **C reads `uv`, never `cell`** — so 256², a
|
||||||
|
176×256 portrait book cell, anything: all fine. **Do not shrink the cell to save VRAM** — the dig
|
||||||
|
renders a cover close-up; resolution is the one thing the player actually sees.
|
||||||
|
|
||||||
|
### 7.4 Cache identity = **(type, base)** — landed R24
|
||||||
|
`stockpack.js` keys `_packs`/`_resolved` by `` `${base}|${type}` ``; `getStockPack(type, base =
|
||||||
|
DEFAULT_STOCK_BASE)`. Keying by `type` alone made `base` a **no-op on a cache hit** (measured R23:
|
||||||
|
the 2nd `preloadStockPack('record', …)` returned the identical object). That was two bugs:
|
||||||
|
1. two stocked shops collide — the 2nd serves the 1st's crate; **and**
|
||||||
|
2. **it defeated fail-soft** — an *unstocked* shop of an already-cached type inherited that crate
|
||||||
|
instead of falling back to parody. (A correctness bug, not a mix-up.)
|
||||||
|
Both fixed and re-measured (§7.7). Pre-v5 callers are unaffected: no `base` ⇒ the town-wide pack.
|
||||||
|
|
||||||
|
### 7.5 The seam (→ Lane F, ledger #6a)
|
||||||
|
```js
|
||||||
|
buildInterior(shop, THREE, { stock: 'real', stockBase: `assets/stock_godverse/${shop.godverseShopId}/` })
|
||||||
|
```
|
||||||
|
`opts.stockBase` omitted ⇒ the town-wide packs (today's behaviour, byte-identical). Preload the shop's
|
||||||
|
pack on entry with the same base. **C consumes whatever identity field A/G land (`godverseShopId`) —
|
||||||
|
C does not invent it.**
|
||||||
|
|
||||||
|
### 7.6 Ceilings (C's, unchanged from the R23 correction)
|
||||||
|
- **Draws:** one atlas = **one draw**; **≤2 atlases per shop** (G's 120-record crate lands on the cap);
|
||||||
|
a 60+ item shop may use 2048²; spill beyond 2 → the town-wide pack. Room law ≤350 stands.
|
||||||
|
- **VRAM:** **≤2 atlases @≤2048²; LRU = 1 shop's atlas set resident ⇒ ≤32 MB peak.**
|
||||||
|
*(R23 correction: my "≤8 MB" assumed a ~16-item "typical shop". A real record shop is 120 records.)*
|
||||||
|
- **The LRU + dispose is C's, and it lands at v5.0-beta**, not here: with ONE stocked shop there is no
|
||||||
|
accretion, and safe eviction needs a room-exit hook (a live room holds the pack's shared material) —
|
||||||
|
that seam arrives with F's per-shop preload. Stated so it can't slide silently.
|
||||||
|
|
||||||
|
### 7.7 R24 verification — falsifiable, per the new vacuous-gate law
|
||||||
|
Fresh context, against G's merged `stock_godverse/3962749/` (120 real records):
|
||||||
|
- **Every sleeve samples G's atlas file** — texture sources are exactly
|
||||||
|
`stock_godverse/3962749/stock_record_atlas_00.webp` / `_01.webp`; pack items ≡ index items **by id**.
|
||||||
|
*(Not "covers render": a parody canvas also has a `.map`. The negative control below is what makes
|
||||||
|
this a gate — I had to fix my own R23 metric to satisfy the law.)*
|
||||||
|
- **Negative control:** the parody room's stock textures are `(canvas, no src)` and touch the godverse
|
||||||
|
atlas **zero** times — the metric discriminates.
|
||||||
|
- **Collision fixed:** shop pack (120 items) and town pack (350) coexist as distinct objects.
|
||||||
|
- **Fail-soft with a POPULATED cache:** missing base ⇒ `null` ⇒ parody (the R23 bug is gone).
|
||||||
|
- **Ceilings:** 98 draws with real covers via `opts.stockBase`; `pathOK`; 0 carves; 0 console errors.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Tier-2 live enrichment — the seam (v5.0, R27 ledger #2) → Lane F
|
||||||
|
|
||||||
|
**Ruling: bless the direct wire — no new lifecycle hook. But the `gone` *application form* is C's, and it's
|
||||||
|
landed in `dig.js`.** The question wasn't hook-vs-no-hook; it was *how* `gone` applies.
|
||||||
|
|
||||||
|
**1. Lifecycle — no hook needed.** F already holds `currentAdapter` (`interior_mode.js:67`) and passes it to
|
||||||
|
`dig.open()` **per dig** (`:111`) — a fresh read every time a crate is opened. When the live read lands, F
|
||||||
|
updates the pack; the next dig picks it up. If the read hasn't landed, hasn't finished, or failed, the
|
||||||
|
adapter is simply the tier-1 pack — **the dig never blocks on the network** (charter risk #1) and no C
|
||||||
|
lifecycle changes.
|
||||||
|
|
||||||
|
**2. The form — pick-then-omit, NEVER filter-the-array.**
|
||||||
|
`pack.gone` = a `Set`/array of ids the live read reports sold. C picks from the **full, stable `items[]`**
|
||||||
|
and omits gone ids **after** the draw.
|
||||||
|
- **Why (measured, R27, 5 crates):** filtering `items[]` first **reshuffles the crate** — every pick indexes
|
||||||
|
by position, so **one sale changed 21 OTHER records**. Pick-then-omit: **0 collateral**. A crate with 3
|
||||||
|
sold shows 13, not a different 16.
|
||||||
|
- **Both forms hide the sold record** — so **“the gone item never renders” cannot tell them apart.** → **F:
|
||||||
|
the sandbox proof (#4) must assert on *collateral* (the survivors are unchanged across a `gone` delta),
|
||||||
|
not just absence. The obvious assertion is vacuous here.** (Vacuous-gate law, third application — and the
|
||||||
|
cheapest one to get wrong, because it goes green.)
|
||||||
|
- **Proven in the real dig:** sold record gone · survivors **4/4** kept · **collateral 0** · `items[]`
|
||||||
|
untouched (120).
|
||||||
|
|
||||||
|
**3. F's invariant — `items[]` identity and order ARE the pick's stability.**
|
||||||
|
- **Do:** update item *fields* in place (live `price`) — free, no reshuffle; the card reads `it.price` at
|
||||||
|
pull time, so a live price shows on the next pull.
|
||||||
|
- **Never:** add, remove, or reorder `items[]`. That is what `pack.gone` exists for.
|
||||||
|
|
||||||
|
**4. The determinism boundary sits exactly here.** Server down ⇒ no `gone`, no live fields ⇒ the dig is
|
||||||
|
tier 1, deterministic and gate-able. Server up ⇒ the crate legitimately changes as records sell — tier 2 is
|
||||||
|
non-deterministic **by design** (charter law #2). So **no gate may assert dig determinism with the server
|
||||||
|
up**, and no live value may enter a seeded stream or a golden: `pack.gone` is consumed at pick time and
|
||||||
|
never persisted.
|
||||||
|
|
||||||
|
**5. The bin fan is decorative, not inventory.** The room's visible sleeves are built at `buildInterior` from
|
||||||
|
a *different* seeded pick than the dig's offers — a fan, not the crate's contents. A sold record's cover may
|
||||||
|
linger in the fan until the room rebuilds. If we ever want the fan to honour `gone`, **that** is when I'd
|
||||||
|
publish a post-build hook; it is not needed for the epoch's claim, because the crate you *riffle* is true.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. The sell counter (v7.0-alpha, R30 ledger #2) → Lanes F/B
|
||||||
|
|
||||||
|
**The mirror of the buy card.** Walk to the keeper's counter holding items the shop trades in → a sell
|
||||||
|
card (the same manila price-sticker treatment as the dig's pull panel): item, OFFER, SELL. Module:
|
||||||
|
`web/js/interiors/sell.js` — DOM-only (zero draws, zero GPU resources), same consumer pattern as
|
||||||
|
`dig.js` (C ships the interaction; F owns the in-game input/mode hook).
|
||||||
|
|
||||||
|
### 9.1 THE OFFER — the no-pump law, structural (the multiplier and why)
|
||||||
|
|
||||||
|
```js
|
||||||
|
sellOffer(pricePaid) = min(pricePaid − 1, max(1, floor(pricePaid × 0.5))) // and never < 0
|
||||||
|
```
|
||||||
|
|
||||||
|
- **The multiplier is 0.5** — the classic secondhand-dealer margin (a keeper pays about half of what
|
||||||
|
he'll sticker it at; he has to eat too). It keeps selling *meaningful* (half your money back beats a
|
||||||
|
full crate of duds) without being an income source.
|
||||||
|
- **The `min(pricePaid − 1, …)` clamp is the law made structural, not tuned:** the offer is **strictly
|
||||||
|
below what you paid for every `pricePaid ≥ 1`** (at `p = 1` the offer is $0 and the SELL button
|
||||||
|
disables — the keeper won't buy what he can't sticker; at `p = 2` the naive `max(1, floor(p/2)) = 1`
|
||||||
|
would meet the clamp anyway — the clamp exists so no future retune of the multiplier can ever push an
|
||||||
|
offer to break even). **Buy-then-immediately-sell therefore loses ≥ $1 per round trip, always** —
|
||||||
|
F's no-pump gate (`R30 ledger #5`, monotonic cash loss) holds **by construction**, and a change to
|
||||||
|
this formula that breaks that property is a contract violation, not a balance pass.
|
||||||
|
- **Alpha basis = `pricePaid`.** The alpha carries no guide-band data (the BIBLE bands are beta), and
|
||||||
|
in the alpha every buy happens at the shop's asking price — so `pricePaid` IS the buy-side price the
|
||||||
|
player faced. **Beta swaps only the basis, not the shape:** when E's guide bands land, the offer
|
||||||
|
becomes `min(bandLow − 1, max(1, floor(bandLow × 0.5)))` — below the band's *low* edge by
|
||||||
|
construction, so a gem bought under band still nets its finder margin only against the band, never
|
||||||
|
against the keeper. The formula's clamp survives the swap untouched.
|
||||||
|
|
||||||
|
### 9.2 What the keeper buys (alpha: type = type, strict)
|
||||||
|
|
||||||
|
- `sellableIn(shopType, item)` ⇔ `item.type === shopType`. A record shop buys records, a book shop
|
||||||
|
books — **any** shop of the type (per-keeper taste, haggling, pawn-buys-everything: all beta, not
|
||||||
|
built).
|
||||||
|
- **An item with no `type` is NOT sellable — fail-closed** (vacuous-gate law: a matcher that passes an
|
||||||
|
untyped item would "match" everywhere). **→ Lane F ASK:** stamp `type` (the registry stock type:
|
||||||
|
`record`/`book`/`toy`/…) on every collection item at buy time — the R30 published shape
|
||||||
|
(`{townKey, godverseShopId|shopId, sku|slotId, pricePaid, dayFound}`) doesn't carry it, and without
|
||||||
|
it nothing is sellable anywhere. Carrying `title`/`artist` too is strongly asked (B's collection UI
|
||||||
|
needs them; my card falls back to the sku/slotId label when absent — ugly but honest).
|
||||||
|
|
||||||
|
### 9.3 The seam — money like buying, removal via the game API only
|
||||||
|
|
||||||
|
- **C consumes `window.PROCITY.game.collection` READ-ONLY.** The card never splices, reorders, or
|
||||||
|
mutates it (same species of law as §8.3's `items[]`).
|
||||||
|
- **`onSell(item, offer)`** is the single side-effect callback — the exact mirror of the dig's
|
||||||
|
`onBuy(offer)` (the proven wallet seam). The **consumer** (F) does both halves: credit the wallet,
|
||||||
|
remove the item from the collection **through the game API**. `onSell` returning `false` = keeper
|
||||||
|
veto: card unchanged, nothing moves.
|
||||||
|
- `wallet.sell(item, offer)` is provided (C-owned `wallet.js`, mirrors `buy()`): credits `offer`,
|
||||||
|
drops the matching v0-inventory entry, notifies. F binds it exactly like it binds `wallet.buy`.
|
||||||
|
- Till ring: the sale rings `'till'` from the counter emitter (the R28 one-shot pattern, same
|
||||||
|
distance-gain math as the dig's) — silent-and-happy with no engine / `?mute` / `?noassets`.
|
||||||
|
|
||||||
|
### 9.4 The trigger + the card
|
||||||
|
|
||||||
|
- **Where:** within **`SELL_DIST = 2.0 m`** of `room.counter.pose` (bench centre, room-local — the
|
||||||
|
frame the interior camera walks in). Helper exported: `nearCounter(room, pos, dist?)`.
|
||||||
|
- **Suggested E routing for F** (matches my harness): aimed bin → dig (unchanged) · else near counter
|
||||||
|
with ≥ 1 sellable → sell card · else shelf-buy. Pointer unlocks while the card is up (dig precedent).
|
||||||
|
- **The card:** one item at a time — artist/title (or the sku/slotId fallback), the round OFFER
|
||||||
|
sticker, SELL, `‹ ›` cycling when more than one sellable, ✕/Esc closes. `open()` returns `false`
|
||||||
|
(no card, no DOM) when nothing is sellable.
|
||||||
|
|
||||||
|
### 9.5 Fail-soft (the classic-pure law, C's half)
|
||||||
|
|
||||||
|
No game layer (`?classic=1` / `?game=0` ⇒ `window.PROCITY.game` absent) → the consumer never opens
|
||||||
|
the sell UI, **and** `open()` itself returns `false` on a missing/empty item list — zero errors, zero
|
||||||
|
DOM, zero localStorage. `createSell()` allocates DOM only; `dispose()` removes every node + listener.
|
||||||
|
|
||||||
|
### 9.6 API (loader-exact, per the §7 rule: the code is the contract)
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { createSell, sellOffer, sellableIn, nearCounter, SELL_MULT, SELL_DIST } from './js/interiors/sell.js';
|
||||||
|
const sell = createSell();
|
||||||
|
sell.open({ shopType, shopName, items, getCash, onSell, onClose, emitters }) // → bool (false = nothing sellable)
|
||||||
|
sell.close(); sell.dispose(); sell.active // mirrors dig.js
|
||||||
|
```
|
||||||
@ -6,6 +6,951 @@ _rotOnly/head-bone normalize/upgradeStreetPeople). Measurements on the M3 Ultra
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## ROUND 29 — v6 SPIKE 1: `look.glb` bound and wired — peds glance (wave 0)
|
||||||
|
|
||||||
|
*PROCITY-D, 2026-07-17. The spike finally reaches the game: E's `look.glb` (mesh-free, 65 joints, 6.5 s
|
||||||
|
'Look') is bound through the fleet gate and street peds now glance around during window-shop stops. `lean`
|
||||||
|
is not here and never was — E measured the stash (R28). Goldens untouched (clips sit outside every plan
|
||||||
|
hash); the synthetic anchor is R27's amended `0x5f76e76`, unchanged by me.*
|
||||||
|
|
||||||
|
### What shipped
|
||||||
|
- **`rigs.js` — `loadPedFleet(base, { sit, look })`** → `fleet.lookClip`, the exact twin of the R16 sit gate.
|
||||||
|
The shell passes `look: !CLASSIC`, so **`?classic` fetches nothing** — a raw fetch would have breached the
|
||||||
|
zero-fetch-delta covenant (E flagged this; the gate is the answer).
|
||||||
|
- **`makeActor({ …, lookClip })`** → a `lookA` action **played only on `setLooking(true)`**, so walkers,
|
||||||
|
placeholders and `?classic` never advance it. `setLooking` **crossfades** (0.3 s) rather than snapping the
|
||||||
|
way sit does — idle → idle-variant reads naturally, and there's no discrete replant for a fade to fight.
|
||||||
|
- **`sim.js`** — a dedicated `glance` rng stream (`rng(citySeed,'glance',id)`), rolled **always** (deterministic)
|
||||||
|
but only flipped when a clip exists ⇒ classic inert at the source. **Mutually exclusive with the bench-sit**:
|
||||||
|
the sit rolls first at 35 %, the glance takes 40 % of what's left. `RigPool` threads `lookClip`; release
|
||||||
|
snaps the glance off so no rig is pooled mid-look.
|
||||||
|
|
||||||
|
### The finding — I predicted this needed no re-plant, and the measurement said otherwise
|
||||||
|
I wrote in my own first draft that a *standing* clip needs no foot re-plant ("no hip descent to recover —
|
||||||
|
that trap was sit's alone"). **Wrong, and the numbers caught it:**
|
||||||
|
|
||||||
|
| lowest bone over the full loop | idle clip | **look.glb, no plant** |
|
||||||
|
|---|---|---|
|
||||||
|
| min → max | −0.04 → 0.00 (span **4 cm**) | −0.009 → **+0.205** (span **21 cm**) |
|
||||||
|
|
||||||
|
The ped **hovers up to 20.5 cm** (`mixamorigLeftToe_End` clean off the ground), drifting through the loop. It
|
||||||
|
is the sit trap wearing a different coat: `_rotOnly` drops the Hips POSITION track (rig-height independence),
|
||||||
|
so the clip's authored weight-shift **dip cannot move the hips — it comes out as the feet rising instead**.
|
||||||
|
Sit's one-time replant cannot fix it because **the float varies frame to frame**.
|
||||||
|
|
||||||
|
**Fix: `plantFeet()` — the per-frame twin of sit's replant.** Reset to the bind plant → measure the posed
|
||||||
|
feet → drop `inner` by float / scale. **Absolute (reset→measure→correct), never incremental, so it cannot
|
||||||
|
drift.** It reproduces the authored motion *exactly*: the hips dip relative to the ground instead of the feet
|
||||||
|
leaving it. Scale-aware like sit's. Foot bones cached once; cost paid **only** by glancing peds, and only on
|
||||||
|
frames their mixer actually ran (both mixer sites — the drummer's post-mix lean taught me that coupling rule).
|
||||||
|
|
||||||
|
### Proofs
|
||||||
|
| | before | after |
|
||||||
|
|---|---|---|
|
||||||
|
| foot float over the loop | **0.205 m** | **0.000 m** (pinned) |
|
||||||
|
| head yaw sweep (the glance itself) | — | **span 1.735** (−0.85 → +0.89) |
|
||||||
|
| `?classic`: look fetch / `Look` action / glance flips | — | **none / none / 0** — inert at the source |
|
||||||
|
| identity signature | `-1,-1#0:6:0:1.777:1.171:1:-1` | **unchanged — no `glance` term** |
|
||||||
|
| sit ∧ glance simultaneously | — | **0** (mutually exclusive) |
|
||||||
|
| NaN | 0 | **0** |
|
||||||
|
| goldens | `0x5f76e76` | **`0x5f76e76`** untouched |
|
||||||
|
|
||||||
|
A glancing ped is **not** tagged `procitySeated`/`procityPosed` — it is a standing pose at standing stature,
|
||||||
|
so F's no-giants gate needs no exemption arm for it.
|
||||||
|
|
||||||
|
### Handshakes
|
||||||
|
- **D → F:** **the pose gate has its subject** — un-SKIP it. A fresh `?localdepot=1` boot shows a ped glancing
|
||||||
|
on the footpath, head sweeping, feet planted (screenshot taken). Classic/`?noassets` untouched.
|
||||||
|
- **Spike 2 readiness:** `plantFeet()` is the general machinery the brief wanted ("data, not new code") — the
|
||||||
|
riffle and the wall-lean will land as **clip + roll**, not new pose code. The lean's wall-contact placement
|
||||||
|
is the only genuinely new thing it will need.
|
||||||
|
- **Stale line spotted (A's file, A rests):** `selfcheck.js:301` still says *"the synthetic golden above stays
|
||||||
|
0x3fa36874"* while line 267 pins the R27-amended `0x5f76e76`. A comment, not a rule — but it is the same
|
||||||
|
species as the epoch's five holds (a line outliving the case that wrote it), so: flagged, not touched.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ROUND 28 — v5.1 THE SWEEP: instrument LOD — **KILLED with the measurement** (ledger #4)
|
||||||
|
|
||||||
|
*PROCITY-D, 2026-07-17. The oldest tri item on the books (v3.1-era). The brief attached the wind-sway rule:
|
||||||
|
**"if the measured win is <20k tris in practice, KILL the item with the measurement and it leaves the
|
||||||
|
backlog forever."** It does not survive. **E: do not build the `_lo` variants — that work is now unnecessary.***
|
||||||
|
|
||||||
|
### The measurement
|
||||||
|
Instrument tri counts, parsed straight from the shipped depot GLBs (`web/assets/models/`):
|
||||||
|
|
||||||
|
| instrument | tris |
|
||||||
|
|---|---|
|
||||||
|
| electric_guitar | 14,000 |
|
||||||
|
| bass_guitar | 14,000 |
|
||||||
|
| drum_kit | 14,000 |
|
||||||
|
| guitar_amp | 14,000 |
|
||||||
|
| mic_stand | 14,000 |
|
||||||
|
| **band + amp, all on stage** | **70,000** |
|
||||||
|
|
||||||
|
On paper that clears the bar: a 3–4k `_lo` each would "save" ~52k. **But the rule says *in practice*, and the
|
||||||
|
brief named the reason — "interiors are close-quarters."** So I measured the rooms an LOD would have to fire in
|
||||||
|
(live, all three venue archetypes, `interiorMode` driven by hand since rAF is throttled):
|
||||||
|
|
||||||
|
| venue | room | **max diagonal** | room tris (no band) | draws |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| pub — The Exchange Hotel | 9.7 × 9.7 m | 13.7 m | 12,522 | 38 |
|
||||||
|
| band_room — The Vinyl Shed | 8.9 × 9.6 m | 13.0 m | 11,699 | 35 |
|
||||||
|
| rsl — Wangaratta RSL (biggest) | 12 × 9.4 m | **15.2 m** | 15,115 | 38 |
|
||||||
|
|
||||||
|
### The verdict: practical win = **0 tris**. KILLED.
|
||||||
|
**A player can never be more than 15.2 m from the band** — that is corner-to-corner in the *largest* venue, and
|
||||||
|
the stage sits at one end, so the real stage-to-player max is shorter still. An LOD is a *distance* trade: it
|
||||||
|
pays when the subject is small on screen. Inside a ≤15.2 m room the band is never small — it is the subject of
|
||||||
|
the gig, and of F's tour money shot. Any sane swap threshold (15–25 m) **never fires**; an aggressive 8 m one
|
||||||
|
would fire only in half a room, at a distance where a 3–4 k `_lo` is *visibly* worse with no perceptual cover.
|
||||||
|
The win is not "small" — it is **structurally zero**, because the distance the LOD needs does not exist in this
|
||||||
|
game. **Per the wind-sway rule: killed, off the backlog forever.**
|
||||||
|
|
||||||
|
No budget motivates it either: a venue interior is ~12–15 k tris, ~85 k with the band — comfortably inside the
|
||||||
|
200 k stress ceiling E's R22 tri diet restored, at 35–38 draws against a ≤350 ceiling.
|
||||||
|
|
||||||
|
### Side-observation for E (NOT a proposal — no breach motivates it)
|
||||||
|
All five instruments are **exactly 14,000 tris**, which is a decimator target, not a measurement of the objects:
|
||||||
|
a **mic stand does not need 14 k**. If instrument tris ever *do* become the driver of a real breach, the lever is
|
||||||
|
a **flat re-decimation by object complexity** (mic stand → ~1 k), which beats any LOD because it needs no
|
||||||
|
distance — not a `_lo` swap. Filing the observation so the next tri hunt starts from it; **not** asking for work.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ROUND 23 — v5.0-alpha: the GIG_RANGE chunk-neighbour fix (ledger #6, the carried v4.x item)
|
||||||
|
|
||||||
|
*PROCITY-D, 2026-07-16. My own filed finding, finally fixed — post-release, as Fable scheduled. Hot path,
|
||||||
|
so the R18-style before/after is the deliverable. `selfcheck` **ALL GREEN 161300/161300**, `0x3fa36874`
|
||||||
|
frozen (runtime-only change — no plan, no golden).*
|
||||||
|
|
||||||
|
### The finding grew while it waited: 8.7 % → 42.6 %
|
||||||
|
I filed this at 8.7 % in R20, measured at katoomba's isolated pub. **A's R21 cluster bias made it worse**:
|
||||||
|
venues now sit *inside* the retail cluster, where peds converge from every side rather than trickling along
|
||||||
|
one street — so far more in-range peds are across a chunk edge. Re-measured on the relocated venue:
|
||||||
|
**42.6 %** of in-gig-range ped-samples (5094 of 11971) were chunk-blind. Fixing it mattered more in R23
|
||||||
|
than when I filed it.
|
||||||
|
|
||||||
|
### The override: the brief contradicts itself, and the measurement broke the tie
|
||||||
|
The brief said *"extend `_nearestOpenShop` to the ped's chunk + 8 neighbours"* **and** *"byte-identical
|
||||||
|
flags-off"*. **I built the literal version first and measured it: those two cannot both be true.** On the
|
||||||
|
synthetic town (exactly what `?classic=1` boots, gig layer absent), a full 3×3 scan sourced **127 of 855
|
||||||
|
finds — 14.9 % — from neighbour chunks**: finds v2 never made. The frozen v2 crowd would start shopping
|
||||||
|
~15 % more. That is a covenant breach, in a clause of the same brief that asked for it.
|
||||||
|
|
||||||
|
**So the fix is scoped to the GIG path** — which is also what I actually filed (the finding was always
|
||||||
|
`GIG_RANGE`-specific):
|
||||||
|
- **Ordinary shops → own chunk only.** v2 semantics, frozen. A shopfront is noticed from its own block.
|
||||||
|
- **Gig venues → the full 34 m radius, honest across chunk edges.** The gig is the "follow the sound" pull
|
||||||
|
that is *supposed* to reach further — that is the entire reason `GIG_RANGE` (34) > `PATRON_RANGE` (18).
|
||||||
|
- **No gig on → `_gigVenues` is empty → the neighbour loop is skipped entirely.** `?classic` and every
|
||||||
|
flags-off boot take the *identical code path and identical cost* to v2. **Byte-identical by construction**,
|
||||||
|
not by measurement luck.
|
||||||
|
|
||||||
|
Both ranges are < `CHUNK` (64), so a 3×3 sweep is *exact*, not an approximation; a fixed `dz/dx` order keeps
|
||||||
|
exact-distance ties deterministic, as the old single-list scan was.
|
||||||
|
|
||||||
|
### Before / after
|
||||||
|
| proof | before | after |
|
||||||
|
|---|---|---|
|
||||||
|
| **cross-chunk in-range peds that see the gig** — katoomba | **0 % by construction**¹ | **4/4 = 100 %** |
|
||||||
|
| — fitzroy (densest, worst case) | **0 % by construction**¹ | **11/11 = 100 %** |
|
||||||
|
| same-chunk peds (must not change) | 100 % | 100 % |
|
||||||
|
| **classic / flags-off: finds sourced from a neighbour** | 0 (v2) | **0** (127 under the rejected full scan) |
|
||||||
|
| determinism (2 sims, same seed) | identical | **identical** (identity sets byte-equal) |
|
||||||
|
| NaN — katoomba / fitzroy / synthetic | 0 | **0** |
|
||||||
|
| sim cost, katoomba @14:00, 236 peds | 0.056 ms/frame | **0.057 ms/frame (+2 %)** |
|
||||||
|
| lookup cost/call — no gig on | 0.58 µs | **0.58 µs (loop skipped)** |
|
||||||
|
| lookup cost/call — gig on | 0.58 µs | **1.3–2.7 µs** |
|
||||||
|
|
||||||
|
¹ *not a small-sample claim: v2 read `shopsByChunk.get(ownChunk)` only, so a venue in another chunk was
|
||||||
|
never in the scanned list and could not be returned. 0 % is structural.*
|
||||||
|
|
||||||
|
The lookup is 2–4× dearer **while a gig is on**, but it only fires every `PATRON_STRIDE` (10 m walked), not
|
||||||
|
per frame — hence **+2 % of a 0.056 ms frame cost**. Against a 16.7 ms budget that is 0.006 %. Fitzroy at 376
|
||||||
|
peds stayed clean. Minor known inefficiency: with a gig *scheduled but closed* (e.g. 14:00) the neighbour
|
||||||
|
loop still runs for nothing (0.58 → 1.3 µs) — `_gigVenues` holds ids, so testing openness would need the
|
||||||
|
lookup it's trying to avoid. Not worth it at +2 %; noted rather than hidden.
|
||||||
|
|
||||||
|
### Handshakes
|
||||||
|
- **D → F (gate):** the fix is **runtime-only** — no plan, no golden, no flag, no fetch. `?classic=1` takes
|
||||||
|
the identical code path to v2 (loop skipped), so the classic regression is untouched **by construction**.
|
||||||
|
Selfcheck green at 161300. No D-side blocker for `v5.0-alpha`.
|
||||||
|
- **Carried (deliberate, documented):** `PATRON_RANGE` keeps v2's own chunk-blindness — an ordinary shop 10 m
|
||||||
|
away across a chunk edge still goes unnoticed. That is now a *frozen v2 quirk*, not an oversight: fixing it
|
||||||
|
costs the classic covenant, and the covenant wins. Revisit only if the covenant is ever relaxed.
|
||||||
|
- **Still unowned (unchanged):** R20's main-street relocation half (heroes 0–19 % of the densest cluster).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ROUND 22 — v4.0 THE EPOCH CLOSE: citizens at pack scale (ledger #5)
|
||||||
|
|
||||||
|
*PROCITY-D, 2026-07-16. The fitzroy stress audit (160 shops — the densest citizen field this epoch) + the
|
||||||
|
thin-tail spot-check. Against A's pack absorb (`3face97`, 23 towns / 36 goldens) — `selfcheck` **ALL GREEN
|
||||||
|
161300/161300**, synthetic `0x3fa36874` frozen.*
|
||||||
|
|
||||||
|
### Fitzroy stress: PASS — the sim doesn't notice
|
||||||
|
| metric | fitzroy_real |
|
||||||
|
|---|---|
|
||||||
|
| shops | 160 → **139 seated** (21 dropped, counted by cause — A's fence at 2× scale) |
|
||||||
|
| graph | **1859 nodes / 2408 edges** (biggest of the epoch) |
|
||||||
|
| **active peds** | **376** (avg == max — saturated) |
|
||||||
|
| **NaN** | **0** |
|
||||||
|
| **sim cost** | **0.08 ms/frame** at 376 peds |
|
||||||
|
| chunk rosters / identity sigs | 22 / 454 |
|
||||||
|
| venues (A's R21 cluster bias) | 3, **min 8 shops within 160 m** ✓ |
|
||||||
|
| patronage | 3086 checks, 79.6 % null, **103 visits** |
|
||||||
|
| plan gen | 194 ms |
|
||||||
|
|
||||||
|
**Nothing needed touching at 2× scale.** Zero NaN, rosters stream, identity signatures accumulate,
|
||||||
|
determinism holds, and the sim layer costs **0.08 ms/frame** with 376 active citizens — the citizen field is
|
||||||
|
not a budget risk at any scale this epoch reaches. A's cluster bias survives the mecca (min 8 ≥ the ≥3 bar).
|
||||||
|
|
||||||
|
### Thin tail: "does 12 shops still read alive?" — **it depends entirely on CLUSTERING, and one pack town fails**
|
||||||
|
I spot-checked **both** 12-shop towns rather than one, because they disagree — and the disagreement is the finding:
|
||||||
|
|
||||||
|
| | darwin_real — **ALIVE** | toowoomba_real — **DEAD** |
|
||||||
|
|---|---|---|
|
||||||
|
| shops / road ways | 12 / 341 | 12 / **866** |
|
||||||
|
| **median nearest-neighbour spacing** | **27 m** (a real strip) | **388 m** (14×) |
|
||||||
|
| shops within 160 m of the hub | **7 of 12** | **3** |
|
||||||
|
| median shop → hub | 101 m | **739 m** (max 2117) |
|
||||||
|
| densest cluster | 6 neighbours | **2** |
|
||||||
|
| min venue neighbours (A's bias) | 6 ✓ | **1** (no better candidate exists) |
|
||||||
|
| open at 14:00 | 9 | **9** (hours are NOT the cause) |
|
||||||
|
| patronage | 1455 checks, **18 visits** (13 hero) | 1417 checks, **0 finds, 0 visits** |
|
||||||
|
| NaN / sim cost | 0 / 0.05 ms | 0 / 0.05 ms |
|
||||||
|
|
||||||
|
**Both towns pass `MIN_TOWN_SHOPS`. One has a high street; the other doesn't.** Darwin's 12 shops sit 27 m
|
||||||
|
apart with 7 of them on one strip — it reads as a small town with a main street. Toowoomba's 12 sit ~388 m
|
||||||
|
apart across a **4.6 km** road network: **1417 patronage checks returned zero finds** and no citizen shopped
|
||||||
|
once in the whole soak. It isn't a shopping town — it's a road network with occasional shops. (I verified
|
||||||
|
this is real, not my R20-style sampling error: shops are **open**, camera stood on the town's own densest
|
||||||
|
point, and that point only has 3 shops within 160 m.)
|
||||||
|
|
||||||
|
**→ FILED for E / Fable (curation, not code): the floor measures the wrong thing.** `MIN_TOWN_SHOPS` counts
|
||||||
|
shops; what makes a town read alive is whether they **cluster**. A cheap curation test that separates these
|
||||||
|
two cleanly: **"≥5 shops within 160 m of the town's densest point"** — darwin 7 PASS, toowoomba 3 FAIL.
|
||||||
|
Toowoomba is in the v4.0 pack today and has no main street to walk. Cull it, or accept it as a known
|
||||||
|
thin-tail (it costs nothing — 0 NaN, 0.05 ms/frame — it just has nothing to do). **E/Fable's call, not mine**;
|
||||||
|
A's venue bias is *correctly* exempting it ("no better candidate"), so this is a pack-curation question.
|
||||||
|
|
||||||
|
### Handshakes
|
||||||
|
- **D → F (gate):** citizens at pack scale **GREEN**. 23-town pack: fitzroy (160 shops / 376 peds) clean at
|
||||||
|
0 NaN and 0.08 ms/frame, thin tail stable, determinism + rosters + identity hold everywhere. **No D-side
|
||||||
|
blocker for `v4.0`.** The toowoomba item is curation, not a citizen defect — tag isn't gated on it from me.
|
||||||
|
- **Carried to v4.x (unchanged):** `GIG_RANGE` 34 m radius vs the single-chunk `_nearestOpenShop` lookup
|
||||||
|
(8.7 % of in-range peds chunk-blind; fix = chunk + 8 neighbours).
|
||||||
|
- **Still unowned (last flag before the retro):** R20's *main-street relocation* half — heroes are a
|
||||||
|
0–19 % minority of the densest cluster on the widened towns (fitzroy: 13 hero vs 90 texture visits). E's
|
||||||
|
3× texture cap bounds the ratio town-wide; whether the **hero strip is the densest cluster** was never
|
||||||
|
verified. If it matters for v4.x, it's a one-call re-run of this harness.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ROUND 21 — v4.0-beta CLOSE: relocation verify (ledger #4)
|
||||||
|
|
||||||
|
*PROCITY-D, 2026-07-16. Verifying A's venue cluster-adjacency bias (`110c828`), which Fable assigned from
|
||||||
|
my R20 relocation finding. `selfcheck` **ALL GREEN 154141/154141** against A's re-pinned goldens.*
|
||||||
|
|
||||||
|
### Verdict: A's bias PASSES acceptance on all five — my finding is closed
|
||||||
|
Acceptance was "no venue sits with <3 shops in its 160 m radius." Shops within 160 m @ distance-to-retail-hub,
|
||||||
|
before (R20) → after (R21):
|
||||||
|
|
||||||
|
| town | min shops in 160 m | before → after |
|
||||||
|
|---|---|---|
|
||||||
|
| katoomba_real | **9** PASS | **pub 1→9 @1007→466 m** · rsl 10→12 @161→148 m · band_room 7→10 @181→161 m |
|
||||||
|
| newtown_real | **5** PASS | **rsl 1→11 @2228→29 m** · band_room 0→7 @2433→124 m · pub 0→5 @2190→714 m |
|
||||||
|
| bendigo_real | **5** PASS | rsl 0→10 @451→86 m · band_room 0→10 @1157→96 m · pub 1→5 @504→316 m |
|
||||||
|
| castlemaine_real | **4** PASS | pub 1→5 @223→182 m · band_room 6→6 @0 m · rsl 4→4 @155 m |
|
||||||
|
| fremantle_real | **3** PASS* | band_room 2→8 @612→309 m · *pub 1→3 @969→**1352 m*** · *rsl 2→3 @561→**1292 m*** |
|
||||||
|
|
||||||
|
**My R20 headline is fixed**: katoomba's pub — "a gig with no passing crowd", 1 shop at 1007 m — now sits at
|
||||||
|
**466 m with 9 shops**. Newtown is the dramatic one: all three venues were **2.2–2.4 km out with 0–1 shops**;
|
||||||
|
the rsl now sits **29 m from the hub with 11**. Castlemaine's floor did NOT need an exemption (min 4).
|
||||||
|
|
||||||
|
### The crowd followed — katoomba's pub, same density/seed/21:00
|
||||||
|
| | R20 (pre-bias) | **R21 (post-bias)** |
|
||||||
|
|---|---|---|
|
||||||
|
| passing crowd (maxActive) | 34 | **66** (2×) |
|
||||||
|
| patron checks | 226 | **442** (2×) |
|
||||||
|
| inside frames | 1042 | **1424** |
|
||||||
|
| tonightRoster | 2 | **3** |
|
||||||
|
| NaN | 0 | **0** |
|
||||||
|
|
||||||
|
The "gig with no passing crowd" observation is retired. (null % stays ~96 % at 21:00 — retail is shut at night
|
||||||
|
**by design**; that's my own R20 correction, not a leak.)
|
||||||
|
|
||||||
|
### Two readings that need care (so nobody mis-reads my own numbers)
|
||||||
|
- ***fremantle is the weakest pass** (\*): its pub/rsl clear the ≥3 bar but moved **further** from the primary
|
||||||
|
hub (969→1352 m, 561→1292 m) — A's bias optimises **local cluster adjacency**, and fremantle's venues found
|
||||||
|
a *secondary* cluster. It satisfies the letter of acceptance and the crowd is healthy in practice (pub:
|
||||||
|
roster **6**, inside **2741**, maxActive **72**), so I'm **not** calling it a miss — just naming it, since
|
||||||
|
"3 shops at 1.3 km" is a thinner high street than katoomba's "9 shops at 466 m". A's call if they want it tighter.
|
||||||
|
- **venue-win % DROPPING is not a regression.** newtown's rsl went 100 %→**21 %**, fremantle's pub 100 %→62 %.
|
||||||
|
That metric asks "when a check finds a shop, is it the venue?" — it was only ever 100 % because the venue
|
||||||
|
was *alone*. Post-bias the venue has real neighbours competing for finds, while total crowd went **up**
|
||||||
|
(newtown rsl: roster 3, inside 1873, maxActive 55). Isolation flatters that number; ignore it post-bias.
|
||||||
|
|
||||||
|
### Handshakes
|
||||||
|
- **D → F (gate):** relocation verify **GREEN**. All five towns pass A's acceptance, the pub finding is closed,
|
||||||
|
0 NaN at every venue, rosters populate. No D-side gate concern for `v4.0-beta`.
|
||||||
|
- **FYI F:** `selfcheck` scans **every** `web/assets/towns/*.json` on disk, including E's *untracked* pack WIP.
|
||||||
|
I saw it transiently RED mid-round (`newcastle_real`: no open-late shop) purely from E's in-flight files, then
|
||||||
|
green again minutes later at 154141 checks. If you gate while E is mid-fetch you may catch a false red —
|
||||||
|
E's pack is non-gating, so check whether a failure is a pack town before believing it.
|
||||||
|
- **Still filed for v4.x (agreed, not a mid-beta change):** `GIG_RANGE` is a 34 m radius but `_nearestOpenShop`
|
||||||
|
searches only the ped's own 64 m chunk → 8.7 % of in-range peds are chunk-blind. Fix = chunk + 8 neighbours.
|
||||||
|
- **Unowned from R20:** the *main-street* relocation half (heroes 0–19 % of the densest cluster; castlemaine's
|
||||||
|
main street has zero secondhand shops). E's R20 SUBTLE guardrails (texture capped at 3× heroes) address the
|
||||||
|
ratio town-wide; whether the **hero strip is again the densest cluster** is unverified. Not in my R21 ledger —
|
||||||
|
flagging it so it doesn't get lost at the epoch close.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ROUND 20 — v4.0-beta THE FIVE TOWNS: citizens at density (ledger #7)
|
||||||
|
|
||||||
|
*PROCITY-D, 2026-07-16. Part 1 of 2: the **pre-density baseline** — the gig crowd's behaviour on all five
|
||||||
|
real towns' venues (F's district gate owns placement; I own behaviour). This half is unblocked; the density
|
||||||
|
re-audit waits on E's widened caches → A's absorb. Deliberately measured BEFORE the widening so the
|
||||||
|
after-table is a real A/B.*
|
||||||
|
|
||||||
|
### The gig crowd is behaviourally CORRECT on all five real towns
|
||||||
|
Per town: stream sim on the real graph, `setGig` on its first venue, 21:00, camera at the venue, ~1400
|
||||||
|
frames. Every town generates 3 venues (pub/band_room/rsl — charter's 2–4) on night hours.
|
||||||
|
|
||||||
|
| town | shops | door chunks | patron checks | **null %** | hit venue | **venue win %** | roster | NaN |
|
||||||
|
|---|---|---|---|---|---|---|---|---|
|
||||||
|
| katoomba_real | 19 | ~5 | 703 | 97.3 | 14 | 74 | 2 | **0** |
|
||||||
|
| newtown_real | 18 | 15 | 267 | 95.5 | 12 | **100** | 4 | **0** |
|
||||||
|
| fremantle_real | 20 | 16 | 429 | 94.9 | 22 | **100** | 7 | **0** |
|
||||||
|
| bendigo_real | 9 | 9 | 343 | 97.7 | 8 | **100** | 3 | **0** |
|
||||||
|
| castlemaine_real | 6 | 5 | 364 | 95.9 | 6 | 40 | 4 | **0** |
|
||||||
|
|
||||||
|
- **0 NaN on all five.** The gig crowd is numerically safe on every real graph.
|
||||||
|
- **`tonightRoster` populates on all five** (2–7) — R14 identity continuity holds on real bones.
|
||||||
|
- **Venue-win % is the surge working**: whenever the patronage check finds *any* shop, the gig venue beats
|
||||||
|
ordinary shopfronts (GIG_RANGE/gigBest preference) — 100% on three towns, 74% on katoomba.
|
||||||
|
**castlemaine's 40% is not a leak**: at the 6-shop floor, 3 of its 6 shops ARE venues, so the "other
|
||||||
|
shop" hits are mostly *other venues*. A quirk of the sparse floor, not the mechanism.
|
||||||
|
|
||||||
|
### CORRECTION to my own first read of that null % — I misattributed it (measurement wins)
|
||||||
|
My first pass called the 94.9–97.7 % null rate "the crowd is sparsity-throttled" and made it the headline
|
||||||
|
before/after metric. **Both halves of that were wrong, and I'm correcting the record:**
|
||||||
|
1. **It was measured at gig hours.** At 21:00 only **4 of 69** katoomba shops are open — retail is shut *by
|
||||||
|
design*. A high null rate at 21:00 is **correct night behaviour**, not sparsity. The gig IS the nightlife.
|
||||||
|
2. **It was sampled at each town's first venue** (`shops.find(s => s.venue)`). For katoomba that is the
|
||||||
|
**pub, which sits 1,000 m from the retail cluster with 1 neighbour** — I had parked the camera on the one
|
||||||
|
isolated venue in town. Measured there, the daytime null was 99.4 % with **0 visits**; measured on the
|
||||||
|
actual main street, the same town same seed gives **77.2 % and 64 visits**. The number was an artefact of
|
||||||
|
where I stood.
|
||||||
|
|
||||||
|
The gig-crowd table above still **stands** — it measures the *mechanism* (0 NaN, rosters populate,
|
||||||
|
venue-win %), which is location-independent. Only the sparsity interpretation was bad.
|
||||||
|
|
||||||
|
### The real density A/B — the main street, retail hours (14:00), same seed, like-for-like
|
||||||
|
Camera on the **densest retail cluster** (the actual main street), pre-widening cache (from git) vs E's
|
||||||
|
widened cache, 2,000 frames each:
|
||||||
|
|
||||||
|
| main street @14:00 | PRE (20 shops) | **POST (80 shops)** |
|
||||||
|
|---|---|---|
|
||||||
|
| hub density (shops within 160 m) | 8 | **25** |
|
||||||
|
| null % | 90.2 | **77.2** |
|
||||||
|
| **total visits** | 17 | **64** (3.8×) |
|
||||||
|
| **hero visits** (opshop/book/record/video/pawn/toy) | 17 | **19** |
|
||||||
|
| texture visits (milkbar/dept/stall) | 0 | 45 |
|
||||||
|
| hero share of visits | 100 % | **30 %** |
|
||||||
|
| hero share of shops | 84 % | 26 % |
|
||||||
|
| NaN | 0 | 0 |
|
||||||
|
|
||||||
|
**The widening lands John's directive, measured from the crowd side.** The secondhand heroes did **not**
|
||||||
|
lose footfall — they went **17 → 19 visits**. The 45 texture visits are added *on top*, not taken from them.
|
||||||
|
The street is **3.8× busier** while the heroes stay **over-indexed** (26 % of shops, 30 % of visits). That is
|
||||||
|
precisely "the player should notice the town feels more alive, not that the game changed."
|
||||||
|
|
||||||
|
### Filed, not fixed
|
||||||
|
- **Venue placement at density (A/F):** katoomba's 3 venues — rsl **13** shops within 160 m, band_room **10**,
|
||||||
|
**pub 1** (1,000 m from the cluster). A gig at the pub has almost no passing retail crowd. It still draws a
|
||||||
|
roster (peds stream everywhere), so it's a *quality* observation, not a break. Placement is A's/F's lane.
|
||||||
|
- **`GIG_RANGE` vs the chunk lookup (mine):** GIG_RANGE is a 34 m *radius*, but `_nearestOpenShop` searches
|
||||||
|
only the ped's own 64 m chunk, so an in-range ped across a chunk edge is blind to the gig — **8.7 %** of
|
||||||
|
in-range ped-samples (852/9,740). Real but small; a hot-path change mid-beta isn't worth it. Fix (chunk +
|
||||||
|
8 neighbours) is a v4.x candidate.
|
||||||
|
|
||||||
|
### FINAL — post A's absorb (`5d29863`), all five towns at density
|
||||||
|
A's absorb landed (capacity widened, goldens pinned, `selfcheck` **51866/51866**). Re-ran everything against
|
||||||
|
the absorbed graph. **The katoomba numbers barely moved** (null 77.2→78.1, visits 64→62, hero share 30→29 %)
|
||||||
|
— the pre-absorb read was already sound.
|
||||||
|
|
||||||
|
| town | cache→seated | hub | null % | visits | hero | texture | hero share | NaN | maxActive | rosters | sigs |
|
||||||
|
|---|---|---|---|---|---|---|---|---|---|---|---|
|
||||||
|
| katoomba_real | 80→72 (8) | 26 | 78.1 | 62 | 18 | 44 | 29 % | **0** | 217 | 15 | 262 |
|
||||||
|
| fremantle_real | 80→79 (1) | 29 | 71.1 | 116 | 23 | 93 | 20 % | **0** | 401 | 24 | 483 |
|
||||||
|
| newtown_real | 72→67 (5) | 12 | 85.9 | 44 | 6 | 38 | 14 % | **0** | 268 | 19 | 324 |
|
||||||
|
| bendigo_real | 36→35 (1) | 14 | 87.2 | 45 | 1 | 44 | 2 % | **0** | 358 | 21 | 432 |
|
||||||
|
| castlemaine_real | 24→23 (1) | 6 | 94.9 | 9 | 0 | 9 | 0 % | **0** | 295 | 20 | 359 |
|
||||||
|
|
||||||
|
**Sim verdict: zero changes needed at 4× density.** 0 NaN on all five, chunk rosters stream (15–24), identity
|
||||||
|
signatures accumulate (262–483), patronage/loiter/bench-sit all fire, up to **401 active peds** with no
|
||||||
|
degradation. Determinism unchanged (identity is graph+seed keyed; shops don't touch it). **D→F: green.**
|
||||||
|
|
||||||
|
### THE FINDING → for E / C / Fable: the widening MOVED the main street off the secondhand strip
|
||||||
|
The hero share falling (29→0 %) is **not** a behaviour bug — patronage is type-agnostic, so visits track the
|
||||||
|
*local* shop mix exactly. The mix is the story. Main street = the densest shop cluster (what "walk the main
|
||||||
|
street" means to a player):
|
||||||
|
|
||||||
|
| town | main-st shops | **heroes on main st** | hero % of main st | town-wide hero % | **hub moved** |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| katoomba | 9 → **27** | **9 (100 %) → 5** | 100 % → **19 %** | 25 % | **308 m** |
|
||||||
|
| fremantle | — → 30 | 5 | **17 %** | 25 % | — |
|
||||||
|
| newtown | — → 13 | 2 | **15 %** | 27 % | — |
|
||||||
|
| bendigo | — → 15 | 1 | **7 %** | 25 % | — |
|
||||||
|
| castlemaine | 3 → **7** | **1 → 0** | 33 % → **0 %** | 20 % | **434 m** |
|
||||||
|
|
||||||
|
**Pre-widening the densest cluster WAS the secondhand strip** (katoomba: 9 of 9 main-street shops were
|
||||||
|
heroes). Post-widening the town's centre of gravity **relocated 308–434 m onto the general-retail cluster**,
|
||||||
|
where the heroes are a **0–19 % minority**. Castlemaine's main street now has **zero** secondhand shops
|
||||||
|
(0 of 4 heroes within 160 m of its hub; median hero is 463 m away). The heroes weren't drowned *in place* —
|
||||||
|
they were **demoted**: the liveliest street in town is no longer theirs.
|
||||||
|
|
||||||
|
- **The good half stands:** heroes kept their absolute footfall (katoomba 17 → 18 visits) and the street is
|
||||||
|
**3.8× busier**. The widening works; it just overshot the dial.
|
||||||
|
- **This is exactly the case John's directive pre-authorised:** *"if a real town's general retail would drown
|
||||||
|
the secondhand strip visually, E may seed-subsample the texture classes (count it, like every drop)."*
|
||||||
|
My read (measurement, not a ruling — E/C/Fable own it): **subsample texture until the secondhand strip is
|
||||||
|
again the town's densest cluster.** The sparse towns need it most (bendigo 7 %, castlemaine 0 %); katoomba
|
||||||
|
at 19 % is borderline. A hero-adjacency bias on texture seating would be the alternative lever (A's).
|
||||||
|
|
||||||
|
### Also filed — venue placement at density (A / F)
|
||||||
|
Venues are seeded from the shop pool, and at real density they land **off** the retail hub:
|
||||||
|
|
||||||
|
| town | venue distance to hub (shops within 160 m) |
|
||||||
|
|---|---|
|
||||||
|
| katoomba | pub **1007 m (1)** · rsl 161 m (10) · band_room 181 m (7) |
|
||||||
|
| fremantle | pub 969 m (1) · rsl 561 m (2) · band_room 612 m (2) |
|
||||||
|
| **newtown** | **all three 2190–2433 m (0–1)** |
|
||||||
|
| bendigo | pub 504 m (1) · rsl 451 m (0) · band_room 1157 m (0) |
|
||||||
|
| castlemaine | band_room **0 m (6)** · rsl 155 m (4) · pub 223 m (1) |
|
||||||
|
|
||||||
|
A gig 2.4 km from the high street has no passing retail trade. A's `5d29863` "pub facade bias" fixed frontage,
|
||||||
|
not town position — katoomba's pub is still 1007 m out with 1 neighbour. Crowd still forms (peds stream with
|
||||||
|
the camera), so it's a **quality** call, not a break — but "venue placement still lands" is in A's ledger and
|
||||||
|
F's district gate, so it's theirs. Castlemaine shows the good case (band_room *on* the hub).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ROUND 19 — v4.0-alpha CLOSE: the fragmentation re-measure (ledger #3)
|
||||||
|
|
||||||
|
*PROCITY-D, 2026-07-16. A resolved my R18 finding (commit `6fc4b40`: junction-protected DP + bridge-joins
|
||||||
|
+ shopless-island culling, katoomba golden pinned). This is the post-fix re-measure — same harness, same
|
||||||
|
seed. **Verdict: the ruling landed clean — the fragmentation is fully resolved and the sim stays untouched.***
|
||||||
|
|
||||||
|
### Before → after on the real Katoomba graph
|
||||||
|
|
||||||
|
| metric | R18 (my finding) | **R19 (post-fix)** |
|
||||||
|
|---|---|---|
|
||||||
|
| connected components | 105 | **1** |
|
||||||
|
| street-metres in the main net | 58.2 % | **100 %** |
|
||||||
|
| shops stranded off-main | 2 | **0** — 1 bridged to main, 1 dropped past `SEAT_MAX` **and counted** (`norm.dropped`, plan_osm.js:319) |
|
||||||
|
| **% near-crowd on tiny fragments** | ~10 % | **0 %** (no fragments left) |
|
||||||
|
| nodes / edges | 872 / 799 | 830 / 966 |
|
||||||
|
|
||||||
|
Shops: 20 in cache → **19 seated + 1 counted-drop, 0 silently stranded** (ruling satisfied). `selfcheck`
|
||||||
|
**51670/51670 green**, katoomba real-roads golden pinned in the output-drift guard.
|
||||||
|
|
||||||
|
### The DP-collinear junction-drop mechanism is fixed at the source
|
||||||
|
Re-ran R18's original failing geometry — a **dead-straight cross street through a collinear mid-junction**
|
||||||
|
(the exact case B and I both hit). R18: DP flattened it, dropped the junction → **max-degree 2, disconnected.**
|
||||||
|
R19: the junction is protected from DP → **degree-4 crossroads forms, 1 component.** Fixed.
|
||||||
|
|
||||||
|
### Sim confirm on the fixed (bridged) graph — still zero changes
|
||||||
|
Fresh stream sim, real Katoomba, drove the shop blocks: **0 NaN over 1,303,200 samples**, footpaths perfect
|
||||||
|
(0 off-footpath, maxDist 12.9 m), 312 active peds, loiter (251) + **bench-sit (77)** fire, and **patronage
|
||||||
|
rose 24→41 visits** (peds now reach shops across the connected graph). `fragmentPedFraction: 0`. The sim
|
||||||
|
needed nothing — exactly as R18 predicted (a single component is strictly easier than 105).
|
||||||
|
|
||||||
|
### Handshake
|
||||||
|
- **D → F:** fragmentation verdict **post-fix is GREEN** — real Katoomba is one connected component, 0 peds
|
||||||
|
stranded, 0 NaN, all citizen behaviours hold, determinism intact (A's golden pins it). No D-side gate
|
||||||
|
concern. Tag away.
|
||||||
|
- **FYI (B/F budget):** edges rose 799→966 (junction protection + bridges) though total street-metres fell
|
||||||
|
(islands culled) — flagged for the tris budget alongside the BIG_CITY fix; not a sim concern.
|
||||||
|
- No D-side spawn-bias mitigation needed this epoch — A's pass left nothing to mitigate. (Beta backlog item
|
||||||
|
retired.)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ROUND 18 — v4.0-alpha REAL ROADS: citizens on real bones (ledger #5)
|
||||||
|
|
||||||
|
*PROCITY-D, 2026-07-16. The audit: does the citizen sim behave on real (irregular) street topology?
|
||||||
|
E landed `katoomba_real`'s real road geometry (484 OSM ways → 872 nodes / 799 edges) mid-audit, so
|
||||||
|
this is measured on the **real Katoomba graph**, not just a proxy. In-shell: fresh `CitizenSim` built
|
||||||
|
on `plan.streets`, driven headless-of-render (rAF throttled). No source changes — the finding is the
|
||||||
|
verdict + the failure list.*
|
||||||
|
|
||||||
|
### Verdict: the graph contract IS the contract — the sim needs ZERO changes
|
||||||
|
The sim's graph-walk is topology-agnostic by construction (seeded random walk over nodes/edges, dead-end
|
||||||
|
U-turn), so real geometry should just work. It does. Proven on a **crafted irregular town** (deg-4
|
||||||
|
crossroads, 3 T-junctions, 5 dead-ends, a 30° acute fork, a cycle) AND the **real Katoomba graph**
|
||||||
|
(7.2° acute junction, a 1303 m edge, 105 components):
|
||||||
|
|
||||||
|
| assertion | crafted irregular | **real Katoomba** |
|
||||||
|
|---|---|---|
|
||||||
|
| NaN/Inf ped positions | 0 / 240k | **0 / 1,095,500** |
|
||||||
|
| off-footpath samples (ped beyond its own edge band) | 0 / 240k | **0** (maxDist 12.9 m = main offset) |
|
||||||
|
| edges traversed | 11 / 11 | full main network |
|
||||||
|
| peds pinned at dead-ends | 0 | 0 |
|
||||||
|
| loiter fires | 154 ev | **174 ev** |
|
||||||
|
| **bench-sit fires (R17)** | 47 ev | **60 ev** |
|
||||||
|
| patronage fires (stream mode) | 165 visits | **24 visits, 4.3k inside-frames** |
|
||||||
|
| chunk streaming over non-grid | 7 live rosters / 93 active | **13 chunks / 307 active** |
|
||||||
|
| determinism (2 sims, same seed) | 0/120k pos mismatch; identity set visit-order-independent | **byte-identical rerun; 1167 sigs** |
|
||||||
|
|
||||||
|
Identity signature format is intact (`chunkKey#i:pedIndex:pvar:height:speed:edge:forward`), no `sit`
|
||||||
|
term — my R17 `benchsit` stream doesn't perturb it. **Nothing in the sim needed touching for real roads.**
|
||||||
|
|
||||||
|
### The measured leak → FILED FOR A: the real-roads graph is fragmented
|
||||||
|
`katoomba_real`'s street graph comes out in **105 disconnected components**. The main component holds
|
||||||
|
**58% of street-metres (52.8 km) and 18 of 20 shops** — the core town is coherent — but **42% of street
|
||||||
|
metres and 2 shops strand** on 102 islands (9 single nodes, 76 fragments of 2–4 nodes, 17 of 5–20).
|
||||||
|
Live cost to citizens: **~10% of near-main crowd presence** lands on tiny fragments (aimless wandering,
|
||||||
|
can't reach the main street or its shops); the 2 stranded shops get no main-crowd traffic.
|
||||||
|
|
||||||
|
- **Primary cause — expected, not a bug:** on real Katoomba the median fragment is **71.5 m** from the
|
||||||
|
main net → **mostly peripheral / bbox-clipped roads and genuinely-separate service ways**, which is
|
||||||
|
what an OSM extract in a bounded box always contains. The main town is coherent; this is the tail.
|
||||||
|
- **Secondary mechanism (corroborated with Lane B):** A's `buildRealRoads` runs Douglas–Peucker **per-way
|
||||||
|
before snapping**, so a junction point that is collinear on the through-way gets **dropped**, with no
|
||||||
|
mid-edge split where one way's endpoint lands on another way's segment — a near-straight through-street
|
||||||
|
with a mid-way junction can lose it → the crossing way strands. I hit this **three times** crafting the
|
||||||
|
test town (fixed by modelling junctions as shared way-endpoints, as OSM does); **Lane B independently
|
||||||
|
hit the same DP-collinear drop** on A's `selfcheck` fixture (LANE_B_NOTES §18). On *real* Katoomba this
|
||||||
|
bites only the **9 of 85** tiny fragments that sit <10 m from the main network (20 within 30 m) — the
|
||||||
|
ones a snap-tolerance / endpoint-split pass could legitimately join. A **fidelity knob** (charter risk #4).
|
||||||
|
- **Reconciles with Lane B:** B verified real Katoomba's **126 intersections form correctly** (= my 27
|
||||||
|
deg-4 + 99 deg-3 junction nodes) and concluded connectivity is fine — true *for the main component*. My
|
||||||
|
add is the **global** measurement B didn't take: those 126 junctions live in a 480-node main component,
|
||||||
|
but the full graph is still 105 components with 42 % of street-metres on islands. Both true, compatible.
|
||||||
|
- **The sim degrades gracefully** — fragment peds just walk their island; **zero NaN, no crash**. This is
|
||||||
|
A's graph-construction call (connect-tolerance / mid-edge split), not a sim change. A D-side mitigation
|
||||||
|
(bias spawns toward the main component) is a **beta** item and needs A's connectivity decision first.
|
||||||
|
|
||||||
|
### Handshakes
|
||||||
|
- **D → A:** the fragmentation measurement above (105 components, 42% street-metres + 2 shops stranded,
|
||||||
|
root cause + the 9 close-fragments that could be joined). Your knob, your call.
|
||||||
|
- **D → F (gate):** the sim is clean on the real graph — no D-side gate concern. Bench-sit + loiter +
|
||||||
|
patronage + determinism all hold on `katoomba_real`. When you re-run the town matrix with the roads
|
||||||
|
dimension, the citizen path is green from my side.
|
||||||
|
- **Re-run note:** the whole audit is parameterized on `plan.streets`; it re-runs on any town's real
|
||||||
|
graph in one call (used it on both the crafted town and real Katoomba this round).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ROUND 17 — v3.2 THE TAIL: the drummer's pelvic-lean (ledger #1)
|
||||||
|
|
||||||
|
*PROCITY-D, 2026-07-16. The R16 honest limit — the upright bent-leg sit read stiff — is fixed. Files
|
||||||
|
`rigs.js`, `band.js`. Byte-identical proof holds (my signature discipline).*
|
||||||
|
|
||||||
|
The sit clip can't carry a pelvic tilt (`Hips.quaternion` is unusable on these rigs — R16), so the R16
|
||||||
|
drummer sat bolt-upright. **Fix: a small constant forward torso lean applied POST-MIX.** `rigs.js`
|
||||||
|
`spawnRig({seated})` now caches the mid-spine bone (`seatBone`), and `seatedLean(spine)` rotates it forward
|
||||||
|
~0.34 rad each frame AFTER the mixer resets it (so it's a constant offset, never an accumulation). `band.js`
|
||||||
|
calls it for `m.seated` members only. The drummer now leans into the kit — verified **head 0.28 m forward
|
||||||
|
of the hips**, reads as a real drummer from the side (the R16 stiffness is gone).
|
||||||
|
|
||||||
|
**Byte-identical proof**: standing rigs get **no `seatBone`** (found only in `spawnRig`'s seated block) and
|
||||||
|
`seatedLean` is only called for seated figs. Verified with the lean live: streamed walkers **188/188** face
|
||||||
|
travel, keeper is a standing rig (stature **1.718**, `hasSeatBone:false`, `innerRotY:π`). No existing
|
||||||
|
consumer moves. Seated-only, parallel path — same discipline as R16.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ROUND 17 — v3.2 THE TAIL: bench-sit loiter (ledger #2, the carried R16 stretch)
|
||||||
|
|
||||||
|
*PROCITY-D, 2026-07-16. The loiter hook that's waited since v1: street peds occasionally sit during a
|
||||||
|
window-shop stop. Files `rigs.js` (`makeActor` gains an opt-in `setSitting`), `sim.js` (the seeded
|
||||||
|
choice + the near-tier toggle). Byte-identical for every non-sitting ped — the load-bearing proof.*
|
||||||
|
|
||||||
|
### The finding I had to solve first
|
||||||
|
The R16 seated re-plant lives in `spawnRig` (drummer path) and is **scale-naive** — it drops `inner` by the
|
||||||
|
posed lowest-bone height, which only lands at y=0 for **unscaled** rigs. But the street crowd runs on
|
||||||
|
`makeActor` rigs that the sim scales **per-citizen** (`fig.scale = height/1.75`). So bench-sit is *not* the
|
||||||
|
one-line "reuse the existing seam" the brief hoped for — the re-plant had to become **scale-aware**.
|
||||||
|
|
||||||
|
### What shipped
|
||||||
|
- **`makeActor(rig, {…, sitClip})`** gains a third action `sitA`, **played only on `setSitting(true)`** — so
|
||||||
|
walkers, placeholders, and `?classic` (no `sitClip` → `sitA` null → `setSitting` no-ops) are byte-identical:
|
||||||
|
the sit action never advances the mixer and the plant never moves.
|
||||||
|
- **`setSitting(s)`** snaps in/out of the sit (no crossfade — a background ped popping to a sit is fine) and
|
||||||
|
does the **scale-aware re-plant**: after baking the pose it measures the posed lowest bone's *world* y and
|
||||||
|
drops `inner` by `(worldLowestY − figY) / fig.scale.y`, landing the posed soles on the footpath at any
|
||||||
|
citizen scale. Standing up restores the cached `bindPlantY` exactly. Tags `procitySeated`.
|
||||||
|
- **`sim.js`**: a **dedicated** rng stream `sitRng = rng(citySeed,'benchsit',id)` (independently keyed → does
|
||||||
|
**not** shift `turn`/`loit`/`patron`, so the walk/loiter pattern and the identity signature are unchanged).
|
||||||
|
At the window-shop loiter trigger the roll fires ALWAYS (deterministic) but only flips `c.sit` when a sit
|
||||||
|
clip is loaded (`?classic` inert at the source). The near-tier update toggles `setSitting(c.sit && loiter>0)`.
|
||||||
|
`RigPool.release` forces `setSitting(false)` so a pooled rig is never handed out mid-sit.
|
||||||
|
- No lean: bench-sitters sit **upright** (natural on a verandah step), so the drummer's `seatedLean` is not
|
||||||
|
applied here — only `setSitting`'s pose + re-plant. No bench-position binding — they sit where they loitered
|
||||||
|
(the footpath/verandah edge under the awnings).
|
||||||
|
|
||||||
|
### Byte-identical proof (in-shell, default boot, seed 20261990)
|
||||||
|
Forced a near rig through stand → sit → stand, bone-measured (posed skeleton; `Box3` is bind-pose for skinned
|
||||||
|
meshes and lies here):
|
||||||
|
|
||||||
|
| metric | standing | seated | +1 s sit-idle | stood back |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| head bone y | 1.466 | **1.070** | 1.071 | 1.466 |
|
||||||
|
| bone span | 1.667 | **1.299** (∈ seated gate [0.9,1.5]) | 1.285 | 1.667 |
|
||||||
|
| lowest bone y (feet) | 0.027 | **0.000** (planted) | 0.005 (holds) | 0.027 |
|
||||||
|
| `inner.position.y` | 0.929 | 0.548 | 0.548 | **0.929** (restored) |
|
||||||
|
| `procitySeated` | false | **true** | true | false |
|
||||||
|
|
||||||
|
Head drops 40 cm, feet re-plant to the footpath at citizen scale (0.978), the one-time plant **holds** through
|
||||||
|
the sit-idle, and standing up restores the rig **byte-for-byte**. `innerRotY:π` throughout (facing convention
|
||||||
|
intact). Draws **35 · tris 6k** at the seated block (seated peds are already-near rigs — ~0 draw delta).
|
||||||
|
|
||||||
|
- **Seeded path fires**: 6–9 distinct peds sat naturally per ~100 s soak (of ~10 window-shop loiters).
|
||||||
|
- **Determinism unchanged**: identity signature `-4,2#0:5:1:1.711:1.370:0:-1` — no `sit` term; plan golden
|
||||||
|
`0x3fa36874`, 15277/15277.
|
||||||
|
- **Classic covenant airtight at the source**: `?classic=1` → `fleetSitClip:false`, 31 loiters over the soak
|
||||||
|
but `everSat:0` (the gate never flips `c.sit`), zero fetch delta, `anySeatedNow:0`.
|
||||||
|
|
||||||
|
Both R17 ledger items (#1 pelvic-lean, #2 bench-sit) are delivered — the tail is drained.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ROUND 16 — v3.1 THE FLIP: the drummer finally sits + backline unification
|
||||||
|
|
||||||
|
*PROCITY-D, 2026-07-16. Ledger #2 (sit-clip wiring) + #5 (amp at C's `backline[]`). Files `rigs.js`,
|
||||||
|
`band.js`. Verified against C's landed `stage.backline[]` + E's `sit.glb`; F's flip/gate pending (I built to
|
||||||
|
F's published seated-stature contract). **Byte-identical proof holds** (the load-bearing requirement).*
|
||||||
|
|
||||||
|
### The drummer sits (ledger #2) — and Fable's prescribed `_rotWithHips` is WRONG for these rigs
|
||||||
|
The R14 recon + F's brief prescribed a `_rotWithHips` variant (keep `Hips.quaternion` to preserve the
|
||||||
|
pelvic tilt). **In-shell it MANGLES the pose** — head folds to hip height, body laid flat — because the
|
||||||
|
fleet rigs' bind-pose Hips orientation differs from the Mixamo clip's, exactly the failure `_rotOnly`'s own
|
||||||
|
comment documents ("a different-orientation source lays it flat"). So the sit clip **drops `Hips.quaternion`
|
||||||
|
like every other clip** (rides the existing `_rotOnly`); the seated read comes from the leg-bone bends +
|
||||||
|
a new **seated foot-replant** in `spawnRig`. `_rotWithHips` is removed.
|
||||||
|
- **`spawnRig({seated})`** (OPT-IN): after the clip plays, tick the mixer once and re-plant the POSED
|
||||||
|
lowest bone to y=0 so the drummer's feet sit on the deck (recovers the hip descent). Standing spawns
|
||||||
|
skip it entirely.
|
||||||
|
- **The drummer**: uses `fleet.sitClip` when present (retiring the R14 `SEAT_DROP` sink — it sits on the
|
||||||
|
riser and the re-plant seats it), tags `fig.userData.procitySeated = true` (F's gate). Reads seated from
|
||||||
|
the **crowd POV** (the kit hides the lower body); measured stature **~1.1–1.3 m** (F's seated gate
|
||||||
|
[0.9,1.5] ✓). Falls back to the `SEAT_DROP` standing-sunk hack if `sitClip` is absent (?noassets, or
|
||||||
|
before F wires `{sit}`), so it never breaks a boot.
|
||||||
|
- **Honest limit**: without the pelvic tilt (Hips.quaternion unusable), it's an upright bent-leg sit — a
|
||||||
|
bit stiff from a hard side/overhead angle, hidden by the kit from the front. A cleaner pose is a manual
|
||||||
|
pelvic-lean pass (v3.2). It's a genuine improvement over the standing-sunk hack (real bent legs, feet on
|
||||||
|
the deck, passes the seated gate) — good enough for the flip.
|
||||||
|
|
||||||
|
### Byte-identical proof (keepers / browsers / walkers) — the load-bearing part
|
||||||
|
The changes are a PARALLEL/opt-in path: `_rotOnly` untouched (walk/idle unchanged), `spawnRig`'s re-plant
|
||||||
|
is `seated`-gated (default off), `fleet.sitClip` is additive, `makeActor` (walkers) untouched. Verified
|
||||||
|
in-shell with `fleet.sitClip` **null AND loaded**: streamed walkers **188/188** face travel, keeper is a
|
||||||
|
standing rig (stature **1.718**, `innerRotY:π`, `replanted:false` — the seated re-plant does NOT run). No
|
||||||
|
existing consumer moves.
|
||||||
|
|
||||||
|
### → Lane F: the `{sit}` load seam (classic-safe by construction)
|
||||||
|
`loadPedFleet(base, { sit })` — pass **`sit: <gig-layer active>`** (true on the default boot, **false under
|
||||||
|
`?classic=1`**). Gated because a raw `sit.glb` fetch would break the classic zero-fetch-delta covenant;
|
||||||
|
default is `sit:false`, and absent/off → `fleet.sitClip` null → the drummer falls back gracefully. My code
|
||||||
|
is committed graceful, so it lands safely before F's commit; the drummer only sits once F passes `sit:true`
|
||||||
|
(same commit as F's [0.9,1.5] seated-stature gate).
|
||||||
|
|
||||||
|
### Amp at C's `stage.backline[]` (ledger #5)
|
||||||
|
`_addBackline` now plants at **C's `stage.backline[0]`** (`{x,z,ry,y}`, up-stage, clear of every bandPose
|
||||||
|
+ watchPoint) instead of D's R15 hardcoded pose — C confirmed `backline[0]` == that pose, so nothing moves
|
||||||
|
on screen (pub 1.44, 0.32, −3.27, ry=π; **0.75 m bass clearance**). Since C dropped its primitive
|
||||||
|
ampStacks, D now owns the whole backline: **placeholder-persists** (a primitive amp shows immediately, the
|
||||||
|
`guitar_amp` GLB swaps in), and the primitive is the **`?noassets` fallback** (fleet-gated, no fetch).
|
||||||
|
Verified: GLB swaps in, primitive under `?noassets`, no crash, 0 fetch.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ROUND 15 — v3.0 close: the amp one-liner (ledger #4)
|
||||||
|
|
||||||
|
*PROCITY-D, 2026-07-16. Sole task: move `band.js _addBackline` to C's §3 pose. Everything else (continuity,
|
||||||
|
ratified sit-clip deferral) is closed.* The R13 amp sat at `z = stage.z + 0.15` (down-stage) — 0.29 m from
|
||||||
|
the bass rig, interpenetrating the bassist (C's audit caught it). Moved up-stage-right to C's blessed pose
|
||||||
|
**`(stage.x + w·0.36, deckY, stage.z − d·0.22)`** = (1.44, 0.32, −3.27) at the pub: **0.75 m clear** of the
|
||||||
|
bass, behind the front line, flanking C's primitive ampStacks. Verified: draw/tri delta **0** (same GLB
|
||||||
|
moved — pub interior 50 draws · ~183k tris), `?noassets` unaffected (amp is fleet-gated dressing → `props 0`,
|
||||||
|
band placeholders, no crash).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ROUND 14 — v3.0 release: identity continuity (the ped you followed is in the crowd)
|
||||||
|
|
||||||
|
*PROCITY-D, 2026-07-16. The last charter promise. Behind `?gigs=1`; files `sim.js`, `band.js`, `queue.js`.
|
||||||
|
**No golden moves** — both goldens are plan-gen hashes (independent of runtime crowd identity), and an
|
||||||
|
empty roster is byte-identical to R13. Verified in-shell against A's 3-venue plan.*
|
||||||
|
|
||||||
|
### The tonight roster (sim.js) — who came to the gig
|
||||||
|
A per-venue **Map<key,{pedIndex,height}>** (`_venueRoster`) of the identities who entered a gig venue
|
||||||
|
**tonight**, from two sources:
|
||||||
|
- **Surge occupants** — a streamed ped that patronage steers into a gig venue self-records in `_enter`
|
||||||
|
(gated `this._gigVenues.has(shopId)`), keyed by the ped's stable `c.id` so re-entry doesn't dup. Unlike
|
||||||
|
R9 occupancy (which clears on emerge), the roster **persists past the ped's dwell** — "who came tonight".
|
||||||
|
- **Queue admits** — F relays each `VenueQueue.admitOne()` via `citizens.recordVenueEntry(venueShopId, {pedIndex,height,key})`.
|
||||||
|
|
||||||
|
It's **bounded + per-night**: cleared automatically on the `setGig(id, false)` on→off transition (the gig
|
||||||
|
ends → the crowd disperses), so it survives interior exit/re-enter (gig stays on) but resets each night.
|
||||||
|
Pure bookkeeping — **no rng draw, no GPU** (draw/tri delta 0).
|
||||||
|
|
||||||
|
### The crowd consumes it (band.js) — the front slots become the roster
|
||||||
|
`GigCrew.spawn(roomGroup, { …, roster })`: crowd slot `i` fills from `roster[i]` (its `{pedIndex,height}`),
|
||||||
|
seeded strangers fill the remainder. This is the release-round's load-bearing safety: each crowd slot is
|
||||||
|
an **independently-keyed rng stream** (`gig`/`crowd:<gid>:<slot>`), and `_make`'s existing override branches
|
||||||
|
already skip a slot's own height+pickRig draws when identity is supplied — so a roster override touches
|
||||||
|
**only that slot**, never another slot / the sway-phase stream / the band / either golden. An **empty
|
||||||
|
roster is byte-identical to R13** (verified). Roster heights are the sim's 1.55–1.95 m, inside the
|
||||||
|
no-giants [1.4,2.0] gate. `crew.spawn` returns `{ band, crowd, fromRoster }` and each member carries
|
||||||
|
`member.pedIndex` (for F's continuity smoke — assert on the seam, not pixels).
|
||||||
|
|
||||||
|
### → Lane F: the seam (three touches; the clear is automatic)
|
||||||
|
```js
|
||||||
|
// 1. interior — pass the venue's roster into the crowd (interior_mode.js:239):
|
||||||
|
crew.spawn(current.group, { stage: current.stage, watchPoints: current.watchPoints,
|
||||||
|
gig: tonight, roster: citizens.tonightRoster(shop.id) });
|
||||||
|
// 2. street — relay each queue admit into the roster (index.html per-venue loop ~380-393):
|
||||||
|
q.spawn(scene, { queueZone, gigId, onAdmit: (id) => citizens.recordVenueEntry(venueShopId, id) });
|
||||||
|
// 3. nothing — the roster auto-clears on your existing setGig(id, false) at the gig's end.
|
||||||
|
```
|
||||||
|
Accessor for your smoke: `window.PROCITY.citizens.tonightRoster(venueShopId) → [{pedIndex, height}]`
|
||||||
|
(insertion-ordered, deduped, bounded). Assert **crowd ⊇ roster ∩ cap** via `crew.crewInfo.fromRoster` +
|
||||||
|
`member.pedIndex ∈ roster`. `admitOne()` now returns `{pedIndex,height,key}` (was `1`) — truthy, so your
|
||||||
|
existing `if (q.admitOne())` still holds. Also: `crew.spawn` is now null-`gig`-safe (quiet-night guard).
|
||||||
|
|
||||||
|
### Verified in-shell (seed 20261990, The Exchange Hotel #116)
|
||||||
|
| check | result |
|
||||||
|
|---|---|
|
||||||
|
| roster API — record / dedup-by-key / invalid-pedIndex-ignore / per-venue / clear-on-gig-off | all ✓ |
|
||||||
|
| **surge continuity** — real peds ducked in → roster → crowd | **8/8** (same pedIndex + height) |
|
||||||
|
| **queue continuity** — admits relayed via `onAdmit` → roster | **4/4** |
|
||||||
|
| consume — front slots = roster, tail = seeded strangers unchanged | ✓ |
|
||||||
|
| **determinism** — empty-roster crowd == R13 baseline (pedIndex + stature per slot) | byte-identical ✓ |
|
||||||
|
| **soak** — 7 nights × 3 venues, enter/exit + re-spawn | leak **geo Δ0 / tex Δ0**, roster **bounded** (≤ cap, cleared per night), **no identity churn** |
|
||||||
|
| budget | interior gig **64 draws ≤350**, **0 new rigs** (continuity is bookkeeping) |
|
||||||
|
| `?noassets=1&gigs=1` | crowd = placeholders using roster heights, **no crash, 0 fetch** |
|
||||||
|
|
||||||
|
### Debt #2 (the drummer sit clip) — INVESTIGATED, deferred to v3.1 (real blockers, not a punt)
|
||||||
|
The clip **does exist** — `~/Documents/mixamo-fetch/out/` is NOT on m3ultra (Fable's path is stale; the
|
||||||
|
bank lives on the tailnet source box `johnking@100.91.239.7`). `Sitting_Idle.fbx` there converts cleanly
|
||||||
|
via Blender to a 394 KB **mesh-free `sit.glb`** that binds (65/65 bones; the fleet's `_canon` keeps the
|
||||||
|
colon, `mixamorig:Hips`, so raw Mixamo naming is already canonical). **But wiring it is a v3.1 change, not
|
||||||
|
a release-week one, for two real reasons:**
|
||||||
|
1. **`_rotOnly` breaks a seated pose** — it drops `Hips.quaternion` (pelvic tilt) + all positions (hip
|
||||||
|
descent), giving a "levitating L-sit". Correct wiring needs a parallel `_rotWithHips` filter + a
|
||||||
|
post-pose foot re-plant in `spawnRig` — a change to the **shared** rig stack (keepers/browsers/band/crowd).
|
||||||
|
2. **A genuinely seated figure is ~1.2 m crown-above-feet**, which **fails F's no-giants stature floor
|
||||||
|
[1.4,2.0]** (`flags_check.py:695`) — it needs an F smoke exemption for the seated band member.
|
||||||
|
Both land a qa-red change pending an F handshake, in the freeze round, for a polish item — the drummer
|
||||||
|
already reads seated from the crowd POV (money shot) via the `SEAT_DROP` hack. **Rides Fable's existing
|
||||||
|
v3.1 item "bench-sit loiter from the same `sit` clip".** Recipe (for v3.1): scp Sitting_Idle.fbx → Blender
|
||||||
|
`--background --python` import/export GLB (no bone rename needed) → `web/models/peds/sit.glb` → `_rotWithHips`
|
||||||
|
loader + `spawnRig` re-plant + F stature exemption. See LANE_D_NOTES wishlist #1.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ROUND 13 — v3.0-beta the district: the RY fix, instruments/drummer, the queue, multi-venue surge
|
||||||
|
|
||||||
|
*PROCITY-D, 2026-07-15. Behind `?gigs=1`; verified in-shell against A's landed 3-venue plan (pub #116
|
||||||
|
`pubrock` / rsl #404 `covers` / band_room #490 `grunge`, 14 gigs / 14 posters) and C's landed 4-pose
|
||||||
|
stage (guitar/vocal/bass + a seated `drums` on the riser). Files: `rigs.js`, `band.js`, new `queue.js`,
|
||||||
|
`sim.js`. `keepers.js` / `placeholder.js` / `impostor.js` UNCHANGED — the RY fix at the source corrects
|
||||||
|
them for free.*
|
||||||
|
|
||||||
|
### Debt #4 — the RY audit: fixed once at the source (⚠ this changes the flags-off street VISUAL)
|
||||||
|
**Finding (confirmed in-scene):** the imported ped GLBs' visual front is local **+Z**, but *everything
|
||||||
|
downstream assumes −Z* — `sim.js` heading math (`facing = atan2(-dx,-dz)`), C's pose `ry`
|
||||||
|
(counter.stand / bandPoses / watchPoints / browse points), the placeholder (`placeholder.js:61` builds
|
||||||
|
toes to −Z *on purpose*, "like the GLB rigs"), and the impostor bake/pick convention. So the GLB was the
|
||||||
|
lone outlier: **keepers faced the back wall, browsers faced away, and every streamed rig walker moon-walked
|
||||||
|
(faced anti-travel)** — all subtly, for ~12 rounds, because near+mid were consistently-wrong-together
|
||||||
|
(seamless) and peds are mostly seen from behind. R12's band carried a local `RY_FLIP=π` that patched only
|
||||||
|
the gig.
|
||||||
|
|
||||||
|
**Fix:** one line in `rigs.js buildFigure` — `inner.rotation.y = Math.PI` — normalises every rig's front
|
||||||
|
to −Z, matching the rest of the system. Then `RY_FLIP` is deleted from `band.js` (band/crowd use C's `ry`
|
||||||
|
verbatim). Because the impostor atlas bakes this same `buildFigure`/`spawnRig` output and the layer's
|
||||||
|
yaw-pick already uses the −Z convention, near↔mid stays seamless (both flip together). It's **purely
|
||||||
|
visual** — a Y-rotation leaves every bone's world-Y untouched, so head-height / feet-plant, the no-giants
|
||||||
|
gate, and all seeded identity/determinism are byte-unchanged (**no golden move**).
|
||||||
|
|
||||||
|
**Verified:** keeper now faces the shop floor (was the back wall); **223/223** gig-mode + **188/188**
|
||||||
|
flags-off streamed walkers face their edge-travel direction (avg dot **1.000**; was −1.0); band faces the
|
||||||
|
crowd + crowd faces the stage with `RY_FLIP` gone; `?noassets` placeholder band (which the old flip left
|
||||||
|
facing the backdrop) is now correct too.
|
||||||
|
|
||||||
|
- **→ Lane C (pose contract, please land in LANE_C_PUB.md):** *"Rig visual front is canonical **local −Z**
|
||||||
|
(`rigs.js buildFigure` normalises the GLB's native +Z). All pose `ry` (counter.stand, bandPoses,
|
||||||
|
watchPoints, browse points) are the −Z-front heading the rig should face — no lane compensates; the R12
|
||||||
|
`RY_FLIP` is gone."* Your existing `ry` values were already correct under this — nothing to change.
|
||||||
|
- **→ Lane F / Fable (flags-off flag):** the fix is in the shared rig stack, so the **default (flags-off)
|
||||||
|
street crowd now faces travel** instead of moon-walking. Determinism + the golden plan hash are
|
||||||
|
untouched (visual only), but if any regression snapshots *pixels* it will move — it's a bug fix, re-pin
|
||||||
|
if needed (improvement, not a leak).
|
||||||
|
|
||||||
|
### Debt #2 — instrument GLBs wired (placeholder-persists, fail-soft, fleet-gated)
|
||||||
|
`band.js` keeps its primitive instruments as the immediate/`?noassets` fallback, then swaps E's manifest
|
||||||
|
GLBs in async when they resolve (same pattern as `interiors/glb.js`). Role → fitting: `guitar`→
|
||||||
|
`electric_guitar`, `bass`→`bass_guitar`, `vocal`→`mic_stand` (planted), `drums`→`drum_kit` (planted at the
|
||||||
|
riser), plus one `guitar_amp` as backline dressing. Manifest is **self-fetched** (promise-cached) so band.js
|
||||||
|
stays decoupled from C's glb.js, and the whole upgrade is **gated on `fleet.ready`** → under `?noassets`
|
||||||
|
(fleet null) it never fetches (proven: fresh-tab network shows **0** `procity_fit_*` / `manifest`
|
||||||
|
requests). Disposal-guarded by a generation counter; GLB clones share the cached geo/mats (detach-only,
|
||||||
|
never dispose — only the `ownGeo`-tagged primitives are freed). Orientation lives in one `GLB_FIT` table
|
||||||
|
(guitars need a +Z roll to sling; the kit is scaled 0.66 to fit C's tight 0.73 m vocal↔drums gap).
|
||||||
|
|
||||||
|
### Debt #3 — the drummer (4-piece, Fable's ruling)
|
||||||
|
`bandPoses.forEach` already picks up C's 4th `drums` pose (`seated:true`, riser `y`). The drummer sinks
|
||||||
|
`SEAT_DROP` (0.52 m) so the kit hides the straight legs and it reads seated **from the front — the crowd
|
||||||
|
POV / money shot**; from a hard side angle the sink shows (a standing rig faked as seated). A true seated
|
||||||
|
pose is a clip job — see the wishlist. Build is **defensive**: if John had vetoed to a trio, C simply
|
||||||
|
omits the pose and the band is 3 with zero D change.
|
||||||
|
|
||||||
|
### The outdoor queue (charter item — new `web/js/citizens/queue.js`)
|
||||||
|
`VenueQueue`: a seeded 2–6 line on the same gate-protected rig path, idle-animated, that drains as punters
|
||||||
|
are admitted and empties by close. Consumes B's `venue.queueZone` when present, else a straight line off
|
||||||
|
the door — the fallback trails along the **frontage tangent** so it hugs the building instead of spilling
|
||||||
|
onto the road. Verified: spawn (5 rigs, human-sized, hug the frontage, face the venue), `admitOne()` drains
|
||||||
|
the head + shuffles everyone up to target, and a seeded auto-drain safety. **F seam** (wire per venue off
|
||||||
|
the street-side gig state):
|
||||||
|
```js
|
||||||
|
const q = new VenueQueue({ citySeed: plan.citySeed, fleet });
|
||||||
|
q.spawn(streetGroup, { door, queueZone, gigId }); // at 'doors' (+ 'on' overflow); door = {x,z,ry}, ry = facing the entrance
|
||||||
|
q.admitOne(); // on each admitted punter (cover-charge / entry hook)
|
||||||
|
q.update(dt); // each street frame — idle + lerp + slow auto-drain
|
||||||
|
q.disposeAll(); // at 'done' / venue out of view
|
||||||
|
```
|
||||||
|
**→ Lane B:** publish `venue.queueZone = { x, z, ry, len }` — `(x,z)` = head by the door, `ry` = the
|
||||||
|
heading a punter faces (toward the entrance), `len` = line length; the line trails single-file opposite the
|
||||||
|
facing, so pick `ry` to run it along the verandah.
|
||||||
|
|
||||||
|
### Multi-venue surge (`sim.js`)
|
||||||
|
`setGig(venueShopId, on)` widened from a single `_gigVenue` to a `_gigVenues` **Set** so several concurrent
|
||||||
|
gigs each pull their own block; `_nearestOpenShop` now prefers the nearest gig venue in `GIG_RANGE` across
|
||||||
|
all of them. F's R12 single-venue call is a subset — still works. Non-gig patronage path is byte-identical.
|
||||||
|
|
||||||
|
### Measured budgets (seed 20261990, "Boolarra Heads", The Exchange Hotel #116)
|
||||||
|
| thing | number | budget |
|
||||||
|
|---|---|---|
|
||||||
|
| gig-night interior, rig fleet + GLB instruments | **56 draws** · 183k tris | ≤350 draws ✓ |
|
||||||
|
| — of which instruments (5 GLBs @ ~14k, E's ≤15k target) | ~70k tris | (no interior tri cap) |
|
||||||
|
| band / crowd | **4** on the deck (3 front + seated drummer) · **8** at 8 watch points | crowd ≤ watchPoints ✓ |
|
||||||
|
| outdoor queue | seeded **2–6** rigs (~1 draw each) at the frontage | stated |
|
||||||
|
| `?noassets`+`?gigs` | band+queue placeholders, **0** GLB/manifest fetch | silent-and-fine ✓ |
|
||||||
|
| flags-off street walkers face travel | 188/188 (dot 1.0) | determinism byte-unchanged ✓ |
|
||||||
|
|
||||||
|
*RSL (#404) fields the crowd-cap stress case: `CROWD_CAP` is 12, real cap = `watchPoints.length` per venue
|
||||||
|
(≤ always holds). Re-measure the RSL interior once its archetype dressing lands.*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ROUND 9 — interior presence: occupancy truth + browser rigs (→ Lane F handoff below)
|
||||||
|
|
||||||
|
**Follow a ped into a shop and find them browsing.** D owns occupancy truth; C owns the browse points;
|
||||||
|
F wires the handoff. Validated end-to-end in-shell (`?stock=real`). `qa.sh --strict` GREEN; v1
|
||||||
|
(`?roster=v1`) path untouched.
|
||||||
|
|
||||||
|
### D owns occupancy truth ✅ (the C→D→F seam)
|
||||||
|
Patronage now records who's inside which shop, keyed by `shopId` (from the door point). New API:
|
||||||
|
```js
|
||||||
|
citizens.occupancyOf(shopId) → { count, occupants: [{ seed, enteredAt, pedIndex }] }
|
||||||
|
```
|
||||||
|
`count` = peds patronage currently has inside (pre-cap); `pedIndex` lets a browser BE the exact ped who
|
||||||
|
ducked in off the street (identity continuity). Occupancy is cleaned on emerge, on chunk-drop, and on
|
||||||
|
stream toggle. **Deterministic**: same seed + same update sequence → identical occupied shops AND
|
||||||
|
occupants (verified: 17 shops, seed:pedIndex byte-equal across two runs).
|
||||||
|
- **Consistency (D2)**: an `inside` ped is hidden on the street (not in the rendered set — verified 0
|
||||||
|
inside peds rendered) and re-emerges at the door. No double-presence.
|
||||||
|
|
||||||
|
### Browser rigs ✅ (KeeperManager, the leak-proven pattern)
|
||||||
|
`keepers.js` gained `browse:true` (faces the shelf via `ry`, **no player-greet**) + `pedIndex`
|
||||||
|
(pick the exact fleet ped) + `seedKey` (distinct browsers per shop). Each browser is a **merged ped
|
||||||
|
= ~1 draw**. Validated over **12 enter/exit cycles**: browser count == `min(occupancy, 3)` **12/12**,
|
||||||
|
worst interior **61 draws ≤350** (C's headroom holds), **leak-free (0 GPU delta after warmup)**,
|
||||||
|
disposed on every exit (`keepers.disposeAll()` in interior_mode).
|
||||||
|
|
||||||
|
### → Lane F: the handoff in `web/js/world/interior_mode.js` (F owns it) — verified working
|
||||||
|
Two-line wiring at interior build (F already has `keepers` + `current.browsePoints` from C):
|
||||||
|
```js
|
||||||
|
// on enter(shop), after buildInterior:
|
||||||
|
const occ = citizens.occupancyOf(shop.id);
|
||||||
|
const pts = (current.browsePoints || []).slice(0, Math.min(occ.count, 3)); // cap 3
|
||||||
|
pts.forEach((pt, i) => keepers.spawn(current.group, {
|
||||||
|
x: pt.x, z: pt.z, ry: pt.ry, shopId: shop.id, browse: true,
|
||||||
|
pedIndex: occ.occupants[i] ? occ.occupants[i].pedIndex : null,
|
||||||
|
seedKey: `${shop.id}#${i}`,
|
||||||
|
}));
|
||||||
|
// exit() already calls keepers.disposeAll() → browsers freed with the room. Leak-proven.
|
||||||
|
```
|
||||||
|
And **`web/index.html`**: `citizens.setShops(...)` door points must carry `shopId: s.id` (add it to the
|
||||||
|
R8 recipe — one field). Everything else (patronage, weather) is unchanged.
|
||||||
|
|
||||||
|
**Closing-time (pending A's ruling):** my dwell model already handles it sensibly — `_openAt(hours)`
|
||||||
|
stops *new* entries once a shop closes, and existing occupants clear within their short seeded dwell
|
||||||
|
(5–20 s, tiny vs the hour-long closing window), so no one is stuck inside a shut shop. I'll adopt A's
|
||||||
|
explicit ruling when it lands if it differs.
|
||||||
|
|
||||||
|
Evidence: `docs/shots/laneD/r9_browsers_in_shop.jpg` (two browsers in a toy shop among the priced stock).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ROUND 8 — shop patronage v0 + weather reaction (→ Lane F wiring below)
|
||||||
|
|
||||||
|
**The crowd comes alive: streamed peds duck into open shops they pass and re-emerge; at night only the
|
||||||
|
open-late video shop draws visitors; rain thins + shelters the crowd.** Default-on for the streamed
|
||||||
|
roster (post-flip), behind `?patronage=0` off-switch. Verified in-shell (real plan shops) + test page.
|
||||||
|
`qa.sh --strict` GREEN. v1 (`?roster=v1`) path untouched (no patron fields, golden identity holds).
|
||||||
|
|
||||||
|
### Patronage (D1) ✅
|
||||||
|
State machine per streamed ped: walking → (every ~10m, if a nearby OPEN shop is within 18m, a seeded
|
||||||
|
roll) → **going** (steer to the door) → **inside** (hidden, seeded 5–20s dwell) → **emerge** (resumes
|
||||||
|
its footpath walk). Hours-aware via the shop's `hours`.
|
||||||
|
- **Day-long**: peds visibly enter/leave shops of all types (noon: up to ~38 inside near a shopfront,
|
||||||
|
7 types visited).
|
||||||
|
- **Night**: at 22:48 only the video shop (`hours[1]≥22`) is open → patronage is **video-only**
|
||||||
|
(`onlyVideo:true`), and its block stays lively (the R6 night floor). The patronage roll ramps up as
|
||||||
|
the streets empty so the few peds out concentrate at the one open shop.
|
||||||
|
- **Deterministic** (chunk-keyed identities unchanged: 150 re-derive from seed), **leak-free** (0 GPU
|
||||||
|
delta over a 2-walk patronage churn — enter/emerge reuses the existing actor lifecycle), and
|
||||||
|
**budget-neutral / positive** (inside peds are hidden → draws −1 vs patronage-off at the same spot).
|
||||||
|
|
||||||
|
### Weather reaction (D2) ✅ — reads Lane B's `PROCITY.weather` contract (does NOT import weather.js)
|
||||||
|
`sim.setWeather({state,intensity})` each frame. **Rain** (intensity 0.8): density **−56%** (in the
|
||||||
|
40–60% target), walk speed +~14%, and patronage chance up → more peds shelter in shops (measured
|
||||||
|
inside 6→25). **Overcast**: ~−10%. **Clear**: v1 behaviour. Seed 20261990 rolls `clear` (v1-identical);
|
||||||
|
use `?weather=rain` to force it.
|
||||||
|
|
||||||
|
### → Lane F: wiring in `web/index.html` (F owns the shell)
|
||||||
|
1. **Shop door points** — build once from the plan, feed the sim:
|
||||||
|
```js
|
||||||
|
const CH = 64, shopsByChunk = new Map();
|
||||||
|
for (const s of plan.shops) {
|
||||||
|
const l = plan.lots.find(x => x.id === s.lot); if (!l) continue;
|
||||||
|
const ry = l.ry || 0, fx = -Math.sin(ry), fz = -Math.cos(ry); // facade normal → the street
|
||||||
|
const x = l.x + fx * (l.d / 2 + 0.6), z = l.z + fz * (l.d / 2 + 0.6); // door at the shopfront
|
||||||
|
const k = citizens.chunkKeyAt(x, z);
|
||||||
|
(shopsByChunk.get(k) || shopsByChunk.set(k, []).get(k)).push({ x, z, hours: s.hours });
|
||||||
|
}
|
||||||
|
if (!rosterV1) citizens.setShops(shopsByChunk); // patronage needs door points; inert without
|
||||||
|
if (params.get('patronage') === '0') citizens.setPatronage(false); // off-switch
|
||||||
|
```
|
||||||
|
(PATRON_RANGE 18m tolerates door-point imprecision; if a door reads wrong, nudge the `+0.6`.)
|
||||||
|
2. **Weather** — in the street branch each frame: `citizens.setWeather(window.PROCITY.weather);`
|
||||||
|
(B's contract is always a valid `{state,intensity}`; F sets `{clear,0}` when `?weather` is off.)
|
||||||
|
3. `setNightLivelyChunks` (openLate block) — already wired in F1; unchanged.
|
||||||
|
|
||||||
|
**Baseline note (flip protocol):** patronage default-on may move the flags-off draw baseline a hair —
|
||||||
|
in the direction of *fewer* draws (inside peds hidden), and the default spawn view has ~0 near-rigs so
|
||||||
|
it's near-nil. If F1's regression trips, re-pin per the flip protocol (it's an improvement, not a leak).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## ROUND 7 — MERGE VALIDATED · **GO for the roster flip** (→ Lane F: flip on this) · escape hatch ready
|
## ROUND 7 — MERGE VALIDATED · **GO for the roster flip** (→ Lane F: flip on this) · escape hatch ready
|
||||||
|
|
||||||
**Verdict: GO. Lane E's R7 ped-merge closed the one gap the R6 memo named. Full-density streamed
|
**Verdict: GO. Lane E's R7 ped-merge closed the one gap the R6 memo named. Full-density streamed
|
||||||
@ -392,7 +1337,10 @@ bind directly through the canonical `mixamorig` skeleton exactly like walk/idle,
|
|||||||
data task, not new rig work. **Verify each exists before requesting; never re-download.**
|
data task, not new rig work. **Verify each exists before requesting; never re-download.**
|
||||||
|
|
||||||
Wanted for richer loiter/keeper behaviour, in priority order:
|
Wanted for richer loiter/keeper behaviour, in priority order:
|
||||||
1. `sit` — bench-sit at benches/verandah steps (sim loiter already has the hook; needs the clip).
|
1. `sit` — bench-sit at benches/verandah steps (sim loiter has the hook; needs the clip). **R13: the gig
|
||||||
|
drummer also wants this (or a bespoke `drum`/`play-drums` loop)** — the 4th band member is currently a
|
||||||
|
standing rig sunk `SEAT_DROP` behind the kit, which reads seated only from the front. A real seated clip
|
||||||
|
retires the sink hack (band.js `SEAT_DROP` + the `m.seated` branch in `update`).
|
||||||
2. `lean` — loiter against a shopfront (window-shopping stops).
|
2. `lean` — loiter against a shopfront (window-shopping stops).
|
||||||
3. `look-around` / `idle-look` — window-shopping variety + a better keeper greet than the body-turn.
|
3. `look-around` / `idle-look` — window-shopping variety + a better keeper greet than the body-turn.
|
||||||
4. `talk` / `gesture` — pairs chatting on the footpath (spawn in seeded 2-groups).
|
4. `talk` / `gesture` — pairs chatting on the footpath (spawn in seeded 2-groups).
|
||||||
|
|||||||
43
docs/LANES/LANE_G_GODVERSE.md
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
# Lane G — GODVERSE (the marriage lane)
|
||||||
|
|
||||||
|
**Who:** runs on **ultra** (John's M1 Ultra — the machine with the thriftgod/dealgod Postgres),
|
||||||
|
branch `lane-G-godverse`. Fable-on-ultra integrates this side; your integrator rules on anything
|
||||||
|
that touches A–F's files. **This lane owns:** `pipeline/godverse_*.py`,
|
||||||
|
`web/assets/towns/*_godverse.json`, this brief, `G-progress.md`. It touches nothing else without
|
||||||
|
a ruling.
|
||||||
|
|
||||||
|
## Mission
|
||||||
|
|
||||||
|
PROCITY built the town; GODVERSE has the *census*. thriftgod's Postgres holds **2,928 real
|
||||||
|
Australian secondhand shops** (names, types, lat/lng, suburbs, opening hours — DealGod-scraped,
|
||||||
|
OSM-anchored) and dealgod holds **1.77M real items with photos**. Lane G plugs that real world
|
||||||
|
into your engine, in slices, without disturbing the v4 lanes:
|
||||||
|
|
||||||
|
1. **Real shops on real streets** (LANDED, first cut) — `pipeline/godverse_town.py` marries an
|
||||||
|
existing OSM cache's `roads[]` with thriftgod's real shop census for the same bbox →
|
||||||
|
`<town>_godverse.json` (`source: "godverse+osm"`, schema v2 unchanged). First artifact:
|
||||||
|
`newtown_godverse` — 18 real Newtown shops (5 record, 6 opshop, 4 book…) on the real 1,086-road
|
||||||
|
graph. Boots clean: `?plansrc=osm&town=newtown_godverse`.
|
||||||
|
2. **Real stock in the bins** (NEXT) — your `?stock=real` hook + `build_stock_pack.py` atlases,
|
||||||
|
fed from dealgod's live per-shop inventory instead of a static TSV. Contract sketch in
|
||||||
|
G-progress.md; needs your ruling on atlas budgets per interior.
|
||||||
|
3. **The economy backend** (LATER, the big one) — thriftgod's server (items, basket, buy/sell,
|
||||||
|
GodBay, THE BIBLE) as the transactional brain behind your interiors. Design doc first; nothing
|
||||||
|
moves without both integrators agreeing.
|
||||||
|
|
||||||
|
## House-law compliance
|
||||||
|
|
||||||
|
Seeded everything ✓ (adapter is deterministic; no RNG). Zero-asset law untouched ✓ (a godverse
|
||||||
|
cache is just another town JSON). Licensing: shop names/locations are OSM-derived (ODbL,
|
||||||
|
attribution kept) + DealGod's own scrape; item photos are the stores' own product shots —
|
||||||
|
🟢 for the in-house games, flag before any public/commercial release.
|
||||||
|
|
||||||
|
## Open asks for the m3 crew (see G-progress.md for detail)
|
||||||
|
|
||||||
|
- **The Draco ruling** — your GLB law says no Draco; ultra's new thriftgod hero props are
|
||||||
|
Draco-compressed. Pick one law for the shared depot; either side can re-emit.
|
||||||
|
- **Per-town bookmark coords** — `DBG.bookmarks` teleports assume the synthetic town; on OSM
|
||||||
|
towns they land inside geometry.
|
||||||
|
- **Density fence** for godverse caches — 18 real shops across a full suburb bbox is sparse by
|
||||||
|
your round-20 standards; consider bbox-cropping godverse towns to the retail spine (King St)
|
||||||
|
or letting the R20 density widening backfill non-census buildings.
|
||||||
78
docs/LANES/ROUND10_INSTRUCTIONS.md
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
# PROCITY — Round 10 lane instructions (from Fable, integrator)
|
||||||
|
|
||||||
|
Date: 2026-07-15 · Written after round-9 review. **v2.0 does NOT ship yet** — round 9 landed a
|
||||||
|
blocking visual regression that both D and F missed in their own evidence. Round 10 is short and
|
||||||
|
surgical: fix it, prove it can't come back, re-shoot, then tag.
|
||||||
|
|
||||||
|
## Review verdict on round 9
|
||||||
|
|
||||||
|
The engineering was real — occupancy truth, browse points, buy-anywhere, the tram, the audit all
|
||||||
|
landed and the harness is green. But the round's headline feature shipped visibly broken:
|
||||||
|
|
||||||
|
**BLOCKER — interior figures are giants.** John spotted it in the preview shots ("people way too
|
||||||
|
big for the rooms, clipping thru roof") and he is right. Verified live in-browser today
|
||||||
|
(seed 20261990, "Second Time Redfern", room H = 3.4 m):
|
||||||
|
|
||||||
|
- keeper head bone at **world Y = 3.83 m** — a ~3.8 m person; `keepers.js` requested 1.58–1.92 m
|
||||||
|
- `buildFigure` (rigs.js) normalizes off the head bone measured in **bind pose**: raw headY ≈ 82
|
||||||
|
→ `inner.scale = 0.0213`. But once the idle clip poses the skeleton, the head sits at raw ≈ 180.
|
||||||
|
The bind-pose measurement underestimates the animated rig by ~2.2× → every interior rig is ~2×
|
||||||
|
room scale. The clip filter is NOT the cause (verified: idleClip has zero `.position` tracks).
|
||||||
|
- Street peds are unaffected (the sim's actor path scales differently) — which is exactly why the
|
||||||
|
street shots look fine and the interior ones don't.
|
||||||
|
- Evidence that this was missable but shouldn't have been missed: it is plainly visible in
|
||||||
|
**D's own acceptance shot** (`docs/shots/laneD/r9_browsers_in_shop.jpg` — heads through the
|
||||||
|
ceiling, a heel the size of the till) and in F's tour (`docs/shots/v2_tour/browsers.png`), which
|
||||||
|
F captioned "framing's a bit close". Compare `docs/shots/v1_tour/record_interior.png` — the v1
|
||||||
|
keeper was correct. The regression window is the R9 interior-presence work.
|
||||||
|
|
||||||
|
Consequences: local `v2.0` tag **deleted** (it pointed at the broken state; it was never pushed —
|
||||||
|
origin still ends at v2.0-rc). main IS pushed, so lanes work from a synced tree. F re-tags after
|
||||||
|
the fix lands.
|
||||||
|
|
||||||
|
## Process lesson (all lanes, please actually read)
|
||||||
|
|
||||||
|
A screenshot you attach is evidence you have LOOKED at. "Documents the feature" is not a review.
|
||||||
|
From this round on: any acceptance shot containing a humanoid gets one explicit sanity line in the
|
||||||
|
progress note — "figures human-sized relative to doors/fittings: yes". Ten seconds. The v1-vs-R9
|
||||||
|
comparison above is what it catches.
|
||||||
|
|
||||||
|
## Lane D — the fix (lead, blocking)
|
||||||
|
|
||||||
|
1. Fix interior rig scale in `rigs.js` `buildFigure` (or the `keepers.js` call into it): normalize
|
||||||
|
**pose-aware** — bind the idle clip, `mixer.update(0)` (or one small dt), THEN measure the head
|
||||||
|
bone and set the scale so runtime head-Y == requested height. Alternatively reuse the exact
|
||||||
|
normalization the street actor path uses (street peds are correct; keepers should ride the same
|
||||||
|
math). Keep feet-planting correct after the change (`minY` re-measure must use the same pose).
|
||||||
|
2. Re-verify determinism (same seed → same keeper/browser identity + height) and the leak/GPU-delta
|
||||||
|
soak you already have.
|
||||||
|
3. **Regression guard (required):** extend the interior smoke to enter a shop with occupancy and
|
||||||
|
assert for EVERY spawned interior fig: head-bone world-Y within [1.4, 2.0] AND < room `dims.H`.
|
||||||
|
Wire it into `tools/qa.sh` strict so a giant can never pass a gate again.
|
||||||
|
4. Re-shoot `laneD/r9_browsers_in_shop.jpg` → `laneD/r10_browsers_fixed.jpg`, and write the
|
||||||
|
sanity line.
|
||||||
|
|
||||||
|
## Lane F — verify, re-shoot, ship (after D lands)
|
||||||
|
|
||||||
|
1. Pull D's fix, re-run the interior-presence smoke + the new scale gate; `qa.sh --strict` green.
|
||||||
|
2. **Re-shoot the tour shots containing interior figures**: `browsers.png` at minimum. Also re-shoot
|
||||||
|
`night_crowd.png` — the current one is near-black with no visible crowd and HUD fps 21; the
|
||||||
|
money shot needs the video-shop neon crowd actually readable (push time to the open-late shop,
|
||||||
|
frame the queue, check fps ≥ 30 or note why).
|
||||||
|
3. Re-run the full-default soak once (the fix touches per-frame keeper code; confirm no perf drift).
|
||||||
|
4. Docs freeze addendum: one paragraph in the release notes on the R10 fix.
|
||||||
|
5. **Tag `v2.0` on the fixed commit** (fresh annotated tag), push main + tags. This closes v2.0.
|
||||||
|
6. Update the tour README so shot filenames stay stable for the v3 site.
|
||||||
|
|
||||||
|
## Lanes A / B / C / E — on call only
|
||||||
|
|
||||||
|
Nothing blocking from you this round. C: if D's pose-aware measure needs a browse-point clearance
|
||||||
|
guarantee (point too close to a shelf for a 1.9 m fig), answer quickly; otherwise all four lanes
|
||||||
|
stand by for v3 planning. B's tram and E's audit are accepted as shipped.
|
||||||
|
|
||||||
|
## Standing items
|
||||||
|
|
||||||
|
- **Audio: fourth round parked.** John — one line unblocks it: "generate freely" / "use library X"
|
||||||
|
/ "skip for v2". Until then it stays out of scope.
|
||||||
|
- After `v2.0` tags: v3 epoch pick (editor/overrides · venue district + gigs · multiplayer
|
||||||
|
presence) — John and Fable choose together.
|
||||||
79
docs/LANES/ROUND11_INSTRUCTIONS.md
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
# PROCITY — Round 11 lane instructions (from Fable, integrator)
|
||||||
|
|
||||||
|
Date: 2026-07-15 · **v2.0 is TAGGED AND SHIPPED** (`9b1c54e`, pushed with main). Round-10 review:
|
||||||
|
D's span-normalize fix verified three independent ways (fresh-Chromium gate 24/24 figs human-sized,
|
||||||
|
re-shot tour eyeballed, served-file check); F's stale-cache diagnosis of the 3.83 m re-measure was
|
||||||
|
correct — I reproduced the same trap and proved it (browser cache held pre-fix rigs.js while the
|
||||||
|
server served the fix). Clean round, both lanes.
|
||||||
|
|
||||||
|
**Theme: AUDIO.** John has answered the standing question: **"generate freely."** v2.1 makes the
|
||||||
|
town audible. A and D have the round off. B, E, F work; C answers one contract question.
|
||||||
|
|
||||||
|
## House audio law (same spirit as the asset law)
|
||||||
|
|
||||||
|
1. **The game runs silent-and-happy with zero audio assets** — missing/blocked audio must produce
|
||||||
|
silence, never errors. `?mute=1` forces it. The `?noassets=1` gate now also implies no audio fetches.
|
||||||
|
2. **Autoplay policy**: nothing plays before the first user gesture (pointer-lock click is the
|
||||||
|
natural anchor). No console warnings on boot.
|
||||||
|
3. **Deterministic flavour**: what a shop plays is seeded (citySeed + shopId), like everything else.
|
||||||
|
4. **Budget**: total shipped audio ≤ 25 MB, lazy-loaded; no per-frame allocations in the audio path.
|
||||||
|
5. **Free-first**: generate on MODELBEAST (local MusicGen / stable-audio-open / procedural synthesis
|
||||||
|
— E picks the tool and documents it in the pipeline README). Flag any paid need BEFORE spending;
|
||||||
|
default budget is $0.
|
||||||
|
|
||||||
|
## Lane E — generate the audio pack (lead)
|
||||||
|
|
||||||
|
Stand up `pipeline/gen_audio.py` (mirror the gen_skins.py pattern: promptable, seeded, `--dry-run`,
|
||||||
|
harvest step, always eyeball — well, *ear*-ball — before shipping). Deliver `web/assets/audio/` +
|
||||||
|
a manifest section (`audio: { ambience, sfx, music }` with files, loop points, gain hints).
|
||||||
|
|
||||||
|
Pack v1 (keep it tight — ~25 files max):
|
||||||
|
- **Ambience beds (loopable, 30–60 s)**: street-day (birds/traffic-distant/wind), street-night
|
||||||
|
(crickets, distant hum), rain layer (composes over either), interior room-tone (one per 2–3 shop
|
||||||
|
types is fine).
|
||||||
|
- **Interaction SFX (short, dry)**: door open + shop doorbell, till/buy chime, record-bin riffle
|
||||||
|
(the dig), footstep set (2–3 surfaces × 4 variants), UI toast, tram bell + rumble (B's tram).
|
||||||
|
- **Music beds**: 3–5 instrumental 90s-flavoured loops for shop interiors (record shop must play
|
||||||
|
music; milk bar radio vibe; video store synth hum). Parody-safe: instrumental originals only, no
|
||||||
|
real-song imitations of specific recordings.
|
||||||
|
- Format: mono/stereo OGG (+ M4A fallback if trivial), 44.1 kHz, loudness-normalized (state LUFS).
|
||||||
|
- Licence/provenance line per file in AUDIT.md (generated-local = fine, just say so).
|
||||||
|
|
||||||
|
## Lane B — the WebAudio engine (street side)
|
||||||
|
|
||||||
|
`web/js/world/audio.js` (B-owned): one AudioContext, unlocked on first gesture; layers with
|
||||||
|
crossfades wired to systems that already exist — day-segment (street beds), weather (rain layer
|
||||||
|
gain follows intensity), tram proximity, player footsteps from the movement code. Shop-door
|
||||||
|
**spill**: standing near an open shop door leaks its interior bed at low gain (simple
|
||||||
|
distance-gain, no HRTF needed for v2.1). Respect all house-law points; expose `setMasterGain`,
|
||||||
|
`mute()`, and a tiny `window.PROCITY.audio` surface for F's smokes.
|
||||||
|
|
||||||
|
## Lane C — one contract answer + optional hook
|
||||||
|
|
||||||
|
Where should the interior bed + room-tone attach? Cleanest is C exposing `room.audio = { musicKey,
|
||||||
|
toneKey }` (seeded per shop) on buildInterior's return so F's interior_mode just plays it. Confirm
|
||||||
|
or propose better — one note in docs/LANES/. Optional: a `places`-tagged radio/hifi prop as the
|
||||||
|
visual source in music-playing shops (v2.2 candidate if it doesn't fit the round).
|
||||||
|
|
||||||
|
## Lane F — integration + smokes + v2.1
|
||||||
|
|
||||||
|
1. Wire interior beds via C's contract into interior_mode (enter → fade in, exit → fade out,
|
||||||
|
dispose clean); street layer via B's audio.js.
|
||||||
|
2. New smokes: boot-with-audio (no console errors, nothing plays pre-gesture), `?mute=1`,
|
||||||
|
`?noassets=1` (zero audio fetches), enter/exit leak check (AudioNodes released).
|
||||||
|
3. Soak once with audio on; then tag **v2.1** and push (you own the tag this round — push it with
|
||||||
|
main; no hold needed unless something's ugly).
|
||||||
|
4. Verification hygiene, now standing law: **verify in a fresh browser context** (the Playwright
|
||||||
|
gates already do) — a reused tab's module cache burned two of us this round.
|
||||||
|
|
||||||
|
## Lanes A + D — rest day
|
||||||
|
|
||||||
|
Nothing assigned. (D: F flagged that a visible outdoor night queue at the late shop is wanted for
|
||||||
|
the night money shot someday — it's a v3 mechanic, parked in V2_IDEAS, no action now.)
|
||||||
|
|
||||||
|
## For John
|
||||||
|
|
||||||
|
- **v3 epoch pick** (audio round buys us time): ① editor/overrides · ② venue district + gig system
|
||||||
|
· ③ multiplayer presence. One word kicks off v3 planning next round.
|
||||||
|
- Night-scene *visual* readability (streetlamp pools, window spill at distance) is a recurring
|
||||||
|
polish candidate — cheap to bundle into whichever v3 epoch you pick.
|
||||||
114
docs/LANES/ROUND12_INSTRUCTIONS.md
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
# PROCITY — Round 12 lane instructions (from Fable, integrator)
|
||||||
|
|
||||||
|
Date: 2026-07-15 · **v2.1 shipped** (audio round — clean, all four working lanes, tag on origin).
|
||||||
|
Round 12 opens the v3 epoch: **VENUES & GIGS** (`docs/V3_VENUES.md`). This round is
|
||||||
|
**v3.0-alpha: one pub, end-to-end, behind `?gigs=1`**. Vertical slice — a Friday night that
|
||||||
|
actually happens — before any district breadth.
|
||||||
|
|
||||||
|
## John's charter rulings (all three questions answered — these are now spec)
|
||||||
|
|
||||||
|
1. **Crowd: both.** Most of the audience stands and watches; a seeded subset dances.
|
||||||
|
2. **Events: free AND paid, mixed.** Cover charge is seeded per gig — some nights are free
|
||||||
|
(local acts), some charge $2–$10 at the door through the existing wallet.
|
||||||
|
3. **Band names: generator + drop-in.** A's generator produces names; an OPTIONAL editable
|
||||||
|
drop-in file lets John hand-write names (Feral Galahs tier) that mix into the pool.
|
||||||
|
|
||||||
|
## Laws for the round
|
||||||
|
|
||||||
|
- **Prime flag law (unchanged from v2):** everything lands behind `?gigs=1`, default OFF.
|
||||||
|
Flags-off boot stays byte-identical; goldens frozen; F's flags-off regression must stay green.
|
||||||
|
- All v2 house law carries: deterministic from citySeed · asset-free fallback (a gig with no GLBs
|
||||||
|
= placeholder band on a primitive stage — still a gig) · audio law (gesture-gated, silent-and-
|
||||||
|
happy) · budgets are numbers (gig-night interior ≤350 draws incl. crowd; state your worst) ·
|
||||||
|
human-sized line on humanoid shots · fresh browser context for verification.
|
||||||
|
- **A publishes the `plan.gigs` interface FIRST** (half-day handshake rule) — D, C, F all hang
|
||||||
|
off it.
|
||||||
|
|
||||||
|
## Lane A — gig truth (publish interface first)
|
||||||
|
|
||||||
|
1. **Venue lot**: one `pub` per town (registry type + placement rule — spine end or corner
|
||||||
|
anchor; must not displace the openLate shop). Venue is enterable like any shop.
|
||||||
|
2. **`plan.gigs`** (seeded, in the plan, documented in CITY_SPEC v3 section):
|
||||||
|
`[{ gigId, venueShopId, bandName, genreKey, night, startSeg, endSeg, cover }]` — nightly
|
||||||
|
entries; `cover` seeded: ~half free (0), else integer $2–$10. Poster assignments: which
|
||||||
|
walls/poles advertise which gig (reuse the sign/poster placement seam B already consumes).
|
||||||
|
3. **Band names**: generator via wordlists (90s Aussie pub-rock flavour) **+ the drop-in**:
|
||||||
|
`web/assets/custom_bands.json` — OPTIONAL file, schema `{ "bands": ["The Feral Galahs", …] }`.
|
||||||
|
Rules: absent file → pure generator, zero errors, zero fetch under `?noassets=1`; present →
|
||||||
|
custom names are seeded into the pool with priority (a town with N custom names should
|
||||||
|
surface most of them). Deterministic given (seed, file contents). Document the schema +
|
||||||
|
an example file in CITY_SPEC so John can just edit it.
|
||||||
|
4. Carried debt, now due (D consumes it this round): the **closing-time occupant ruling**.
|
||||||
|
|
||||||
|
## Lane C — the band room (pub archetype)
|
||||||
|
|
||||||
|
1. Pub interior archetype: low stage, PA/amp positions (E GLB slots, primitive fallback),
|
||||||
|
bar counter (reuse counter contract — the barkeep is just a keeper), sticky-carpet floor
|
||||||
|
skin choice, green-room door blocked (house pattern).
|
||||||
|
2. **`room.watchPoints[]`** — the browse-points pattern, crowd edition: 6–8 seeded floor poses
|
||||||
|
facing the stage, `{ x, z, ry, dance: bool }` with `dance` seeded ~⅓ true (John: "a bit of
|
||||||
|
both"). Same coord frame + ry convention as `counter.stand`. Publish the interface in your
|
||||||
|
first commit; validate no overlap with fittings/keeper/browse points.
|
||||||
|
3. `room.audio` gains `gigKey` when built with a gig on (F passes gig state via opts —
|
||||||
|
quiet-night pub = normal interior with roomtone).
|
||||||
|
|
||||||
|
## Lane D — the band and the crowd
|
||||||
|
|
||||||
|
1. **Band trio on stage**: 3 rigs at seeded stage poses with instrument-holding idles
|
||||||
|
(guitarist/bassist strum-sway, drummer seated bob — procedural bone-sway on top of the idle
|
||||||
|
clip is fine, no new mocap required). Instruments are E's GLBs when present, primitives when
|
||||||
|
not.
|
||||||
|
2. **Audience at C's watch points**: the keeper/browser spawn path (it's now gate-protected
|
||||||
|
against giants). `dance:false` → stand-and-watch idle (subtle weight shift). `dance:true` →
|
||||||
|
dance variant (procedural bob/sway/step loop, seeded phase so the crowd doesn't metronome).
|
||||||
|
3. Gig-night patronage surge at the venue while state=on (uses A's closing-time ruling), and
|
||||||
|
identity continuity where it falls out naturally (the queue is v3.0-beta — NOT this round).
|
||||||
|
4. Budget + leak numbers in your progress note, as ever.
|
||||||
|
|
||||||
|
## Lane E — instruments, posters, gig sound
|
||||||
|
|
||||||
|
1. GLBs via the MODELBEAST pipeline: electric guitar, bass, drum kit (one merged kit), amp,
|
||||||
|
mic stand. House GLB law, footprints, thumbnails, depot publish (tailnet path), manifest.
|
||||||
|
2. Poster/flyer skin pack: 3–4 gig-poster templates that take band-name overprint the way shop
|
||||||
|
signs take names (B consumes for walls/poles; C may use one inside the pub).
|
||||||
|
3. `gen_audio.py` extension: one pub-rock instrumental live bed (the alpha genre), crowd-walla
|
||||||
|
loop, applause sting. Same OGG+M4A/loudness/provenance discipline as R11.
|
||||||
|
|
||||||
|
## Lane B — the venue from the street (alpha-light)
|
||||||
|
|
||||||
|
1. Lit pub frontage at night while a gig is on: marquee/poster placement (E's skins + A's
|
||||||
|
poster assignments), warm window spill, sign lit through `endSeg`.
|
||||||
|
2. Muffled-gig spill through the wall/door (extend the R11 door-spill: low-pass filtered gig
|
||||||
|
bed at low gain when state=on). B owns the street side of the audio seam.
|
||||||
|
3. **Stretch, non-blocking**: first slice of night-readability polish (streetlamp pools on the
|
||||||
|
venue's block) — it makes the alpha money shot.
|
||||||
|
|
||||||
|
## Lane F — Friday night fires (last, wires everything)
|
||||||
|
|
||||||
|
1. **Gig state machine** off the clock: `quiet → doors (startSeg−1) → on → done`, driven by
|
||||||
|
`plan.gigs`; exposed on `window.PROCITY.gigs` for smokes.
|
||||||
|
2. `?gigs=1` seam + **flags-off regression stays green** (prime law).
|
||||||
|
3. **Door charge**: entering the venue while doors/on and `cover > 0` debits the wallet with a
|
||||||
|
toast (`$5 cover — THE FERAL GALAHS`); insufficient funds → polite knockback toast, no entry
|
||||||
|
(keeps the wallet honest, zero new UI). Free gigs walk straight in. Re-entry same night = no
|
||||||
|
re-charge (seeded stamp, not new persistence).
|
||||||
|
4. Audio wiring: gig bed via C's `gigKey` inside, B's muffled spill outside; audio smokes extend
|
||||||
|
to the gig path.
|
||||||
|
5. Smokes: gig schedule determinism (two runs byte-equal), crowd cap ≤ watchPoints, band always
|
||||||
|
3, gig-night interior draw budget, no-giants inherited on stage/crowd rigs, `?noassets=1`
|
||||||
|
still silent-and-fine with `?gigs=1`.
|
||||||
|
6. Alpha acceptance shot: gig on, band + mixed standing/dancing crowd, human-sized line. Then
|
||||||
|
tag **v3.0-alpha** (remember: `git tag` and `git push` as separate commands — the classifier
|
||||||
|
blocks them combined).
|
||||||
|
|
||||||
|
## Order
|
||||||
|
|
||||||
|
A first (plan.gigs + venue lot interface) → C (watch points + archetype) in parallel with E
|
||||||
|
(assets/audio) → D (band + crowd) → B (frontage/spill) → F last. A and C: publish interfaces in
|
||||||
|
your FIRST commit even if implementation follows.
|
||||||
|
|
||||||
|
## Standing
|
||||||
|
|
||||||
|
- v3.0-beta preview (next round, do not start): the district (2–4 venues), posters town-wide,
|
||||||
|
the outdoor queue, walla+applause polish, full night-readability pass.
|
||||||
|
- John: no open questions this round — all three charter rulings received and encoded above.
|
||||||
173
docs/LANES/ROUND13_INSTRUCTIONS.md
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
# PROCITY — Round 13 lane instructions (from Fable, integrator)
|
||||||
|
|
||||||
|
Date: 2026-07-15 · **v3.0-alpha shipped** (one pub end-to-end, `?gigs=1`, tag on origin;
|
||||||
|
qa `--strict` 6/6 green, gig-night interior 47 draws / 171 asset-free of 350).
|
||||||
|
Round 13 is **v3.0-beta: THE DISTRICT** — 2–4 venues, a full week's schedule, posters town-wide,
|
||||||
|
the outdoor queue, walla + applause, and the night-readability pass. Same flag, same laws.
|
||||||
|
|
||||||
|
## First: the R12 debt ledger (all six are owned this round)
|
||||||
|
|
||||||
|
F wired the alpha last and surfaced these (LANE_F_NOTES §12). Each is assigned below in its
|
||||||
|
owner's section, but here is the ledger so nothing dangles:
|
||||||
|
|
||||||
|
1. **gigKey contract (C ↔ E ↔ B ↔ F)** — C emitted `gig-pubrock`, E shipped `pubrock-live`,
|
||||||
|
B hardcoded `pubrock-live`, F bridged at the glue. **Ruling: the manifest key IS the gigKey.**
|
||||||
|
Canonical form `gig-<genreKey>` (`gig-pubrock`, `gig-grunge`, …). E renames the bed in
|
||||||
|
manifest + gen_audio, B's spill reads the same key, F deletes the `GIG_BED` bridge, and the
|
||||||
|
contract line lands in CITY_SPEC §v3. One key, zero mapping — genre #2 must not rediscover
|
||||||
|
the R1 facade-skin trap.
|
||||||
|
2. **Instrument GLBs unwired (D)** — E's 5 GLBs landed in R12; D's band still holds primitives.
|
||||||
|
D owns the join via its own `opts.instrumentFor(role)` seam.
|
||||||
|
3. **No drummer (C + D)** — R12 spec said drummer, C's poses were a front-line trio, D built to
|
||||||
|
C. **Ruling (Fable): bring implementation to spec — the kit gets played.** C adds a 4th
|
||||||
|
`drums` pose on the riser; D adds the drummer (seated bob, E's `drum_kit`). The band is now
|
||||||
|
**4** (front-line trio + drummer); F's `band==3` smoke moves to 4. *John can veto to a
|
||||||
|
front-line trio — say so and C/D skip it, F amends the spec line instead.*
|
||||||
|
4. **RY_FLIP audit (C + D)** — D found C's pose `ry` plants the fleet mesh 180° off (fleet
|
||||||
|
visual front is local +Z, not the −Z the sim comments assume) and compensated with
|
||||||
|
`RY_FLIP = π`. C+D: agree whose convention is canonical, write it into the pose contract in
|
||||||
|
LANE_C_PUB.md, and check whether **keepers/browsers have been subtly back-to-front all
|
||||||
|
along**. If a flip is needed there, it's a visual fix — no golden moves.
|
||||||
|
5. **Poster placement (A)** — B reports main-street posters land mid-road on intersection
|
||||||
|
nodes. A: add a verge/pole offset so posters seat on furniture, not bitumen.
|
||||||
|
6. **Stature-vs-crown gate (F)** — F's smoke measures stature (`crown − base.y`) because the
|
||||||
|
band stands on a 0.32 m deck; `tools/qa/interior_scale_check.py` still asserts world crown
|
||||||
|
and inherits the bug the day a keeper stands on a platform. F: port the stature measure
|
||||||
|
into the shared gate (keep the ceiling-clip assert as its own line).
|
||||||
|
|
||||||
|
## Laws for the round (unchanged)
|
||||||
|
|
||||||
|
- **Prime flag law:** everything behind `?gigs=1`, default OFF; flags-off boot byte-identical;
|
||||||
|
v2 goldens frozen; the **gig golden `0xa6ae5a5e` re-pins this round** (multi-venue changes
|
||||||
|
gig-plan output — A re-pins it in the same commit as the change, CITY_SPEC amendment law).
|
||||||
|
- Deterministic from citySeed · asset-free fallback (`?noassets=1` + `?gigs=1` stays
|
||||||
|
silent-and-fine) · audio law (gesture-gated, fail-soft) · budgets are numbers — gig-night
|
||||||
|
interior **≤350** incl. crowd; street **≤300 draws / 200k tris** *at the busiest venue block
|
||||||
|
with all frontages/posters/queue in* — state your measured worst · human-sized line on every
|
||||||
|
humanoid shot · fresh browser contexts for verification.
|
||||||
|
- **A publishes the multi-venue `plan.gigs` interface FIRST** (half-day handshake rule) —
|
||||||
|
C, D, B, F all hang off it.
|
||||||
|
|
||||||
|
## Lane A — the district in the plan (publish interface first)
|
||||||
|
|
||||||
|
1. **2–4 venues per town** (seeded count). Registry types: `pub` (have it), **`band_room`**,
|
||||||
|
**`rsl`**. Placement rules per charter: pub at spine end/corner (as now), band_room on the
|
||||||
|
warehouse fringe, RSL off-spine. Same conversion-in-place trick as R12 (keep lot id +
|
||||||
|
geometry, no overlap, never the openLate shop, enterable). Each venue carries `venueKind`
|
||||||
|
and a **`genreKey` by kind**: pub → `pubrock`, band_room → `grunge`, rsl → `covers`
|
||||||
|
(E ships the two new beds; John may re-flavour — see Standing).
|
||||||
|
2. **`plan.gigs` goes district**: 7 nights × all venues (not every venue plays every night —
|
||||||
|
seeded dark nights are fine and read true; ~4–6 gigs per venue per week). Same entry shape;
|
||||||
|
`cover` law unchanged (~half free; RSL skews cheap, band_room skews free). Custom band
|
||||||
|
names spread across the whole schedule, still priority-seeded, still ≤1 slot per name per
|
||||||
|
night across town (no band plays two venues at once).
|
||||||
|
3. **`plan.posters` goes town-wide**: each venue's tonight-gig gets frontage + spine coverage;
|
||||||
|
add the **verge/pole offset** (debt #5). Posters may advertise different venues' gigs on
|
||||||
|
the same pole run — that's the district reading.
|
||||||
|
4. Re-pin the gig golden + CITY_SPEC v3 section update (venues table, genre-by-kind map,
|
||||||
|
the gigKey contract line from debt #1 — coordinate the wording with C/E).
|
||||||
|
|
||||||
|
## Lane C — two more rooms + the drummer
|
||||||
|
|
||||||
|
1. **`band_room` and `rsl` interior archetypes.** Reuse the pub recipe machinery — different
|
||||||
|
dressing, same contracts: band_room = smaller/scuzzier (posters on walls — E's skins, low
|
||||||
|
ceiling, stage barely a riser); RSL = carpet-and-bistro (bigger floor, stage at one end,
|
||||||
|
bar counter). Both return `stage`, `watchPoints[]` (band_room 6–8, RSL 8–12 — RSL is the
|
||||||
|
crowd-cap stress case), `audio.gigKey = 'gig-'+genreKey`, quiet-night variants. Budgets in
|
||||||
|
your note.
|
||||||
|
2. **`drums` pose** on the riser in all three archetypes (debt #3): `stage.bandPoses` grows to
|
||||||
|
4, role `drums`, seated height noted for D.
|
||||||
|
3. **RY audit with D** (debt #4): pin the pose `ry` convention in LANE_C_PUB.md, check
|
||||||
|
keepers/browsers facing, fix or document.
|
||||||
|
4. Publish the two new archetype interfaces (watchPoints counts, stage shapes, gigKeys) in
|
||||||
|
your FIRST commit even if dressing follows.
|
||||||
|
|
||||||
|
## Lane D — instruments in hand, a drummer, and the queue
|
||||||
|
|
||||||
|
1. **Wire E's instrument GLBs** (debt #2): manifest fittings via your `instrumentFor(role)`
|
||||||
|
seam — electric_guitar/bass_guitar/mic_stand in hand, drum_kit planted at the `drums` pose,
|
||||||
|
guitar_amp as stage dressing if it reads (C's call on placement). Orientation/scale is
|
||||||
|
yours; primitives remain the fallback (asset law). Note the draw/tri delta — E's meshes are
|
||||||
|
heavy (30–96k tris) and E is decimating in parallel; wire against whatever's in the
|
||||||
|
manifest and re-measure after E's pass.
|
||||||
|
2. **The drummer** (debt #3): 4th rig, seated bob at the `drums` pose, behind the kit.
|
||||||
|
3. **The outdoor queue** (charter item, parked since v2): patrons visibly queue at the venue
|
||||||
|
door during `doors` (and overflow during `on` if the room is at watchPoint cap). Seeded
|
||||||
|
short line (2–6), spaced poses along the frontage (B marks the zone — consume it if
|
||||||
|
present, else straight line from the door), drains into the venue as entries happen, gone
|
||||||
|
by `done`. Queue rigs ride the same gate-protected spawn path; budget stated.
|
||||||
|
4. **Multi-venue surge**: `setGig` per venue (multiple concurrent). Identity continuity stays
|
||||||
|
opportunistic — the full "ped you followed is in the crowd" promise is v3.0, not now.
|
||||||
|
|
||||||
|
## Lane E — two genres, lighter meshes, the publish
|
||||||
|
|
||||||
|
1. **Two new gig beds**: `gig-grunge` (band_room) and `gig-covers` (RSL — mellower, organ-ish
|
||||||
|
covers-band feel). Same procedural discipline as R11/12 (seeded, loopable, OGG+M4A,
|
||||||
|
loudness, provenance). **Rename `pubrock-live` → `gig-pubrock`** (debt #1) in gen_audio +
|
||||||
|
manifest in the same commit B/F switch readers.
|
||||||
|
2. **Decimation/LOD pass on the 5 instrument GLBs** — the amp is 96k tris and a district
|
||||||
|
means multiple stages. Target ≤15k per instrument (they're 1m props, not hero assets);
|
||||||
|
re-run footprints/thumbs; keep originals in the depot as `_hi` variants.
|
||||||
|
3. **Depot publish** of the instrument GLBs — *gated on John's OK* (the tailnet push the
|
||||||
|
harness blocked in R12). If John OKs it in this round's kickoff, publish and clear the
|
||||||
|
Gate-3 provenance flag; if not, note it and carry.
|
||||||
|
4. Poster pack: 1–2 extra templates only if the town-wide spread looks repetitive in B's
|
||||||
|
verification — B's call, don't pre-build.
|
||||||
|
|
||||||
|
## Lane B — the night the district reads by
|
||||||
|
|
||||||
|
1. **Night-readability pass** (the epoch's bundled polish, now due): streetlamp pools on
|
||||||
|
venue blocks + window light spill so the Friday-night walk reads. Budget-safe (state the
|
||||||
|
draw/tri cost); scoped to venue blocks first, town-wide only if budget holds.
|
||||||
|
2. **Generalize the venue presentation to N venues**: frontage glow + posters per venue,
|
||||||
|
driven per-venue by F's state (`update(gigStates)` — agree the shape with F early).
|
||||||
|
Spill goes per-genre: read each venue's `gigKey` bed (debt #1's canonical key), same
|
||||||
|
470 Hz lowpass law.
|
||||||
|
3. **Queue zone marker**: a subtle door-adjacent zone (rope/chalk/nothing-fancy, primitive
|
||||||
|
fine) publishing `venue.queueZone = {x,z,ry,len}` for D's queue poses.
|
||||||
|
4. **Bookmarks debt** (F's ask + R3 §9): add `venue_night` (pub frontage, `seg:5`) and close
|
||||||
|
out the missing bookmark set in one pass, so `tools/qa/gig_shot.py` can script the street
|
||||||
|
money shot.
|
||||||
|
|
||||||
|
## Lane F — the district fires (last)
|
||||||
|
|
||||||
|
1. **Gig state machine goes per-venue**: one latch per venue off the same `procity:segment`
|
||||||
|
spine (keep the getter law; keep the landing-segment night-roll law — they were both
|
||||||
|
hard-won). `window.PROCITY.gigs` becomes per-venue state keyed by venueShopId, with the
|
||||||
|
alpha shape kept as a compat alias for one round (B reads it mid-round).
|
||||||
|
2. **Walla + applause wiring**: E's `ambience-crowd-walla` under the gig bed while `on`;
|
||||||
|
`sfx-applause` sting on `on → done`. Audio law throughout.
|
||||||
|
3. **Cover charge per venue** (existing seam; per-venue paid stamps — a night at two venues
|
||||||
|
is two covers).
|
||||||
|
4. **Delete the `GIG_BED` bridge** once E's rename lands (debt #1) and add the CITY_SPEC
|
||||||
|
contract line with A/C/E.
|
||||||
|
5. **Gate hardening** (debt #6): stature measure into `interior_scale_check.py`.
|
||||||
|
6. **Smokes**: district determinism (two runs byte-equal across all venues), per-venue crowd
|
||||||
|
≤ watchPoints, band==4, queue ≤ cap and drains by `done`, every venue's gigKey resolves
|
||||||
|
(the debt-#1 assert, now for 3 genres), street budget at the busiest venue block,
|
||||||
|
`?noassets=1`+`?gigs=1` silent-and-fine, flags-off regression green.
|
||||||
|
7. Acceptance: the district money shots — street: lit frontage + queue + posters at night
|
||||||
|
(use B's `venue_night` bookmark); interior: 4-piece with real instruments + mixed crowd,
|
||||||
|
human-sized line. Then tag **v3.0-beta** (`git tag` and `git push` as separate commands —
|
||||||
|
the classifier blocks them combined).
|
||||||
|
|
||||||
|
## Order
|
||||||
|
|
||||||
|
A first (multi-venue `plan.gigs` + venue kinds + golden re-pin) → C (archetypes + drums pose)
|
||||||
|
in parallel with E (beds + decimation + rename) → D (instruments/drummer/queue) → B (night
|
||||||
|
pass + N-venue frontage + queue zone + bookmarks) → F last. A and C: interfaces in your FIRST
|
||||||
|
commit. The debt-#1 rename is a three-way (E rename, B reader, F bridge-delete) — land E's
|
||||||
|
side early so B/F switch inside the round, and nobody merges a half-renamed manifest.
|
||||||
|
|
||||||
|
## Standing
|
||||||
|
|
||||||
|
- **v3.0 preview (next round, do not start):** identity continuity into the crowd, the
|
||||||
|
money-shot tour (John's queue shot), soaks, docs freeze, the v3.0 tag.
|
||||||
|
- **John — three items, only #1 blocks anything:**
|
||||||
|
1. **Depot publish OK?** E's 5 instrument GLBs are staged locally; the tailnet depot push
|
||||||
|
needs your explicit go (harness classifier flagged it outward-facing). Until then qa
|
||||||
|
Gate 3 carries a provenance flag.
|
||||||
|
2. **Genre flavours**: defaults are band_room → grunge, RSL → covers. Re-flavour freely
|
||||||
|
(names only — E synthesizes to order).
|
||||||
|
3. **Drummer ruling**: Fable ruled 4-piece (kit on the riser, spec intent). Veto to
|
||||||
|
front-line trio if you prefer — before D starts, ideally.
|
||||||
155
docs/LANES/ROUND14_INSTRUCTIONS.md
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
# PROCITY — Round 14 lane instructions (from Fable, integrator)
|
||||||
|
|
||||||
|
Date: 2026-07-16 · **v3.0-beta shipped** (the district, `?gigs=1`, tag local; qa `--strict` 6/6,
|
||||||
|
0 warn — the queue assert went live after D landed). Seed 20261990 = Boolarra Heads: 3 venues,
|
||||||
|
14 gigs/week, tonight Screaming Utes @ The Exchange Hotel $10 + The Dead Ringers @ the RSL free.
|
||||||
|
Round 14 is **v3.0: THE RELEASE** — identity continuity into the crowd (the one remaining charter
|
||||||
|
promise), the money-shot tour, the soaks, docs freeze, and the `v3.0` tag. **No new systems this
|
||||||
|
round** — if it isn't continuity, hardening, or documentation, it's v3.1.
|
||||||
|
|
||||||
|
## First: the R13 debt ledger (all owned this round)
|
||||||
|
|
||||||
|
R13 closed its six debts cleanly (gigKey contract landed everywhere, instruments wired, drummer
|
||||||
|
seated, RY convention fixed **at the source** — D's `buildFigure` normalisation also silently fixed
|
||||||
|
keepers/browsers/walkers that had been back-to-front whenever the GLB fleet loaded; good catch, C+D).
|
||||||
|
What carries:
|
||||||
|
|
||||||
|
1. **Alpha alias retirement (B → F, ordered).** F kept the R12 single-venue
|
||||||
|
`window.PROCITY.gigs` shape (`.state/.on/.venueShopId/.cover/.paid`) as a one-round compat
|
||||||
|
alias; that round is over. **B first** removes its fallback readers (`audio.js` alias→clock
|
||||||
|
chain, `venue.update()` bare-string arm — keep the clock fallback, drop the alias hop), **then
|
||||||
|
F** deletes the alias so `byVenue` + the per-venue accessors are the only surface. CITY_SPEC §v3
|
||||||
|
runtime block loses the alias line in the same commit (F).
|
||||||
|
2. **Drummer sit hack (D).** The 4th member is a standing rig sunk `SEAT_DROP` behind the kit —
|
||||||
|
reads seated only from the front. `~/Documents/mixamo-fetch/out/` holds **34 clips incl. `sit`**
|
||||||
|
(LANE_D_NOTES wishlist #1 — **check what exists first, never re-download**). Wire the real clip,
|
||||||
|
retire `SEAT_DROP` + the `m.seated` branch.
|
||||||
|
3. **Town-wide streetlamp pools (B) — RULING: stays venue-scoped for v3.0.** Town-wide needs
|
||||||
|
always-on `furniture.js` changes and that fights flags-off parity in release week. Venue blocks
|
||||||
|
read fine (F's `venue_night` shot proves it). Document as a v3.1 candidate in LANE_B_NOTES;
|
||||||
|
no code.
|
||||||
|
4. **Poster repetition (B judges, E executes).** B: during the tour audit, eyeball the spine poster
|
||||||
|
runs — if the per-gig instanced skins read repetitive, ask E for 1–2 more templates. E: build
|
||||||
|
only on B's ask (R13 law, unchanged).
|
||||||
|
|
||||||
|
Closed for good, for the record: depot publish (John OK'd mid-R13; Gate-3 provenance flag cleared,
|
||||||
|
`digalot.fyi` serves the 14k instruments + `_hi` originals).
|
||||||
|
|
||||||
|
## Laws for the round (unchanged, plus release discipline)
|
||||||
|
|
||||||
|
- **Prime flag law holds through the tag**: `?gigs=1` stays **opt-in for v3.0** (Fable's default —
|
||||||
|
John may overrule, see Standing #2). Flags-off boot byte-identical, synthetic golden
|
||||||
|
`0x3fa36874`, gig golden `0x1f636349` — **neither moves this round**. Any diff that would move
|
||||||
|
a golden is out of scope by definition; park it.
|
||||||
|
- Deterministic from citySeed · asset-free (`?noassets=1&gigs=1` silent-and-fine) · audio law
|
||||||
|
(gesture-gated, fail-soft) · budgets: gig-night interior ≤350, street ≤300 draws / 200k tris at
|
||||||
|
the busiest venue block · human-sized line on every humanoid shot · **fresh browser contexts**
|
||||||
|
(the stale-module trap bit B in R13 — `cache:'reload'` + hard nav, or the no-store server).
|
||||||
|
- **Shared-tree discipline**: commit atomically by explicit pathspec; lanes land concurrently.
|
||||||
|
- **Release-round law: no new rng draws on any seeded stream.** Continuity work must ride new
|
||||||
|
sub-streams or consume existing identities — if two runs of R13 code and R14 code disagree on
|
||||||
|
any golden or any R13 smoke, the change is wrong.
|
||||||
|
|
||||||
|
## Lane D — identity continuity: the ped you followed is in the crowd (the headline)
|
||||||
|
|
||||||
|
The last charter promise, deferred at R12 and R13 ("opportunistic, not now"). Now.
|
||||||
|
|
||||||
|
1. **Continuity contract.** The sim already knows who enters a venue (queue `admitOne()` drains +
|
||||||
|
surge occupants). Record a per-venue **tonight roster** of entered identities
|
||||||
|
({pedIndex, height, …whatever pins "the same person"}), and have `GigCrew.spawn` consume it:
|
||||||
|
crowd slots fill from the roster first, seeded strangers only for the remainder. Design is
|
||||||
|
yours; the acceptance is behavioural: **follow a punter from the queue through the door, walk
|
||||||
|
in behind them, and find them in the crowd — same ped, same height.** Works across exit/re-enter
|
||||||
|
(roster persists for the night, not per interior build).
|
||||||
|
2. **Determinism**: the roster is a consequence of seeded sim behaviour; interior crowd identity
|
||||||
|
for a given (seed, night, entry time-segment) must be stable enough for F's smoke — expose
|
||||||
|
`window.PROCITY.citizens.tonightRoster(venueShopId) → [{pedIndex, …}]` (or your preferred name —
|
||||||
|
tell F) so the smoke can assert crowd ⊇ roster ∩ cap without screenshot forensics.
|
||||||
|
3. **The sit clip** (debt #2 above). Data task, not rig work — clips bind through the canonical
|
||||||
|
`mixamorig` skeleton like walk/idle.
|
||||||
|
4. **Sim soak**: 30+ min equivalent (accelerated segments fine) with gigs on — full week roll,
|
||||||
|
repeated surge/drain/queue cycles at all venues; assert leak geo0/tex0, roster bounded, no
|
||||||
|
identity churn. Numbers in your notes.
|
||||||
|
|
||||||
|
Budget note: continuity is bookkeeping, not new rigs — draw/tri delta should be ~0; state it.
|
||||||
|
|
||||||
|
## Lane B — alias retirement + the tour audit
|
||||||
|
|
||||||
|
1. **Drop the alias readers** (debt #1 — you go first, tell F when landed).
|
||||||
|
2. **Bookmark/tour audit**: every bookmark (16 v2 + `venue_night` + `district_posters`) still
|
||||||
|
frames un-letterboxed, 0 errors, with `?gigs=1` on — the district changed the streetscape under
|
||||||
|
some of the old poses. Fix poses, don't touch world code.
|
||||||
|
3. **`queue_night` bookmark** — John's queue shot: close framing on the pub door at `doors`/`on`,
|
||||||
|
queue line + marquee + poster in frame, punters facing the door. This is the release hero shot;
|
||||||
|
F scripts it into the tour.
|
||||||
|
4. **Poster repetition call** (debt #4): judge, and either "fine" in your notes or a one-line ask
|
||||||
|
to E.
|
||||||
|
|
||||||
|
## Lane C — venue audit + docs freeze (light round, by design)
|
||||||
|
|
||||||
|
1. **Interior release audit**: one pass over the three venue archetypes against CITY_SPEC §v3 +
|
||||||
|
LANE_C_PUB — watchPoint caps, stage/riser numbers, gigKey resolution, quiet-night variants —
|
||||||
|
and re-run your 60-room soak + drawSweep against D's continuity changes (crowd identities
|
||||||
|
change; geometry must not). Fix only regressions.
|
||||||
|
2. **`guitar_amp` placement ruling** (R13 left it "C's call"): D planted it as stage dressing —
|
||||||
|
bless the placement or give D the corrected pose; one line in LANE_C_PUB either way.
|
||||||
|
3. **Docs freeze**: LANE_C_PUB.md + LANE_C_AUDIO.md are the durable contracts — final wording
|
||||||
|
pass, mark them v3.0-frozen.
|
||||||
|
|
||||||
|
## Lane E — provenance close-out (light round, by design)
|
||||||
|
|
||||||
|
1. **`publish.py --verify` drift check** against the live depot post-R13-publish; qa Gate 3 must
|
||||||
|
be green with zero flags for the tag.
|
||||||
|
2. **Poster templates** only on B's ask (debt #4).
|
||||||
|
3. **Docs freeze**: provenance table + audio/GLB inventories in LANE_E_NOTES current and
|
||||||
|
v3.0-frozen. No new synthesis this round unless John re-flavours a genre (Standing #3, R13 —
|
||||||
|
still open, still names-only).
|
||||||
|
|
||||||
|
## Lane A — invariant sweep + CITY_SPEC freeze
|
||||||
|
|
||||||
|
1. **District seed sweep**: 400-seed run asserting the district invariants — 2–4 venues, ≥1 pub,
|
||||||
|
never the openLate shop, no band twice in one night town-wide, every poster behind
|
||||||
|
`poleOffset`, per-venue 4–6 nights — plus **one osm town** proving graceful placement (no
|
||||||
|
spine/warehouse district). Promote any invariant not already in `selfcheck.js` into it.
|
||||||
|
2. **CITY_SPEC v3 freeze**: you own the spec — final pass over §v3 (venues table, genre map,
|
||||||
|
gigKey contract, gig golden `0x1f636349`, F's runtime block minus the retired alias), then mark
|
||||||
|
the section frozen for the tag. Coordinate the alias-line deletion with F (debt #1).
|
||||||
|
|
||||||
|
## Lane F — soaks, the tour, the tag (last)
|
||||||
|
|
||||||
|
1. **Delete the alpha alias** once B lands (debt #1) + the CITY_SPEC line (with A).
|
||||||
|
2. **Continuity smoke**: extend `smoke_gigs` — queue identities admitted at `doors` appear in the
|
||||||
|
interior crowd via D's roster seam (assert on the seam, not pixels); crowd ⊇ roster ∩ cap;
|
||||||
|
determinism arm re-run.
|
||||||
|
3. **Full-week soak**: drive the segment spine through all 7 nights × all venues in one context —
|
||||||
|
every latch fires per `plan.gigs` schedule (dark nights stay quiet), covers stamp per venue per
|
||||||
|
night, walla/applause never double-fire, leak geo0/tex0 across ≥20 enter/exit cycles. Wire it
|
||||||
|
into qa as a non-strict extra (strict stays the 6 gates).
|
||||||
|
4. **The money-shot tour**: `tools/qa/tour_shots.py` — scripted capture of the release set in one
|
||||||
|
deterministic run: `rain_verandah` (winmap+rain hero) · `tram_stop` · `venue_night` ·
|
||||||
|
`district_posters` · **`queue_night` (John's shot)** · gig interior (4-piece + instruments +
|
||||||
|
crowd). Contact sheet into `docs/shots/release_v3/`.
|
||||||
|
5. **Docs assembly**: README v3 section (what shipped, the flags table, the shots), confirm every
|
||||||
|
lane's freeze landed, LANE_F_NOTES §14 runbook.
|
||||||
|
6. **Acceptance + tag**: `qa.sh --strict` 6/6 + continuity smoke + week soak green → tag **v3.0**.
|
||||||
|
Tag and push are separate commands; the push itself waits on John (Standing #1).
|
||||||
|
|
||||||
|
## Order
|
||||||
|
|
||||||
|
**B first** (alias readers out — F is blocked on it for the deletion) and **D first** (continuity —
|
||||||
|
F's smoke and the tour hang off the roster seam); they're disjoint files, run in parallel. A, C, E
|
||||||
|
anytime, independent. **F last**, as always. Mid-round handshakes: B→F "readers gone", D→F the
|
||||||
|
roster accessor name, C→D the amp ruling.
|
||||||
|
|
||||||
|
## Standing
|
||||||
|
|
||||||
|
- **v3.1 preview (do not start):** flag-default flip (if John rules it), town-wide lamp pools,
|
||||||
|
tram-gig composition (ride to the gig), wind sway, bench-sit loiter from the same `sit` clip.
|
||||||
|
- **John — all three items RESOLVED at kickoff (2026-07-16):**
|
||||||
|
1. **The push — DONE.** John OK'd it; Fable pushed `main` (R13 epoch + these instructions) and
|
||||||
|
the `v3.0-beta` tag to origin at kickoff. Release tags only, as ever — lane-round tags stay
|
||||||
|
local. Push discipline unchanged for lanes: commit local, F/Fable pushes at round end.
|
||||||
|
2. **Flag default at v3.0 — RULED (John concurs):** `?gigs=1` stays **opt-in** for the tag;
|
||||||
|
default-flip is a v3.1 candidate after the soak bakes. No golden re-pin this round.
|
||||||
|
3. **Genre re-flavour — CLOSED:** John: "fine for now." pubrock/grunge/covers are final for
|
||||||
|
v3.0; the docs freeze proceeds on those names.
|
||||||
138
docs/LANES/ROUND15_INSTRUCTIONS.md
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
# PROCITY — Round 15 lane instructions (from Fable, integrator)
|
||||||
|
|
||||||
|
Date: 2026-07-16 · **Round-14 verdict: A–E landed and green; LANE F NEVER RAN.** The R14 release
|
||||||
|
round produced excellent hardening from five lanes — D's continuity seam (8/8 surge + 4/4 queue
|
||||||
|
identities, empty-roster byte-identical), B's alias-reader retirement + 19-bookmark tour audit +
|
||||||
|
`queue_night` hero pose, A's 400-seed invariant sweep + §v3 freeze, C's adversarial venue audit
|
||||||
|
(which caught a real amp defect), E's provenance freeze (Gate 3 green, 0 blockers) — **but v3.0 is
|
||||||
|
NOT tagged.** F's whole release brief (continuity wiring, alias deletion, week soak, the tour, the
|
||||||
|
tag) is untouched, and the round surfaced one live bug and one cosmetic defect that gate the money
|
||||||
|
shots. **Round 15 closes the release.** F carries its R14 brief in full; A and D each land one
|
||||||
|
small fix; B and C verify; E is on-call only. Nothing else — the freeze holds.
|
||||||
|
|
||||||
|
## The ledger (everything between here and the tag)
|
||||||
|
|
||||||
|
1. **Frontage posters are on the BACK WALL (A — live bug, gates John's hero shot).**
|
||||||
|
`gigs.js:205` seats each playing venue's own-frontage poster at local **−Z** ("house
|
||||||
|
convention"), but B's buildings canon puts the street facade on **+Z** — B measured the pub's
|
||||||
|
poster 15.9 m behind the street face. Every venue's own poster has been invisible from the
|
||||||
|
street since R13, and `queue_night` frames no poster because of it. Fix: flip item 1 of
|
||||||
|
`buildPosters` to `+sin,+cos`. This moves `plan.posters` → **the gig golden moves — this is the
|
||||||
|
round's ONE sanctioned golden move** (amendment law: re-pin `0x1f636349 → new` in the same
|
||||||
|
commit, and amend the §v3 FROZEN marker's golden line in that commit too — the freeze allows
|
||||||
|
exactly this, a recorded amendment).
|
||||||
|
2. **Alias deletion (F — B unblocked you, with a map).** B's sweep: cross-lane CLEAN — no A/C/D/E
|
||||||
|
file reads the scalar alias; the only remaining readers are F's own ~20 tool lines. Delete
|
||||||
|
`gig_state.js:141–151`, migrate `tools/flags_check.py` + `tools/qa/gig_shot.py`
|
||||||
|
(`.venueShopId → venueShopIds[0]`/explicit id · `.state → stateOf(id)` · `.cover → coverOf(id)`
|
||||||
|
· `.bandName → bandNameOf(id)`), and delete the two CITY_SPEC §v3 alias sentences (~lines
|
||||||
|
271–272 — A deliberately left them for you to avoid a doc-ahead-of-code race). While in that
|
||||||
|
spec block, add the omitted **`nightOf(id)`** to the accessor list (A's note). One atomic commit.
|
||||||
|
3. **Continuity wiring + smoke (F — D handed you three touches).**
|
||||||
|
`crew.spawn(…, { roster: citizens.tonightRoster(shop.id) })` at `interior_mode.js:239`;
|
||||||
|
`onAdmit: id => citizens.recordVenueEntry(venueShopId, id)` in the shell's per-venue queue loop
|
||||||
|
(`index.html` ~380–393); the clear is automatic on your existing `setGig(id,false)`. Smoke:
|
||||||
|
assert **crowd ⊇ roster ∩ cap** on the seam (`crew.crewInfo.fromRoster`, `member.pedIndex ∈
|
||||||
|
tonightRoster(id)`), not pixels; re-run the determinism arm (empty roster must stay
|
||||||
|
byte-identical to R13).
|
||||||
|
4. **`guitar_amp` reposition (D — C's ruling, not blessed as-is).** Your backline plant is 0.29 m
|
||||||
|
from the bassist (interpenetrates; confirmed by render + three audit agents) and redundant with
|
||||||
|
C's primitive ampStacks. Move to C's corrected pose (LANE_C_PUB §3): up-stage-right
|
||||||
|
`(stage.x + stage.w·0.36, deckY, stage.z − stage.d·0.22)`. One-liner in `band.js
|
||||||
|
_addBackline`; C verifies. (Full `stage.backline[]` unification stays v3.1.)
|
||||||
|
5. **Settled-tris honesty (F).** C caught that your gig smoke measures **before** the async
|
||||||
|
instrument GLBs land: reported ~34k, settled reality ~182k tris (5×14k instruments + rigs).
|
||||||
|
The interior gate is draws (passes at ≤71) so no violation — but measure **settled** (wait for
|
||||||
|
fleet + instruments, or shoot-twice) in the smoke and the tour, and report both numbers in your
|
||||||
|
notes. Same discipline for tour shots generally (B: orphan-chunk inflation — shoot from a clean
|
||||||
|
load or twice).
|
||||||
|
6. **Drummer sit clip — deferral RATIFIED (v3.1).** D investigated properly: the clip exists (on
|
||||||
|
the tailnet source box — Fable's `~/Documents/mixamo-fetch` path was stale), converts and binds
|
||||||
|
clean, but correct wiring needs a `_rotWithHips` variant in the **shared** rig stack + a
|
||||||
|
seated-stature exemption in F's no-giants gate. Two cross-cutting changes for a polish item in
|
||||||
|
freeze week = v3.1. The `SEAT_DROP` hack ships in v3.0; it reads seated from the crowd POV.
|
||||||
|
|
||||||
|
## Laws (unchanged, one exception)
|
||||||
|
|
||||||
|
- Prime flag law: `?gigs=1` **opt-in at the tag** (John concurred, R14). Flags-off byte-identical;
|
||||||
|
synthetic golden `0x3fa36874` frozen. **Gig golden moves ONCE** (ledger #1, A's commit only).
|
||||||
|
- Release law: no new rng draws on any seeded stream · no new systems · deterministic from
|
||||||
|
citySeed · asset-free silent-and-fine · audio gesture-gated fail-soft · budgets (interior ≤350
|
||||||
|
draws · street ≤300 / 200k at the busiest venue block) · human-sized line on humanoid shots ·
|
||||||
|
fresh browser contexts (no-store server; rAF is throttled in automation — drive live-spawned
|
||||||
|
things like the queue explicitly, per B's note).
|
||||||
|
- Shared-tree discipline: atomic commits by explicit pathspec.
|
||||||
|
|
||||||
|
## Lane A — the poster flip (first, everything waits on you)
|
||||||
|
|
||||||
|
1. Ledger #1: flip `buildPosters` item 1 to the +Z street face; re-pin the gig golden; amend the
|
||||||
|
§v3 FROZEN marker (golden line + a one-line amendment note) — all in ONE commit. Re-run your
|
||||||
|
400-seed sweep (the off-carriageway assert must still hold with the flipped face — a frontage
|
||||||
|
poster sits on the facade plane, not the verge, so expect green; prove it).
|
||||||
|
2. Tell B + F the new golden the moment it lands.
|
||||||
|
|
||||||
|
## Lane B — verify the hero (after A)
|
||||||
|
|
||||||
|
1. Re-shoot `queue_night`: A's flip should seat the pub's own poster by the door **in the existing
|
||||||
|
frame with no pose change** (your prediction — verify it). Confirm `district_posters` +
|
||||||
|
`venue_night` still read; spot-check the spine runs (posters now face the street everywhere).
|
||||||
|
2. Nothing else. Your R14 alias/tour/pools work is done and frozen; `tram_stop`'s ~293k close-ped
|
||||||
|
tris is on the v3.1 list.
|
||||||
|
|
||||||
|
## Lane C — verify the amp (after D)
|
||||||
|
|
||||||
|
1. Re-render the pub + RSL stages with D's corrected plant: amp clear of the bassist (state the
|
||||||
|
measured clearance), flanking the primitives, nothing interpenetrating. One line in your notes;
|
||||||
|
your freeze stands otherwise.
|
||||||
|
|
||||||
|
## Lane D — the amp one-liner
|
||||||
|
|
||||||
|
1. Ledger #4: move the backline plant to C's §3 pose. No other code — your continuity work and the
|
||||||
|
ratified sit-clip deferral are closed. Confirm draw/tri delta 0 and `?noassets` unaffected.
|
||||||
|
|
||||||
|
## Lane E — no tasks (frozen)
|
||||||
|
|
||||||
|
On-call only: if F's tour surfaces an asset defect, fix on ask. Nothing else — your provenance
|
||||||
|
freeze is the release contract.
|
||||||
|
|
||||||
|
## Lane F — the release (last; this is your R14 brief, carried in full)
|
||||||
|
|
||||||
|
1. **Continuity wiring + smoke** (ledger #3) — D's three touches, assert on the seam.
|
||||||
|
2. **Alias deletion** (ledger #2) — after this round's B/A commits land; one atomic commit
|
||||||
|
(gig_state + your two tools + the spec sentences + `nightOf`).
|
||||||
|
3. **Full-week soak**: all 7 nights × all venues in one context — every latch fires per
|
||||||
|
`plan.gigs` (dark nights stay quiet), covers stamp per venue per night, walla/applause never
|
||||||
|
double-fire, roster clears per night (D's soak already proves the sim side; yours proves the
|
||||||
|
shell), leak geo0/tex0 across ≥20 enter/exit cycles. Non-strict qa extra.
|
||||||
|
4. **The money-shot tour**: `tools/qa/tour_shots.py` — one deterministic run capturing the release
|
||||||
|
set into `docs/shots/release_v3/`: `rain_verandah` · `tram_stop` · `venue_night` ·
|
||||||
|
`district_posters` · **`queue_night` (John's hero — queue + marquee + A's now-visible poster)**
|
||||||
|
· gig interior settled (4-piece + real instruments + roster crowd). Settled-measurement
|
||||||
|
discipline (ledger #5); drive the queue live (B's rAF note).
|
||||||
|
5. **Docs assembly**: README v3 section (what shipped, flags table, the shots), confirm all five
|
||||||
|
freezes landed, LANE_F_NOTES §14/§15 runbook, F-progress current.
|
||||||
|
6. **Acceptance + tag + push**: `qa.sh --strict` 6/6 + continuity smoke + week soak green → tag
|
||||||
|
**`v3.0`** → `git push origin main` and `git push origin v3.0` as separate commands. John's
|
||||||
|
R14 kickoff authorization to keep origin current stands; release tags only, lane tags stay
|
||||||
|
local.
|
||||||
|
|
||||||
|
## Order
|
||||||
|
|
||||||
|
**A first** (the poster flip + re-pin — B's verify and F's hero shot hang off it). **D** anytime
|
||||||
|
(one line); **C** verifies D; **B** verifies A. **F last**, once A+D are in. Handshakes: A→B/F the
|
||||||
|
new golden; D→C "amp moved"; B→F "hero verified".
|
||||||
|
|
||||||
|
## Standing — the v3.1 backlog (consolidated; do not start)
|
||||||
|
|
||||||
|
Flag-default flip (`?gigs=1` → on, full golden re-pin) · drummer sit clip (`_rotWithHips` +
|
||||||
|
seated-stature gate exemption) · instrument LOD (settled interiors ~182k tris) · town-wide
|
||||||
|
streetlamp pools (flags-off-safe design needed) · `stage.backline[]` unification (GLB amp replaces
|
||||||
|
primitives) · wind sway · bench-sit loiter (same sit clip) · no-spine osm fixture for A's sweep ·
|
||||||
|
`tram_stop` close-ped tri diet · C's `audioEmitter` idea.
|
||||||
|
|
||||||
|
## John — nothing blocks this round
|
||||||
|
|
||||||
|
All three R14 items were resolved at kickoff and stand. FYI only: the gig golden re-pins once
|
||||||
|
(ledger #1, a real bug fix under the amendment law), and F tags + pushes `v3.0` at round end under
|
||||||
|
your standing OK. If you'd rather eyeball the tour contact sheet before the tag, say so and F holds
|
||||||
|
the tag on your review.
|
||||||
152
docs/LANES/ROUND16_INSTRUCTIONS.md
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
# PROCITY — Round 16 lane instructions (from Fable, integrator)
|
||||||
|
|
||||||
|
Date: 2026-07-16 · **v3.0 SHIPPED** — tagged and on origin (`0490bdd`), qa `--strict --soak`
|
||||||
|
7 passed · 0 failed · 0 warn. R15 was a model close: A's poster flip (a real R13-era bug — every
|
||||||
|
venue's own bill was on the back wall), B's hero verify + the John-sanctioned poster
|
||||||
|
night-emissive, D's amp move, C's clearance verify, and F's five-commit release close
|
||||||
|
(continuity wired, alias retired, week soak, the tour, docs). The R15 gotcha ledger is closed;
|
||||||
|
the settled-measurement discipline and the −Z/+Z convention write-ups are now durable law.
|
||||||
|
|
||||||
|
Round 16 is **v3.1: THE FLIP** — the town shows its best by default. `?gigs=1` has soaked through
|
||||||
|
alpha, beta, and release; the flag flips ON, and the mature v2 flags flip with it. Plus the parked
|
||||||
|
polish that's earned its slot: the drummer finally sits, the streetlamps go town-wide, the backline
|
||||||
|
unifies, and the corner-poster tweak. Tag **`v3.1`** at close.
|
||||||
|
|
||||||
|
## The flip (Fable's ruling — John may veto any part at kickoff, see Standing)
|
||||||
|
|
||||||
|
**Defaults flip ON: `gigs`, `weather`, `winmap`, `tram`.** All four are mature (weather/winmap/tram
|
||||||
|
soaked since v2; gigs through three v3 stages). Each keeps an individual opt-out (`?gigs=0`,
|
||||||
|
`?weather=0`, `?winmap=0`, `?tram=0`), and a new **`?classic=1`** forces all four off at once.
|
||||||
|
|
||||||
|
**The prime law is REWRITTEN, not repealed — the covenant moves to `?classic=1`:**
|
||||||
|
- **Classic law:** the `?classic=1` boot is byte-identical to the v2 baseline **forever** —
|
||||||
|
synthetic fingerprint `0x3fa36874`, zero gig layer, zero fetch delta vs the documented baseline.
|
||||||
|
This is the new frozen regression target; the old "flags-off" gate becomes the "classic" gate.
|
||||||
|
- **Default law:** the default boot is the full experience — plan fingerprint pins to the gig
|
||||||
|
golden (`0x4f4a549d`, or A's re-pin below), deterministic from citySeed, and every existing
|
||||||
|
smoke (district, continuity, cover, noassets) runs against the DEFAULT boot.
|
||||||
|
- `?noassets=1` composes with the default boot and stays silent-and-fine (custom_bands fetch and
|
||||||
|
all asset fetches gated exactly as today).
|
||||||
|
|
||||||
|
Everything else stands: determinism from citySeed · audio gesture-gated fail-soft · budgets
|
||||||
|
(interior ≤350 draws · street ≤300 / 200k) **now measured on the default boot, all four flags on,
|
||||||
|
at the busiest venue block — B re-baselines** · human-sized line · fresh contexts / no-store
|
||||||
|
server / drive live-spawned things explicitly · atomic commits by pathspec.
|
||||||
|
|
||||||
|
## The ledger
|
||||||
|
|
||||||
|
1. **The flip itself (F).** Shell defaults + `?classic=1` + the individual `=0` opt-outs, then the
|
||||||
|
harness: `flags_check.py`'s "flags-off regression" arms swap to **classic regression**
|
||||||
|
(`?classic=1` → byte-identical v2), a new **default-boot gate** (default fingerprint == gig
|
||||||
|
golden; gig layer present; 0 errors), and every smoke re-pointed at the default boot. qa.sh
|
||||||
|
gate names updated. This is the round's structural change — land it FIRST.
|
||||||
|
2. **The drummer sits (E produces, D wires).** E: fetch `Sitting_Idle.fbx` from the tailnet source
|
||||||
|
box (D's documented recipe, LANE_D_NOTES — the local `~/Documents/mixamo-fetch` path is stale),
|
||||||
|
convert to a mesh-free `sit.glb` (65-bone, raw `mixamorig:` naming is already canonical),
|
||||||
|
manifest + provenance + depot publish under John's R13 authorization. D: the shared-rig-stack
|
||||||
|
wiring — a `_rotWithHips` variant (keep `Hips.quaternion` + hip descent) + post-pose foot
|
||||||
|
re-plant in `spawnRig`, used ONLY by seated spawns; keepers/browsers/walkers must be
|
||||||
|
byte-identical (prove it — this touches the shared stack). Drummer retires `SEAT_DROP` and the
|
||||||
|
`m.seated` sink. **Bench-sit loiter from the same clip is the stretch goal** — only if the
|
||||||
|
drummer lands clean with round to spare.
|
||||||
|
3. **Seated-stature gate contract (F publishes in this brief — no mid-round handshake).** A seated
|
||||||
|
figure fails the no-giants floor by design. Contract: D tags seated figs
|
||||||
|
`fig.userData.procitySeated = true`; F's `interior_scale_check.py` + `flags_check.py` stature
|
||||||
|
asserts accept seated figs at **stature ∈ [0.9, 1.5] m** (still < ceiling, feet planted). F
|
||||||
|
lands the gate change in commit #1 so D can verify against it immediately.
|
||||||
|
4. **Town-wide streetlamp pools (B).** Ruling: the pools ride the **gig layer** (they're the v3
|
||||||
|
nightlife look — classic boot stays pool-free, parity preserved by construction). Extend the
|
||||||
|
R13 instanced-pool pass from venue blocks to the town's streetlight/shelter positions
|
||||||
|
(furniture positions are in the plan — read them, don't touch `furniture.js`). Night-driven as
|
||||||
|
now. Budget-stated: this is the likely draw-cost item of the round; if town-wide busts the
|
||||||
|
street budget at the busiest block, scope to main-street + venue blocks and say so.
|
||||||
|
5. **`stage.backline[]` unification (C, then D).** C exposes seeded backline poses (amp/spare-cab
|
||||||
|
positions up-stage, clear of all four bandPoses) and drops its primitive ampStacks from venue
|
||||||
|
recipes; D plants E's `guitar_amp` GLB (primitive fallback under `?noassets`) at `backline[]`
|
||||||
|
instead of its own hardcoded pose. One venue render each archetype to verify no
|
||||||
|
interpenetration (C's R15 clearance discipline).
|
||||||
|
6. **Corner-poster tweak (A).** R15 finding: a corner venue's frontage poster can sit near a
|
||||||
|
crossing street's kerb (seed 1 band_room, 1.58 m). Nudge the frontage poster ALONG the facade
|
||||||
|
away from the crossing-street end (stay on the facade plane, keep the `ry+π` read-from-street
|
||||||
|
fix). Moves `plan.posters` → **the round's one sanctioned gig-golden re-pin** (amendment law,
|
||||||
|
same commit, tell B/F). Re-run the 400-seed sweep; keep the frontage-poster exemption honest
|
||||||
|
(assert distance-to-crossing-kerb ≥ some floor now that you control it).
|
||||||
|
7. **No-spine osm fixture (A).** The R14 sweep's honest gap: both osm fixtures have a main spine,
|
||||||
|
so `pickVenues`' no-spine branch is unproven. Build the minimal single-row fixture and add it
|
||||||
|
to the sweep.
|
||||||
|
|
||||||
|
Parked still (v3.2 backlog — do not start): instrument `_lo` LOD (interior gate is draws; 182k
|
||||||
|
settled tris breaches nothing), `tram_stop` close-ped tri diet, wind sway, C's `audioEmitter`.
|
||||||
|
|
||||||
|
## Lane F — the flip, first and last (you bookend the round)
|
||||||
|
|
||||||
|
**Opening commit(s), before other lanes need the tree:**
|
||||||
|
1. Ledger #1 — the shell flip + `?classic=1` + harness rework. The default boot is now the gig
|
||||||
|
boot; prove classic byte-identical (`0x3fa36874`), prove default == gig golden, all smokes
|
||||||
|
green on the default boot, `?noassets` silent-and-fine on the default boot.
|
||||||
|
2. Ledger #3 — the seated-stature gate contract, same opening push.
|
||||||
|
|
||||||
|
**Closing, after all lanes land:**
|
||||||
|
3. Re-run everything: `qa.sh --strict --soak` (gates now classic+default), the week soak on the
|
||||||
|
default boot, and re-shoot the tour — `tour_shots.py` shots become DEFAULT-boot shots (drop the
|
||||||
|
per-shot flag juggling where the default now covers it; keep `?classic=1` as one comparison
|
||||||
|
shot in the contact sheet — "the town before/after v3"). New shots into `docs/shots/release_v3_1/`.
|
||||||
|
4. Docs: CITY_SPEC gets the v3.1 amendment (flip + classic law, coordinate with A), README flags
|
||||||
|
table updated (defaults + opt-outs + classic), LANE_F_NOTES §16.
|
||||||
|
5. Tag **`v3.1`**, push main + tag (standing authorization).
|
||||||
|
|
||||||
|
## Lane A — corner posters + the no-spine fixture
|
||||||
|
|
||||||
|
Ledger #6 (the flip-adjacent placement fix + golden re-pin) and #7 (the fixture). CITY_SPEC v3.1
|
||||||
|
amendment wording with F (you own the spec; F owns the flags table). Nothing else — your sweep and
|
||||||
|
freeze machinery is exactly right as-is.
|
||||||
|
|
||||||
|
## Lane B — town-wide pools + the default re-baseline
|
||||||
|
|
||||||
|
1. Ledger #4 — the pools, town-wide, gig-gated, night-driven, instanced.
|
||||||
|
2. **Default-boot re-baseline**: with F's flip landed, measure the busiest block on the DEFAULT
|
||||||
|
boot (gigs + weather-rain-worst-case + winmap + tram all live) — worst draws/tris, stated in
|
||||||
|
your notes. This is the number the budget law now means. If rain + district + pools compose
|
||||||
|
over 300 draws, say so loudly — that's a Fable scope call, not silent trimming.
|
||||||
|
3. Bookmark spot-check on the default boot (poses were framed against opt-in flags; weather/tram
|
||||||
|
now show up in old frames — confirm nothing letterboxes or blocks).
|
||||||
|
|
||||||
|
## Lane C — the backline handoff
|
||||||
|
|
||||||
|
Ledger #5, C's half: `stage.backline[]` poses (seeded, clear of bandPoses + watchPoints), drop the
|
||||||
|
primitive ampStacks from the three venue recipes, publish the shape in LANE_C_PUB §3 (amend the
|
||||||
|
frozen doc — v3.1 marker, same discipline as A's spec amendments). Verify with D once wired.
|
||||||
|
|
||||||
|
## Lane D — the drummer sits
|
||||||
|
|
||||||
|
Ledger #2 (wiring half) + ledger #5 (plant the amp at C's `backline[]`). The shared-stack proof is
|
||||||
|
the load-bearing part: keepers/browsers/walkers byte-identical before/after `_rotWithHips` (it
|
||||||
|
must be a parallel path, not a change to `_rotOnly`). Tag seated figs per ledger #3. Bench-sit is
|
||||||
|
stretch — drummer first, prove it, then decide.
|
||||||
|
|
||||||
|
## Lane E — the sit clip + provenance
|
||||||
|
|
||||||
|
Ledger #2 (asset half): fetch → convert → `sit.glb` → manifest + provenance + depot publish
|
||||||
|
(John's R13 authorization stands; flag it in your notes). Update the frozen provenance table with
|
||||||
|
a v3.1 amendment marker. On-call for B if the pool sprite needs anything (it shouldn't — it's
|
||||||
|
procedural).
|
||||||
|
|
||||||
|
## Order
|
||||||
|
|
||||||
|
**F first** (the flip + gate contract — the round's foundation; nothing else lands meaningfully
|
||||||
|
until the default boot exists). Then **A, B, C, E in parallel** (disjoint files; C before D on the
|
||||||
|
backline shape, E before D on the clip). **D** once C's backline + E's clip + F's gate are in.
|
||||||
|
**F last** (re-baseline acceptance, tour, docs, tag `v3.1`). Handshakes: F→all "flip landed, gates
|
||||||
|
renamed"; A→B/F the new golden; C→D the backline shape; E→D "sit.glb on depot"; B→F the default
|
||||||
|
worst-case numbers.
|
||||||
|
|
||||||
|
## Standing
|
||||||
|
|
||||||
|
- **John — one decision, veto-by-kickoff:** the flip scope. Fable flips **gigs + weather + winmap
|
||||||
|
+ tram** ON by default (`?classic=1` preserves the exact v2 boot forever; every flag keeps an
|
||||||
|
individual `=0` opt-out). If you want any of the four to stay opt-in — most plausibly `tram` —
|
||||||
|
say so before F's opening commit; F implements whatever's standing when they start. FYI: the
|
||||||
|
gig golden re-pins once (A's corner-poster fix), and E publishes `sit.glb` to the depot under
|
||||||
|
your R13 authorization.
|
||||||
|
- **v3.2 backlog (parked):** instrument `_lo` LOD · tram_stop close-ped tri diet · wind sway ·
|
||||||
|
`audioEmitter` · full flag retirement (deleting the `=0` opt-outs once classic has soaked).
|
||||||
135
docs/LANES/ROUND17_INSTRUCTIONS.md
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
# PROCITY — Round 17 lane instructions (from Fable, integrator)
|
||||||
|
|
||||||
|
Date: 2026-07-16 · **v3.1 SHIPPED** — THE FLIP is live on origin (`9277238`, tag `v3.1`). The
|
||||||
|
default boot is the full town: gigs + weather + winmap + tram, the seated drummer, town-wide
|
||||||
|
pools, `?classic=1` as the frozen covenant (`0x3fa36874`), default pinned to gig golden
|
||||||
|
`0xb1d48ea1`. Default-boot budgets have huge margin (worst street view 185 draws / 131k tris).
|
||||||
|
|
||||||
|
**R16 review — two lanes overrode the brief with evidence, and both were right.** D proved my
|
||||||
|
prescribed `_rotWithHips` mangles these rigs (bind-pose Hips mismatch — the exact failure
|
||||||
|
`_rotOnly`'s comment documents) and shipped the honest alternative (opt-in seated foot-replant,
|
||||||
|
byte-identical proof for every existing consumer). A proved the corner-poster "nudge" was
|
||||||
|
geometrically impossible (0/187 slides recover clearance — the crossing edge is parallel to the
|
||||||
|
facade) and fixed the root cause in `pickVenues` instead (0/851 posters below the floor across
|
||||||
|
400 seeds). This is the standard: **the brief is a hypothesis; the measurement wins.** B absorbed
|
||||||
|
the order inversion correctly (explicit all-flags URLs as the stand-in default), E's clip work was
|
||||||
|
exemplary (mesh-stripped, licence-zoned, 195/195 bind), C's backline handoff matched D's pose to
|
||||||
|
the millimetre. v3.1 was the cleanest epoch close yet.
|
||||||
|
|
||||||
|
Round 17 is **v3.2: THE TAIL + THE SCOUT** — drain the deferred-polish ledger (each item small,
|
||||||
|
each named for rounds), and run bounded groundwork for the next epoch so John can charter v4 on
|
||||||
|
evidence instead of promises. Tag **`v3.2`** at close.
|
||||||
|
|
||||||
|
## Where v4 stands (Fable's assessment — John picks, see Standing)
|
||||||
|
|
||||||
|
The V2_IDEAS parking lot has three surviving epoch-scale candidates (dig/real-stock ALREADY
|
||||||
|
SHIPPED in v2 — the README flags table, not the round ledgers, is the source of truth for what's
|
||||||
|
live): **the real map** (thriftgod `build_city()` route — Overpass → ~2,918 real AU shops → real
|
||||||
|
geography through the same CityPlan contract), **the in-game editor** (seeded-everything +
|
||||||
|
override tables), and **multiplayer presence**. My recommendation is **THE REAL MAP**: it's the
|
||||||
|
most PROCITY-native (the generator eating real geography), it has a proven reference
|
||||||
|
implementation and a proven consumer (`?plansrc=osm` + three fixtures incl. A's silverton), and it
|
||||||
|
composes with everything shipped — venues, citizens, interiors, and the dig all hang off the plan
|
||||||
|
contract and never change. R17 scouts it with real data (A+E below); the full charter is R18,
|
||||||
|
after John sees a real town boot.
|
||||||
|
|
||||||
|
## The ledger
|
||||||
|
|
||||||
|
1. **Drummer pelvic-lean pass (D — the R16 honest limit).** The upright bent-leg sit reads stiff
|
||||||
|
from side/overhead angles. A manual pelvic-lean on top of the played clip (a small constant
|
||||||
|
Spine/Hips lean applied post-mix for seated figs only, or a hand-keyed additive — D's call).
|
||||||
|
Same byte-identical discipline: seated-only path, standing untouched.
|
||||||
|
2. **Bench-sit loiter (D — the carried R16 stretch).** Same `sit.glb`, the sim loiter hook has
|
||||||
|
waited since v1: street peds occasionally sit at benches/verandah steps (seeded choice, seeded
|
||||||
|
duration, the seated re-plant + `procitySeated` tag already exist). Budget/determinism stated;
|
||||||
|
classic boot untouched (clip fetch is already `!CLASSIC`-gated).
|
||||||
|
3. **Backline render confirmation (C — close the R16 pending line).** C's verify of D's wired GLB
|
||||||
|
at `backline[0]` was "pending D's R16 commit"; D landed. One render pass per archetype
|
||||||
|
(pub/band_room/rsl), confirm no interpenetration at the measured clearances, one line in your
|
||||||
|
notes. The handshake closes.
|
||||||
|
4. **`publish.py --verify` false drift (E).** `sit.glb` (like `vintage-cash-register.glb`) is
|
||||||
|
non-`procity_`-prefixed, so the drift filter reports it "not live" when it IS live. Teach the
|
||||||
|
verifier the published-name list instead of the prefix heuristic — it bites every future clip.
|
||||||
|
5. **Wind sway (B — parked since R7; ship it or kill it).** The one missing weather element now
|
||||||
|
that weather is default-on. Vertex-shader sway on trees/awnings, weather-driven (calm → rain
|
||||||
|
gusts). It touches shared materials — the reason it's been parked; coordinate the material
|
||||||
|
seam with C/E only if actually needed (trees/awnings are B-built street geometry — verify
|
||||||
|
first, it may be entirely yours). `?classic=1` must stay byte-identical (weather is off there);
|
||||||
|
`?weather=0` sways nothing. If a real cross-lane material conflict surfaces, write it up and
|
||||||
|
KILL the item permanently instead — either way it leaves the backlog this round.
|
||||||
|
6. **The real-map scout (A + E — bounded, data-first, NO new game systems).**
|
||||||
|
- **E**: port thriftgod's `build_city()` Overpass route into `pipeline/build_towns.py` →
|
||||||
|
produce **3–5 real AU town caches** (`city_source.json`-shaped; Katoomba-real should be one,
|
||||||
|
for the fixture comparison) with the dedupe/suburb/parody-name/seeded-hours pipeline.
|
||||||
|
Committed as data under `web/assets/towns/` (or A's preferred home). **Provenance: OSM is
|
||||||
|
ODbL — attribution ships with the data** (a `SOURCES.md` + an in-repo notice; you own the
|
||||||
|
licence table). Overpass fetch is outward-facing network — John OK'd the scout (Standing),
|
||||||
|
cache aggressively, hit the API once per town.
|
||||||
|
- **A**: define/pin the cache contract (E builds to it), feed the real caches through
|
||||||
|
`plan_osm` — real towns are messier than fixtures (dead ends, no spine, odd densities);
|
||||||
|
harden until all 3–5 boot with valid plans + district placement. Pin a golden per real town;
|
||||||
|
add them to the sweep. **Scope fence: no new plan features** — the scout proves the existing
|
||||||
|
contract eats real data, nothing more.
|
||||||
|
7. **Town-matrix smoke (F).** The gate side of the scout: run the core smokes (boot, determinism,
|
||||||
|
budgets, district placement, `?noassets`) across the full town matrix — synthetic hero seeds +
|
||||||
|
3 fixtures + E's real caches. One table in your notes: town × gate. This is the evidence John
|
||||||
|
charters v4 on.
|
||||||
|
|
||||||
|
Parked still (v3.3+/v4 backlog): instrument `_lo` LOD · tram_stop close-ped tri diet ·
|
||||||
|
`audioEmitter` · flag retirement (classic keeps soaking) · the editor · multiplayer presence.
|
||||||
|
|
||||||
|
## Lane A — the cache contract + real towns through the plan (ledger #6)
|
||||||
|
|
||||||
|
Publish the contract FIRST (half-day rule — E builds the pipeline against it; you own what
|
||||||
|
`plan_osm` accepts). Then harden against E's real caches as they land, pin per-town goldens,
|
||||||
|
extend the sweep. Report the ugliest real-geometry failure you had to absorb — that list is the
|
||||||
|
v4 charter's risk section.
|
||||||
|
|
||||||
|
## Lane B — wind sway ships or dies (ledger #5)
|
||||||
|
|
||||||
|
Weather-driven vertex sway, classic/weather-off byte-identical, budget stated (it's a shader —
|
||||||
|
expect ~0 draws). If shared-material reality kills it, the kill write-up is a full deliverable —
|
||||||
|
name the exact conflict so it never haunts the backlog again.
|
||||||
|
|
||||||
|
## Lane C — close the backline handshake (ledger #3)
|
||||||
|
|
||||||
|
One render pass, three archetypes, one note line. Then on-call (A may want interior sanity checks
|
||||||
|
on real-town shop types — your registry fallbacks should already cover unknown types; confirm if
|
||||||
|
asked).
|
||||||
|
|
||||||
|
## Lane D — the drummer's dignity + the bench (ledger #1, #2)
|
||||||
|
|
||||||
|
Pelvic-lean first (small, contained), bench-sit second. Both ride existing seams (`sit.glb`,
|
||||||
|
seated re-plant, loiter hook, `procitySeated`). Byte-identical proofs as R16 — the parallel-path
|
||||||
|
discipline is now your signature; keep it.
|
||||||
|
|
||||||
|
## Lane E — the town pipeline + the verify fix (ledger #4, #6)
|
||||||
|
|
||||||
|
The scout pipeline is the round's biggest single item — data discipline over cleverness: cache the
|
||||||
|
Overpass responses raw (re-runs must not re-fetch), ODbL attribution in-repo, parody names through
|
||||||
|
the existing generator, provenance table amended (v3.2 marker). The verify fix is a warm-up.
|
||||||
|
|
||||||
|
## Lane F — the matrix + the close (ledger #7)
|
||||||
|
|
||||||
|
1. Town-matrix smoke across synthetic + fixtures + real caches (after A/E land).
|
||||||
|
2. Round close: `qa.sh --strict --soak` green on the default boot, bookmark spot-check (B's sway
|
||||||
|
is in old frames now), docs (README flags table if `?weather` semantics grew a sway note,
|
||||||
|
LANE_F_NOTES §17), tag **`v3.2`**, push main + tag (standing authorization).
|
||||||
|
|
||||||
|
## Order
|
||||||
|
|
||||||
|
**A publishes the cache contract first**; E's pipeline + B/C/D tail items run in parallel (all
|
||||||
|
disjoint); A hardens as E's caches land; **F last** (matrix needs A+E; close needs everyone).
|
||||||
|
Handshakes: A→E the contract; E→A each cache as it lands; D→C n/a this round; B→F "sway shipped"
|
||||||
|
or "sway killed"; A+E→F "matrix inputs ready".
|
||||||
|
|
||||||
|
## Standing
|
||||||
|
|
||||||
|
- **John — both items RESOLVED at kickoff (2026-07-16):**
|
||||||
|
1. **v4 headline — DECIDED: THE REAL MAP.** John chose it at kickoff. R17's scout is therefore
|
||||||
|
not speculative — it's the v4.0 groundwork; the full charter lands with the R18 instructions.
|
||||||
|
2. **Overpass fetch — AUTHORIZED.** John OK'd the real-map scout, which inherently includes E's
|
||||||
|
handful of public Overpass API requests (once per town, raw responses cached in-repo so
|
||||||
|
re-runs never re-fetch). E: proceed; keep the ODbL attribution discipline.
|
||||||
|
- **v3.3+/v4 backlog:** instrument LOD · tram tri diet · audioEmitter · flag retirement · the two
|
||||||
|
unpicked v4 candidates.
|
||||||
84
docs/LANES/ROUND18_INSTRUCTIONS.md
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
# PROCITY — Round 18 lane instructions (from Fable, integrator)
|
||||||
|
|
||||||
|
Date: 2026-07-16 · **v3.2 SHIPPED** (tag on origin, town matrix 10×5 green). R17 review, short
|
||||||
|
version: the tail is DRAINED — wind sway shipped after ten rounds parked (and the park reason
|
||||||
|
turned out to be a phantom: the tree material was B's own all along — measurement over folklore),
|
||||||
|
D's drummer leans into the kit and street peds sit on verandah steps (with a genuine finding en
|
||||||
|
route: the seated re-plant was scale-naive and had to become scale-aware for per-citizen-scaled
|
||||||
|
rigs), C closed the backline handshake AND proactively proved 18 real OSM shop types build valid
|
||||||
|
interiors, E's 5-town pipeline is deterministic and ODbL-clean, A hardened `plan_osm` against
|
||||||
|
2,828 real Canberra shops and handed the charter its risk list, F closed the last integration gap
|
||||||
|
(the shell never loaded the caches — the classic A-publishes/F-wires seam) and gated the whole
|
||||||
|
matrix. Every lane at full quality. The scout did its job: **John chartered v4 = THE REAL MAP.**
|
||||||
|
|
||||||
|
**Read the charter first: [docs/V4_REAL_MAP.md](../V4_REAL_MAP.md).** Round 18 is
|
||||||
|
**v4.0-alpha: REAL ROADS** — one town (`katoomba_real`) gets its actual street geometry. The whole
|
||||||
|
alpha exists to surface what irregular real geometry breaks downstream. Tag **`v4.0-alpha`** at
|
||||||
|
close.
|
||||||
|
|
||||||
|
## The ledger
|
||||||
|
|
||||||
|
1. **Cache schema v2 — `roads[]` (A publishes FIRST, E builds to it).** The town cache grows an
|
||||||
|
optional `roads[]` (OSM way geometry, simplified): A defines the shape (suggested: polylines of
|
||||||
|
`{lat,lon}` points + a `kind` from the OSM `highway=*` class), bumps `schema` to
|
||||||
|
`procity-town-cache/2`, extends `validateTownCache` (v1 caches stay valid — roads absent ⇒ the
|
||||||
|
marched fallback, so nothing shipped regresses).
|
||||||
|
2. **Real road graph in the plan (A — the epoch's headline lift).** `plan_osm` consumes `roads[]`:
|
||||||
|
project to the local metre frame, simplify (Douglas-Peucker or your pick — you own the fidelity
|
||||||
|
knob, charter risk #4), build the CityPlan street graph from real topology (nodes at real
|
||||||
|
intersections, straight-segment edges), classify main/side from the OSM `highway` kind + shop
|
||||||
|
density, seat each shop's lot on its nearest real edge at its real position/side. SAME CityPlan
|
||||||
|
schema out — B/C/D/F consume it unchanged by contract. Golden per the amendment law
|
||||||
|
(`katoomba_real`'s re-pins; synthetic + fixtures untouched). District placement (`pickVenues`)
|
||||||
|
must still land 2–4 venues on the real graph.
|
||||||
|
3. **Roads fetch (E).** Extend `build_towns.py`: per-town bounded `highway=*` Overpass query
|
||||||
|
(same authorization + raw-cache discipline as R17 — commit the raw response, fetch once),
|
||||||
|
emit `roads[]` per A's v2 contract. **`katoomba_real` first** (the alpha town), then the other
|
||||||
|
four as data-only commits (A hardens against them as they land; only katoomba gates the round).
|
||||||
|
4. **Irregular-geometry audit (B — charter risk #3, the alpha's real risk).** Boot
|
||||||
|
`katoomba_real` with real roads and hunt what breaks: chunk streaming over a non-grid graph,
|
||||||
|
building/lot seating at acute intersections, footpath/verge furniture cadence on short or
|
||||||
|
curved-ish edge chains, tram (its loop assumes a spine — if the real graph breaks it, fence it:
|
||||||
|
tram disabled on roads-towns is an acceptable alpha fence, written down). Fix what's yours,
|
||||||
|
file what's A's (lot geometry) or D's (sim graph) with measurements.
|
||||||
|
5. **Citizens on real bones (D).** The sim walks the street graph + chunk-streamed rosters on the
|
||||||
|
real topology: verify walkers navigate real intersections, loiter/bench-sit/patronage behave,
|
||||||
|
identity + determinism hold on `katoomba_real`. Cheap if the graph contract really is the
|
||||||
|
contract — prove it, and file what leaks with measurements.
|
||||||
|
6. **Interior spot-soak on the alpha town (C — small).** Your R17 type-fallback proof covered
|
||||||
|
types; now enter every `katoomba_real` shop in-shell (20 doors), assert valid interiors, no
|
||||||
|
carve fails, budgets. One table. Otherwise on-call.
|
||||||
|
7. **The alpha gate (F, last).** Extend the town matrix with a roads dimension (`katoomba_real`
|
||||||
|
boots real-roads: boot/determinism/budget/district/noassets-fallback green); the classic +
|
||||||
|
default-boot gates prove the alpha changed nothing outside the flagged path; a first
|
||||||
|
real-street money shot (Katoomba main street, default flags, settled) into
|
||||||
|
`docs/shots/v4_alpha/`; docs (README gets a "real towns" paragraph, LANE_F_NOTES §18); tag
|
||||||
|
**`v4.0-alpha`**, push (standing authorization).
|
||||||
|
|
||||||
|
## Laws for the round
|
||||||
|
|
||||||
|
Everything in the charter's law section. Alpha-specific: real roads ride the **cache schema**, not
|
||||||
|
a URL flag (a v2 cache with roads = real roads; v1 = marched fallback — no new flag surface);
|
||||||
|
default + classic boots are untouched by construction (they never load a town cache); budgets
|
||||||
|
measured on `katoomba_real`'s busiest real block; every irregular-geometry failure gets WRITTEN
|
||||||
|
DOWN (fixed or fenced) — the alpha's deliverable is as much the failure list as the town.
|
||||||
|
|
||||||
|
## Order
|
||||||
|
|
||||||
|
**A first** (schema v2 contract — half-day rule), **E immediately after** (katoomba roads cache;
|
||||||
|
the other towns trail). Then **A again** (the graph lift, against real data), with **B + D + C**
|
||||||
|
auditing in parallel as soon as A's roads-plan boots (A→B/D/C handshake: "katoomba_real boots with
|
||||||
|
real roads, golden pinned"). **F last.** This round has a real serial spine (A→E→A→others→F) —
|
||||||
|
expect A to be the long pole; B/D/C, if you're blocked on A, your audit prep (test scaffolding,
|
||||||
|
assertion lists) is not.
|
||||||
|
|
||||||
|
## Standing
|
||||||
|
|
||||||
|
- **John — nothing blocks this round.** FYI: E's roads fetch rides your R17 Overpass
|
||||||
|
authorization (same API, same once-per-town + raw-cache discipline). The alpha hero town is
|
||||||
|
`katoomba_real` — if you'd rather see a different town first (Newtown/Fremantle/Bendigo/
|
||||||
|
Castlemaine), say so before E starts.
|
||||||
|
- **v4.0-beta preview (next round, do not start):** all five towns on real roads · the town
|
||||||
|
selector UI (B) · districts/gigs proven per-town · tram ruling on real graphs.
|
||||||
|
- **Backlog (unchanged):** instrument LOD · tram tri diet · audioEmitter · flag retirement ·
|
||||||
|
awning-canopy ripple (B's R17 note) · editor · multiplayer presence.
|
||||||
95
docs/LANES/ROUND19_INSTRUCTIONS.md
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
# PROCITY — Round 19 lane instructions (from Fable, integrator)
|
||||||
|
|
||||||
|
Date: 2026-07-16 · **R18 verdict: the alpha did exactly what the charter asked — it surfaced the
|
||||||
|
failure list. NOT yet tagged, and correctly so.** A shipped schema v2 + the graph lift (proven on
|
||||||
|
synthetic irregular geometry) but the A→E→A finalization never fired — E's katoomba roads landed
|
||||||
|
after A's commits, so the real-roads golden is unpinned and the lift is unhardened against real
|
||||||
|
data. B/C/D audited against E's landed data anyway (right call) and the audits are excellent:
|
||||||
|
**B** — the streetscape is road-agnostic, real Katoomba boots 0-error and reads well, but the
|
||||||
|
`BIG_CITY` heuristic misfires (20 shops < 120 ⇒ small-town streaming on a 799-edge/5 km graph ⇒
|
||||||
|
**280k tris, breaching the 200k gate**; proven one-line fix). **D** — the sim needs zero changes
|
||||||
|
(1M+ samples, 0 NaN, byte-identical rerun), but the real graph is **105 disconnected components**
|
||||||
|
(42% of street-metres on islands, 2 shops stranded, ~10% of near-crowd wandering fragments), with
|
||||||
|
the DP-collinear junction-drop independently confirmed by B and D as a contributing mechanism.
|
||||||
|
**C** — 20/20 real doors build valid interiors. **E** — 484 real streets in schema v2 + the other
|
||||||
|
four towns as data, plus a poster-clearance finding filed for A. F never ran — its gate would have
|
||||||
|
failed on the budget breach + unpinned golden, so nothing was mis-skipped. This is the alpha
|
||||||
|
working as designed: surface, measure, file.
|
||||||
|
|
||||||
|
Round 19 is **the ALPHA CLOSE** — A resolves the filed findings and finalizes, F applies the
|
||||||
|
budget fix and gates, D re-measures, and the tag lands. Tag **`v4.0-alpha`** at close.
|
||||||
|
|
||||||
|
## The ledger
|
||||||
|
|
||||||
|
1. **A — the finalization (the round's spine; everything hangs off you).**
|
||||||
|
a. **The fragmentation ruling (D's finding — Fable rules, you own the details):** the town is
|
||||||
|
the **main component plus joined near-fragments; far shopless islands are CULLED.**
|
||||||
|
Concretely: (i) protect junction/shared-snap points from Douglas–Peucker (the collinear
|
||||||
|
junction-drop B and D both hit — simplify *between* junctions, never through them);
|
||||||
|
(ii) join fragments within a snap/mid-edge-split tolerance (D measured 9 islands <10 m,
|
||||||
|
20 <30 m — pick the tolerance and defend it); (iii) cull remaining islands **unless they
|
||||||
|
hold a shop** — a stranded shop is always either connected (nearest-edge connector) or
|
||||||
|
explicitly dropped with a count in `norm.dropped`. Rationale: bbox-clipped periphery is
|
||||||
|
noise, and 42% dead street-metres waste streaming budget (it's half of B's tris problem).
|
||||||
|
b. **E's poster-clearance finding** (LANE_E_NOTES §18) — resolve it in the same hardening pass.
|
||||||
|
c. **Harden against real katoomba** (the deferred A→E→A leg), verify the R18 failure-list
|
||||||
|
items you predicted (overflow drop — did Katoomba keep all 20 shops? crossing-without-
|
||||||
|
shared-node — did any survive into the graph?), **pin `katoomba_real`'s real-roads golden**,
|
||||||
|
and fire the handshake.
|
||||||
|
d. **Sweep + pin the other four towns** (E's data is already in) — beta pull-forward that
|
||||||
|
costs you one loop now and saves a round later. Any town whose real data breaks the lift:
|
||||||
|
file it, fence it, don't force it — katoomba alone gates the tag.
|
||||||
|
2. **F — B's `BIG_CITY` fix + the alpha gate (last).**
|
||||||
|
a. The one-liner is yours (`index.html`): extend the heuristic to the graph —
|
||||||
|
`plan.shops.length > 120 || (plan.streets?.edges?.length || 0) > 200` (B proved the effect:
|
||||||
|
280k → 104k tris at the venue block, 72 draws). Land it early so D/B re-measures see it.
|
||||||
|
b. The gate, after A: town-matrix roads dimension (katoomba_real real-roads:
|
||||||
|
boot/determinism/budget/district/noassets-fallback), classic + default-boot gates untouched,
|
||||||
|
budgets on the busiest real block post-fix.
|
||||||
|
c. **The money shot:** an uncommitted `docs/shots/v4_alpha/katoomba_mainstreet.png` is sitting
|
||||||
|
in the tree (almost certainly B's audit evidence — verify provenance in B's notes). Reshoot
|
||||||
|
deterministically via the tour harness for the official shot, commit both or supersede.
|
||||||
|
d. Docs (README "real towns" paragraph, LANE_F_NOTES §18/§19 honest record incl. why F
|
||||||
|
correctly didn't run in R18), tag **`v4.0-alpha`**, push (standing authorization).
|
||||||
|
3. **D — re-measure after A's connectivity pass.** The same audit table, post-fix: component
|
||||||
|
count, % street-metres in the main net, stranded shops (must be 0 or explicitly dropped),
|
||||||
|
% near-crowd on fragments (should collapse toward 0). One table, one verdict line. Your R18
|
||||||
|
crafted-irregular-town scaffold makes this cheap — that's why you built it.
|
||||||
|
4. **B — the town selector (beta pull-forward, NON-blocking for the tag).** A small in-game
|
||||||
|
town picker (HUD/debug-menu grade, not a title screen): synthetic + fixtures + real towns
|
||||||
|
(`osmTownKeys()` + registered caches), selecting navigates to the right URL params. Your
|
||||||
|
HUD/dbg territory; document any shell seam for F rather than editing `index.html` (F owns it).
|
||||||
|
If A's pass or F's gate needs your re-measure (tris post-cull, strips at tight corners —
|
||||||
|
A's failure-item #3 asked you to audit corner strips with a measurement), that comes first.
|
||||||
|
5. **C — on-call.** Your alpha work is done (20/20). If A's connectivity pass moves lots, a
|
||||||
|
one-loop re-run of the katoomba door soak confirms nothing regressed.
|
||||||
|
6. **E — on-call.** Re-emit caches only if A's junction handling needs different data (e.g.
|
||||||
|
pre-DP full geometry is already shipped — likely nothing). Your data discipline this epoch
|
||||||
|
has been faultless; nothing else asked.
|
||||||
|
|
||||||
|
## Laws
|
||||||
|
|
||||||
|
Charter laws (V4_REAL_MAP.md) unchanged. This round's golden rules: `katoomba_real`'s golden is
|
||||||
|
UNPINNED until A pins it (so A's connectivity work is free to iterate — pin once, at the end);
|
||||||
|
the four other towns' goldens pin with A's sweep; synthetic/fixtures/classic/default remain
|
||||||
|
frozen — the alpha changed nothing outside the cache-schema path, and the gate proves it.
|
||||||
|
|
||||||
|
## Order
|
||||||
|
|
||||||
|
**F's one-liner first** (independent, unblocks honest re-measures) and **A's finalization in
|
||||||
|
parallel** (the long pole). Then **D re-measures**, **C spot-confirms**, **B's selector** rides
|
||||||
|
alongside. **F gates last** once A pins and D confirms. Handshakes: A→all "katoomba_real real
|
||||||
|
roads, golden pinned, findings resolved"; D→F "fragmentation verdict post-fix"; B→F "selector
|
||||||
|
seam" (if any).
|
||||||
|
|
||||||
|
## Standing
|
||||||
|
|
||||||
|
- **John — nothing blocks this round.** FYI: Fable ruled on the fragmentation (main component +
|
||||||
|
joins, cull shopless islands) — it's a town-quality call; overrule if you'd rather keep the
|
||||||
|
full clipped periphery. The tag lands as `v4.0-alpha` at close.
|
||||||
|
- **v4.0-beta preview (next round, do not start):** all five towns proven end-to-end on real
|
||||||
|
roads (A's pins land this round — beta becomes verification + polish) · the town selector
|
||||||
|
wired + polished · tram ruling on real graphs (route-by-shop-adjacency vs fence) · D's
|
||||||
|
spawn-bias mitigation if any fragmentation survives A's pass.
|
||||||
|
- **Backlog:** unchanged (instrument LOD · tram tri diet · audioEmitter · flag retirement ·
|
||||||
|
awning ripple · editor · multiplayer).
|
||||||
102
docs/LANES/ROUND20_INSTRUCTIONS.md
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
# PROCITY — Round 20 lane instructions (from Fable, integrator)
|
||||||
|
|
||||||
|
Date: 2026-07-16 · **v4.0-alpha SHIPPED** (tag on origin, `ddd1e68`). The alpha close was textbook:
|
||||||
|
A's junction-protected Douglas–Peucker + bridge/cull took Katoomba from **105 components → 1**
|
||||||
|
(100% street-metres connected, 0 silently-stranded shops — every drop counted), D re-measured with
|
||||||
|
the R18 harness and confirmed the ruling landed clean (near-crowd on fragments: ~10% → 0%), F
|
||||||
|
landed B's `BIG_CITY` one-liner early exactly as ordered (280k → 12k tris in play) and gated the
|
||||||
|
close, B shipped the town selector with **zero shell seam** (hud.js owns it end-to-end) plus the
|
||||||
|
corner-strip spot-check, C re-soaked 19/19 doors, E stayed cleanly on-call. The money shot is the
|
||||||
|
epoch thesis made real: Katoomba's actual main street — asphalt, zebra crossing, walking crowd,
|
||||||
|
gig posters, PROCITY's look on real bones. All five real-town goldens are pinned.
|
||||||
|
|
||||||
|
Round 20 is **v4.0-beta: THE FIVE TOWNS** — the two honest alpha findings become the beta's
|
||||||
|
headline (density), the tram gets its ruling, and every real town is proven end-to-end. Tag
|
||||||
|
**`v4.0-beta`** at close.
|
||||||
|
|
||||||
|
## The two filed findings that shape this round
|
||||||
|
|
||||||
|
1. **Poster density scales with edges, not shops** (A's R19 finding + F's one open budget edge):
|
||||||
|
real Katoomba emits **438 posters** (~14 on synthetic), which reads busy and drives the one
|
||||||
|
matrix breach — `venue_night` at **200,965 tris (+0.5% over the 200k soft ceiling)**. In-play
|
||||||
|
views are fine (121k); this is the stress view.
|
||||||
|
2. **Real towns are sparse** (F's density note): 19 secondhand shops spread across a whole real
|
||||||
|
town ⇒ a real *street* with life on it, not a retail strip. The vision says "walk the actual
|
||||||
|
main street" — main streets have bakeries, cafes, chemists, clothes shops. We fetch only the
|
||||||
|
thriftgod heritage classes.
|
||||||
|
|
||||||
|
## The ledger
|
||||||
|
|
||||||
|
1. **Poster cap (A).** Cap spine posters per real town — seeded subsample of the pole run,
|
||||||
|
scaled by shop count not edge count (you own the number; ~2–3× the synthetic feel is the
|
||||||
|
target, not 438). Real-town goldens re-pin (amendment law, one commit); synthetic + gig
|
||||||
|
frozen. This closes F's venue_night edge — tell F to re-measure.
|
||||||
|
2. **THE DENSITY WIDENING (the beta headline — C rules, E fetches, A absorbs).** Fable's ruling
|
||||||
|
(John veto in Standing): real towns fetch **general retail**, not just secondhand — the main
|
||||||
|
street should hold its real bakery, cafe, chemist, newsagent, clothes shops. Mechanics:
|
||||||
|
- **C publishes FIRST**: the OSM-class → registry-archetype mapping table (e.g. cafe/bakery →
|
||||||
|
`milkbar`, pharmacy/newsagent/clothes → `retail`/`opshop` flavors — C owns which existing
|
||||||
|
archetype each class wears; **no new archetypes this round**, the mapping is the deliverable;
|
||||||
|
your R18 proof says unknowns degrade safely anyway). Publish in LANE_C_PUB (v4-beta marker).
|
||||||
|
- **E re-fetches** with the widened `shop=*` class list (same bbox, raw-cached, one more
|
||||||
|
Overpass pass per town under the standing authorization), maps per C's table, re-emits the
|
||||||
|
five caches (v2, deterministic) + **`web/assets/towns/index.json`** (key, town, state, shop
|
||||||
|
count, roads flag) so B's selector stops hardcoding `REAL_TOWNS`.
|
||||||
|
- **A absorbs the density**: five towns re-run at 3–6× shop count — seating capacity, overflow
|
||||||
|
accounting (`norm.dropped` stays honest), venue placement still lands, goldens re-pin once
|
||||||
|
at the end. If a town's real density overwhelms an edge, widen capacity or split runs —
|
||||||
|
your R18 overflow fence, now load-tested.
|
||||||
|
3. **The tram ruling (B).** Alpha left the tram on an arbitrary longest-chain (21% of mains).
|
||||||
|
Beta ruling: **route by shop adjacency** — pick the main chain whose edges front the most
|
||||||
|
shops (the actual high street; on synthetic towns this must resolve to the current spine so
|
||||||
|
the goldens/feel don't move). If the shop-adjacent chain reads wrong on any real town, fence
|
||||||
|
the tram per-town instead (the `edges > 200` signal you left ready) and say which towns run it.
|
||||||
|
4. **Ground polish at real intersections (B).** The R19 spot-check accepted overlap-fill; now the
|
||||||
|
finer pass: acute-angle junctions and z-fighting at the 58 degree-4 intersections. Fix what's
|
||||||
|
cheap, file what's structural (A's lot geometry) with measurements.
|
||||||
|
5. **Selector polish (B + F).** Derive towns from E's `index.json` (drop the hardcode); F rules
|
||||||
|
the gating (always-on HUD vs `?dbg`) at close — F's call, one line either way.
|
||||||
|
6. **Interiors across the five (C).** After E's widened caches land: sampled door-soak per town
|
||||||
|
(the R18/R19 harness — every town, sampled doors at the new density, all types resolve, no
|
||||||
|
carve fails, budgets). One table, five rows.
|
||||||
|
7. **Citizens at density (D).** Re-run the R18 audit on the densest post-widening town: patronage
|
||||||
|
at 3–6× targets, chunk rosters, determinism, identity — and the gig crowd on all five towns'
|
||||||
|
venues (the district gate covers placement; you cover behaviour). Cheap by design — your
|
||||||
|
scaffolds exist. File anything that leaks with measurements.
|
||||||
|
8. **The beta gate (F, last).** Full five-town matrix on real roads (boot/determinism/budget/
|
||||||
|
district/noassets/roads per town), the venue_night re-measure post-poster-cap (the +965 edge
|
||||||
|
must close), classic + default gates frozen as ever, selector gating ruling, beta money shot
|
||||||
|
(the densest real main street at golden hour — the retail strip reading), docs (README beta
|
||||||
|
paragraph, notes §20), tag **`v4.0-beta`**, push (standing authorization).
|
||||||
|
|
||||||
|
## Laws
|
||||||
|
|
||||||
|
Charter (V4_REAL_MAP.md) unchanged. Golden discipline this round: real-town goldens re-pin
|
||||||
|
**twice at most** (A's poster cap; A's density absorb — batch them if the timing allows), each
|
||||||
|
under the amendment law; synthetic `0x3fa36874`, gig `0xb1d48ea1`, fixtures — frozen. E's caches
|
||||||
|
stay deterministic (same raw → same cache); the widened fetch is a NEW raw snapshot, committed
|
||||||
|
like the first. Budgets unchanged and now measured at real density — if the widened towns bust
|
||||||
|
them in play (not just stress views), that's a Fable scope call, say so loudly.
|
||||||
|
|
||||||
|
## Order
|
||||||
|
|
||||||
|
**C's mapping table first** (half-day rule — E builds to it), **A's poster cap in parallel**
|
||||||
|
(independent). Then **E** (fetch + re-emit + index), then **A absorbs**, then **B / C / D**
|
||||||
|
verify at density (B's tram + ground items are independent of density — start anytime). **F
|
||||||
|
last.** Handshakes: C→E the table; E→A "caches re-emitted"; A→all "densities absorbed, goldens
|
||||||
|
pinned"; B→F tram verdict + selector; C/D→F density verdicts.
|
||||||
|
|
||||||
|
## Standing
|
||||||
|
|
||||||
|
- **John — the density widening is APPROVED (kickoff, 2026-07-16), with one directive: SUBTLE.**
|
||||||
|
John: "subtle changes are great." Interpretation for C + E: general retail is **texture, not
|
||||||
|
takeover** — the secondhand shops stay the heroes of the town (they're the game). Practically:
|
||||||
|
keep the widened class list tasteful (the everyday main-street staples, not every OSM `shop=*`
|
||||||
|
value); if a real town's retail would drown the secondhand strip visually, E may seed-subsample
|
||||||
|
the texture classes (count it, like every drop). The player should notice the town feels more
|
||||||
|
alive, not that the game changed. FYI standing: tram routes by shop adjacency (B may fence
|
||||||
|
per-town); the selector ships from an index; poster density gets capped.
|
||||||
|
- **v4.0 preview (next round, do not start):** the curated town pack (~15–25 towns through E's
|
||||||
|
pipeline at scale) · the release tour on real streets · docs freeze · the `v4.0` tag.
|
||||||
|
- **Backlog:** instrument LOD · tram tri diet · audioEmitter · flag retirement · awning ripple ·
|
||||||
|
editor · multiplayer.
|
||||||
91
docs/LANES/ROUND21_INSTRUCTIONS.md
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
# PROCITY — Round 21 lane instructions (from Fable, integrator)
|
||||||
|
|
||||||
|
Date: 2026-07-16 · **R20 verdict: the density round delivered — but the beta is NOT tagged.**
|
||||||
|
What landed (all verified in notes): **A** capped posters 438→31 (+ a new `REAL_TOWN_GIG_GOLDENS`
|
||||||
|
guard) and absorbed the 3–6× densities (292 shops across five towns, drops down to 5.5% and now
|
||||||
|
counted BY CAUSE, pub facade bias extended, goldens re-pinned once); **C** published the
|
||||||
|
class→archetype mapping; **E** widened the fetch under John's SUBTLE directive — enforced by
|
||||||
|
construction (heroes never subsampled, texture capped at 3× heroes, deterministic seed-subsample,
|
||||||
|
every drop counted); **B** ruled the tram by shop adjacency (the alpha tram fronted 0 shops on 4
|
||||||
|
of 5 real towns — "it really was a highway bus"), fenced katoomba/fremantle/bendigo per the
|
||||||
|
fallback, and found+fixed the phantom-stop bug (Newtown: 149 stops → 16); **D** delivered the
|
||||||
|
density A/B — heroes kept footfall (17→19 visits) while the street got 3.8× busier — and published
|
||||||
|
a model self-correction (the sparsity null% was an artefact of measuring at 21:00 from the one
|
||||||
|
isolated venue; the record is corrected in place). Quality is at series-best.
|
||||||
|
|
||||||
|
**Open (why there's no tag):** F never ran (gate + tag), C's density door-soak (R20 ledger #6)
|
||||||
|
wasn't done, B never consumed E's `index.json` (`hud.js:11` still hardcodes `REAL_TOWNS`), and D
|
||||||
|
filed the **main-street relocation finding**: katoomba's pub sits 1,000 m from the retail cluster
|
||||||
|
(1 shop within 160 m vs the RSL's 13) — a gig with no passing crowd. Round 21 closes all of it.
|
||||||
|
Tag **`v4.0-beta`** at close.
|
||||||
|
|
||||||
|
## The ledger
|
||||||
|
|
||||||
|
1. **Venue cluster bias (A — D's relocation finding).** At real-town density, venue candidates
|
||||||
|
should prefer retail-cluster adjacency: bias `pickVenues` toward candidates with ≥N shops
|
||||||
|
within ~160 m (you own N and the weighting; keep the kind placement flavors — pub corner,
|
||||||
|
band_room fringe, rsl off-spine — as biases within the cluster-adjacent set, not absolutes).
|
||||||
|
**Synthetic + fixtures must be a no-op** (goldens frozen — the synthetic town is one dense
|
||||||
|
cluster anyway); real-town goldens re-pin ONCE (base+gig, amendment law). Acceptance: no venue
|
||||||
|
in any of the five towns sits with <3 shops in its 160 m radius unless the town genuinely has
|
||||||
|
no better candidate (castlemaine's floor may — say so if it does).
|
||||||
|
2. **Selector from the index (B — the missed R20 #5).** Drop the `hud.js:11` hardcode; fetch
|
||||||
|
E's `web/assets/towns/index.json` (fail-soft: index missing → current hardcode as fallback,
|
||||||
|
zero errors). Selector shows shop counts from the index if it's cheap. Nothing else — your
|
||||||
|
tram/ground work is done and verified.
|
||||||
|
3. **Density door-soak (C — the missed R20 #6).** The five towns at their new densities, sampled
|
||||||
|
doors per town (~25/town covers every archetype incl. the texture classes' first real
|
||||||
|
appearance — milkbar/dept/stall interiors on real shops), assert valid interiors / no carve /
|
||||||
|
budgets / types resolve per your §6 table. One table, five rows. **Run AFTER A lands** (venue
|
||||||
|
relocation changes which lots are venues).
|
||||||
|
4. **Relocation verify (D).** After A: re-run the katoomba venue-adjacency numbers (the pub
|
||||||
|
should now sit in/near the cluster; your venue-win % and the "gig with no passing crowd"
|
||||||
|
observation should improve). One before/after line per town. Your GIG_RANGE chunk-blindness
|
||||||
|
finding stays filed for v4.x (agreed — not a mid-beta hot-path change).
|
||||||
|
5. **Town-pack pull-forward (E — NON-GATING for the tag).** Begin v4.0's curated pack: propose
|
||||||
|
the candidate list (~15–25 AU towns with real secondhand density — you have the data instincts
|
||||||
|
from the first five), fetch + emit as **data-only commits** (same discipline: bounded bbox,
|
||||||
|
raw snapshots, C's mapping, SUBTLE guardrails, index.json grows). A absorbs + pins in R22 —
|
||||||
|
do NOT block the beta tag on any of it. If Overpass rate-limits at pack scale, batch politely
|
||||||
|
(sleep between towns) — the standing auth covers the fetches, courtesy covers the cadence.
|
||||||
|
6. **The beta gate (F, last).**
|
||||||
|
a. Five-town matrix at density (boot/determinism/budget/district/noassets/roads), against A's
|
||||||
|
R21 re-pinned goldens.
|
||||||
|
b. **`venue_night` re-measure** — A's poster cap must close your +965-tri edge; record the
|
||||||
|
new number.
|
||||||
|
c. Tram in the matrix: newtown + castlemaine RUN (stops sane), the three fenced towns show
|
||||||
|
no tram and no errors (B's fence signal).
|
||||||
|
d. **Selector gating ruling** (your R19 open call): always-in-HUD vs `?dbg`-gated — rule it,
|
||||||
|
one line, done.
|
||||||
|
e. Beta money shot: the densest real main street at golden hour (fremantle or katoomba
|
||||||
|
post-widening — continuous shopfronts, crowd, heroes among texture), into
|
||||||
|
`docs/shots/v4_beta/`.
|
||||||
|
f. Docs (README beta paragraph, LANE_F_NOTES §20/§21 incl. the honest why-R20-had-no-tag
|
||||||
|
record), tag **`v4.0-beta`**, push (standing authorization).
|
||||||
|
|
||||||
|
## Laws
|
||||||
|
|
||||||
|
Charter unchanged. One golden move this round (A's #1, batched base+gig, once). Synthetic
|
||||||
|
`0x3fa36874`, gig `0xb1d48ea1`, fixtures, classic — frozen. E's pack commits are data-only and
|
||||||
|
touch no golden until A absorbs them (R22).
|
||||||
|
|
||||||
|
## THE EXACT RUN ORDER (John asked)
|
||||||
|
|
||||||
|
1. **Start together: A + B + E.** (A: venue bias — the only golden-moving item, the round's
|
||||||
|
spine. B: selector-from-index — independent. E: town-pack fetches — independent, non-gating.)
|
||||||
|
2. **When A commits (goldens re-pinned): start C + D.** (C: door-soak on the relocated world.
|
||||||
|
D: relocation verify. Both read A's handshake note for the new goldens.)
|
||||||
|
3. **When C + D have committed (and B is in): start F.** (E does NOT gate F — if E's pack is
|
||||||
|
still fetching, F tags without it.)
|
||||||
|
|
||||||
|
Three waves: **[A, B, E] → [C, D] → [F].**
|
||||||
|
|
||||||
|
## Standing
|
||||||
|
|
||||||
|
- **John — nothing blocks this round.** FYI: Fable assigned D's relocation finding to A as a
|
||||||
|
cluster-adjacency bias (real towns only, synthetic untouched); E begins the v4.0 town pack
|
||||||
|
early as data-only commits. Both are course-corrections inside the chartered epoch.
|
||||||
|
- **v4.0 preview (R22, do not start beyond E's data):** A absorbs + pins the pack · the release
|
||||||
|
tour on real streets · docs freeze across lanes · the `v4.0` tag · then the epoch retro.
|
||||||
|
- **Backlog:** GIG_RANGE chunk-neighbour lookup (D, v4.x) · instrument LOD · tram tri diet ·
|
||||||
|
audioEmitter · flag retirement · awning ripple · editor · multiplayer.
|
||||||
96
docs/LANES/ROUND22_INSTRUCTIONS.md
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
# PROCITY — Round 22 lane instructions (from Fable, integrator)
|
||||||
|
|
||||||
|
Date: 2026-07-16 · **v4.0-beta SHIPPED** (`201a2bb`, tag on origin; matrix 10 towns × 7 gates
|
||||||
|
green; qa `--strict` GREEN; selfcheck now 161k). R21 review, short: A's cluster bias turned out
|
||||||
|
to fix a **systemic** stranding (11 of 15 venues under threshold → 0 of 15; Newtown's RSL moved
|
||||||
|
2,228 m → 29 m from the retail hub), D closed its own finding with the before/after table, B and
|
||||||
|
C cleared the missed R20 items, E pulled the entire v4.0 town pack forward (**22 towns · 1,192
|
||||||
|
shops · 298 heroes · all 8 states/territories**, byte-identical re-runs, non-gating as briefed),
|
||||||
|
and F closed the gate honestly — including recording that the one budget edge got WORSE, and
|
||||||
|
correcting its own first bad money shot instead of shipping it. **Lane G is merged** at the
|
||||||
|
boundary as ruled (`dd4489c`); the godverse town passes the sweep (161,264 green).
|
||||||
|
|
||||||
|
Round 22 is **v4.0: THE EPOCH CLOSE** — the tri diet first, the pack absorbed, the release tour
|
||||||
|
on real streets, the Lane G items, docs freeze, the tag. Tag **`v4.0`** at close.
|
||||||
|
|
||||||
|
## The ledger
|
||||||
|
|
||||||
|
1. **The `venue_night` tri diet (FIRST — the one open gate edge).** F's re-measure: katoomba
|
||||||
|
200,965 → **221,935 tris (+11%)**, bendigo ~201k. A's poster cap worked (438→111); **E's
|
||||||
|
density widening ate the savings** — more shops = more night geometry at the venue block.
|
||||||
|
Two steps, measure-first:
|
||||||
|
a. **B diagnoses** (streetscape budget is B's): a layer-by-layer `renderer.info` breakdown at
|
||||||
|
katoomba `venue_night` (buildings / posters / furniture / pools / peds / interiors-spill) —
|
||||||
|
name the driver with numbers, half a day.
|
||||||
|
b. **The named owner fixes** (A if it's plan geometry (lot/poster counts), B if render
|
||||||
|
(merge/instance/LOD), E if asset tris), F re-measures. **Must land < 200k** — this is a
|
||||||
|
release round; the stress view IS the gate.
|
||||||
|
2. **A absorbs the pack (the round's spine).** E's 17 new towns + Lane G's `newtown_godverse`:
|
||||||
|
feed all through the lift, pin the **36 waiting goldens** (selfcheck prints them), and absorb
|
||||||
|
what the meccas surface — **fitzroy is 160 shops, 2× anything absorbed so far**; expect
|
||||||
|
overflow/capacity work (your R20 fence, now at scale; drops stay counted by cause). The
|
||||||
|
cluster bias + poster cap + junction pipeline all run at 23-town scale for the first time —
|
||||||
|
anything that breaks gets the usual treatment: fix or fence, written down. Batch the pins:
|
||||||
|
ONE golden commit at the end.
|
||||||
|
3. **The Lane G items (queued from FABLE_TO_LANE_G):**
|
||||||
|
- **B**: per-town bookmarks — resolve bookmark poses against the ACTIVE plan (the
|
||||||
|
synthetic-coord teleports land inside OSM geometry; you did this for `poseForVenue` — the
|
||||||
|
old set gets the same treatment, plus F's R21 cluster-pose finding below is the right
|
||||||
|
primitive to share).
|
||||||
|
- **F**: `DBG.enterShop(selector)` grows name/type/id matching (cross-repo QA one-liners).
|
||||||
|
- **E**: refresh the stale MODELBEAST notes (trellis_mac proven, the :8777 queue, ultra's
|
||||||
|
⚠️ finish-farm warning folded into pipeline docs verbatim, credited).
|
||||||
|
- **Lane G (on ultra, their branch): G2a is GREEN as of the beta tag** — static per-shop
|
||||||
|
real-stock atlases behind `?stock=real`, per the ruling. **C + E: the contract review is
|
||||||
|
due this round** (atlas per shop · interior budget ceilings are C's · fail-soft to parody).
|
||||||
|
Answer in G-progress or LANE_C_PUB; G builds to it next.
|
||||||
|
4. **The release tour (F).** `tour_shots.py` grows the v4 set → `docs/shots/release_v4/`:
|
||||||
|
the Fremantle golden-hour hero (have it), a mecca main street (fitzroy, once A absorbs), a
|
||||||
|
thin-tail town (proof the floor holds), the synthetic town (unchanged, the control), and one
|
||||||
|
`?classic=1` frame (the covenant, visible). **Use your cluster-pose finding as the tour's
|
||||||
|
posing primitive** (project the densest spatial cluster onto its front edge, stand on the
|
||||||
|
road) — and publish it as a `dbg.js`-consumable so B's bookmark fix (#3) shares it.
|
||||||
|
5. **Interiors + citizens at pack scale (C + D, after A).**
|
||||||
|
- **C**: sampled door-soak across all 23 towns (your five-town harness, scaled — sample
|
||||||
|
~10 doors/town, every archetype covered town-set-wide). One table.
|
||||||
|
- **D**: the fitzroy stress audit (160 shops — the densest citizen field yet: patronage,
|
||||||
|
rosters, determinism) + spot-check one thin-tail town (does 12 shops still read alive?).
|
||||||
|
6. **Docs freeze + the tag (F last, with A).**
|
||||||
|
- A: CITY_SPEC v4 amendments final (schema v2, the lift, connectivity rulings, cluster bias,
|
||||||
|
poster cap), charter risk-list retired-or-carried line by line.
|
||||||
|
- E: provenance table v4-final (22 towns ODbL + the pack's SOURCES.md; the licence spine
|
||||||
|
stays complete).
|
||||||
|
- F: README v4 section (the towns ARE the product — say it with the selector + tour), notes
|
||||||
|
§22, `qa.sh --strict --matrix` green, tag **`v4.0`**, push. **After the tag: Fable writes
|
||||||
|
the epoch retro** (not a lane deliverable).
|
||||||
|
|
||||||
|
## Laws
|
||||||
|
|
||||||
|
Charter laws hold. Golden discipline: A pins the 36 in ONE commit; the tri-diet fix may move
|
||||||
|
real-town goldens if it's plan-side (amendment law, same commit as the fix); synthetic
|
||||||
|
`0x3fa36874`, gig `0xb1d48ea1`, fixtures, classic — frozen, as always. The 200k stress edge is a
|
||||||
|
BLOCKING gate this round (release rounds don't ship known breaches — the R19 precedent).
|
||||||
|
|
||||||
|
## THE EXACT RUN ORDER
|
||||||
|
|
||||||
|
1. **Start together: B (tri diagnosis, #1a) + A (pack absorb, #2) + E (#3 notes + #6 provenance
|
||||||
|
prep).** B's diagnosis is half a day — it lands while A grinds.
|
||||||
|
2. **When B names the tri driver: the owner fixes (#1b)** — if it's A, fold into the absorb; if
|
||||||
|
B/E, parallel commit. **F re-measures immediately.**
|
||||||
|
3. **When A pins the goldens: start C (#5 soak) + D (#5 audits) + F (#3 enterShop + #4 tour
|
||||||
|
build-out).** C+E answer G2a's contract review (#3) in this window too.
|
||||||
|
4. **When C, D, and the tri re-measure are green: F closes (#6)** — docs, matrix, tag `v4.0`,
|
||||||
|
push. Fable retro follows.
|
||||||
|
|
||||||
|
Waves: **[B, A, E] → [tri-fix owner] → [C, D, F-prep] → [F close]**.
|
||||||
|
|
||||||
|
## Standing
|
||||||
|
|
||||||
|
- **John — nothing blocks this round.** FYI: Lane G is merged and their G2a (real stock atlases,
|
||||||
|
static half) is green to start on their branch per the standing ruling; the C/E contract
|
||||||
|
review happens this round. The tri-diet is blocking for the tag — if the fix turns out to need
|
||||||
|
scope you'll hear it loudly, not silently.
|
||||||
|
- **After v4.0 (the retro decides, do not start):** G2b/G3 design (the economy — both
|
||||||
|
integrators + you) · the v4.x backlog (GIG_RANGE chunk lookup · instrument LOD · tram tri diet
|
||||||
|
· flag retirement · awning ripple · fabric canopies) · and the next epoch conversation
|
||||||
|
(editor vs multiplayer vs the GODVERSE deepening).
|
||||||
89
docs/LANES/ROUND23_INSTRUCTIONS.md
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
# PROCITY — Round 23 lane instructions (from Fable, integrator)
|
||||||
|
|
||||||
|
Date: 2026-07-16 · **v4.0 SHIPPED, the epoch retro is written ([RETRO_V4.md](../RETRO_V4.md)),
|
||||||
|
and John has chartered v5: [THE REAL SHOP](../V5_REAL_SHOP.md).** Read the charter first — the
|
||||||
|
tier ladder (parody → static real → live GODVERSE) and the five laws are the epoch's spine.
|
||||||
|
|
||||||
|
Round 23 is **v5.0-alpha: THE FIRST REAL CRATE** — one real shop's real stock, riffled and
|
||||||
|
bought, end-to-end, plus the G3 design doc drafted for ratification. The v4.x tail runs as the
|
||||||
|
light parallel lane the retro promised. Tag **`v5.0-alpha`** at close.
|
||||||
|
|
||||||
|
## The ledger
|
||||||
|
|
||||||
|
1. **G2a: the first real atlas (Lane G, on ultra — the round's headline).** Build the per-shop
|
||||||
|
atlas pipeline per the R22-reviewed contract (C's ceilings · atlas-per-shop keyed by godverse
|
||||||
|
shop id · fail-soft to parody): dealgod inventory → a static atlas + index for ONE real
|
||||||
|
Newtown record shop in `newtown_godverse`. Files stay in the G namespace
|
||||||
|
(`pipeline/godverse_*`, `web/assets/stock_godverse/**`). Deterministic (byte-identical
|
||||||
|
re-run against the same inventory snapshot — snapshot the query result like you snapshot
|
||||||
|
Overpass raw). Provenance per the licence law (charter law #3). **This deliverable is
|
||||||
|
gating, so per the merge amendment (charter law #5) your branch merges mid-round once
|
||||||
|
Fable-m3 reviews.** Ping via G-progress when it's up.
|
||||||
|
2. **G3: the design doc draft (Lane G + both Fables).** `docs/G3_ECONOMY_DESIGN.md` covering:
|
||||||
|
API surface (read stock / price / reserve / buy) · async-enrichment caching (risk #1 — the
|
||||||
|
dig never blocks on network) · the determinism boundary (charter law #2) · failure modes
|
||||||
|
(server dies mid-session → tier 1 without a stutter) · census↔lot identity matching (risk
|
||||||
|
#3, mismatches counted) · **the write-back question stated as a decision for John** (charter
|
||||||
|
law #4 — options with trade-offs, no default). Draft this round; ratification is R24's
|
||||||
|
kickoff. Nothing server-side ships before it.
|
||||||
|
3. **The interior side of the real crate (C).** When G's atlas merges: verify the contract in
|
||||||
|
the room — the godverse record shop's crates resolve the per-shop atlas through the existing
|
||||||
|
`stockAdapter` path, interior draw/tex ceilings hold with real covers, `?noassets` and
|
||||||
|
missing-atlas boots fail soft to parody with zero errors. If the contract needs an
|
||||||
|
amendment, you own the wording (LANE_C_PUB), G re-emits.
|
||||||
|
4. **The atlas validator (E).** The format side: a `validate_atlas` check in the pipeline
|
||||||
|
(dimensions ≤ C's ceilings, provenance fields present, index↔atlas consistency, determinism
|
||||||
|
note) so G's output is gate-checkable on m3 without the dealgod DB. Plus the retro's
|
||||||
|
curation item: **the thin-tail clustering floor** — re-bbox toowoomba onto its actual retail
|
||||||
|
strip (or retire it, your call, counted either way) using D's median nearest-neighbour
|
||||||
|
spacing as the curation metric.
|
||||||
|
5. **The spacing warn (A).** D's metric becomes law: `validateTownCache` warns when median
|
||||||
|
nearest-neighbour spacing exceeds a threshold you pick from the pack's measured spread
|
||||||
|
(darwin 27 m alive, toowoomba 388 m dead — plenty of daylight for a line). Warn, not error —
|
||||||
|
E curates, the validator flags. Re-pin only what E's toowoomba fix moves.
|
||||||
|
6. **GIG_RANGE chunk-neighbour fix (D — the carried v4.x item).** The 8.7% blind spot: extend
|
||||||
|
`_nearestOpenShop` to the ped's chunk + 8 neighbours. Hot path, so bring the R18-style
|
||||||
|
before/after (perf cost, blind-spot elimination, determinism unchanged, byte-identical
|
||||||
|
flags-off). Post-release is exactly when this change is safe.
|
||||||
|
7. **The alpha gate (F, last).**
|
||||||
|
a. THE CRATE, end-to-end in a fresh browser: boot `newtown_godverse` with `?stock=real`,
|
||||||
|
walk into the real record shop, riffle — **real covers render** — buy, wallet debits,
|
||||||
|
the item leaves the crate (tier-1 static semantics: gone this session, back next boot —
|
||||||
|
that's correct until G3).
|
||||||
|
b. The tier ladder proven: same shop under `?noassets` (tier 0, zero fetch), missing-atlas
|
||||||
|
(soft fall to parody), `?stock=real` (tier 1) — all silent-and-fine; classic + default
|
||||||
|
gates frozen as ever.
|
||||||
|
c. `qa.sh --strict --matrix` green (the matrix now includes E's toowoomba fix), notes §23,
|
||||||
|
README one-paragraph v5 teaser, tag **`v5.0-alpha`**, push.
|
||||||
|
|
||||||
|
## Laws
|
||||||
|
|
||||||
|
The charter's five (offline · determinism boundary · licence · write-back · two-studio) plus
|
||||||
|
everything inherited. Golden discipline: only E's toowoomba re-bbox may move a town golden
|
||||||
|
(amendment law, one commit). No server code anywhere this round — the G3 doc is paper.
|
||||||
|
|
||||||
|
## THE EXACT RUN ORDER
|
||||||
|
|
||||||
|
1. **Start together: G (atlas + doc draft, on ultra) + E (#4 validator + toowoomba) + A (#5
|
||||||
|
spacing warn) + D (#6 GIG_RANGE).** All disjoint.
|
||||||
|
2. **When G's atlas is up and Fable merges it: start C (#3).** E's validator should be ready to
|
||||||
|
run against the merged atlas — coordinate in G-progress.
|
||||||
|
3. **When C passes and E/A/D are in: F closes (#7)** — the crate, the ladder, the tag.
|
||||||
|
4. **G3 doc review runs asynchronously all round** — both Fables comment in the doc; it lands
|
||||||
|
ratified-or-revised at R24 kickoff with John's write-back ruling.
|
||||||
|
|
||||||
|
Waves: **[G, E, A, D] → [merge, C] → [F]**, with the doc threading through.
|
||||||
|
|
||||||
|
## Standing
|
||||||
|
|
||||||
|
- **John — one decision incoming, not blocking this round:** the write-back question (does an
|
||||||
|
in-game purchase ever touch real thriftgod inventory — sandbox, reservation, or full
|
||||||
|
write-back?). It arrives framed with trade-offs in the G3 doc at R24 kickoff. FYI: G's branch
|
||||||
|
merges mid-round this epoch when its deliverable is gating (charter law #5); item-photo
|
||||||
|
licensing stays in-house-green, flagged before anything public.
|
||||||
|
- **B — deliberately light this round** (you've carried heavy since R16): on-call for C if the
|
||||||
|
crate render surfaces anything street-side, plus first pick of the v4.x polish list next
|
||||||
|
round (awning ripple and the tram-stop tri diet are yours when you want them).
|
||||||
|
- **v5.0-beta preview (do not start):** every hero shop in 1–2 godverse towns stocked · mint()
|
||||||
|
pricing baked into atlases · fitzroy_godverse if the census is dense there · the dig UX for
|
||||||
|
real items.
|
||||||
108
docs/LANES/ROUND24_INSTRUCTIONS.md
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
# PROCITY — Round 24 lane instructions (from Fable, integrator)
|
||||||
|
|
||||||
|
Date: 2026-07-17 · **R23 verdict: THE HOLD WAS RIGHT.** F held the `v5.0-alpha` tag on a round
|
||||||
|
where every suite was green — `qa.sh --strict --matrix` 7/7, MATRIX 10×7, selfcheck 152,015 —
|
||||||
|
because the round's headline could not be reached: G's 120 real records (Monster Robot Party's
|
||||||
|
actual crate 550, off the real POS) have **no street** (the shop isn't in any town cache), **no
|
||||||
|
identity** (`godverse_id: null` on every godverse shop — the schema has no such field), and **no
|
||||||
|
compatible contract** (C's published atlas naming and C's own runtime loader are mutually
|
||||||
|
incompatible — an atlas named per the contract can never be loaded by the code meant to load
|
||||||
|
it). G built runtime-compatible; E built contract-compatible. **Nobody was wrong; the contract
|
||||||
|
was.** And two gates would have passed without touching their subject — E's validator returns 0
|
||||||
|
on an empty glob, and #7a-as-written goes green on the *generic* v2 pack's real covers with zero
|
||||||
|
fetches to `stock_godverse/` ever. "Release rounds don't ship known breaches" — F applied the
|
||||||
|
law to its own tag. This is the discipline working at the exact moment it matters most.
|
||||||
|
|
||||||
|
Round 24 is **THE ALPHA COMPLETION** — the three gaps closed by their owners, the two process
|
||||||
|
fixes made law, and the tag earned. Tag **`v5.0-alpha`** at close (F, and only when #7a runs
|
||||||
|
honestly).
|
||||||
|
|
||||||
|
## Two process fixes, now law (from R23's findings)
|
||||||
|
|
||||||
|
1. **THE VACUOUS-GATE LAW (all lanes, immediately):** a gate must FAIL — or explicitly SKIP with
|
||||||
|
a printed reason — when its subject is absent. A gate that can pass without touching its
|
||||||
|
subject is not a gate. "No atlases yet ⇒ clean pass" and "real covers render" were the same
|
||||||
|
species: presence-of-a-thing checked instead of the-right-thing. Every new gate states what
|
||||||
|
its subject is and proves it touched it (a fetch log line, a matched-file count, an id
|
||||||
|
equality — something falsifiable).
|
||||||
|
2. **Charter law #5 amendment — F executes gating merges.** The mid-round merge was Fable's per
|
||||||
|
the charter and Fable was never invoked — the round's headline sat unmerged while five lanes
|
||||||
|
built around it. F proved the fix in the act: verify namespace disjointness empirically, then
|
||||||
|
execute. Going forward: **when a G deliverable is gating, Lane F executes the merge as part
|
||||||
|
of its integration duty** (disjointness verified, not assumed); Fable reviews at the round
|
||||||
|
boundary as before. One less cross-Mac synchronization point.
|
||||||
|
|
||||||
|
## The ledger
|
||||||
|
|
||||||
|
1. **The contract fix (C — the root cause is yours to close).** Your published atlas contract
|
||||||
|
and your own `stockpack.js:25` loader disagree. Ruling: **the runtime is the authority** —
|
||||||
|
rewrite the contract to what the loader family actually implements, extended per-shop:
|
||||||
|
per-shop atlases live under a per-shop `base` (`stock_godverse/<godverseShopId>/`), index
|
||||||
|
named as the loader expects, fields named as the loader reads them (the `licence`/`license`
|
||||||
|
and nested-vs-top-level provenance drift dies here — pick what the code reads, document it,
|
||||||
|
one truth). Plus F's filed item: **key the pack cache by `type+base`** so a per-shop pack
|
||||||
|
and the town-wide pack coexist. Publish the corrected contract FIRST (half-day rule — G and
|
||||||
|
E both re-emit against it).
|
||||||
|
2. **The identity field (A, with G).** The town-cache schema grows an optional
|
||||||
|
**`godverseShopId`** on shop entries (schema stays v2 — optional field, absent everywhere
|
||||||
|
except godverse caches; `validateTownCache` requires it on `source: "godverse+osm"` shops).
|
||||||
|
G's adapter emits it from the census (the POS shop id IS the id — no mapping table). A: the
|
||||||
|
validator arm + selfcheck assert; G: re-emit `newtown_godverse` with ids.
|
||||||
|
3. **The street (E).** Build **`redhill_real`** — one bounded Overpass fetch under the standing
|
||||||
|
authorization — so Monster Robot Party's real shop has its real street. Same discipline as
|
||||||
|
the pack (raw snapshot, ODbL, spans, C's mapping). Hand the donor to G.
|
||||||
|
4. **The validator, made honest (E).** Fix `validate_atlas.py` per the vacuous-gate law: an
|
||||||
|
empty match set prints `SKIP: no atlases present (0 files matched glob <g>)`; a present-but-
|
||||||
|
unparseable or present-but-unmatched atlas FAILS; field names align with C's corrected
|
||||||
|
contract (your own docstring and your own check currently disagree — F caught it). Then run
|
||||||
|
it against G's re-emitted atlas and gate on the result. Also: the **ballarat** curation
|
||||||
|
(A's spacing warn caught it at 255 m — re-bbox onto its strip or retire, counted, your call).
|
||||||
|
5. **The re-emit + the second town (G, on ultra).** Against C's corrected contract and A's
|
||||||
|
identity field: re-emit the Monster Robot atlas keyed by `godverseShopId`, run the adapter
|
||||||
|
on E's `redhill_real` donor → **`redhill_godverse`** (the town whose record shop is THE
|
||||||
|
shop). Update the G3 doc §4 if the corrected contract changes any API field naming. Your
|
||||||
|
deliverables are gating → F merges mid-round per the amended law.
|
||||||
|
6. **The wire + the honest gate + the tag (F, last).**
|
||||||
|
a. Pass the per-shop `base` through the stock path (the seam you deliberately didn't build
|
||||||
|
against a three-quarters spec — the spec is now whole).
|
||||||
|
b. **#7a, honestly:** boot `redhill_godverse` + `?stock=real`, enter Monster Robot Party via
|
||||||
|
your selector, and assert on falsifiables: network log shows fetches under
|
||||||
|
`stock_godverse/<id>/`, rendered titles ∈ the atlas index (id equality, not "covers
|
||||||
|
render"), buy debits, tier-1 gone-this-session semantics. Then the ladder re-proof
|
||||||
|
(tier 0 zero-fetch / missing-atlas soft-fall / tier 1) and the frozen classic + default
|
||||||
|
gates.
|
||||||
|
c. `qa.sh --strict --matrix` green (with E's honest validator now IN the gate set), notes
|
||||||
|
§24, the held README v5 teaser ships WITH the tag (they're one claim, as you said), tag
|
||||||
|
**`v5.0-alpha`**, push.
|
||||||
|
7. **On-call:** B (standing light-round offer holds — R25 gets you first pick of the polish
|
||||||
|
list), D (done — the GIG_RANGE fix landed scoped and clean).
|
||||||
|
|
||||||
|
## Laws
|
||||||
|
|
||||||
|
Charter v5 laws + the two new ones above. Golden discipline: E's ballarat fix and the
|
||||||
|
`redhill_real`/`redhill_godverse` pins are the round's town-golden moves (A pins, batched);
|
||||||
|
synthetic/classic/gig/fixtures frozen. No server code — G3 stays paper until ratified.
|
||||||
|
|
||||||
|
## THE EXACT RUN ORDER
|
||||||
|
|
||||||
|
1. **Start together: C (#1 contract fix — publish first) + A (#2 identity arm) + E (#3 redhill
|
||||||
|
+ #4 validator + ballarat).**
|
||||||
|
2. **When C's contract and A's field are published: G (#5) re-emits + builds redhill_godverse.**
|
||||||
|
3. **When G pushes: F merges (amended law #5), then closes (#6)** — wire, honest gate, tag.
|
||||||
|
|
||||||
|
Waves: **[C, A, E] → [G] → [F merge + close]**. B/D on-call.
|
||||||
|
|
||||||
|
## Standing — JOHN: THE WRITE-BACK RULING (§7 of the G3 doc — due this round)
|
||||||
|
|
||||||
|
The G3 draft frames it exactly as charter law #4 required: **does an in-game purchase ever touch
|
||||||
|
real monsterrobot/Square inventory?** Three options — A: sandbox (read-live, sell-local; zero
|
||||||
|
real-world risk; the one-of-a-kind thrill is fiction) · B: reservation (a real expiring hold via
|
||||||
|
the POS's existing `hold_expires_at`; genuinely one-of-a-kind AND reversible; needs a hold
|
||||||
|
budget, a staff-visible reason code, and the-shop-always-wins race policy) · C: full write-back
|
||||||
|
(game sale = real sale; a business decision, not an engineering one).
|
||||||
|
|
||||||
|
**Fable's recommendation (yours to overrule): ship v5.0 on Option A, charter Option B as a
|
||||||
|
v5.x upgrade** behind a hold budget you set — B is the strongest truth-per-unit-risk in the doc,
|
||||||
|
but it deserves plumbing that has soaked, not plumbing that tags this round. Option C should
|
||||||
|
only ever happen as a deliberate business launch, if ever. Say A / B / C (or A-then-B, the
|
||||||
|
recommendation) and the G3 doc ratifies with your ruling inline.
|
||||||
98
docs/LANES/ROUND25_INSTRUCTIONS.md
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
# PROCITY — Round 25 lane instructions (from Fable, integrator)
|
||||||
|
|
||||||
|
Date: 2026-07-17 · **R24 verdict: THE CRATE WALKS — and the tag correctly holds a second round.**
|
||||||
|
F proved the epoch's claim falsifiably: Monster Robot Party resolves its own per-shop base,
|
||||||
|
**120/120 rendered titles from its atlas and 0/120 from the generic pack, the GPU samples
|
||||||
|
`stock_godverse/3962749/*.webp`**, a real record debits $15 exactly, the ladder re-proved on
|
||||||
|
every rung, and the wire is a verified no-op off the godverse path. G gave the shop its street
|
||||||
|
(Red Hill, 884 real roads, the shop's coordinate John-supplied because **no dataset we hold
|
||||||
|
derives it** — honest sourcing), proved **Ruling 2 was load-bearing not cosmetic** (census-only
|
||||||
|
Red Hill = 306 m spacing warn + a poster inside a kerb; with the texture layer it passes — the
|
||||||
|
headline town was one design decision from shipping flagged), and recorded John's **A-then-B
|
||||||
|
write-back ruling as mechanism**: `/reserve` + `/buy` stay ABSENT, not stubbed — a sandbox a
|
||||||
|
config typo can flip isn't a sandbox. C owned the R23 root cause squarely. E built the street
|
||||||
|
and the honest validator. A published the identity field first, as briefed.
|
||||||
|
|
||||||
|
**Why the tag holds:** the tree carries 8 selfcheck reds F cannot fix — 2 are re-emitted
|
||||||
|
`newtown_godverse` goldens awaiting A's pin (A ran before G; a handoff gap, not a defect), and
|
||||||
|
**6 are A's sweep asserting what Fable's own Ruling 2 makes impossible by design** (every-shop-
|
||||||
|
keyed on deliberately-mixed caches). Plus R24's finding chain, in escalating cost: the brief's
|
||||||
|
own honest-gate spec was vacuous (positional `rec_0000…` ids collide across packs — id equality
|
||||||
|
can't discriminate; **the vacuous-gate law's first application broke the law**, and the fix —
|
||||||
|
titles + texture URLs — is F's, not the brief's); A's uniqueness check collapses `undefined`s
|
||||||
|
into one Set entry (fails structurally on mixed caches AND can't catch a real duplicate — F
|
||||||
|
measured the guarded hazard at zero); E's validator prints the licence flag from British
|
||||||
|
`licence` while the contract mandates US `license`, so **"FLAG BEFORE ANY PUBLIC/COMMERCIAL
|
||||||
|
RELEASE" silently never prints on real product photos**; and F's own classic gate cried wolf
|
||||||
|
(fixed by F, two assertions two verdicts). Round 25 closes all of it. Tag **`v5.0-alpha`** at
|
||||||
|
close — third time, earned.
|
||||||
|
|
||||||
|
## Process rulings (from R24's events)
|
||||||
|
|
||||||
|
1. **Lane G is a full lane on main.** G committed straight to main and it was right: the
|
||||||
|
namespace is disjoint, the commits were pathspec-atomic, and the branch ceremony has served
|
||||||
|
its purpose. Same treaty as A–F from here: own files only, atomic pathspec commits;
|
||||||
|
anything cross-namespace still needs a ruling first. (The R23 merge amendment is thereby
|
||||||
|
retired along with the branch.)
|
||||||
|
2. **Ruling 2 is confirmed as law** — G measured it: mixed caches (keyed GODVERSE layer +
|
||||||
|
inherited texture layer) are the *design*, not an accident A's gates may assert away.
|
||||||
|
3. **The brief-was-wrong entry for the record:** my #7a spec (id equality) was itself vacuous.
|
||||||
|
F's replacement (title sets + texture-URL provenance) is the canonical honest-gate pattern
|
||||||
|
for stock — measurement over brief applies to the integrator's gate specs too.
|
||||||
|
|
||||||
|
## The ledger
|
||||||
|
|
||||||
|
1. **A — reconcile the sweep with Ruling 2, pin the six (the round's spine).**
|
||||||
|
- The sweep's godverse arm goes per-layer: keyed asserts apply ONLY to shops carrying
|
||||||
|
`godverseShopId` (the GODVERSE layer); texture-layer shops are legitimately unkeyed.
|
||||||
|
- Replace the uniqueness check with one that works: uniqueness over **defined** ids only
|
||||||
|
(F measured zero real duplicates — the check should be able to catch one if it ever
|
||||||
|
appears, which the Set-collapse version cannot).
|
||||||
|
- Pin the six goldens: `newtown_godverse` base+gig (moved by G's keyed re-emit — values are
|
||||||
|
in G's commit), `redhill_real` base+gig, `redhill_godverse` base+gig.
|
||||||
|
- Acceptance: **selfcheck ALL GREEN, zero warns pending** except any E leaves deliberately.
|
||||||
|
2. **E — the licence line, made unmissable (conflict #1).** Align the validator with C's §7.3
|
||||||
|
(top-level provenance, US `license` — one truth; your own docstring/check disagreement dies
|
||||||
|
in the same commit). Per the vacuous-gate law: when provenance exists, the licence evidence
|
||||||
|
line is a REQUIRED printed output and its absence is a FAIL — the flag exists for a human to
|
||||||
|
read, on real product photos; a gate that can say OK while hiding it is the R23 species.
|
||||||
|
3. **C — one contract line; G — the sku re-emit (F's finding #1, made structural).** Positional
|
||||||
|
slot ids collide by construction. C: amend §7 — atlas-pack slot ids MUST be unique across
|
||||||
|
packs (sku-derived: `sku_<POS id>` or similar — your wording). G: re-emit the two atlases
|
||||||
|
with sku slot ids (index-only change — no town cache touched, no golden moves; the .webp
|
||||||
|
bytes shouldn't move either, same as your R24 re-bake). E's validator asserts the form.
|
||||||
|
This makes id-equality gates discriminating forever, before there are 50 atlases to migrate.
|
||||||
|
4. **F — land your R24 work, then the tag.** Your wire, the DBG interior handoff, the crate
|
||||||
|
smoke, the classic-gate fix, and the notes are staged/uncommitted as of the R24 close —
|
||||||
|
commit them (F-pathspec) if the harness verdict hasn't already. Then, after A/E/G land:
|
||||||
|
full `qa.sh --strict --matrix` with the honest validator AND your crate smoke in the gate
|
||||||
|
set, the README v5 teaser ships WITH the tag (one claim, as you ruled), tag **`v5.0-alpha`**,
|
||||||
|
push. Your hold streak is the system working — end it only on green.
|
||||||
|
5. **B / D — on-call.** B: the standing first-pick-of-polish offer moves to the beta round,
|
||||||
|
where the dig UX for real items will want street-side support anyway.
|
||||||
|
|
||||||
|
## Laws
|
||||||
|
|
||||||
|
Charter v5 + the vacuous-gate law (now with its own first counterexample on record) + the
|
||||||
|
process rulings above. Golden moves this round: A's six pins only. No server code; the ratified
|
||||||
|
G3 stays paper until the beta charters tier-2 read-live.
|
||||||
|
|
||||||
|
## THE EXACT RUN ORDER
|
||||||
|
|
||||||
|
1. **Start together: A (#1) + E (#2) + C (#3's one line).**
|
||||||
|
2. **When C's line lands: G (#3) re-emits.** (G needs nothing else — A's pins don't gate the
|
||||||
|
re-emit since atlas indexes aren't in goldens.)
|
||||||
|
3. **When A + E + G are in: F (#4)** — commit your R24 work if not already, full gate, tag,
|
||||||
|
push.
|
||||||
|
|
||||||
|
Waves: **[A, E, C] → [G] → [F]**. B/D on-call.
|
||||||
|
|
||||||
|
## Standing
|
||||||
|
|
||||||
|
- **John — nothing needs you.** Your A-then-B ruling is recorded as binding mechanism in the
|
||||||
|
G3 doc (§7); the alpha tags this round on green.
|
||||||
|
- **v5.0-beta preview (next round, do not start):** G2a at scale — every hero shop in
|
||||||
|
newtown_godverse + redhill_godverse stocked (the census inventory question: most census shops
|
||||||
|
aren't on the POS — the beta decides what a real-but-un-inventoried shop's crate holds) ·
|
||||||
|
mint()-seeded tier-0 variety per the G3 doc's note · the dig UX for real items (price cards
|
||||||
|
are real data) · B's polish pick.
|
||||||
147
docs/LANES/ROUND26_INSTRUCTIONS.md
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
# PROCITY — Round 26 lane instructions (from Fable, integrator)
|
||||||
|
|
||||||
|
Date: 2026-07-17 · **R25 verdict: one commit and one harness verdict from done.** A cleared all
|
||||||
|
8 reds with the round's best self-accounting ("healthy towns my gate called broken — it fired
|
||||||
|
54/72 on a good town"; keyed asserts now per-layer, the six goldens pinned, selfcheck
|
||||||
|
**156,211/156,211**). C's contract line and G's sku re-emit landed (index-only, as designed —
|
||||||
|
id-equality gates are discriminating forever). F's permanent crate smoke earned its keep on day
|
||||||
|
one by catching **two of F's own bugs** — the sharper one being the round's theme biting its
|
||||||
|
author: **plan ids and godverse ids share a numeric space**, so `enterShop(31)` silently entered
|
||||||
|
Wholefood Cafe (plan id 31) instead of Silky Oak Furnature (godverse id 31), and the soft-fall
|
||||||
|
assertion passed vacuously on the wrong shop. F fixed the selector to disambiguate and made the
|
||||||
|
smoke prove its target is keyed. *Recorded as a standing note: the two id spaces are disjoint
|
||||||
|
namespaces — no code or gate may compare a bare number across them.*
|
||||||
|
|
||||||
|
**Open (why there is still no tag):** E's ledger #2 — the licence-line fix, and it's good work
|
||||||
|
(runtime-authority glob, loud SKIP, licence evidence as a required line) — sits **uncommitted**
|
||||||
|
in the tree; and F is mid-harness on its re-run. Round 26 opens by closing both, then the beta.
|
||||||
|
|
||||||
|
## WAVE 0 — close the alpha (before anything else starts)
|
||||||
|
|
||||||
|
*Status update (Fable): **WAVE 0 IS COMPLETE.** F's half DONE — verdict GREEN, committed
|
||||||
|
(`2994200`), tag held a third time, correctly (a tag whose tree lacks the code that produced
|
||||||
|
its green would be a lie). E's half DONE — the owner committed its own rebuild (`9b1df5c`) with
|
||||||
|
the true root cause: the `or` fallback WAS the bug (permissive check, strict print — one path
|
||||||
|
couldn't feel the other break). F adversarially verified the work pre-commit (broke it both
|
||||||
|
ways, watched it bite); Fable re-ran the validator and pushed. **F: step 2 is GO — confirm and
|
||||||
|
tag `v5.0-alpha` on resume, then the beta waves below.***
|
||||||
|
|
||||||
|
1. **E: commit your own work.** `pipeline/validate_atlas.py` carries your R25 rebuild, unstaged.
|
||||||
|
If your session died before the commit: a fresh Lane E session verifies the diff against
|
||||||
|
ledger #2 (it should recognize its own writing), runs the validator against G's two sku
|
||||||
|
re-emitted atlases (the licence line must PRINT — that's the point), and commits E-pathspec.
|
||||||
|
Nobody else touches this file — it's yours.
|
||||||
|
2. **F: tag on E's commit.** Your verdict is in and green; the moment E's commit lands, run the
|
||||||
|
short confirm (`qa.sh --strict`), tag **`v5.0-alpha`**, push, README teaser with it.
|
||||||
|
|
||||||
|
## Then: v5.0-beta — EVERY CRATE DIFFERENT (the honest version)
|
||||||
|
|
||||||
|
The R25 preview said "every hero shop stocked." The honest constraint: **only Monster Robot
|
||||||
|
Party has a POS** — the census's other shops are real businesses with unknowable inventory.
|
||||||
|
The beta makes every keyed crate *distinct and plausible* without pretending to data we don't
|
||||||
|
hold, on three rungs:
|
||||||
|
|
||||||
|
- **Real depth where the data is real:** Monster Robot's other crates (the G3 §4 crate-rotation
|
||||||
|
menu, baked statically).
|
||||||
|
- **Minted plausibility where it isn't:** the G3 doc's own note, promoted — seeded `mint()`
|
||||||
|
(`random.Random(shop_id)`, ordered sample; deterministic) bakes per-shop atlases for keyed
|
||||||
|
shops without POS inventory, **provenance-marked `tier: "mint"`** so no gate, doc, or human
|
||||||
|
ever mistakes plausible for real.
|
||||||
|
- **Parody below that:** the texture layer and everything unkeyed stays exactly as it is.
|
||||||
|
|
||||||
|
## The ledger
|
||||||
|
|
||||||
|
1. **The mint contract line (C, first — half-day rule).** §7 grows the `tier` field:
|
||||||
|
`"real"` (POS snapshot) vs `"mint"` (seeded sample) — required in provenance, and the dig's
|
||||||
|
price card may show it subtly or not at all (your call, it's a vibe question), but gates
|
||||||
|
must see it.
|
||||||
|
2. **The mint baker + Monster Robot depth (G).** Seeded mint atlases for the keyed non-POS
|
||||||
|
hero shops in both godverse towns (deterministic: same shop_id → byte-identical bake);
|
||||||
|
Monster Robot's full crate menu baked as the real tier. Licence zone per atlas as ever —
|
||||||
|
mint items sample real dealgod listings, so the photo provenance is the same in-house-green
|
||||||
|
zone, flagged the same way.
|
||||||
|
3. **The validator grows the tier arm (E).** `tier` required on every atlas; `mint` atlases
|
||||||
|
must NOT carry `pos_snapshot` fields and vice versa (the separation is the honesty); the
|
||||||
|
licence line prints for both. Vacuous-gate law applies: an atlas with no tier FAILS.
|
||||||
|
4. **The dig UX for real items (C).** The price card gets the real fields the atlas index
|
||||||
|
already carries (artist / title / price / condition) — the card should read like a record
|
||||||
|
shop price sticker, not a database row. Budget ceilings unchanged.
|
||||||
|
5. **B returns — dig-adjacent street polish (your pick, at last).** First pick of the polish
|
||||||
|
list as promised, weighted toward the beta: shopfront signage/window treatment for stocked
|
||||||
|
godverse shops (a crate-diggers' shop should read as one from the footpath), or the awning
|
||||||
|
ripple, or the tram-stop tri diet — your choice, one item, measured as ever.
|
||||||
|
6. **The atlas manifest (F's R24 finding #4, made structural — C line, G emits, F consumes,
|
||||||
|
E validates).** Fail-soft-by-404 collides with the zero-console-errors law: the runtime
|
||||||
|
probes per-shop atlases blind and eats 404 noise (8 in F's gate run — tolerated by
|
||||||
|
attribution, which is a workaround, not a design). Fix at the root: G emits
|
||||||
|
`web/assets/stock_godverse/index.json` — the manifest of which `godverseShopId`s carry
|
||||||
|
atlases (types + tier per entry; mint atlases included). C: the §7 contract line. F: the
|
||||||
|
wire consults the manifest before fetching — no entry, no probe, zero 404s, the
|
||||||
|
zero-console-errors law holds without exceptions. E: manifest↔files consistency in the
|
||||||
|
validator (an atlas on disk absent from the manifest FAILS, and vice versa — vacuous-gate
|
||||||
|
law shape).
|
||||||
|
7. **The beta gate (F).** *(Status, Fable: waves 1–2 are COMPLETE — C/B/E/G all landed. NOTE:
|
||||||
|
C RULED the brief's `tier` field wrong and is right — `tier` is the charter's ladder rung and
|
||||||
|
a mint atlas is also rung 1; the field is **`sourcing: "real"|"mint"`**, and E built to C's
|
||||||
|
contract not my brief ("the contract's author is the authority for the contract's shape").
|
||||||
|
Your gate asserts `sourcing`, not `tier`. G measured 15/15 keyed crates distinct, 0 shared
|
||||||
|
titles, manifest derived from disk; B's street mark reads from the manifest, not the key.)*
|
||||||
|
Per-shop distinctness (independently verify G's 15/15 — assert on title sets, the R24
|
||||||
|
pattern), `sourcing` provenance visible in gate output (a mint crate never reports as real),
|
||||||
|
zero console errors WITHOUT 404-attribution exceptions (the manifest makes this honest —
|
||||||
|
including the 13 keyed unfetchable-type shops that now stay silent), the ladder re-proof,
|
||||||
|
`qa.sh --strict --matrix`, tag **`v5.0-beta`**, push.
|
||||||
|
7. **A / D — on-call.** A: nothing pending (your R25 close was clean). D: nothing — unless F's
|
||||||
|
distinctness gate wants a crowd check at a stocked shop (patronage at Monster Robot on a
|
||||||
|
Saturday feels like a D flourish, not a requirement).
|
||||||
|
|
||||||
|
## WAVE 4 — the mint-id ruling (added post-hold; the beta's last inch)
|
||||||
|
|
||||||
|
*F held the tag a FOURTH time (`528829e`): E's `SLOT_ID_PREFIX = "sku_"` is sourcing-blind, so
|
||||||
|
all 14 mint crates fail a rule written before mint existed — the same shape as R24's sweep
|
||||||
|
demanding a cafe carry a godverseShopId. The rule's INTENT holds (F measured: mint ids are
|
||||||
|
`mint_<dealgod listing id>`, non-positional, seeded, 0 reused across all 15 crates); only the
|
||||||
|
literal prefix fails. And G's refusal to go green the easy way is RATIFIED — a POS sku and a
|
||||||
|
dealgod listing id are different id spaces, and filing one under `sku_` would recreate the R24
|
||||||
|
collision inside the economy layer, where tier-2's sold-means-gone looks up POS skus. The
|
||||||
|
prefixes ARE the namespace fence.*
|
||||||
|
|
||||||
|
1. **C — one contract line (§7):** slot-id form is sourcing-scoped: real → `sku_<POS id>`,
|
||||||
|
mint → `mint_<dealgod listing id>`; both non-positional, both unique across packs; the
|
||||||
|
prefix encodes the id-space so the two can never collide — bless that as the design, not an
|
||||||
|
accident.
|
||||||
|
2. **E — the one-line landing:** the slot-id check reads `sourcing` (you already read it at
|
||||||
|
:178) and asserts the matching prefix per C's line. Real crate: `sku_`. Mint crate: `mint_`.
|
||||||
|
Anything else, or a cross-wearing crate: FAIL.
|
||||||
|
3. **F — re-run atlas-QA, then the tag:** `qa.sh --strict --matrix` (should flip 6/1/0 →
|
||||||
|
7/0/0), tag **`v5.0-beta`**, push. Your four holds are the epoch's spine — this one cost one
|
||||||
|
line and would have cost a mis-sold record.
|
||||||
|
|
||||||
|
## Laws
|
||||||
|
|
||||||
|
Charter v5 + vacuous-gate + the id-namespace note above (now with its economy-layer corollary:
|
||||||
|
**id prefixes are namespace fences** — `sku_` is the POS space, `mint_` is the dealgod-listing
|
||||||
|
space, and no gate or lookup may treat them as one). Golden discipline: no town-cache changes
|
||||||
|
this round → no golden moves (mint atlases are `stock_godverse/**` files, outside every
|
||||||
|
golden). No server code — tier 2 stays design until the beta ships and the v5.0 round charters
|
||||||
|
it against the ratified G3.
|
||||||
|
|
||||||
|
## THE EXACT RUN ORDER
|
||||||
|
|
||||||
|
0. **WAVE 0: E commits; F lands the verdict and (on green) tags `v5.0-alpha`.** Hard stop until
|
||||||
|
the tag verdict is known.
|
||||||
|
1. **Start together: C (#1 contract line + #4 dig card) + B (#5 pick).**
|
||||||
|
2. **When C's line lands: G (#2 mint baker + depth) and E (#3 tier arm) in parallel.**
|
||||||
|
3. **When G + E are in: F (#6)** — distinctness gate, ladder, tag **`v5.0-beta`**, push.
|
||||||
|
|
||||||
|
Waves: **[E-commit, F-verdict] → [C, B] → [G, E] → [F]**. A/D on-call.
|
||||||
|
|
||||||
|
## Standing
|
||||||
|
|
||||||
|
- **John — nothing needs you.** The A-then-B ruling is binding and recorded; the beta stays
|
||||||
|
static-tier (no server); mint is the G3 doc's own blessed note, promoted with provenance
|
||||||
|
honesty.
|
||||||
|
- **v5.0 preview (do not start):** tier-2 read-live per the ratified G3 (Option A semantics —
|
||||||
|
read-live, sell-sandboxed, `/reserve`+`/buy` stay absent) · the kill-the-server-mid-session
|
||||||
|
gate · the v5 tour (the crate walk as a money shot) · docs freeze · the `v5.0` tag · the
|
||||||
|
epoch retro.
|
||||||
217
docs/LANES/ROUND27_INSTRUCTIONS.md
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
# PROCITY — Round 27 lane instructions (from Fable, integrator)
|
||||||
|
|
||||||
|
Date: 2026-07-17 · **R26 wave-4 verdict: C and E landed the mint-id fence, and both wrote the
|
||||||
|
same honest headline — "the rule was the stale thing, not E's check."** C owned its stale §7.2a
|
||||||
|
squarely (second time a C contract line met a case written after it and another lane took the
|
||||||
|
red); E proved the fix is *stronger* than the rule it replaces (the easy green was dropping the
|
||||||
|
prefix check; instead the prefix is now a sourcing-scoped namespace fence). The epoch's four
|
||||||
|
holds now all trace to the same species — a rule meeting a case written before the case
|
||||||
|
existed — and all four were caught before they could lie. The system is working exactly as
|
||||||
|
designed.
|
||||||
|
|
||||||
|
**Open: F's wave-4 step 3.** The beta tag is one re-run away. Round 27 opens there, then closes
|
||||||
|
the epoch: **v5.0 — THE LIVE CRATE.**
|
||||||
|
|
||||||
|
## WAVE 0 — F tags the beta
|
||||||
|
|
||||||
|
`qa.sh --strict --matrix` (the 6/1/0 should flip to 7/0/0 — E's fence is in, C's line is law),
|
||||||
|
tag **`v5.0-beta`**, push. Nothing below starts until this lands.
|
||||||
|
|
||||||
|
## Then: v5.0 — THE LIVE CRATE (tier 2, per the ratified G3)
|
||||||
|
|
||||||
|
The charter's last rung, under John's binding A-then-B ruling: **read-live, sell-sandboxed** —
|
||||||
|
`/reserve` and `/buy` stay ABSENT (not stubbed; a sandbox a config typo can flip isn't a
|
||||||
|
sandbox). The server enriches, never gates: every boot path, gate, and tag works with the
|
||||||
|
server dead. Scope honesty up front: if tier 2 proves bigger than one round, F holds and we
|
||||||
|
split — four holds have proven the pattern; nobody forces an epoch close.
|
||||||
|
|
||||||
|
## The ledger
|
||||||
|
|
||||||
|
1. **The read endpoints (G, on ultra — server-side).** G3 §4 as ratified: `GET
|
||||||
|
/godverse/v1/shop/<godverse_id>/stock` (items + `gone:[sku]` + etag) and `/crates` (the
|
||||||
|
rotation menu), read-only over the live POS, serving **metadata only** (covers stay static
|
||||||
|
tier-1 files — the API never serves the atlas). Monster Robot is the only real shop; that's
|
||||||
|
the point — one shop, live, true. Deterministic-where-possible discipline: same POS state →
|
||||||
|
same response (etag'd).
|
||||||
|
2. **The tier-2 reader (F, with C's seam).** Async enrichment at room build: on entering a
|
||||||
|
keyed real-sourced shop, the shell fires the stock read (timeout-guarded, etag-cached) and
|
||||||
|
the crate applies the `gone` list + live prices when the response lands — the dig NEVER
|
||||||
|
blocks on the network (charter risk #1). C: if the crate needs a post-build content-adjust
|
||||||
|
hook, that's your seam to publish (half-day rule); if the existing adapter re-read covers
|
||||||
|
it, say so and F wires direct. Server dead / unreachable / slow → tier 1 exactly, silently.
|
||||||
|
3. **The kill-the-server gate (F — the epoch's defining gate).** Three arms, all falsifiable:
|
||||||
|
boot server-up → live fields present (assert etag/`gone` consumed); **kill the server
|
||||||
|
mid-session** → next read degrades to tier 1 with ZERO console errors and no stutter
|
||||||
|
(frame-time measured across the failure); boot server-down → pure tier 1, byte-identical to
|
||||||
|
the beta behaviour. This is charter law #1 made a gate. It runs as its own smoke class —
|
||||||
|
server lifecycle is controlled by the gate itself, so the assertion is deterministic even
|
||||||
|
though live stock isn't.
|
||||||
|
4. **Sold-means-gone, sandboxed (F + G).** The server's `gone` list removes items from the
|
||||||
|
live crate read (a record sold IN THE REAL SHOP disappears from the game crate — that's the
|
||||||
|
GODVERSE magic, direction: real→game). In-game buys stay session-local per Option A. Assert
|
||||||
|
both directions of the sandbox: a real `gone` item never renders; a game buy never appears
|
||||||
|
in any server state (there's no verb to carry it — prove the absence).
|
||||||
|
5. **The v5 tour + money shot (F).** `docs/shots/release_v5/`: the crate walk — Musgrave Road
|
||||||
|
→ Monster Robot Party's frontage (B's mark visible) → the riffle with real covers → the
|
||||||
|
price sticker → the buy. Plus one mint-shop frame (the honest caption: plausible, not real)
|
||||||
|
and the `?classic=1` control. The R21 cluster-pose primitive poses the street frames.
|
||||||
|
6. **Docs freeze (all lanes, light).** A: CITY_SPEC v5 section (the tier ladder, sourcing,
|
||||||
|
the id-space fences, the manifest) + charter risk-list retired-or-carried, v4-style. E:
|
||||||
|
provenance v5-final (mint atlases + the licence line's story). C: LANE_C_PUB v5-frozen
|
||||||
|
marker. G: the G3 doc updated design→as-built. B/D: notes current; nothing else owed.
|
||||||
|
7. **The tag + the retro.** F: `qa.sh --strict --matrix` + the kill-the-server gate green →
|
||||||
|
tag **`v5.0`** → push. Then **Fable writes the epoch retro** (not a lane deliverable), and
|
||||||
|
the v6 conversation opens with John (the editor and multiplayer are still chartered; the
|
||||||
|
GODVERSE has more rungs — Option B's reservation upgrade is already chartered as v5.x).
|
||||||
|
|
||||||
|
## Laws
|
||||||
|
|
||||||
|
Charter v5 laws, all now with case law behind them · the vacuous-gate law · the id-namespace
|
||||||
|
fences (`sku_`/`mint_`, plan-id/godverse-id) · no golden moves expected this round (tier 2
|
||||||
|
touches no plan, no cache, no atlas bytes) · the offline law is THE gate this round, not a
|
||||||
|
footnote.
|
||||||
|
|
||||||
|
## WAVE-2 STATUS + F'S THREE LOAD-BEARING INPUTS (Fable, post-wave review)
|
||||||
|
|
||||||
|
*A/B/C/E/G are ALL IN. The beta tagged. What remains is F's close (#2 reader → #3 gate → #4
|
||||||
|
sandbox → #5 tour → #7 tag). The waves handed F three inputs that change the brief's letter —
|
||||||
|
build to THESE, not to my original wording:*
|
||||||
|
|
||||||
|
1. **C's seam ruling (§8): direct wire, no hook — but PICK-THEN-OMIT is law.** The naive wiring
|
||||||
|
(filter `pack.items`) RESHUFFLES the crate — C measured one sale changing 21 other records.
|
||||||
|
The reader draws from the stable full list and omits `gone` after: a crate with 3 sold shows
|
||||||
|
13, not a different 16. **Assert zero collateral in the gate** (sell one fixture record, the
|
||||||
|
other 119 identities unchanged). "Selling one record must remove one record, not re-stock
|
||||||
|
the shop."
|
||||||
|
2. **G's finding: `gone[]` has no subject in production.** The real POS has recorded no sale
|
||||||
|
since 2026-07-01 — 0 of crate 550's 120 records have ever sold, so a sold-means-gone assert
|
||||||
|
on live data passes vacuously forever. G built the remedy: **`make-fixture`** gives the gate
|
||||||
|
a subject. The gate MUST use it (vacuous-gate law — G flagged it before the gate could be
|
||||||
|
born blind).
|
||||||
|
3. **G's lifecycle is the kill switch:** `godverse_server.py start|stop|status` — the
|
||||||
|
kill-the-server gate drives it directly. The three fences are machine-enforced (read-only
|
||||||
|
credential measured permission-denied; `/reserve`+`/buy` → 501; metadata only, mint shops
|
||||||
|
404) — assert on them as fences, not re-derive them.
|
||||||
|
|
||||||
|
*Also for the record: E's freeze caught redhill_godverse missing from the towns roster for
|
||||||
|
three rounds (on disk, pinned, bootable by key — invisible only in B's selector; the one
|
||||||
|
consumer nobody gated). E owned it, fixed it, and wrote the gate-shape lesson. B verified the
|
||||||
|
R26 handshake against G's real manifest. A judged the risk list line-by-line and correctly
|
||||||
|
CARRIED risk #1 — "nothing retires this line except that gate going green."*
|
||||||
|
|
||||||
|
## WAVE-3 STATUS + TWO RATIFICATIONS (Fable, post-reader review)
|
||||||
|
|
||||||
|
*F's reader is IN (`39cfc3d`): pick-then-omit with `gone`/`price` applied IN PLACE (identity
|
||||||
|
and order preserved), fire-and-forget with a 1200 ms abort + circuit breaker, real-sourced
|
||||||
|
shops only. Browser-proven: 3 fixture sales → 0 collateral; server killed mid-session → 13.1 ms
|
||||||
|
enter, 14 ms worst frame gap, zero errors. Two F rulings are hereby RATIFIED as law:*
|
||||||
|
|
||||||
|
1. **Tier 2 is opt-in: `?live=1` / `?live=<url>`; absent → byte-identical to the beta.** F's
|
||||||
|
reasoning is the R26 lesson applied to a server — a default boot must never ask a question
|
||||||
|
that has no answer on machines that aren't John's. "Tier 2 is a place you go, not a thing
|
||||||
|
that happens to you." Auto-discovery is a v5.x question with a reachability contract.
|
||||||
|
2. **The collateral assertion is the canonical sold-means-gone gate.** "The gone item never
|
||||||
|
renders" cannot discriminate pick-then-omit from filter-the-array — both hide the record;
|
||||||
|
only COLLATERAL (the other 119 identities and order unchanged) tells them apart. Gates
|
||||||
|
assert collateral. (The vacuous-gate law's most precise application yet.)
|
||||||
|
|
||||||
|
**F's remaining close, in order:** formalize the browser proofs as the permanent smoke class
|
||||||
|
(#3's three arms + #4's both-directions sandbox, collateral-asserted, fixture-subject via
|
||||||
|
`make-fixture`) → the v5 tour (#5 — the crate walk, the mint-shop honest caption, the classic
|
||||||
|
control) → docs (#6 F-side: README v5 + the flags table gains `?live`) → `qa.sh --strict
|
||||||
|
--matrix` + the new smoke class → tag **`v5.0`** → push. Then Fable writes the epoch retro.
|
||||||
|
|
||||||
|
## WAVE-4 (Fable): two ratifications, one micro-task, and the tag path
|
||||||
|
|
||||||
|
**RATIFIED — F's §27 console-error ruling, and it is strictly stronger than the brief it
|
||||||
|
replaces.** The brief's "ZERO console errors on server death" (my wording, echoed in A's risk
|
||||||
|
line #1) is physically unachievable: a dead-host fetch logs `ERR_CONNECTION_REFUSED` at the
|
||||||
|
browser level where no `.catch()` reaches — to log zero, the reader would have to know the
|
||||||
|
server is dead without asking. One refusal is the irreducible cost of discovering death. The
|
||||||
|
ratified assertion: **zero errors from game code + EXACTLY ONE refused connection** — which
|
||||||
|
proves the circuit breaker, where "zero" could never distinguish a working breaker from a
|
||||||
|
broken one (both fail it). F filed this loudly as the beneficiary instead of relaxing it
|
||||||
|
quietly; that is the standard. (A: your risk-line wording is superseded by this record — a
|
||||||
|
one-line amendment at your leisure, not a blocker.)
|
||||||
|
|
||||||
|
**RATIFIED — the control-based frame assertion.** F's original `<250 ms` threshold passed
|
||||||
|
while measuring Lane C's room build and calling it network behaviour; the control (same walk,
|
||||||
|
breaker tripped, no network: 202 ms) exposed it, and the death's true cost is **Δ+2 ms**.
|
||||||
|
Assertions on frame cost are DELTAS AGAINST A CONTROL from here on, not absolute numbers —
|
||||||
|
the R21 lesson, now law for performance gates.
|
||||||
|
|
||||||
|
**THE MICRO-TASK — B: the frontage pose (F's filed ask).** F produced three frames of empty
|
||||||
|
road trying to re-derive your frontage anchor, then stopped — correctly (the cluster primitive
|
||||||
|
answers "where is the town's heart", and Red Hill's heart doesn't contain Monster Robot; a
|
||||||
|
frontage frame asks a different question). You own the frontage geometry, the R26 shopfront
|
||||||
|
mark, and the `poseForVenue` machinery: publish a pose helper or bookmark that frames a shop's
|
||||||
|
frontage BY LOT (keyed off `shop.lot` / godverseShopId — `poseForShopFront(shopOrId, dist)`),
|
||||||
|
verified on Monster Robot (mark visible, door centred, sleeves-through-glass a bonus). One
|
||||||
|
commit, B-pathspec. This also closes the gap for every future godverse tour frame.
|
||||||
|
|
||||||
|
**Then F closes:** the 4th frame through B's helper → full `qa.sh --strict --matrix` +
|
||||||
|
the (now-permanent, corrected) kill-the-server smoke class → tag **`v5.0`** → push. Fable's
|
||||||
|
retro follows.
|
||||||
|
|
||||||
|
## WAVE-5 (Fable): THE SPEC RULING — the facade is +Z, and the fifth hold stands
|
||||||
|
|
||||||
|
*B's micro-task found why F photographed empty road three times: it was never the pose.
|
||||||
|
`plan_osm.js:443` aims the lot's −Z at the road (implementing CITY_SPEC:71); `buildings.js:404`
|
||||||
|
draws the facade on +Z (CITY_SPEC:335, B's canon). The spec contradicts itself; each generator
|
||||||
|
implemented one half; synthetic follows B's canon so every golden has been green over a wrong
|
||||||
|
product since R18 — v4.0 shipped on it. The epoch's signature species at its worst: not a gate
|
||||||
|
failing on correct behaviour, but a green suite over a wrong world.*
|
||||||
|
|
||||||
|
**THE RULING: the visible facade is LOCAL +Z. B's canon is canonical.** It has been the de
|
||||||
|
facto truth since v1 (buildings.js drew it there from the start; venue.js seats door-glow/
|
||||||
|
queue/posters there; A's own R15 write-up said "the visible venue face is +Z"). CITY_SPEC:71's
|
||||||
|
−Z line described the SIM convention (rig-forward at ry=0) and got written into the lots
|
||||||
|
contract by ambiguity — the same −Z/+Z species as R13's RY_FLIP and R15's posters, now at the
|
||||||
|
root. The spec amends so the lots contract states: **facade/door/frontage = local +Z**; the
|
||||||
|
sim/rig-forward convention is documented separately as the different thing it is.
|
||||||
|
|
||||||
|
**A — the fix, the sweep, the pins (ONE commit):**
|
||||||
|
1. Land B's proven one-liner in the real-roads seating (`ry = atan2(-nx,-nz)` — B measured
|
||||||
|
katoomba 0/72 → 72/72 facing and F's money shot works 4 m from Musgrave Road; B reverted it
|
||||||
|
because it's your file).
|
||||||
|
2. **Measure the marched-osm path for the same flaw** — don't assume it's clean because nobody
|
||||||
|
noticed; that's how this one survived nine rounds. State the verdict either way.
|
||||||
|
3. Amend CITY_SPEC:71 per the ruling, re-pin every moved golden (the 36 real-town pins move;
|
||||||
|
marched/synthetic only if your measurement says so), all in the same commit per the
|
||||||
|
amendment law.
|
||||||
|
4. **The cross-convention gate (the lesson made law):** the selfcheck gains an assert measured
|
||||||
|
in B's canon — every lot's +Z facade faces its frontEdge street side — so the two
|
||||||
|
conventions are compared AGAINST EACH OTHER forever, not each against its own spec line.
|
||||||
|
A self-consistent check over a self-contradicting spec passes under either convention;
|
||||||
|
only the cross-check sees it. (F proved this the hard way and recorded it.)
|
||||||
|
|
||||||
|
**F — then the close:** re-gate (the matrix against A's re-pins), re-shoot the tour (the
|
||||||
|
frontage frame through B's `poseForShopFront` — and `street_noon` on real towns, which has
|
||||||
|
been "46 draws of bare road" since R18, becomes a real street), `qa.sh --strict --matrix`,
|
||||||
|
tag **`v5.0`**, push.
|
||||||
|
|
||||||
|
**B — done, with honours:** the helper (door-centred by projection, `doorCentreNdcX = 0.0000`),
|
||||||
|
the selector extracted to ONE home (`'g:31'` — the id-namespace fence now has a single
|
||||||
|
resolver), the self-correction of your own R27 intel, and the discipline of proving A's fix
|
||||||
|
then reverting it. Nothing further owed.
|
||||||
|
|
||||||
|
## THE EXACT RUN ORDER
|
||||||
|
|
||||||
|
0. **WAVE 0: F re-runs and tags `v5.0-beta`.** Hard stop until it lands.
|
||||||
|
1. **Start together: G (#1 endpoints, on ultra) + C (#2 seam ruling: publish the hook or bless
|
||||||
|
the direct wire) + A/E (#6 docs prep — can run the whole round).**
|
||||||
|
2. **When G's endpoints are live on the tailnet and C's seam is ruled: F (#2 reader).**
|
||||||
|
3. **F (#3 kill-the-server gate + #4 sandbox proofs + #5 tour), then #7: tag `v5.0`, push.**
|
||||||
|
4. **Fable: the epoch retro.**
|
||||||
|
|
||||||
|
Waves: **[F-beta-tag] → [G, C, A+E] → [F reader] → [F gate/tour/tag] → [retro]**. B/D on-call.
|
||||||
|
|
||||||
|
## Standing
|
||||||
|
|
||||||
|
- **John — one infra FYI, nothing to decide:** the kill-the-server gate needs the thriftgod
|
||||||
|
server reachable from m3 over the tailnet AND stop/start control from the gate (G owns the
|
||||||
|
mechanism — a lifecycle endpoint or an ssh'd systemd unit, G's call; it's in-house infra
|
||||||
|
under existing access). Your A-then-B ruling continues to hold the write verbs absent.
|
||||||
|
- **v5.x / v6 preview (the retro decides, do not start):** Option B's reservation upgrade
|
||||||
|
(behind your hold budget) · more POS shops if monsterrobot ever gets siblings · the editor ·
|
||||||
|
multiplayer presence · the v4.x/v5.x polish tails.
|
||||||
63
docs/LANES/ROUND28_INSTRUCTIONS.md
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
# PROCITY — Round 28 lane instructions (from Fable, integrator)
|
||||||
|
|
||||||
|
Date: 2026-07-17 · **v5.0 SHIPPED, retro written ([RETRO_V5.md](../RETRO_V5.md)). Nothing is
|
||||||
|
owed anywhere.** Round 28 is deliberately LIGHT: v6's first spike (the one that needs nothing
|
||||||
|
new) plus one parked polish item per lane — the items that have waited politely since as far
|
||||||
|
back as R7. Tag **`v5.1`** at close. The v6 spikes 2/3 stay gated on John's mocap vids and the
|
||||||
|
ultra integration call; nobody starts them.
|
||||||
|
|
||||||
|
## The ledger
|
||||||
|
|
||||||
|
1. **SPIKE 1 — the free stash (E + D; v6 groundwork, the charter's first thread).**
|
||||||
|
- **E**: convert two cached Mixamo clips from the tailnet stash — **`lean`** and
|
||||||
|
**`look-around`** — via the proven `fbx_to_clip.py` path (mesh-free, 65-bone, licence
|
||||||
|
table lines per clip: Mixamo royalty-free, same zone as walk/idle/sit). Depot + local per
|
||||||
|
the sit.glb precedent.
|
||||||
|
- **D**: wire them into street loiter — a window-shopping stop occasionally leans
|
||||||
|
(shopfront-adjacent) or looks around (anywhere). Seeded rolls on your existing `benchsit`
|
||||||
|
pattern (own rng stream, identity signature untouched), classic-safe by the same
|
||||||
|
`!CLASSIC` fetch gate, byte-identical proofs as ever (setSitting taught you the
|
||||||
|
scale-aware traps — `lean` against a wall wants the same care).
|
||||||
|
- Acceptance: a fresh boot shows peds leaning and glancing; classic/`?noassets` untouched;
|
||||||
|
the R16 seated-stature precedent covers any pose the no-giants gate misjudges (tag
|
||||||
|
`procityPosed` if needed — F's gate arm from R16 generalizes).
|
||||||
|
2. **Awning fabric ripple (B — parked since R17's wind round, your promised pick).** The
|
||||||
|
striped fabric canopies share the rigid awning InstancedMesh; split them (or attribute-mask
|
||||||
|
them) and give ONLY the fabric a gentle wind ripple riding your existing `wind.js` uniforms.
|
||||||
|
Weather-driven like the trees; `?classic`/`?weather=0` byte-identical by the same proof
|
||||||
|
pattern; budget stated (~0 expected — it's your shader).
|
||||||
|
3. **`audioEmitter` (C — your own parked idea, at last).** Positional interior audio: the gig
|
||||||
|
bed/walla emitting from the stage rather than the room, the till from the counter. Scope it
|
||||||
|
yourself (it's your idea — the brief states acceptance only): audible positioning in a venue
|
||||||
|
+ a normal shop, fail-soft under `?mute`/`?noassets`, no new assets (it's routing, not
|
||||||
|
content), B's audio engine consulted not modified (publish the ask if you need an engine
|
||||||
|
seam — B's file).
|
||||||
|
4. **Instrument LOD (E + D — the oldest tri item, v3.1-era).** E: `_lo` variants (~3–4k) for
|
||||||
|
the five instruments (the `_hi` depot pattern in reverse); D: distance swap in the band
|
||||||
|
(interiors are close-quarters — if the measured win is <20k tris in practice, KILL the item
|
||||||
|
with the measurement and it leaves the backlog forever, the wind-sway rule).
|
||||||
|
5. **The gate + the tag (F, last).** New-clip figure checks (lean/look poses through the
|
||||||
|
scale gate), a bookmark spot-check (leaning peds in old frames), `qa.sh --strict --matrix`,
|
||||||
|
notes §28, tag **`v5.1`**, push. Nothing here moves a golden (clips and shaders are outside
|
||||||
|
every plan hash) — flag loudly if anything disagrees.
|
||||||
|
6. **A + G — rest.** A: on-call (nothing should touch a plan). G: your next work is v5.x
|
||||||
|
Option B when John sets a hold budget, or mocap support on ultra — nothing on m3 this round.
|
||||||
|
|
||||||
|
## THE EXACT RUN ORDER
|
||||||
|
|
||||||
|
1. **Start together: E (#1 clips + #4 `_lo` variants) + B (#2) + C (#3).**
|
||||||
|
2. **When E's clips land: D (#1 wiring, then #4 swap-or-kill).**
|
||||||
|
3. **When B/C/D are in: F (#5)** — gate, tag `v5.1`, push.
|
||||||
|
|
||||||
|
Waves: **[E, B, C] → [D] → [F]**. A/G rest.
|
||||||
|
|
||||||
|
## Standing
|
||||||
|
|
||||||
|
- **John — two unblockers for v6, both yours, both parallel to this round:** (1) film 2–3
|
||||||
|
mocap test vids into MOCAPGOD's queue (`docs/CAPTURE.md` there — the crate-riffle first);
|
||||||
|
(2) nudge Fable-on-ultra for the MOCAPGOD-C/MIRPAMO integration call. When both exist,
|
||||||
|
R29 runs Spikes 2/3 and the v6 charter ratifies on their results.
|
||||||
|
- Also from the retro, the real-world note: **the shop POS has recorded no sale since
|
||||||
|
2026-07-01** — check the till sync when you're next in.
|
||||||
|
- **Backlog after this round** (if #4 kills cleanly): tram-stop tri diet · flag retirement ·
|
||||||
|
Option B · tier-2 auto-discovery · the v6 tracks. The polish debt is nearly drained.
|
||||||
82
docs/LANES/ROUND29_INSTRUCTIONS.md
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
# PROCITY — Round 29 lane instructions (from Fable, integrator)
|
||||||
|
|
||||||
|
Date: 2026-07-17 · **v5.1 SHIPPED** (`cdce67c`, tag on origin; qa 7/0/0, matrix 10×7, goldens
|
||||||
|
unmoved as predicted). R28 review, short: the sweep was the culture working — B and D each
|
||||||
|
**killed their item with a measurement** (the wind-sway rule twice in one round; the backlog
|
||||||
|
polish debt is now effectively drained), D's mid-round STOP saved E a pointless bake, C shipped
|
||||||
|
`audioEmitter` after measuring its own v3-era prescription inert (panner ratio 1.0000 —
|
||||||
|
bit-identical door vs stage), E shipped `look.glb` with the bytes sha1-verified BACK off the
|
||||||
|
public depot and found **`lean` does not exist** (the brief's sixth corrected premise — the
|
||||||
|
stash never had it), and F tagged with the pose gate honestly SKIPPED: **the spike never
|
||||||
|
reached the game.** Round 29 finishes Spike 1 and runs Spikes 2/3 — the v6 charter ratifies on
|
||||||
|
their results. Most of this round is GATED ON JOHN (below); wave 0 runs today.
|
||||||
|
|
||||||
|
## WAVE 0 — finish Spike 1 (runnable NOW, no dependencies)
|
||||||
|
|
||||||
|
**D:** bind and wire `look.glb` — `loadPedFleet(base, { look })` per the sit gate (a raw fetch
|
||||||
|
breaches the classic zero-fetch covenant, E already flagged it), a seeded glance during
|
||||||
|
window-shop loiter (own rng stream, identity signature untouched, byte-identical proofs — your
|
||||||
|
own R17/R28 pattern). **F:** the pose gate gains its subject — un-SKIP it, verify a glancing
|
||||||
|
ped in a fresh boot, spot-check one old frame. No tag for this alone; it rides Spike 2's close.
|
||||||
|
|
||||||
|
## THE GATE ON THIS ROUND — John's two unblockers (everything below waits)
|
||||||
|
|
||||||
|
1. **Film the Spike-2 shot list into MOCAPGOD's queue** (protocol: MOCAPGOD `docs/CAPTURE.md`):
|
||||||
|
- **the crate-riffle** (at the shop if you can — it's the epoch's poster clip),
|
||||||
|
- **the wall-lean** (E's R28 finding made this a *need*, not a nice-to-have — the stash
|
||||||
|
never had one),
|
||||||
|
- one free pick (busker strum, trolley push, phone check — dealer's choice).
|
||||||
|
2. **The ultra integration call**: MOCAPGOD Lane C (retarget) vs MIRPAMO's retarget — one
|
||||||
|
machine, decided by Fable-ultra before either is built twice. Nudge that session.
|
||||||
|
|
||||||
|
## SPIKE 2 — the full mocap loop (when the queue has vids)
|
||||||
|
|
||||||
|
Runs mostly on ultra (MOCAPGOD C/D lanes + the integration call), lands on m3 as clips:
|
||||||
|
- **E (m3):** receive the retargeted clips, convert/validate to the house clip form (the
|
||||||
|
look.glb checklist: mesh-free, 65 joints, bind-verified), **licence line: in-house / NC-
|
||||||
|
flagged** per the MOCAPGOD decision — the validator prints it or fails (the R25 law).
|
||||||
|
- **D:** wire the riffle to a crate-digger context and the lean to shopfront loiter (the R28
|
||||||
|
wave-0 machinery generalizes — this should be data, not new code).
|
||||||
|
- **F:** the spike's falsifiable gate: a ped performs a clip that DID NOT EXIST before John
|
||||||
|
filmed it — assert the clip's provenance chain (queue video id → BVH → clip sha) end to end,
|
||||||
|
not "a ped moves". Then tag **`v6.0-spike`** (a checkpoint tag, not an epoch tag) with the
|
||||||
|
spike report.
|
||||||
|
|
||||||
|
## SPIKE 3 — a generated body (parallel to Spike 2, ultra-heavy)
|
||||||
|
|
||||||
|
flux_local concept → hunyuan3d_mlx mesh → **MIRPAMO autorig** → walk retarget → **in-game
|
||||||
|
through the no-giants gate**. Owner: G/ultra produces; E validates + licences (CC0-equiv,
|
||||||
|
generated); D spawns it as a fleet variant behind the same opt pattern. HONESTY CLAUSE: this is
|
||||||
|
the least-proven link in the whole v6 stack — a clean failure report ("autorig quality on
|
||||||
|
generated meshes: X") is a full deliverable and shapes the charter as much as a success.
|
||||||
|
|
||||||
|
## After the spikes — the ratification
|
||||||
|
|
||||||
|
Fable amends `V6_LIVING_STREET.md` DRAFT → ratified (or re-scoped) on the spike reports, and
|
||||||
|
the v6 rounds proper get chartered: clips at scale · personas · street discipline (footpaths +
|
||||||
|
occasional cars) · bodies. **A, B, C rest this round** (A: nothing touches a plan; B/C: your
|
||||||
|
R28 kills closed your queues) — you're first up in v6 proper, where street discipline (B: cars;
|
||||||
|
A: crossings data) and persona interiors (C) are the headline tracks.
|
||||||
|
|
||||||
|
## Laws
|
||||||
|
|
||||||
|
Unchanged, plus the R28 additions to the case law: **a kill with a measurement is a full
|
||||||
|
deliverable** (two more this round — the backlog never carries a zombie again) and **a
|
||||||
|
wishlist is not an inventory** (E measured the stash instead of trusting a four-epoch-old
|
||||||
|
list; briefs cite measured stock only).
|
||||||
|
|
||||||
|
## THE EXACT RUN ORDER
|
||||||
|
|
||||||
|
0. **Now: D + F (wave 0, Spike 1 closes).**
|
||||||
|
1. **John: film the vids + nudge ultra (the gate).**
|
||||||
|
2. **When clips flow: E → D → F (Spike 2), G/ultra → E → D (Spike 3, parallel).**
|
||||||
|
3. **F: the spike gate + `v6.0-spike` checkpoint tag.**
|
||||||
|
4. **Fable: charter ratification → v6 rounds proper.**
|
||||||
|
|
||||||
|
## Standing
|
||||||
|
|
||||||
|
- **John — the two unblockers above are the whole critical path.** Everything m3-side is one
|
||||||
|
small D+F wave and then waiting on footage of you leaning on a wall. The till-sync note from
|
||||||
|
the retro stands too (no POS sale recorded since 2026-07-01).
|
||||||
|
- **Backlog after R28's kills:** tram-stop tri diet · flag retirement (policy, not urgent) ·
|
||||||
|
Option B (your hold budget) · tier-2 auto-discovery. Thinner than it has ever been.
|
||||||
120
docs/LANES/ROUND30_INSTRUCTIONS.md
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
# PROCITY — Round 30 lane instructions (from Fable, integrator)
|
||||||
|
|
||||||
|
Date: 2026-07-17 · **V7 THE GAME is RATIFIED** ([docs/V7_THE_GAME.md](../V7_THE_GAME.md) —
|
||||||
|
read it first, it is the epoch's truth). Round 30 opens **v7.0-alpha: THE SAVE AND THE CRATE**
|
||||||
|
— wave 1 of the smallest set that makes one town play as a complete small game. *(Housekeeping:
|
||||||
|
the phantom R30 that two lanes were spun up for never had a brief — that was Fable's miss, now
|
||||||
|
fixed. If your session finds prior R30 work anywhere, it doesn't exist; this document is R30.)*
|
||||||
|
|
||||||
|
## The ledger (v7.0-alpha wave 1)
|
||||||
|
|
||||||
|
1. **THE SAVE CORE (F — the foundation everything consumes; publish the contract FIRST).**
|
||||||
|
- `web/js/world/save.js` (F-owned): versioned localStorage save implementing **the delta
|
||||||
|
law** — cash · owned items (each = `{townKey, godverseShopId|shopId, sku|slotId, pricePaid,
|
||||||
|
dayFound}` — the sku IS the identity, per the R25/26 fences) · current town · **day
|
||||||
|
number** · schema `procity-save/1`. Save on SLEEP (see #3) and on `beforeunload`;
|
||||||
|
export/import as JSON (John moves machines).
|
||||||
|
- **The published contract** (`window.PROCITY.game`): `{ day, cash, collection[], save(),
|
||||||
|
load(), sleep(), export(), import(json) }` — B builds UI on this, C reads it at the
|
||||||
|
counter. Publish the shape in LANE_F_NOTES §30 in your FIRST commit, implementation after.
|
||||||
|
- **Laws wired from birth:** `?classic=1` → NO save layer constructed, zero localStorage
|
||||||
|
touches (the covenant stays a pure v2 boot); `?game=0` opt-out; a corrupted/foreign save
|
||||||
|
→ rejected loudly, fresh start, the town NEVER breaks (delta law: world state is not in
|
||||||
|
the save, so it can't).
|
||||||
|
2. **THE SELL COUNTER CONTRACT (C — publish first, implement second).** Walk to a keeper's
|
||||||
|
counter with items → a sell card (mirror of the buy card): item, offer price, SELL.
|
||||||
|
**Offer = below the buy-side band, structurally** (the no-pump law — C picks the multiplier,
|
||||||
|
documents it in LANE_C_PUB §9). v7.0-alpha sells at any shop of the item's type (record
|
||||||
|
shop buys records); the fancy stuff (per-keeper taste, haggling) is beta+. C consumes
|
||||||
|
`PROCITY.game.collection` read-only; the debit/credit goes through the wallet exactly like
|
||||||
|
buying (the proven seam).
|
||||||
|
3. **SLEEP = SAVE = TOMORROW (F + B).** v7.0-alpha's day-advance: the HUD gains a SLEEP action
|
||||||
|
(B's surface; enabled any time for the alpha — home-base gating is beta) → `game.sleep()`:
|
||||||
|
save, `day+1`, **stock rotation** (the parody/mint pick streams re-seed as
|
||||||
|
`rng(citySeed, shopId, 'stock', day)` — F threads `day` into the existing stock seeding;
|
||||||
|
REAL stock never rotates, it's real), gig night = `day % 7` (plugs into the existing week
|
||||||
|
schedule), wake at dawn (segment 0).
|
||||||
|
4. **THE COLLECTION UI (B — after F's contract lands).** A collection panel in hud.js (your
|
||||||
|
selector/HUD patterns): cover thumb, title/artist, price paid, where + day found; count in
|
||||||
|
the standing HUD; the SLEEP button + day/cash readout. Money-shot-grade is beta; readable
|
||||||
|
and correct is alpha.
|
||||||
|
5. **THE GATES (F, last).**
|
||||||
|
- **Save/load determinism:** play a scripted session (buy 2, sell 1, sleep 2×) → export →
|
||||||
|
fresh boot → import → assert byte-equal game state AND the world untouched (fingerprint
|
||||||
|
unchanged — the delta law's falsifiable form).
|
||||||
|
- **The no-pump gate v0:** script buy-then-immediately-sell N times → assert monotonic cash
|
||||||
|
LOSS (the spread is the house's). The full adversarial playtest-bot is beta.
|
||||||
|
- **Classic purity:** `?classic=1` → zero localStorage reads/writes (measure, don't trust),
|
||||||
|
no HUD additions, fingerprint frozen.
|
||||||
|
- **Rotation determinism:** same seed, same day → byte-identical shop stock; day N ≠ day
|
||||||
|
N+1 (actually different, not just asserted different).
|
||||||
|
- `qa.sh --strict --matrix` green throughout. **No tag** — the alpha tags when it plays
|
||||||
|
(likely R31, after the loop closes end-to-end).
|
||||||
|
6. **A — the rotation boundary verify (small).** F threads `day` into stock seeding — A
|
||||||
|
verifies it rides RUNTIME streams only: plan generation never sees `day`, all goldens
|
||||||
|
frozen, selfcheck proves it. One note, one line. **D, E, G — rest** (E's guide-band bake
|
||||||
|
and G's GodBay open in beta; D's rumors likewise. If footage lands in the MOCAPGOD queue
|
||||||
|
meanwhile, v6's Spike 2 takes priority for E/D — the epochs interleave, Fable sequences).
|
||||||
|
|
||||||
|
## Laws
|
||||||
|
|
||||||
|
The charter's five (delta · no-pump · game-money · classic-pure · seeded-rotation) + everything
|
||||||
|
inherited. No goldens move this round (rotation is runtime; A verifies the boundary). The
|
||||||
|
vacuous-gate law applies to every new gate above — each names its subject and proves it touched
|
||||||
|
it.
|
||||||
|
|
||||||
|
## THE EXACT RUN ORDER
|
||||||
|
|
||||||
|
1. **Start together: F (save core — contract in first commit) + C (sell contract first,
|
||||||
|
implementation second).**
|
||||||
|
2. **When F's contract is published: B (collection UI + SLEEP surface).** F continues
|
||||||
|
(sleep/rotation threading) in parallel — disjoint files.
|
||||||
|
3. **When F + B + C are in: F gates (#5), A verifies the boundary (#6).**
|
||||||
|
4. Fable reviews; R31 charters the rest of the alpha (or its close, if it plays).
|
||||||
|
|
||||||
|
Waves: **[F, C] → [B, F-cont] → [F-gate, A]**. D/E/G rest unless footage arrives.
|
||||||
|
|
||||||
|
## WAVE-1/2 REVIEW NOTES (Fable, mid-round — the agents' reports are the record)
|
||||||
|
|
||||||
|
**RATIFIED — F's two amendments, both measured:** (1) gig night = **(day − 1) % 7** — the
|
||||||
|
brief's literal `day % 7` flips a fresh boot (day 1) off night 0 and breaks the R13 cover/band
|
||||||
|
gates before the first sleep; the brief was wrong, the measurement wins. (2) **sleep saves
|
||||||
|
AFTER incrementing** — disk always carries the morning you woke into.
|
||||||
|
|
||||||
|
**ADJUDICATED — the cross-lane 6 lines (F edited C's `interiors.js`/`layout.js` for the
|
||||||
|
`stockDay` salt):** SANCTIONED, this instance — because all three conditions held: the owner
|
||||||
|
reviewed and ratified on the record BEFORE the commit ("rides my own sub-stream discipline,
|
||||||
|
absent ⇒ byte-identical"), the edit is marked and cited in the commit, and it is minimal.
|
||||||
|
**Without all three, a cross-lane edit gets reverted on sight.** The law stands: write the
|
||||||
|
ask, not the edit — C folds the seam into LANE_C_PUB at its next session; carried to the
|
||||||
|
epoch retro as a process note.
|
||||||
|
|
||||||
|
**WAVE-3 INPUTS (bind these into the gates session):** the rotation gate must hash **vertex
|
||||||
|
data, not transforms** (F measured its transform fingerprint vacuous over batched parody
|
||||||
|
stock); C's money-pump **negative control** (an item minted into the collection without a
|
||||||
|
successful debit must be impossible — assert `recordFind` only fires on `wallet.buy` truthy);
|
||||||
|
the **E-key sell routing** (§9.4 dig → sell → shelf) is F's held item and lands in the wave-3
|
||||||
|
session BEFORE the gates run against it.
|
||||||
|
|
||||||
|
## ROUND CLOSED (Fable, at the wave-3 review) — **v7.0-alpha wave 1: COMPLETE, ALL GREEN**
|
||||||
|
|
||||||
|
Ten commits, five lane-agent sessions, zero holds, zero cross-lane incidents beyond the one
|
||||||
|
adjudicated. The loop is real and gated: dig → buy → the crate remembers → counter → E → sell
|
||||||
|
(strictly at a loss) → sleep → tomorrow rotates the mint crates and never the real one → the
|
||||||
|
save survives the reboot and the UI reads it. `qa.sh --strict --matrix` **7/0/0, MATRIX 10
|
||||||
|
towns**, selfcheck 156,352, covenant frozen, the day provably cannot enter the world.
|
||||||
|
|
||||||
|
**Fed to R31 (the alpha close — charter it after John PLAYS):** the beta rulings queue
|
||||||
|
(scarcity-per-day pull records · guide bands · per-keeper taste · foreign-town display names
|
||||||
|
via E's index) · F's harness lesson #3 (the HUD hides in interiors by design) · the design
|
||||||
|
fact that re-buying a rotated sleeve is a strict money loser (correct, filed). **The alpha
|
||||||
|
tags when it plays — John's hands on the real URL are the acceptance environment.**
|
||||||
|
|
||||||
|
## Standing
|
||||||
|
|
||||||
|
- **John — nothing blocks this round.** The deploy to digalot.fyi/procity still waits on your
|
||||||
|
VPS-access pick (grant me the ssh rule / run the script yourself) — worth doing THIS round:
|
||||||
|
testing the save loop in a real browser on the real URL is exactly the alpha's acceptance
|
||||||
|
environment. And the MOCAPGOD queue still wants your footage whenever.
|
||||||
|
- **The drive question stands:** say "run it" and Fable spawns these lanes as subagents in the
|
||||||
|
wave order above, reviews between waves, and reports once at the end.
|
||||||
53
docs/LANES/ROUND31_INSTRUCTIONS.md
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
# PROCITY — Round 31 instructions (from Fable, integrator)
|
||||||
|
|
||||||
|
Date: 2026-07-18 · **R30 closed all green** (7/0/0, matrix 10 towns, the loop live and gated).
|
||||||
|
Round 31 is **the v7.0-alpha CLOSE**: play it, fix the friction, tag it. Charter stage check
|
||||||
|
([V7_THE_GAME.md](../V7_THE_GAME.md)): the alpha = persistence + collection + sell-to-keeper +
|
||||||
|
sleep/day + rotation, "ONE town plays as a complete (small) game" — every system landed in R30;
|
||||||
|
what remains is the PLAYING and the honesty about what it feels like.
|
||||||
|
|
||||||
|
## WAVE 0 — THE PLAYTEST (Fable, in person)
|
||||||
|
|
||||||
|
Fable boots the game in a real browser and plays the loop AS A PLAYER, not as a gate: arrive,
|
||||||
|
find a shop, dig, buy, check the crate, sell at a counter, sleep, verify tomorrow feels like
|
||||||
|
tomorrow, reboot, confirm the town remembered. Deliverable: **the friction list** — every
|
||||||
|
"why can't I", every confusing beat, every moment the game fails to explain itself — split
|
||||||
|
into (a) alpha-blocking friction (bugs/confusion that would embarrass the tag) and (b) beta
|
||||||
|
input (design wants, John's ruling queue). Honest numbers where they apply. *John's own
|
||||||
|
playtest remains the beta charter's real input — Fable's hands are the alpha's acceptance
|
||||||
|
stand-in, and the tag notes it.*
|
||||||
|
|
||||||
|
## WAVE 1 — micro-fixes (only if wave 0 surfaces alpha-blockers)
|
||||||
|
|
||||||
|
Assigned to owning lanes as tightly-scoped agents, same treaty. Nothing speculative — only
|
||||||
|
what the playtest actually hit.
|
||||||
|
|
||||||
|
## WAVE 2 — the tag (F)
|
||||||
|
|
||||||
|
Money shot: the crate panel with real finds (the collection IS the alpha's face). Fresh
|
||||||
|
`qa.sh --strict --matrix` on the final tree. Tag **`v7.0-alpha`** (annotated, the playtest
|
||||||
|
verdict in the message), push main + tag. The README gains one line in the manual: sell at
|
||||||
|
the counter with E, sleep to save.
|
||||||
|
|
||||||
|
## ROUND CLOSED (Fable) — **v7.0-alpha TAGGED AND PUSHED** (`2491f1c`)
|
||||||
|
|
||||||
|
Wave 0: Fable played the loop end to end — dig $10 → sell $5 (the spread felt) → keep a $12
|
||||||
|
NM soul record → THE CRATE → full reload → **the town remembered** → SLEEP → day 2. Verdict:
|
||||||
|
**the loop plays.** The playtest found the alpha-blocker every green gate had missed: **the
|
||||||
|
dig was default-OFF on a plain boot** (v2-era opt-in, six epochs past the README's claim; every
|
||||||
|
R30 gate booted `dig=1` explicitly — harness lesson #4, THE EXPLICIT-FLAG BLINDFOLD: a gate
|
||||||
|
that boots its own flags cannot see a broken default). Waves 1+2: the flip (classic-safe,
|
||||||
|
fenced with a naked-boot reachability assert + negative control), the de-staled splash, the
|
||||||
|
money shot (Monster Robot's real crate, 3 real finds), qa 7/0/0 + MATRIX 10 towns on
|
||||||
|
port-isolated servers (a second fix the round forced: the suite no longer silently reuses
|
||||||
|
whatever holds :8130). **The beta queue is filed in LANE_F_NOTES §31.6** — spawn-faces-the-
|
||||||
|
strip, crate-at-the-counter, dawn sky, THE BIBLE, per-day scarcity. The beta charters on
|
||||||
|
John's own playtest whenever he walks the strip.
|
||||||
|
|
||||||
|
## Standing
|
||||||
|
|
||||||
|
- **John — the alpha's real acceptance is you:** play it (locally now, or grant the VPS ssh
|
||||||
|
and Fable ships digalot.fyi/procity same hour). Your friction list charters the beta. The
|
||||||
|
queued beta rulings await: per-day scarcity (pull records in the save) · guide bands (the
|
||||||
|
BIBLE) · keeper taste · foreign-town names via E's index.
|
||||||
|
- v6 footage: the MOCAPGOD queue is still empty; Spike 2 wakes whenever you film.
|
||||||
61
docs/LANES/ROUND32_INSTRUCTIONS.md
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
# PROCITY — Round 32 instructions (from Fable, integrator)
|
||||||
|
|
||||||
|
Date: 2026-07-20 · **R31 closed** — v7.0-alpha tagged and pushed; the loop plays. Round 32 opens
|
||||||
|
**v7.0-beta — THE THRILL, wave 1**: the guide bands land ([V7_THE_GAME.md](../V7_THE_GAME.md)
|
||||||
|
stage 2), scarcity rides the save, and the alpha playtest's friction queue (LANE_F_NOTES §31.6)
|
||||||
|
gets its cosmetic items. **Solo round:** John asked for direct progress; Fable executes all lanes
|
||||||
|
in one session on JING5, same treaty, same gates — lane ownership is honoured in the commit
|
||||||
|
messages, not the parallelism.
|
||||||
|
|
||||||
|
## The design rulings this round encodes (from the ratified charter + §31.6 queue)
|
||||||
|
|
||||||
|
1. **THE BIBLE derives, it is not baked.** E's band thresholds (build_stock_pack.py:88-94 —
|
||||||
|
record 8/25/60 · book 5/15/40 · toy 6/20/50) become a tiny JS mirror
|
||||||
|
(`js/interiors/bible.js`). No pack re-emit, so risk #3 (re-baked atlases orphaning owned
|
||||||
|
skus) never opens. The guide shows a BAND ("guide $25–59"), never a verdict — spotting the
|
||||||
|
gem stays the player's skill.
|
||||||
|
2. **Gems live in the parody bins.** Real/mint packs derive `price_band` FROM `price`, so real
|
||||||
|
asking is always inside its band → with the §9.1 basis swap (sellOffer basis = bandLow),
|
||||||
|
every real round trip stays strictly negative and **the R30 no-pump gate holds untouched,
|
||||||
|
by construction**. Procedural stock gains seeded bands + a seeded under-band gem roll —
|
||||||
|
the only place asking can sit under the guide, which is exactly the op-shop fantasy.
|
||||||
|
3. **Scarcity is what makes the gem loop safe** (§31.6 item 6, ruled WITH the bands as filed):
|
||||||
|
a bought dig slot enters `pulls` in the save (optional field, procity-save/1 — old saves
|
||||||
|
stay valid, old loaders drop the field without rejecting) and that bin omits it after the
|
||||||
|
draw (the proven `pack.gone` pick-then-omit seam — zero collateral). A gem is profit ONCE;
|
||||||
|
tomorrow's rotation re-keys the bin. Growth is bounded by finds, not arbitrage (charter
|
||||||
|
law #2's real meaning).
|
||||||
|
4. **§31.6 items 1–3:** spawn becomes a computed cluster-pose facing the shop strip (classic
|
||||||
|
keeps the frozen v2 pose); THE CRATE surfaces inside interiors (C key — the §31.6
|
||||||
|
"crate at the counter" want, hidden while dig/sell cards own the screen); DAWN gets a
|
||||||
|
cool sky dome swap (classic + weather-override fenced).
|
||||||
|
|
||||||
|
## Gate additions (F)
|
||||||
|
|
||||||
|
`smoke_bible` in flags_check.py: guide stamp on the pull card (real + parody) · offer < bandLow
|
||||||
|
structurally · a seeded parody gem exists and its round trip is positive ONCE (then the slot is
|
||||||
|
gone — re-open the bin and it stays gone; reload and it stays gone) · negative control:
|
||||||
|
`?game=0` re-opens the same bin with the slot PRESENT (the detector discriminates — scarcity is
|
||||||
|
the save's doing, not the dig's). Classic purity untouched (no new module constructs under
|
||||||
|
`?classic=1` / `?dig=0`).
|
||||||
|
|
||||||
|
## ROUND CLOSED (Fable) — ALL GREEN
|
||||||
|
|
||||||
|
`qa.sh --strict --matrix`: **7 passed · 0 failed · 0 warn · 0 skipped**, MATRIX 10 towns green.
|
||||||
|
`smoke_bible`: 80 parody offers across 5 bins all band-lawful; bin1 holds 2 seeded gems (grail
|
||||||
|
asking $19 vs keeper $30); the buy-everything ledger closes to the cent (Σask $333 → Σoffer
|
||||||
|
$135, gems alone +$21, every non-gem strictly negative); the emptied bin stays empty across a
|
||||||
|
full reload (16 pulls in the save) and `?game=0` re-opens it 16/16 (the control discriminates);
|
||||||
|
0 console errors. sell_routing now asserts the exact §9.1 formula and passed on a BANDED item
|
||||||
|
(standard, basis $8 → +$4). Money shots: `docs/shots/v7_beta/guide_card.png` (the guide stamp —
|
||||||
|
"1974 COUNTRY guide $8–24" beside the $24 sticker) + `crate_at_the_counter.png` (THE CRATE open
|
||||||
|
inside a shop, game bar riding bottom-right). Environment note: JING5's gitignored
|
||||||
|
`web/assets/models/` was absent (fresh-checkout trap, now §32.3 in LANE_F_NOTES) — synced from
|
||||||
|
m3ultra, which also un-warned the manifest validator's 15 cross-pack checks. Full record:
|
||||||
|
LANE_F_NOTES §32. Shipped: bible.js + guide stamps + parody bands/gems (C) · pulls on the save
|
||||||
|
+ gone-wiring + digOffers surface (F) · crate-in-interiors + cluster-pose spawn + dawn sky (B)
|
||||||
|
· smoke_bible + the routing amendment (F) · README/docs.
|
||||||
|
|
||||||
|
**The beta's remaining charter** (next rounds): the WANTLIST + seeded rumors (D's keepers
|
||||||
|
finally gossip) · travel costs a day (23 towns become the map) · §31.6-5 (dig Esc under real
|
||||||
|
hands) · then v7.0 = GodBay offline auctions (G) + onboarding + the tour.
|
||||||
52
docs/LANES/ROUND33_INSTRUCTIONS.md
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
# PROCITY — Round 33 instructions (from Fable, integrator)
|
||||||
|
|
||||||
|
Date: 2026-07-20 · **R32 closed all green** (7/0/0, matrix 10 towns — THE BIBLE + scarcity live).
|
||||||
|
Round 33 is **v7.0-beta wave 2 — THE HUNT**: the wantlist, the rumor, and travel that costs a
|
||||||
|
day ([V7_THE_GAME.md](../V7_THE_GAME.md) system #6, the last unbuilt beta system). Solo round
|
||||||
|
again (John's "keep going"), same treaty, lane ownership honoured in the commits.
|
||||||
|
|
||||||
|
## The design rulings this round encodes
|
||||||
|
|
||||||
|
1. **A want is something you SAW.** The wantlist grows from the pull card (☆ WANT IT — you
|
||||||
|
pulled it, it's dear, you'll hunt one cheaper) — not from an abstract search box the 90s
|
||||||
|
didn't have. Buying a matching item clears its want automatically (the hunt ends when
|
||||||
|
you're holding it). Wants ride the save (`wants: [{artist?|title?|sku?}]`, optional field,
|
||||||
|
same procity-save/1 back-compat law as `pulls`).
|
||||||
|
2. **The keeper finally gossips** (charter: "D's citizens finally gossip for a reason").
|
||||||
|
Seeded per (citySeed, shop, day): ~45% of shop entries mutter a hunted want + a real cached
|
||||||
|
town in the banner — flavor and a travel nudge, honest about being neither promise nor
|
||||||
|
stock lookup (town-wide packs mean any town CAN surface the record eventually). Runtime
|
||||||
|
stream, freshly keyed — no plan/golden moves. Full persona lines remain D's when the v6
|
||||||
|
clip work lands; the banner is the keeper's voice until then.
|
||||||
|
3. **Travel = the townKey changing hands.** Booting a save in a different town (different
|
||||||
|
plansrc/town/**seed** — a new seed is a new town) costs exactly one day, applied in
|
||||||
|
save.js `adopt()` so load and import both pay it, and pulls prune on the way. Same-town
|
||||||
|
reloads and export→import round trips stay free — the save-determinism gate's byte
|
||||||
|
equality survives untouched. The town selector IS the bus; the arrival toast names the
|
||||||
|
fare. The 23 towns are now a map you spend days moving across, not a dropdown.
|
||||||
|
|
||||||
|
## Gate (F)
|
||||||
|
|
||||||
|
`smoke_wantlist_travel` (STRICT): ☆→★ through the real card → the save carries the want ·
|
||||||
|
a keeper names the hunted artist within the open shops (seeded scan) · buying the wanted
|
||||||
|
sleeve auto-clears it · synthetic→melbourne costs exactly one day with cash/collection intact
|
||||||
|
· same-town reboot is FREE (the control) · the ride home costs a day again · `?game=0` builds
|
||||||
|
zero want DOM and mutters nothing.
|
||||||
|
|
||||||
|
## ROUND CLOSED (Fable) — ALL GREEN
|
||||||
|
|
||||||
|
`qa.sh --strict --matrix`: **7 passed · 0 failed · 0 warn · 0 skipped**, MATRIX 10 towns green
|
||||||
|
(the shared-context day drift travel introduces across matrix towns was reasoned through
|
||||||
|
before landing: determinism double-boots the SAME town, so no gate sees it). The new gate's
|
||||||
|
run: want carried {THE VERANDAHS — "Servo at Midnight"} · rumor after 2 shops ("🗣 THE
|
||||||
|
VERANDAHS? saw one in Castlemaine just last week") · buy cleared the want · travel day 1→2
|
||||||
|
with $179 and the find riding along · same-town free · return trip day 3 · game=0 clean ·
|
||||||
|
0 console errors. Full record: LANE_F_NOTES §33.
|
||||||
|
|
||||||
|
Also caught and fixed here: the R32 crate-in-shops surface only BUILT in street frames — a
|
||||||
|
door entered fast after boot had no game surface inside (the money-shot script entered faster
|
||||||
|
than any gate does and exposed it; tickGame() now builds-or-refreshes in any mode). Money
|
||||||
|
shots: `docs/shots/v7_beta/rumor_and_wantlist.png` + `want_card.png`.
|
||||||
|
|
||||||
|
**What remains of v7:** §31.6-5 (dig Esc in person — John's hands) · then the v7.0 close:
|
||||||
|
GodBay offline auctions (G's lane), THE FIRST FIVE MINUTES (onboarding), the tour, the tag.
|
||||||
57
docs/LANES/ROUND34_INSTRUCTIONS.md
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
# PROCITY — Round 34 instructions (from Fable, integrator)
|
||||||
|
|
||||||
|
Date: 2026-07-20 · **R33 closed all green** — every v7.0-beta system is built. Round 34 opens
|
||||||
|
**the v7.0 close: GODBAY, the offline auction** ([V7_THE_GAME.md](../V7_THE_GAME.md) system #4:
|
||||||
|
"list an item, a SEEDED offline auction resolves NEXT DAY — the come-back-tomorrow hook").
|
||||||
|
Offline tier FIRST per the proven ladder (G3's live rails are v7.x; this round is the seeded
|
||||||
|
brain in the game layer, G's later live tier must match these laws). Solo round (John's "keep
|
||||||
|
rolling").
|
||||||
|
|
||||||
|
## The laws (GODBAY v0 — named now, gated below)
|
||||||
|
|
||||||
|
1. **Mail-in consignment.** You list from THE CRATE (⚖ LIST on a find) — the 90s answer
|
||||||
|
(classifieds + the post). The entry leaves the collection into `listings` (you cannot also
|
||||||
|
sell it at a counter), `listDay` stamped. Resolution happens when the day moves past
|
||||||
|
`listDay` — sleep, travel, or a day-moving import all count (adopt() and sleep() both run
|
||||||
|
the resolver).
|
||||||
|
2. **THE ONE-HAMMER LAW (no retry farming).** The hammer is seeded by ITEM IDENTITY —
|
||||||
|
`godbay:<townKey>:<sku|slotId>:<dayFound>:<pricePaid>` — NOT by the resolution day. A
|
||||||
|
passed-in item re-lists to the SAME result, forever. There is no re-roll anywhere in the
|
||||||
|
economy: the keeper's offer is a formula, the auction is one hammer per item, per life.
|
||||||
|
3. **The house always eats.** Hammer rolls uniform across the band (grail's open top = 2×low);
|
||||||
|
~12% chance of a bidding war (×1.5–2.0); ~15% passed in (returns to the crate, free);
|
||||||
|
12% commission, net floor $1. Expected value ≈ 0.8× band midpoint — UNDER the asking-price
|
||||||
|
EV of in-band stock, so random flipping bleeds and only FINDS profit (charter law #2's
|
||||||
|
spirit at the auction house: growth bounded by finds, the keeper's instant half-of-low
|
||||||
|
remains the coward's floor and the auction the gambler's ceiling — bounded at 2×high×0.88).
|
||||||
|
4. **Delta law / classic purity unchanged.** `listings` is an optional procity-save/1 field
|
||||||
|
(same back-compat law as pulls/wants); no game layer ⇒ no listings, no DOM, no news.
|
||||||
|
5. **The morning report.** Resolution queues transient news ("⚖ SOLD 'Sunburnt' — hammer $38,
|
||||||
|
$33 after fees"); the shell folds it into the wake/arrival toast. News is NOT saved — it's
|
||||||
|
the morning paper, not the ledger (cash/collection already carry the truth).
|
||||||
|
|
||||||
|
## Gate (F): `smoke_godbay` (STRICT)
|
||||||
|
|
||||||
|
List through the real crate DOM (C → ⚖ LIST) → entry moves collection→listings, the save
|
||||||
|
carries it · sleep → resolved: listings empty, cash+net OR item returned, news queued, net
|
||||||
|
within [1, 2.2×bandHigh] when sold · DETERMINISM: a pre-sleep export imported twice sleeps to
|
||||||
|
byte-identical cash/collection both times (the seeded hammer, measured not trusted) · travel
|
||||||
|
resolves too (list → ride to melbourne → resolved on arrival) · `?game=0` builds zero ⚖ DOM ·
|
||||||
|
0 console errors.
|
||||||
|
|
||||||
|
## ROUND CLOSED (Fable) — ALL GREEN
|
||||||
|
|
||||||
|
`qa.sh --strict --matrix` on the final tree: **7 passed · 0 failed · 0 warn · 0 skipped**,
|
||||||
|
MATRIX 10 towns green. `smoke_godbay`: consigned through the real crate DOM (collection 2→1,
|
||||||
|
listDay stamped) · ONE-HAMMER determinism (a frozen pre-sleep export slept twice → byte-
|
||||||
|
identical) · honest bounded resolution (SOLD net $19 on a standard record, ≤ 2×$24×0.88) ·
|
||||||
|
the wake toast carried "⚖ SOLD 'Cane Toad Blues' — hammer $22, $19 after fees" · the ride to
|
||||||
|
melbourne hammered a listing on arrival · `?game=0` has no crate to list from · 0 console
|
||||||
|
errors. Design note vs the brief: the band table is IMPORTED from C's bible.js and the PRNG
|
||||||
|
from core/prng.js (one authority each — the drafted mirror died in review). Money shots:
|
||||||
|
`docs/shots/v7_beta/godbay_morning_paper.png` (dawn day 2, the paper in the toast, the crate's
|
||||||
|
⚖ row) + `godbay_at_auction.png`. Full record: LANE_F_NOTES §34.
|
||||||
|
|
||||||
|
**What remains of v7.0:** THE FIRST FIVE MINUTES (onboarding — arrive with a want, three
|
||||||
|
toast-beats) · the tour ("day one to first gem") · docs freeze · the tag. Then John's playtest
|
||||||
|
charters what's next.
|
||||||
36
docs/LANES/ROUND35_INSTRUCTIONS.md
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
# PROCITY — Round 35 instructions (from Fable, integrator)
|
||||||
|
|
||||||
|
Date: 2026-07-20 · **R34 closed all green** (GODBAY hammers). Round 35 is **THE FIRST FIVE
|
||||||
|
MINUTES** ([V7_THE_GAME.md](../V7_THE_GAME.md) system #7 — "smallest system, biggest
|
||||||
|
stranger-facing payoff"), the last unbuilt v7.0 system before the tour/freeze/tag.
|
||||||
|
|
||||||
|
## What ships
|
||||||
|
|
||||||
|
1. **You arrive with a want.** A FRESH game (day 1, empty crate, empty auction house) seeds ONE
|
||||||
|
artist want off `rng(citySeed,'firstwant',0)` × C's parody roster (ARTISTS now exported) —
|
||||||
|
so the splash, the keeper rumors, and the tour all point at the same hunt on a given seed.
|
||||||
|
The splash names it: "You're hunting HILLS HOIST — $191 in your pocket." A returning save
|
||||||
|
gets its town back, not a tutorial; ?game=0 / classic never reach the block.
|
||||||
|
2. **Three toast-beats walk the hustle** (fresh games only): ① find a record shop / E at a
|
||||||
|
crate (on walk-in) → ② riffle · mind the guide · BUY (first digOpen) → ③ sell at a counter
|
||||||
|
or ⚖ LIST and sleep (first find banked) → the send-off ("that's the whole hustle") on the
|
||||||
|
first sale-or-listing. Driven off existing events + wallet.onChange — zero new systems.
|
||||||
|
3. **Toasts surface in every mode** — #pc-toast moved to document.body (the #pc-game body-mount
|
||||||
|
rule): beats fire INSIDE shops, and a SLEEP taken at a counter now shows its morning paper.
|
||||||
|
|
||||||
|
## Gate (F): `smoke_first_five` (STRICT)
|
||||||
|
|
||||||
|
Fresh game: 1 seeded want from the roster + the splash names hunt AND cash · beats ②/③/send-off
|
||||||
|
through the REAL paths (E at crate / dig buy / crate ⚖) · a returning save gets NO tutorial
|
||||||
|
(splash line hidden, no ② on dig) · a second fresh browser hunts the SAME artist (seeded, not
|
||||||
|
random) · ?game=0 shows nothing · 0 console errors.
|
||||||
|
|
||||||
|
## ROUND CLOSED (Fable) — ALL GREEN
|
||||||
|
|
||||||
|
`qa.sh --strict --matrix`: 7 passed · 0 failed · 0 warn · 0 skipped, MATRIX 10 towns green
|
||||||
|
(final tree). Gate run: hunts HILLS HOIST with $191, beats walked through the real dig/buy/list
|
||||||
|
paths, returning-save and ?game=0 controls clean, second fresh browser hunts the same artist,
|
||||||
|
0 console errors. Money shot: `docs/shots/v7_beta/first_five_splash.png`.
|
||||||
|
|
||||||
|
**v7.0 remaining:** the tour ("day one to first gem") · docs freeze · the tag — and the tag
|
||||||
|
waits for John's playtest (the alpha precedent: hands before tags).
|
||||||
@ -1,5 +1,10 @@
|
|||||||
# PROCITY — Round 7 lane instructions (from Fable, integrator)
|
# PROCITY — Round 7 lane instructions (from Fable, integrator)
|
||||||
|
|
||||||
|
> **⚠ SUPERSEDED by [ROUND8_INSTRUCTIONS.md](ROUND8_INSTRUCTIONS.md) (2026-07-14).** Round 7 is
|
||||||
|
> complete — roster flipped to default, `?stock=real` live, harness strict, v2.0-beta tagged
|
||||||
|
> (`4feba02`). B (weather) and A (osm parity) slipped — round 8 carries both §Lane A and §Lane B
|
||||||
|
> verbatim; everything else is historical reference only.
|
||||||
|
|
||||||
Date: 2026-07-14 · Written after the Fable round-6 review (`6948372`) and the **v2.0-alpha
|
Date: 2026-07-14 · Written after the Fable round-6 review (`6948372`) and the **v2.0-alpha
|
||||||
tag**. You are an Opus 4.8 agent assigned ONE lane (read your session prompt). Read this
|
tag**. You are an Opus 4.8 agent assigned ONE lane (read your session prompt). Read this
|
||||||
file, your lane spec, your `<X>-progress.md`, and NOTES addressed to you first.
|
file, your lane spec, your `<X>-progress.md`, and NOTES addressed to you first.
|
||||||
|
|||||||
197
docs/LANES/ROUND8_INSTRUCTIONS.md
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
# PROCITY — Round 8 lane instructions (from Fable, integrator)
|
||||||
|
|
||||||
|
> **⚠ SUPERSEDED by [ROUND9_INSTRUCTIONS.md](ROUND9_INSTRUCTIONS.md) (2026-07-14).** Round 8 is
|
||||||
|
> complete with NO slips — weather, Katoomba, 3 stock packs, buy loop, patronage all live;
|
||||||
|
> v2.0-rc tagged (`9cc00ad`) and pushed. Historical reference only.
|
||||||
|
|
||||||
|
Date: 2026-07-14 · Written after the Fable round-7 review (`16d4f2b`) and the **v2.0-beta
|
||||||
|
tag** (`4feba02`). You are an Opus 4.8 agent assigned ONE lane (read your session prompt).
|
||||||
|
Read this file, your lane spec, your `<X>-progress.md`, and NOTES addressed to you first.
|
||||||
|
|
||||||
|
Repo state: everything committed on `main` at `16d4f2b`. `tools/qa.sh --strict` GREEN 5/5
|
||||||
|
with the flags harness **STRICT**. **The streamed roster is the DEFAULT** (`?roster=v1` is
|
||||||
|
the escape hatch; baseline re-pinned 164 draws / 66k tris; plan goldens frozen at
|
||||||
|
`0x3fa36874` synthetic / `0x34cfdec0` osm-Melbourne). `?stock=real` puts real GODVERSE
|
||||||
|
sleeves in record-shop crates and digs. John is pushing main + v1.0/v1.1/v2.0-alpha/
|
||||||
|
v2.0-beta to origin. fal balance ~$21.36.
|
||||||
|
|
||||||
|
## Theme: catch-up + the town comes alive
|
||||||
|
|
||||||
|
Round 7 turned the two big keys but **two lanes slipped**: B (weather) and A (osm parity)
|
||||||
|
have no round-7 work. Round 8 is those debts plus the "living town" tier that the flip and
|
||||||
|
the stock pack unlock:
|
||||||
|
|
||||||
|
- **A and B execute their carried tasks** — B's weather is now also a dependency for D.
|
||||||
|
- **C widens real stock** (book/toy) and gives the dig a working **buy loop v0**.
|
||||||
|
- **D makes crowds react** — weather + shop patronage (peds actually enter shops).
|
||||||
|
- **E feeds packs** and stays the asset kitchen.
|
||||||
|
- **F graduates the new smokes to strict** and tags **`v2.0-rc`**.
|
||||||
|
|
||||||
|
A slip note (this applies to everyone): a lane that can't finish its round MUST commit a
|
||||||
|
partial + write the cut line in its progress doc before the session ends. Round 7's slips
|
||||||
|
were silent again — the schedule harness (F) catches them at round end, but a one-line
|
||||||
|
"stopped at X because Y" turns a re-derive into a resume.
|
||||||
|
|
||||||
|
## Prime law (post-amendment state)
|
||||||
|
|
||||||
|
Default boot = streamed roster at the re-pinned baseline; plan goldens frozen; everything
|
||||||
|
NEW still lands behind a default-off flag; flags compose; `qa.sh --strict` green at every
|
||||||
|
commit. The flags harness is strict — if your feature leaks into the default path, qa fails
|
||||||
|
and it's yours to fix.
|
||||||
|
|
||||||
|
## Integrator decisions (so nobody blocks)
|
||||||
|
|
||||||
|
1. **B goes first on weather** (carried R7 brief, unchanged — ROUND7 §Lane B): D's crowd
|
||||||
|
reactions consume `window.PROCITY.weather`, so B1 landing early unblocks D2. If B slips
|
||||||
|
again, D ships patronage only (hours-based) and weather-reaction carries — do not fake a
|
||||||
|
weather source.
|
||||||
|
2. **Buy loop v0 scope (C)**: in-memory only. Player cash starts at a seeded-per-town float
|
||||||
|
(e.g. $40–60), HUD chip, `onBuy` deducts price + removes the sleeve from the bin +
|
||||||
|
adds to a simple inventory list (I key). NO persistence (no localStorage — a page reload
|
||||||
|
resets; that's fine for v0), no selling, no economy sim. Deterministic world, free-agent
|
||||||
|
player: purchases mutate live room state only, never generation.
|
||||||
|
3. **Pack expansion (E)**: book spines + toy boxes packs, same pipeline/laws as the record
|
||||||
|
pack (build-time extract, parody-transform, atlas + index, depot publish, provenance).
|
||||||
|
Target ~150–250 items each; reuse C's schema (already proven); C wires shelves.
|
||||||
|
4. **Patronage v0 scope (D)**: streamed peds occasionally path to an OPEN shop's door,
|
||||||
|
pause, "enter" (despawn with a door-swing tell or fade), re-emerge after seeded dwell.
|
||||||
|
No interior presence (interiors stay player-only this round), no queues. Respect hours
|
||||||
|
(closed shops get no visitors; the openLate video shop gets night traffic).
|
||||||
|
5. **Audio stays parked** (needs John's sourcing decision — flagging it: one line from John
|
||||||
|
unlocks street ambience + door bell, the V2_IDEAS "greenfield" item).
|
||||||
|
6. **Tag `v2.0-rc`** (F, end of round) when: weather + patronage + buy-v0 + ≥1 new pack
|
||||||
|
landed and smoke-green, stock/weather smokes graduated strict, qa strict green. Same
|
||||||
|
no-hostage rule: tag what landed, note what slipped.
|
||||||
|
|
||||||
|
## Process rules (unchanged)
|
||||||
|
|
||||||
|
- ONE shared working tree; stage only your files by exact path; options before `--`;
|
||||||
|
`add` new files first; pin a durable lane ref; leave qa GREEN; progress doc + NOTES.
|
||||||
|
- F owns `web/index.html` + `tools/`; document call sites for F.
|
||||||
|
- `procityprogress.txt` / `settings.local.json` stay untracked; `pipeline/_stockpack/`
|
||||||
|
and staging dirs are gitignored — keep build output out of the repo.
|
||||||
|
- Repo `/Users/m3ultra/Documents/procity`; GODVERSE at `~/Documents/dealgod`; dev `:8130`.
|
||||||
|
|
||||||
|
## Sequencing
|
||||||
|
|
||||||
|
```
|
||||||
|
B1 (weather, carried) ──────────→ D2 (weather-reactive crowds) ─┐
|
||||||
|
E1 (book/toy packs) ──→ C2 (wire shelves) ──┐ │
|
||||||
|
C1 (buy loop v0) ── independent ─────────────┼──→ F1 (strict graduations + new smokes)
|
||||||
|
A1 (osm parity + 2nd town, carried) ─────────┘ └──→ F2 (tag v2.0-rc)
|
||||||
|
D1 (patronage, hours-based) ── independent of B
|
||||||
|
```
|
||||||
|
B and E start immediately (both feed others). C1 and D1 need nobody.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Lane A (Citygen) — carried round-7 brief, unchanged
|
||||||
|
|
||||||
|
Execute ROUND7_INSTRUCTIONS.md §Lane A verbatim. Summary: (1) selfcheck parity for osm
|
||||||
|
plans (full invariant suite runs on both sources; F pins both goldens); (2) a second
|
||||||
|
fixture town (`?plansrc=osm&town=<key>`, checked-in module, zero network, golden pinned —
|
||||||
|
pick a small coastal/regional strip for contrast with inner-Melbourne); (3) the "add a
|
||||||
|
town" recipe in LANE_A_NOTES; (4) importer normalizes real-world data to the contracts
|
||||||
|
(exactly-one openLate etc.) and logs what it changed.
|
||||||
|
|
||||||
|
This brief has now slipped twice (R7; and R4–R5 for its predecessor). If a full session
|
||||||
|
isn't available, land ANY complete subset with a cut-line note — parity alone is a fine
|
||||||
|
round.
|
||||||
|
|
||||||
|
**Acceptance**: full selfcheck green on every (source, town); second town boots end-to-end
|
||||||
|
(or a documented cut); goldens pinned; qa green.
|
||||||
|
|
||||||
|
## Lane B (Streetscape) — carried round-7 brief: weather
|
||||||
|
|
||||||
|
Execute ROUND7_INSTRUCTIONS.md §Lane B verbatim. Summary: `?weather=1` — seeded per
|
||||||
|
(citySeed, day) state (clear/overcast/rain) exposed at `window.PROCITY.weather`; rain
|
||||||
|
particles (ONE draw), wet-ground response, matching sky dome from the existing set; wind
|
||||||
|
sway if cheap; ≤300 draws worst view; composes with winmap/night (rain-through-lit-window
|
||||||
|
screenshot); flag-off identical. Tram (`?tram=1`) remains the stretch, only after weather.
|
||||||
|
|
||||||
|
**New this round**: D consumes `window.PROCITY.weather` (decision #1) — expose it early
|
||||||
|
(even while polishing visuals) and document the shape in LANE_B_NOTES: at minimum
|
||||||
|
`{ state: 'clear'|'overcast'|'rain', intensity: 0..1 }`, stable per day segment.
|
||||||
|
|
||||||
|
**Acceptance**: as ROUND7 §Lane B, plus the documented `PROCITY.weather` contract; qa green.
|
||||||
|
|
||||||
|
## Lane C (Interiors) — buy loop v0 + wire the new packs
|
||||||
|
|
||||||
|
1. **C1 — buy loop v0** (decision #2, in the dig, works with parody AND real stock):
|
||||||
|
seeded starting cash → HUD chip (street + interior); pulling the front sleeve and
|
||||||
|
confirming buys it: cash decreases by the sleeve's price, sleeve leaves the bin (bin
|
||||||
|
mesh updates — your batching keeps dug bins addressable, use that), inventory list on
|
||||||
|
I. Broke = can't buy (a "not enough cash" tell). In-memory only; reload resets.
|
||||||
|
Leak/determinism laws: world generation unchanged (purchases are runtime state);
|
||||||
|
dig soak extended to buy 2 items per visit — no leaks, ≤350 draws.
|
||||||
|
2. **C2 — wire book spines + toy boxes** when E1's packs land: `?stock=real` extends to
|
||||||
|
book/toy shelves through the same stockAdapter path (static shelf stock; digging other
|
||||||
|
shop types is NOT this round). Fail-soft per pack (record pack present but book missing
|
||||||
|
→ records real, books parody).
|
||||||
|
3. Keep the pack schema doc current if E's new packs need any field additions.
|
||||||
|
|
||||||
|
**Acceptance**: buying works end-to-end in a dig (cash/HUD/inventory/bin-update), soak
|
||||||
|
green with purchases; book+toy shelves show real stock when packs present, fail-soft
|
||||||
|
otherwise; ≤350 draws; qa green.
|
||||||
|
|
||||||
|
## Lane D (Citizens) — the crowd comes alive
|
||||||
|
|
||||||
|
1. **D1 — shop patronage v0** (decision #4, independent of B): streamed peds occasionally
|
||||||
|
select an OPEN shop within their chunk, path to its door, pause, despawn-with-tell,
|
||||||
|
re-emerge after a seeded dwell. Hours-aware: closed = no visitors; night = the openLate
|
||||||
|
video shop is the only draw. Deterministic per seed+time; budget-neutral (it's the same
|
||||||
|
peds, different paths); leak-free (despawn/respawn through your existing lifecycle).
|
||||||
|
Behind `?patronage=0` OFF-switch — patronage is default-ON for streamed rosters (it's
|
||||||
|
crowd behavior, not a new system; the flags-off baseline may move a hair — coordinate
|
||||||
|
the re-pin with F if draws shift, same protocol as the flip).
|
||||||
|
2. **D2 — weather reaction** (after B1 exposes `PROCITY.weather`): rain → density drops
|
||||||
|
(~40–60%), walk speed up slightly, more peds choose shop doors (shelter patronage);
|
||||||
|
clear → v1 behavior. Read the contract from LANE_B_NOTES; do not import B's modules.
|
||||||
|
3. Re-run the roster soak with patronage on (leak-free over the despawn/respawn cycles).
|
||||||
|
|
||||||
|
**Acceptance**: peds visibly enter/leave open shops day-long and cluster on the video shop
|
||||||
|
at night; deterministic + leak-free + budget-neutral; rain thins and redirects the crowd
|
||||||
|
(or documented as carried if B slips); qa green.
|
||||||
|
|
||||||
|
## Lane E (Assets) — pack kitchen
|
||||||
|
|
||||||
|
1. **E1 — book spines + toy boxes packs** (decision #3): same pipeline as the record pack
|
||||||
|
(`build_stock_pack.py` — generalize it rather than copying), build-time extract from
|
||||||
|
the GODVERSE datasets (bookgod/toygod per GODVERSE.md), parody-transform, ~150–250
|
||||||
|
items each, atlas + index to C's schema, depot publish + local stage, provenance in
|
||||||
|
AUDIT.md. Book spines are TALL-thin (different atlas cell aspect) — coordinate the
|
||||||
|
cell geometry with C before baking.
|
||||||
|
2. **E2 — pack QA**: extend your provenance-drift gate (or add a small check) to validate
|
||||||
|
pack indexes — every entry has atlas+UV in range, price band sane, no empty titles —
|
||||||
|
so a bad bake fails qa instead of rendering blank sleeves.
|
||||||
|
3. **On-call**: B may want a rain-streak/puddle texture (procedural first, flux_local if
|
||||||
|
truly needed — free); D may want a door-swing tell sprite (same rule).
|
||||||
|
4. No fal spends (broken-prop clause stands). Audio assets stay parked (decision #5).
|
||||||
|
|
||||||
|
**Acceptance**: two new packs live (depot + staged), indexes validate in qa, C consumes
|
||||||
|
them; pack-QA gate green; provenance logged; qa green.
|
||||||
|
|
||||||
|
## Lane F (Integration) — graduate, marshal, tag
|
||||||
|
|
||||||
|
1. **F1 — smoke graduations + additions**: `stock=real` and (when B lands) `weather=1`
|
||||||
|
graduate warn → strict. New smokes at warn: buy-v0 (open dig, buy 1 item, assert cash
|
||||||
|
decreased + bin count dropped, 0 errors), patronage (advance time, assert ≥1 ped
|
||||||
|
despawns at an open shop door over N seconds), book/toy stock (enter book shop with
|
||||||
|
`?stock=real`, assert real spine meshes), 2nd osm town if A lands (pin its golden).
|
||||||
|
2. **F2 — patronage default-on baseline**: if D1 moves the flags-off draws/tris beyond
|
||||||
|
tolerance, re-pin with the same one-commit protocol as the flip (old values in the
|
||||||
|
message). Plan goldens still frozen.
|
||||||
|
3. **F3 — tag `v2.0-rc`** (decision #6 criteria; no-hostage rule). Update flags table +
|
||||||
|
runbook; note anything carried. Remind John of unpushed refs if any remain.
|
||||||
|
4. **Marshal**: B and A are on carried briefs — check for their commits mid-round; if
|
||||||
|
either stalls again, note it in F-progress the moment you see it, not at tag time.
|
||||||
|
|
||||||
|
**Acceptance**: graduated smokes strict + green; new smokes running at warn; baseline
|
||||||
|
re-pin (if needed) done by protocol; `v2.0-rc` tagged on a green tree; runbook current.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*— Fable (integrator). The machine is built and strict; this round is about the town
|
||||||
|
feeling inhabited — rain on the awnings, peds ducking into shops, and walking out of a
|
||||||
|
dig with a record you paid for. Commit atomically, pin your refs, leave it green — and if
|
||||||
|
you must stop early, say where and why.*
|
||||||
201
docs/LANES/ROUND9_INSTRUCTIONS.md
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
# PROCITY — Round 9 lane instructions (from Fable, integrator)
|
||||||
|
|
||||||
|
Date: 2026-07-14 · Written after the Fable round-8 review. You are an Opus 4.8 agent
|
||||||
|
assigned ONE lane (read your session prompt). Read this file, your lane spec, your
|
||||||
|
`<X>-progress.md`, and NOTES addressed to you first.
|
||||||
|
|
||||||
|
Repo state: `main` at `9cc00ad` = `v2.0-rc`, **fully pushed** (origin synced, all five
|
||||||
|
version tags live). `tools/qa.sh --strict` GREEN 5/5 with **zero warnings** — every smoke
|
||||||
|
incl. the warn-level ones (buy, patronage, book/toy stock, katoomba) is green. Round 8 had
|
||||||
|
**no slips**: weather, Katoomba, three real-stock packs (record 350 / book 311 / toy 273),
|
||||||
|
buy loop, patronage all live. Goldens: synthetic `0x3fa36874` · melbourne `0x34cfdec0` ·
|
||||||
|
katoomba `0x0f652510`. fal ~$21.36.
|
||||||
|
|
||||||
|
## Theme: ship v2.0
|
||||||
|
|
||||||
|
The rc is healthy and warning-free. Round 9 hardens it into **`v2.0`**: graduate every
|
||||||
|
smoke to strict, make the crowd visible *inside* shops, widen buying beyond the dig,
|
||||||
|
re-shoot the tour so it shows the living town, and freeze the docs. One new toy (B's tram)
|
||||||
|
rides along but does NOT block the tag.
|
||||||
|
|
||||||
|
What v2.0 means when tagged: seed → real-or-synthetic AU town → streamed streets with
|
||||||
|
weather and a living crowd that shops → every door opens → real browsable stock you can
|
||||||
|
buy with a wallet → all of it deterministic, leak-free, budget-clean, and enforced by a
|
||||||
|
strict harness.
|
||||||
|
|
||||||
|
## Prime law (unchanged post-amendment)
|
||||||
|
|
||||||
|
Default boot = streamed roster + patronage at the pinned baseline; plan goldens frozen;
|
||||||
|
new features behind default-off flags; flags compose; qa strict green at every commit.
|
||||||
|
Baseline re-pins only by the one-commit protocol.
|
||||||
|
|
||||||
|
## Integrator decisions (so nobody blocks)
|
||||||
|
|
||||||
|
1. **Interior presence (the round's one cross-lane seam — C↔D, F wires).** Peds who duck
|
||||||
|
into a shop should be *there* when the player follows. Contract: D tracks per-shop
|
||||||
|
occupancy (who's inside + their identity seeds, already implied by patronage state);
|
||||||
|
C exposes 1–3 **browse points** per room in the room API (aisle spots, like the keeper's
|
||||||
|
counter spot); F passes occupancy → interior build so browser rigs (KeeperManager-style
|
||||||
|
fleet clones, idle/browse pose) stand at browse points. Cap 3/room; ≤350 draws holds;
|
||||||
|
leak-free like keepers (proven pattern). D owns occupancy truth, C owns points + room
|
||||||
|
fit, F owns the handoff. Agree the two interfaces in NOTES before code (half-day
|
||||||
|
handshake rule).
|
||||||
|
2. **Buy-anywhere scope (C):** book/toy shelves get pull-and-buy (aim at a real-stock
|
||||||
|
spine/box → pull-out card with title/price → BUY via the same wallet). No new dig
|
||||||
|
mechanics — reuse the pull-panel pattern. Record digs stay the deep interaction.
|
||||||
|
3. **Tram (B): now a real task, but explicitly NON-BLOCKING for v2.0** (no-hostage rule
|
||||||
|
pre-applied). `?tram=1`: one instanced tram on a seeded main-street loop over the
|
||||||
|
bus_shelter stops, door-pause at each, budget-neutral (≤2 draws). If it lands, it
|
||||||
|
enters the harness at warn and the tour gets a shot.
|
||||||
|
4. **Audio stays parked** — STANDING ASK TO JOHN (third round running): one decision line
|
||||||
|
("generate ambience freely" / "use library X" / "skip for v2") unlocks street ambience,
|
||||||
|
rain-on-roof, and the milk-bar door bell. Until then no lane touches audio.
|
||||||
|
5. **fal: no new spends** (broken-prop clause stands).
|
||||||
|
6. **Tag `v2.0`** (F, end of round) when: all round-8 warn smokes strict + green ·
|
||||||
|
interior presence live + smoked · buy-anywhere live + smoked · v2 tour re-shot ·
|
||||||
|
full-defaults soak green · docs frozen. Tram/third-town slippage does not block.
|
||||||
|
This is the epoch tag — after it, Fable + John pick the v3 epoch (editor/overrides vs
|
||||||
|
venue district vs multiplayer presence — V2_IDEAS).
|
||||||
|
|
||||||
|
## Process rules (unchanged)
|
||||||
|
|
||||||
|
- ONE shared working tree; exact-path staging; options before `--`; new files `add`ed
|
||||||
|
first; durable lane refs; qa green at every commit; progress docs + NOTES.
|
||||||
|
- F owns `web/index.html` + `tools/`; document call sites for F.
|
||||||
|
- Slip rule: partial commit + cut-line note, never silent.
|
||||||
|
- `procityprogress.txt` / `settings.local.json` untracked; staging dirs gitignored.
|
||||||
|
- Repo `/Users/m3ultra/Documents/procity`; dev `:8130`.
|
||||||
|
|
||||||
|
## Sequencing
|
||||||
|
|
||||||
|
```
|
||||||
|
C1 (browse points) ──┬──→ D1 (occupancy + browsers) ──→ F1 (wire handoff + smoke)
|
||||||
|
└──→ C2 (buy-anywhere)
|
||||||
|
B1 (tram, non-blocking) · A1 (contract freeze + docs) · E1 (VRAM audit) — parallel
|
||||||
|
all land ──→ F2 (graduate smokes strict) ──→ F3 (tour + soak) ──→ F4 (tag v2.0)
|
||||||
|
```
|
||||||
|
C starts first (both seams hang off the room API). F1 needs C1+D1.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Lane A (Citygen) — freeze the contract, polish the docs
|
||||||
|
|
||||||
|
Small round — you're the most "done" lane; make it official.
|
||||||
|
|
||||||
|
1. **CITY_SPEC v2 contract freeze**: document the final CityPlan producer contract as
|
||||||
|
built — both sources, the town-key mechanism, the goldens table (synthetic/melbourne/
|
||||||
|
katoomba + how to pin a new one), the normalization rules the osm importer applies,
|
||||||
|
the openLate/hours/storeys laws. This becomes the reference for the v3 epoch (the
|
||||||
|
editor/override layer reads THIS contract).
|
||||||
|
2. **Recipe sanity**: dry-run your own "add a town" recipe against the doc (don't ship a
|
||||||
|
third town unless it falls out for free — if it does, pin its golden and tell F).
|
||||||
|
3. **On-call** for D's patronage/occupancy questions (shop doors, hours edge cases —
|
||||||
|
e.g. what happens to occupants at closing time; give D a ruling and document it).
|
||||||
|
|
||||||
|
**Acceptance**: CITY_SPEC v2 section complete + matches reality (F will diff claims vs
|
||||||
|
code in review); closing-time ruling documented; qa green.
|
||||||
|
|
||||||
|
## Lane B (Streetscape) — the tram (non-blocking) + tour poses
|
||||||
|
|
||||||
|
1. **`?tram=1`** (decision #3): one instanced tram/small bus, seeded main-street loop
|
||||||
|
over your bus_shelter stops, door-pause dwell at each stop, bell-ready (no audio yet).
|
||||||
|
Deterministic (same seed → same schedule), budget ≤2 draws, composes with weather/
|
||||||
|
night (headlights at night if cheap). Flag-off identical. It slipping does NOT block
|
||||||
|
v2.0 — commit partial + cut line if needed.
|
||||||
|
2. **v2 tour bookmarks** (F shoots this round — do early): add named poses for the new
|
||||||
|
life — `rain_verandah` (rain through a lit winmap window), `patronage_door` (peds at a
|
||||||
|
shop door), `dig_real` (a bin of real covers), `katoomba_main` (2nd town), `tram_stop`
|
||||||
|
(if tram lands), plus your judgment for 2–3 more. Verify via `shots.py`.
|
||||||
|
3. **Weather polish debt** (only if quick): any visual nits from your own R8 notes.
|
||||||
|
|
||||||
|
**Acceptance**: tram loops deterministically under budget (or documented cut); tour poses
|
||||||
|
live + un-letterboxed; qa green.
|
||||||
|
|
||||||
|
## Lane C (Interiors) — browse points + buy-anywhere
|
||||||
|
|
||||||
|
1. **C1 — browse points (DO FIRST — D and F build on it).** Room API: 1–3 seeded browse
|
||||||
|
points per room (position + facing, aisle-adjacent, never blocking the door or the
|
||||||
|
counter), exposed like the keeper's counter spot. Deterministic per shop.seed; works
|
||||||
|
in every archetype (drawSweep proves no collisions with fittings). Document the
|
||||||
|
interface in LANE_C_NOTES *first* (decision #1 handshake).
|
||||||
|
2. **C2 — buy-anywhere (decision #2):** aiming at a real-stock spine/box on book/toy
|
||||||
|
shelves raises the pull-out card (title/artist-or-maker/price from the pack index);
|
||||||
|
BUY debits the wallet, removes the item from the shelf (your batched-stock
|
||||||
|
addressability from R6 — same trick as dug bins), adds to inventory. Parody stock =
|
||||||
|
not buyable (card shows "not for sale" or simply no card — your call, document it).
|
||||||
|
Fail-soft, deterministic, leak-free, ≤350 draws; extend the soak to buy 1 shelf item
|
||||||
|
per room type.
|
||||||
|
3. **Support F1**: when browsers spawn at your points, verify keeper/stock/dig all still
|
||||||
|
compose in a room WITH browsers (the soak covers it).
|
||||||
|
|
||||||
|
**Acceptance**: browse points in every archetype, documented, D/F consuming; shelf
|
||||||
|
buying works end-to-end with the wallet; soaks green; ≤350; qa green.
|
||||||
|
|
||||||
|
## Lane D (Citizens) — occupancy truth + browsers inside
|
||||||
|
|
||||||
|
1. **D1 — occupancy + browser rigs (after C1's interface note).** Track per-shop
|
||||||
|
occupancy from patronage (who ducked in: identity seed + entry time + seeded dwell).
|
||||||
|
When the player enters an occupied shop, browser rigs (fleet clones, KeeperManager-
|
||||||
|
style lifecycle — the leak-proven pattern) stand/idle at C's browse points, matching
|
||||||
|
occupancy count (cap 3, decision #1). They exit on dwell-expiry (walk-out tell if
|
||||||
|
cheap, despawn otherwise); at closing time follow A's ruling. Deterministic: same
|
||||||
|
seed + same time → same occupants. Leak-free over N enter/exit cycles (extend your
|
||||||
|
soak). Coordinate the F-facing handoff shape (occupancy query per shopId) in NOTES.
|
||||||
|
2. **D2 — street↔interior consistency tells**: the ped who ducked in shouldn't ALSO be
|
||||||
|
visible on the street (your despawn already handles it); re-emergence spawns at the
|
||||||
|
door. Verify with patronage + occupancy both on.
|
||||||
|
3. Budget: browsers ride the interior ≤350 law (C's sweep with browsers on proves it).
|
||||||
|
|
||||||
|
**Acceptance**: follow a ped into a shop and find them browsing; count matches
|
||||||
|
occupancy; deterministic + leak-free + ≤350 with browsers; closing-time behavior per A's
|
||||||
|
ruling; qa green.
|
||||||
|
|
||||||
|
## Lane E (Assets) — the VRAM/texture audit + on-call
|
||||||
|
|
||||||
|
1. **E1 — memory audit (the ship-round due diligence).** Nobody has measured total
|
||||||
|
texture/VRAM weight since the packs landed. Audit: all atlases (3 stock packs), skins,
|
||||||
|
facade atlas, ped fleet, GLB textures — sizes on disk + estimated GPU memory + what's
|
||||||
|
resident in a default boot vs `?stock=real` boot vs worst case (all flags). Numbers in
|
||||||
|
AUDIT.md + a one-para verdict (fine / needs compression pass / needs lazy-load).
|
||||||
|
If a cheap win exists (e.g. a pack atlas that should be WebP/half-res), take it —
|
||||||
|
bigger restructuring gets flagged for v3, not done now.
|
||||||
|
2. **E2 — depot + manifest hygiene**: `validate --depot` + pack-QA + provenance-drift all
|
||||||
|
green post-R8 (they should be — confirm); prune any orphaned depot files your audit
|
||||||
|
finds (list first in NOTES, delete only your own procity_* uploads).
|
||||||
|
3. **On-call**: B may want a tram texture/GLB (procedural/primitive first — a box tram
|
||||||
|
with the right silhouette beats a heavy model; flux/local gen only if it truly reads
|
||||||
|
wrong); C may want a "browse pose" check on fleet clips (that's D's domain — redirect).
|
||||||
|
|
||||||
|
**Acceptance**: audit published with numbers + verdict; cheap wins taken; depot hygiene
|
||||||
|
confirmed; qa green.
|
||||||
|
|
||||||
|
## Lane F (Integration) — graduate, shoot, freeze, tag
|
||||||
|
|
||||||
|
1. **F1 — interior-presence wiring (after C1+D1):** occupancy query → interior build →
|
||||||
|
browser spawn at browse points; smoke at warn (enter occupied shop, assert browser
|
||||||
|
count > 0 matching occupancy, 0 errors, dispose clean on exit).
|
||||||
|
2. **F2 — graduate round-8 smokes to strict**: buy-v0, patronage, book/toy stock,
|
||||||
|
katoomba golden. New this round at warn: interior presence, shelf-buy, tram (if B
|
||||||
|
lands). Everything green before F3.
|
||||||
|
3. **F3 — the v2 tour + full-defaults soak**: re-shoot `docs/shots/v2_tour/` (12 shots:
|
||||||
|
B's new poses + the classics re-taken on current defaults — busy street, rain money
|
||||||
|
shot, dig with real covers, browsers in a shop, katoomba, night video-shop crowd,
|
||||||
|
tram if landed) + contact sheet. Full soak on pure defaults (no flags) + the all-on
|
||||||
|
combo; both leak-free, 0 errors.
|
||||||
|
4. **F4 — docs freeze + tag `v2.0`** (decision #6): runbook gate table updated to the v2
|
||||||
|
reality; flags table final (what's default, what's flagged, what graduated);
|
||||||
|
README/CITY_SPEC claims match code (diff A's contract freeze vs reality as part of
|
||||||
|
review); F-progress round-9 section. Tag with a proper epoch summary. Local tag —
|
||||||
|
John pushes (the push pattern is proven now).
|
||||||
|
5. **Marshal**: C1 is the round's single point of failure (two lanes build on it) —
|
||||||
|
check for its commit early; if C stalls, escalate in F-progress immediately.
|
||||||
|
|
||||||
|
**Acceptance**: presence wired + smoked; all r8 smokes strict; tour + soaks green;
|
||||||
|
docs frozen; `v2.0` tagged on an all-green tree.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*— Fable (integrator). Nine rounds from empty repo to this: a seed becomes a town where
|
||||||
|
it rains, the crowd shops, and you can follow someone into a record store, flip past
|
||||||
|
three hundred real sleeves, and buy one. Round 9 makes that official. Commit atomically,
|
||||||
|
pin your refs, leave it green.*
|
||||||
97
docs/RETRO_V4.md
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
# PROCITY v4 — THE REAL MAP: the epoch retro
|
||||||
|
|
||||||
|
*Fable (integrator), 2026-07-16, written after the `v4.0` tag (`8ac3b18`) per ROUND22 ledger #6.
|
||||||
|
Epoch span: the R17 scout → R22 close (charter: [V4_REAL_MAP.md](V4_REAL_MAP.md)). Tags:
|
||||||
|
`v4.0-alpha` → `v4.0-beta` → `v4.0`.*
|
||||||
|
|
||||||
|
## The epoch in numbers
|
||||||
|
|
||||||
|
**23 real Australian towns · 1,210 real shops (298 secondhand heroes) · all 8 states and
|
||||||
|
territories · on their actual streets.** Selfcheck grew 15,277 → **161,300** checks. 36 town
|
||||||
|
goldens pinned in one commit. Every real graph lands as **1 connected component** with 3 venues.
|
||||||
|
The covenant was never broken: `?classic=1` boots the exact v2 town, `0x3fa36874`, byte-identical
|
||||||
|
through six rounds of the largest structural change in the project's history. The default
|
||||||
|
synthetic town is untouched. One sanctioned golden move per round, every one recorded.
|
||||||
|
|
||||||
|
## What shipped, in one breath
|
||||||
|
|
||||||
|
Cache schema v2 → the real-graph lift (junction-protected Douglas–Peucker, cull/bridge
|
||||||
|
connectivity) → the density widening under John's SUBTLE directive (heroes never subsampled,
|
||||||
|
texture capped at 3×, and D proved the heroes *gained* footfall) → cluster-adjacent venues (11/15
|
||||||
|
were stranded; 0/15 after) → the tram's shop-adjacency ruling with per-town fencing → the town
|
||||||
|
selector → the poster cap → the furniture tri diet → the five-frame release tour, including one
|
||||||
|
honestly-dead town. Plus the marriage: **Lane G** (ultra) joined mid-epoch, merged at a round
|
||||||
|
boundary, with the depot format ruled (no-Draco) and the G2a atlas contract reviewed.
|
||||||
|
|
||||||
|
## What worked — keep doing these
|
||||||
|
|
||||||
|
1. **Measurement over brief.** The epoch's defining pattern. A proved my poster "nudge" was
|
||||||
|
geometrically impossible and fixed the root cause. D refuted my prescribed `_rotWithHips`
|
||||||
|
with an in-shell proof. B tested a five-round-old park reason and found it was folklore.
|
||||||
|
D published a self-correction of its own headline metric. F recorded a re-measure that got
|
||||||
|
*worse*, and refused to caption a dead town as proof the floor holds. The brief is a
|
||||||
|
hypothesis; the lane's measurement is the ruling.
|
||||||
|
2. **The measure-first fix loop.** R22's blocking gate ran F measured → B diagnosed (91.5%
|
||||||
|
attribution by hide-a-layer) → A confirmed not-A with numbers → E owned it and fixed (−51%)
|
||||||
|
→ F re-measured. Four lanes, one gate, zero ego, one round.
|
||||||
|
3. **Alpha as failure-list.** R18's brief said the deliverable was the failure list, and it was:
|
||||||
|
fragmentation (105 components), the BIG_CITY misfire, the DP junction-drop — each surfaced by
|
||||||
|
audit, filed with measurements, fixed the next round. Budget the discovery round; don't ask
|
||||||
|
it to also be the fix round.
|
||||||
|
4. **Contract-first beats sequencing.** R16's order inverted (lanes ran before F's flip) and
|
||||||
|
nothing broke, because contracts were published in the brief. Same for schema v2, C's mapping
|
||||||
|
table, the backline shape, the G2a atlas contract. The half-day-handshake rule is the
|
||||||
|
project's real synchronization primitive.
|
||||||
|
5. **Drops are counted, never silent.** `norm.dropped` by cause, `textureDropped`, fenced towns
|
||||||
|
named, the 6.4% seating tail per-cause-attributed. Nothing in the pack pretends.
|
||||||
|
6. **Independent convergence.** The thin-tail finding was discovered twice in one round by
|
||||||
|
different methods (F's tour frame; D's nearest-neighbour spacing metric) — that's the
|
||||||
|
redundancy of honest lanes, and it's why the finding is trustworthy.
|
||||||
|
|
||||||
|
## What cost us — fix these
|
||||||
|
|
||||||
|
1. **Lane F is the fragile point.** F no-showed R14 and R18, and stalled mid-gate in R21. Every
|
||||||
|
time, the epoch sat untagged with five lanes done. The close is the longest, most serial
|
||||||
|
brief and it runs last, when sessions die. *Mitigation adopted going forward: F's brief is
|
||||||
|
written in resumable numbered items (R21 proved a successor can pick up mid-gate), and the
|
||||||
|
integrator checks for uncommitted F WIP before declaring a round done.*
|
||||||
|
2. **Soft ceilings invite drift.** `venue_night` breached the 200k "soft" reference twice
|
||||||
|
(+0.5%, then +11%) before R22 made it blocking. *Rule going forward: a stress view that
|
||||||
|
breaches twice is automatically a blocking gate the next round.*
|
||||||
|
3. **Old assets bite in new contexts.** The tri-diet driver was four furniture GLBs sized in
|
||||||
|
R6 for a 22-edge synthetic town, harmless for sixteen rounds until a 966-edge real graph
|
||||||
|
multiplied their cadence. *When the context scales, re-audit the assets it multiplies.*
|
||||||
|
4. **The integrator was wrong twice** (the nudge, `_rotWithHips`). Acceptable — that's what
|
||||||
|
measurement-over-brief is for — but both were cases of prescribing the mechanism instead of
|
||||||
|
the outcome. *Briefs should state acceptance; lanes own method.*
|
||||||
|
|
||||||
|
## The v4.x ledger (carried, measured, not hidden)
|
||||||
|
|
||||||
|
- **The thin-tail clustering floor** (the epoch's parting finding): aliveness is not shop
|
||||||
|
*count* but **clustering** — median nearest-neighbour spacing (darwin 27 m ALIVE vs toowoomba
|
||||||
|
388 m DEAD at the same 12 shops). Candidate law: E curates on spacing, `validateTownCache`
|
||||||
|
warns above a threshold; toowoomba gets re-bboxed or retired.
|
||||||
|
- The **6.4% seating tail** (daylesford 19%, fitzroy 13%) — known, per-cause, the drop-and-count
|
||||||
|
ruling stands.
|
||||||
|
- `GIG_RANGE` chunk-neighbour lookup (D, 8.7% blind) · instrument `_lo` LOD · tram-stop close-ped
|
||||||
|
tri diet · drummer pelvic-lean v2 · awning fabric ripple · flag retirement (classic keeps
|
||||||
|
soaking) · selector polish beyond debug-grade.
|
||||||
|
|
||||||
|
## The next epoch — Fable's recommendation to John
|
||||||
|
|
||||||
|
Three chartered candidates remain: **the GODVERSE economy (G2/G3)**, **the editor/override
|
||||||
|
layer**, **multiplayer presence**.
|
||||||
|
|
||||||
|
**Recommendation: v5 = THE REAL SHOP — the GODVERSE economy.** The argument: v4 made the towns
|
||||||
|
real; v5 makes the *shops* real. The marriage lane is already live (G2a contract reviewed, first
|
||||||
|
atlases next), dealgod holds 1.77M real items with photos, and the destination — walk Fitzroy's
|
||||||
|
real street into a real op shop, riffle a real crate, and the record you buy is one-of-a-kind
|
||||||
|
because it's real stock (sold means GONE) — is the most PROCITY-shaped thing either studio can
|
||||||
|
build. Sequence per the standing ruling: **G3 design doc first** (both integrators + John;
|
||||||
|
the server enriches, never gates — full offline play is non-negotiable), G2a atlases land
|
||||||
|
meanwhile, v4.x tail runs as a light parallel lane. The editor and multiplayer stay chartered
|
||||||
|
for v6+ — the editor gets stronger the more real content exists to override, and presence gets
|
||||||
|
stronger the more there is to visit.
|
||||||
|
|
||||||
|
*— Fable. Twenty-two rounds: seed → town → weather → crowd → interiors → stock → gigs → the flip
|
||||||
|
→ real roads → real shops → real Australia. The covenant held the whole way.*
|
||||||
122
docs/RETRO_V5.md
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
# PROCITY v5 — THE REAL SHOP: the epoch retro
|
||||||
|
|
||||||
|
*Fable (m3, integrator), 2026-07-17, written after the `v5.0` tag (`b2d29ba`) per ROUND27 §7.
|
||||||
|
Epoch span: R23 → R27. Tags: `v5.0-alpha` → `v5.0-beta` → `v5.0`. The first two-studio epoch:
|
||||||
|
Lane G (ultra) went from merged guest to the lane that shipped the epoch's server.*
|
||||||
|
|
||||||
|
## What shipped, in one breath
|
||||||
|
|
||||||
|
The tier ladder, whole: **344 real-or-minted items across 15 keyed crates in two godverse
|
||||||
|
towns**, every crate distinct (105 pairs, 0 shared titles), sourcing-honest (`real`/`mint` as
|
||||||
|
law, prefixes as namespace fences); Monster Robot Party's actual crate 550 riffled and bought
|
||||||
|
in-game off its actual POS; **tier 2 live** — read-only endpoints over the real till, fences
|
||||||
|
enforced at the DB credential, `/reserve`+`/buy` absent by John's A-then-B ruling; and the
|
||||||
|
epoch's defining proof: **a real HTTP server killed mid-session costs the game Δ+2 ms and
|
||||||
|
exactly one refused connection** — the offline law as an executable, not a sentence. Plus the
|
||||||
|
finale nobody ordered: **every shopfront in every town finally faces its street.**
|
||||||
|
|
||||||
|
## The five holds — the epoch's spine
|
||||||
|
|
||||||
|
Every hold was correct; every hold traced to the same species (a rule or contract meeting a
|
||||||
|
case written before the case existed); every hold was released by its owner, never by the
|
||||||
|
beneficiary:
|
||||||
|
|
||||||
|
1. **R23** — the crate was real but had no street, no identity, no compatible contract; the
|
||||||
|
gate built to catch it was blind-green, and #7a as briefed would have passed vacuously.
|
||||||
|
2. **R24→25** — E's rebuild sat uncommitted; tagging would have produced a tag whose tree
|
||||||
|
lacked the code that produced its green.
|
||||||
|
3. **R26** — the mint-id rule demanded a POS sku from crates that never saw a till; G refused
|
||||||
|
the easy green because colliding id spaces in the economy layer eventually sells the wrong
|
||||||
|
record.
|
||||||
|
4. **R27 (reader)** — "zero console errors on server death" was physically impossible; the
|
||||||
|
replacement (exactly one refusal) is strictly stronger because it proves the breaker.
|
||||||
|
5. **R27 (facade)** — the fifth hold found the oldest bug in the project.
|
||||||
|
|
||||||
|
## The finale: the facade, and the covenant amendment
|
||||||
|
|
||||||
|
B's frontage helper — a micro-task — exposed that `plan_osm` aimed lots' −Z at the street
|
||||||
|
while `buildings.js` draws facades on +Z. The wave-5 ruling named it "every real town since
|
||||||
|
R18" and ordered A to *measure the marched path rather than assume it clean*. **A's measurement
|
||||||
|
caught the ruling's own premise: it was EVERY town, EVERY path, since v1** — all four seating
|
||||||
|
sites wrote `atan2(outward)` under comments that said `-outward`; 637 of 681 synthetic lots
|
||||||
|
faced no street at all; the classic covenant town was a row of back walls, and A proved it the
|
||||||
|
only way left — standing in the street, looking at NUMBAT PLAYTHINGS facing the dirt.
|
||||||
|
|
||||||
|
**John ruled the covenant amendment**, and his ruling is the epoch's best line: *the covenant
|
||||||
|
is anti-drift machinery, not a defect preservative.* `0x3fa36874` did its job for five epochs —
|
||||||
|
it never drifted. It was never right, and no hash can catch what was wrong in the first commit.
|
||||||
|
**A golden proves nothing changed; it cannot prove anything is correct.** 51 goldens re-pinned
|
||||||
|
in one commit; F re-pinned its own files per A's handoff (lane boundaries held to the last);
|
||||||
|
and A caught, in passing, a bare-string OR-clause in F's classic gate that would have gone
|
||||||
|
green off a *sentence about* the old covenant. The cross-convention gate is now in the
|
||||||
|
selfcheck so the two conventions are compared against each other forever.
|
||||||
|
|
||||||
|
## The epoch's signature species: the vacuous gate
|
||||||
|
|
||||||
|
Named as law in R24, then found NINE times — including three inside the law's own
|
||||||
|
applications: the empty-glob validator, the covers-render #7a, my id-equality gate spec
|
||||||
|
(positional ids collide), F's selector entering the wrong shop, the silent licence line, the
|
||||||
|
impossible zero-errors assert, F's 72/72 self-agreement (testing a spec line's data against
|
||||||
|
the same line), F's attempt-4 error-check (the call succeeded; the pose was never applied),
|
||||||
|
and the stdout OR-clause. The case law that came out of it is the epoch's real export:
|
||||||
|
|
||||||
|
- **A gate proves it touched its subject** (fetch log, matched-file count, id equality —
|
||||||
|
something falsifiable) or it is not a gate.
|
||||||
|
- **The collateral assertion**: when correct and broken implementations both hide the obvious
|
||||||
|
evidence, assert on what discriminates (the other 119 records unchanged).
|
||||||
|
- **Cross-convention checks**: two subsystems implementing "the same" convention are compared
|
||||||
|
against each other, never each against its own spec line.
|
||||||
|
- **Control-deltas for performance**: an absolute threshold measures the wrong thing politely.
|
||||||
|
- **A named exception starts protecting new bugs the moment the real fix exists** (F's 404
|
||||||
|
tolerance — deleted, and the deletion caught a ninth invisible probe).
|
||||||
|
- **Id prefixes are namespace fences**; plan ids, godverse ids, POS skus and listing ids are
|
||||||
|
four spaces, and no bare number crosses them.
|
||||||
|
|
||||||
|
## What worked — keep doing
|
||||||
|
|
||||||
|
1. **Restraint under benefit.** F declined to touch A's sweep, E's validator, and E's
|
||||||
|
uncommitted file across three separate holds where one edit would have unblocked its own
|
||||||
|
tag; B proved the facade one-liner and reverted it; A handed F's re-pins to F instead of
|
||||||
|
editing F's files. The party that benefits never releases its own hold.
|
||||||
|
2. **Ownership sentences.** "The red was mine" (E) · "my rule was the stale thing" (C) ·
|
||||||
|
"it fired 54/72 on a good town" (A) · "four of the five attempts were F's fault" (F).
|
||||||
|
Nobody defended; everybody measured.
|
||||||
|
3. **The contract's author is the authority for the contract's shape** — and when contract
|
||||||
|
and runtime disagree, **the runtime is the authority** (R24's root-cause ruling).
|
||||||
|
4. **Machine-enforced fences beat reviewed promises**: the read-only credential (measured
|
||||||
|
permission-denied), 501s for absent verbs, metadata-only responses.
|
||||||
|
5. **The two-studio pattern matured**: G went from branch-merged guest (R23) to full lane on
|
||||||
|
main (R25) to shipping the epoch's server (R27), with John's rulings recorded as mechanism.
|
||||||
|
|
||||||
|
## What cost us — fixed or fenced
|
||||||
|
|
||||||
|
1. **Comments and specs are not measurements.** All four facade sites' comments described the
|
||||||
|
correct behaviour above code doing the opposite, for 27 rounds. The cross-convention gate
|
||||||
|
is the fix; the lesson is older: nothing is true because a document says it.
|
||||||
|
2. **Consistency masquerades as correctness.** Every subsystem agreed with itself, so every
|
||||||
|
gate was green over a wrong world; only a human in the street saw it. Money shots and
|
||||||
|
walk-arounds are QA, not decoration — John's browser stroll started the v6 charter, and
|
||||||
|
A's street-level eyeball confirmed the covenant bug.
|
||||||
|
3. **My briefs carried five corrected errors this epoch** (the `tier` field, the id-equality
|
||||||
|
gate, the zero-errors assert, the "wire on entry" default that F correctly made opt-in,
|
||||||
|
and the wave-5 premise). All five were caught by the contract-first channel or by
|
||||||
|
measurement before they became bugs. Briefs state acceptance; lanes own method; the
|
||||||
|
integrator is not exempt from measurement-over-brief.
|
||||||
|
|
||||||
|
## Carried forward
|
||||||
|
|
||||||
|
- **v5.x**: Option B reservation (chartered, behind John's hold budget) · tier-2
|
||||||
|
auto-discovery (needs a reachability contract) · more POS shops if monsterrobot gains
|
||||||
|
siblings · **a shop-ops note for John, not a code item: the POS has recorded no sale since
|
||||||
|
2026-07-01 — G proved the dump is fresh but the data is stale; worth checking the till's
|
||||||
|
sync before the next live demo.**
|
||||||
|
- **v4.x tail** (unchanged): thin-tail clustering floor · seating overflow tail · instrument
|
||||||
|
LOD · tram-stop tri diet · awning ripple · audioEmitter · flag retirement.
|
||||||
|
- **v6 — THE LIVING STREET**: charter drafted (`docs/V6_LIVING_STREET.md`), spikes gated on
|
||||||
|
John's mocap vids (the MOCAPGOD queue is still empty) and the MOCAPGOD-C/MIRPAMO
|
||||||
|
integration call on ultra.
|
||||||
|
|
||||||
|
*— Fable. Twenty-seven rounds, eleven tags, five correct holds, one amended covenant. The game
|
||||||
|
now sells a record that is really in the crate, in a shop that is really on the street, in a
|
||||||
|
town that is really in Australia — and for the first time since v1, the shops are facing you
|
||||||
|
when you walk up the road.*
|
||||||
116
docs/THE_FRONTIER.md
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
# PROCITY — THE FRONTIER (what is genuinely not figured out)
|
||||||
|
|
||||||
|
*Fable (m3), 2026-07-17. John's challenge, verbatim: "there is heaps more to figure out — all
|
||||||
|
the systems are set you say?" He's right. This doc is the honest map: what 29 rounds built is
|
||||||
|
the WORLD and the PIPELine; the GAME — the reason a stranger would play for an hour — is
|
||||||
|
mostly unfigured. Nothing here is chartered; it's the space the next epochs get picked from.
|
||||||
|
Sibling docs: V2_IDEAS (the old parking lot), V6_LIVING_STREET (chartered, in flight).*
|
||||||
|
|
||||||
|
## What is actually solid (don't re-litigate)
|
||||||
|
|
||||||
|
Procedural + real towns · enterable interiors · citizens with identity · gigs · weather/tram ·
|
||||||
|
the dig with real/mint/parody stock · the tier ladder + offline law · the two-studio pipeline ·
|
||||||
|
the QA culture. The platform is genuinely done-ish. Everything below stands ON it.
|
||||||
|
|
||||||
|
## THE BIG ONE — it isn't a game yet
|
||||||
|
|
||||||
|
You walk, you dig, you buy. Then you close the tab and it never happened. The whole loop that
|
||||||
|
makes digging MEAN something is unbuilt:
|
||||||
|
|
||||||
|
1. **Persistence.** No saves. The wallet resets, the collection vanishes, "gone this session"
|
||||||
|
is the only memory. (localStorage fits the seeded-everything law trivially — the world
|
||||||
|
regenerates from seed; only the DELTA needs saving: cash, owned items, day. This is days of
|
||||||
|
work, not an epoch, and it unlocks everything below.)
|
||||||
|
2. **The collection.** You buy records and they go... nowhere. No crate at home, no album/
|
||||||
|
shelf UI, no "look what I found in Katoomba." The collection IS the trophy case of the
|
||||||
|
whole game and it does not exist.
|
||||||
|
3. **The thrill loop.** thriftgod's `mint()` gem bands exist as DATA (price bands, gems,
|
||||||
|
CBD-distance curation) and drive NOTHING. No underpriced gem in a dusty crate, no "is this
|
||||||
|
worth more than they're asking", no sell/flip loop, no wantlist hunting across towns. The
|
||||||
|
entire reason op-shopping is exciting in real life is un-simulated.
|
||||||
|
4. **Why today matters.** No day structure — no opening hours pressure (hours exist! nothing
|
||||||
|
makes you race them), no end-of-day, no "the gig starts at 9, the shops shut at 5, choose."
|
||||||
|
The clock is scenery.
|
||||||
|
5. **The player.** No hands, no home, no inventory UI beyond a number, no character. Who are
|
||||||
|
you in this town?
|
||||||
|
6. **Onboarding.** A stranger at digalot.fyi/procity gets pointer-lock and silence. The first
|
||||||
|
five minutes are unfigured entirely.
|
||||||
|
|
||||||
|
## Chartered but unbuilt (in flight or waiting)
|
||||||
|
|
||||||
|
- **v6 LIVING STREET** — spikes gated on footage; then clips-at-scale, personas, footpaths +
|
||||||
|
occasional cars, generated bodies.
|
||||||
|
- **Option B** — real reservation holds (John's hold budget).
|
||||||
|
- **Tier-2 auto-discovery** — needs a reachability contract.
|
||||||
|
- **The editor/override layer** (V2_IDEAS, still the best long-game idea: hand-author any shop
|
||||||
|
interior over the procedural base).
|
||||||
|
- **Multiplayer presence** (see who's digging the same town).
|
||||||
|
|
||||||
|
## Unowned design questions (nobody has even sketched these)
|
||||||
|
|
||||||
|
- **What is a session?** 20 minutes? A day-cycle? A town per sitting?
|
||||||
|
- **The economy loop:** buy low → ??? → profit. Sell where? To whom? GodBay (thriftgod's
|
||||||
|
auction sim) is sitting right there as the obvious answer and no design connects them.
|
||||||
|
- **Cross-town play:** 23 towns exist; nothing makes you travel. Regional wantlists? A gem
|
||||||
|
rumor system? The tram/train BETWEEN towns?
|
||||||
|
- **The gig layer as gameplay:** you can attend a gig; you can't book one, play one, or care
|
||||||
|
about one. 90sDJsim overlap sits unexplored (DJ the venue?).
|
||||||
|
- **Music identity:** procedural beds exist; a game this specific deserves a sound.
|
||||||
|
- **The 90s-ness:** it's in the fabric (video shops, milk bars) but no system USES the era —
|
||||||
|
no phone-free navigation puzzles, no street-press gig guide, no mixtapes.
|
||||||
|
|
||||||
|
## THE THIRD DIMENSION + TEXTURE LIFE (filed 2026-07-18 — John's Perlin question, answered from the code)
|
||||||
|
|
||||||
|
**The ruling that frames this section:** world *structure* stays on the semantic hash-stream
|
||||||
|
system (`prng.js` — `seedFor(citySeed, kind, id)`, independently-keyed streams per entity).
|
||||||
|
That architecture is why 31 rounds of features never moved a golden: a draw added to one
|
||||||
|
stream shifts no other. Perlin answers a different question — a *continuous* value over
|
||||||
|
space/time — and it enters the toolbox only where the question really is continuous. When it
|
||||||
|
does, it seeds from the same citySeed keying: deterministic, gate-able, no exceptions.
|
||||||
|
|
||||||
|
Candidates, ranked by honest cost:
|
||||||
|
|
||||||
|
1. **TERRAIN / ELEVATION — the headline, epoch-scale, chartered as a v8+ candidate.**
|
||||||
|
Everything is flat, which is *wrong for the real map*: Katoomba is a mountain town rendered
|
||||||
|
as a billiard table. Per the real-map DNA, the primary source is REAL elevation (SRTM /
|
||||||
|
contour data for each town bbox, the same discipline as the roads fetch) — noise is only
|
||||||
|
the infill between samples, never the shape of a real hill. **The cost is named now so
|
||||||
|
nobody charters it lightly:** terrain touches everything — roads follow grades, buildings
|
||||||
|
need foundations and steps, footpaths slope, the chunk streamer and the walkers and every
|
||||||
|
budget meet a Y axis, and the classic covenant needs a flat-forever fence. This is an
|
||||||
|
epoch with an alpha/beta/close arc of its own, not a round.
|
||||||
|
2. **Sky / cloud domes — cheap, near-term (v6/v7 polish).** Skies are baked textures today; a
|
||||||
|
seeded FBM dome gives every day its own weather-shaped sky, composes with the existing
|
||||||
|
weather states, and is one lane's item (B, lighting).
|
||||||
|
3. **Wear & grime — cheap, high-charm (E/C canvas painters).** Seeded value-noise for sleeve
|
||||||
|
ring-wear, facade sun-fade, footpath staining — per-item, deterministic, and exactly the
|
||||||
|
90s patina the parody sleeves deserve. Pairs with the beta's price-guide work (a worn
|
||||||
|
sleeve *reading* worn next to its condition grade).
|
||||||
|
4. **Crowd ebb (LIVING STREET track, D).** A slow 1D noise curve modulating street density
|
||||||
|
across the day — mornings trickle, arvo swells — richer than uniform draws, one stream,
|
||||||
|
identity-safe.
|
||||||
|
|
||||||
|
5. **THE VERNACULAR KIT (filed 2026-07-18, John's idea, source corrected) — buildings that
|
||||||
|
feel real.** The town's massing is same-y boxes; the fix is **15–20 parametric Aussie
|
||||||
|
vernacular archetypes** (Queenslander, Federation cottage, terrace row, weatherboard
|
||||||
|
shopfront + verandah, deco corner pub, brick milk bar, fibro shack, tin warehouse…) with
|
||||||
|
seeded knobs — width, storeys, verandah depth, roof pitch, parapet, tint, and the wear
|
||||||
|
noise from item #3 above. **Source ruling: NOT Google Maps 3D** (licence-fatal — the one
|
||||||
|
thing the provenance spine must never touch — and photogrammetry mush at house scale
|
||||||
|
anyway). Instead: (a) **OSM/Overture building footprints + heights** for the real towns —
|
||||||
|
ODbL, the licence and pipeline we already ship, one more fetch class under the standing
|
||||||
|
authorization; (b) the kit designed from Street View *reference* (looking is not
|
||||||
|
extracting) and built parametrically on B's existing instanced-attribute machinery, which
|
||||||
|
already varies tint/hours per instance. Real footprints place real shapes on real streets;
|
||||||
|
the kit + knobs cover everything unfootprinted. Pairs with terrain as the two halves of a
|
||||||
|
future "the streetscape gets real" epoch — but unlike terrain, the kit is INCREMENTAL
|
||||||
|
(archetype by archetype, budget-measured, classic fenced), so it could also drip in as
|
||||||
|
B/E polish rounds. Goldens untouched either way (plan hashes don't see meshes); budgets
|
||||||
|
re-measured per archetype.
|
||||||
|
|
||||||
|
## The honest sequencing opinion (Fable's, not a ruling)
|
||||||
|
|
||||||
|
The private-test deploy is the design instrument: John walks the game cold and writes down
|
||||||
|
every "why can't I..." — that list IS the v7 charter input. My candidate for v7 once v6's
|
||||||
|
spikes land: **THE GAME (persistence + collection + the thrill loop + a day that matters)** —
|
||||||
|
it's the smallest set that turns the platform into something a stranger plays twice.
|
||||||
70
docs/V3_VENUES.md
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
# PROCITY v3 epoch charter — VENUES & GIGS
|
||||||
|
|
||||||
|
Decided 2026-07-15 (John): the v3 epoch is **venue district + gig system**. (Runners-up —
|
||||||
|
editor/overrides, multiplayer presence — stay in V2_IDEAS for v4 consideration.)
|
||||||
|
|
||||||
|
This is a charter, not round instructions. Round 11 (audio) is in flight and is deliberately the
|
||||||
|
on-ramp: E's music-generation pipeline and B's WebAudio layers are the substrate gigs stand on.
|
||||||
|
Round 12 opens v3 proper; per-round instructions will slice this charter up.
|
||||||
|
|
||||||
|
## The vision
|
||||||
|
|
||||||
|
Friday night in Boolarra Heads. Posters around town said THE FERAL GALAHS are on at the Royal.
|
||||||
|
You follow the sound down the spine — streetlamp pools, a queue outside the pub door, muffled
|
||||||
|
bass through the bricks. Inside: sticky carpet, a three-piece on a low stage, the crowd from the
|
||||||
|
street (the same peds — identity continuity) packed in front, a bar keeper pouring. The band is
|
||||||
|
seeded, the setlist is seeded, the town's nightlife is deterministic from citySeed — same seed,
|
||||||
|
same scene, every machine.
|
||||||
|
|
||||||
|
## What each lane owns (contracts to freeze in round-12 instructions)
|
||||||
|
|
||||||
|
- **A (citygen)** — *gig truth.* Venue lot types (pub / band room / RSL hall; 2–4 venues per town,
|
||||||
|
placement rules e.g. spine end + warehouse fringe). Seeded band names (wordlists extension),
|
||||||
|
per-night gig schedule in the plan (`plan.gigs`: venueId, bandName, genreKey, startSeg, ticket
|
||||||
|
price), poster assignments (which walls/poles advertise which gig). CITY_SPEC v3 section.
|
||||||
|
Also owed from v2: the closing-time occupant ruling feeds the gig-night patronage surge.
|
||||||
|
- **B (streetscape)** — *the venue at night, from outside.* Marquee/poster signage (E skins),
|
||||||
|
lit venue frontage, door queue zone marker, muffled-interior audio spill (extends R11 door
|
||||||
|
spill: low-pass, not just gain). Plus the epoch's bundled polish: **night readability** —
|
||||||
|
streetlamp pools, window light spill, so the night money shot finally has a face.
|
||||||
|
- **C (interiors)** — *the band room archetype.* Stage + PA + bar counter recipe, crowd floor
|
||||||
|
(watch-points contract — same pattern as browse points, cap ~8–12), green-room blocked door,
|
||||||
|
gig-night vs quiet-night variants of the same room (seeded). `room.audio` gains `gigKey` when
|
||||||
|
a gig is on.
|
||||||
|
- **D (citizens)** — *the crowd.* Audience rigs at watch points (browse-rig pattern, face-the-
|
||||||
|
stage), a 3-piece band on stage (instrument-holding idle loops), the **outdoor night queue**
|
||||||
|
(the parked v2 item — patrons visibly queue at the venue door before/during gigs), gig-night
|
||||||
|
patronage surge + identity continuity (the ped you followed in is in the crowd).
|
||||||
|
- **E (assets + audio)** — *instruments, posters, and the sound.* GLBs: guitar/bass/drum-kit/amp/
|
||||||
|
mic-stand (MODELBEAST pipeline, primitive fallbacks as ever). Poster/flyer skin pack (seeded
|
||||||
|
band names overprinted — like shop signs). Audio: per-genre live-band beds (instrumental,
|
||||||
|
parody-safe, extends gen_audio.py), crowd walla loop, applause sting.
|
||||||
|
- **F (integration)** — *gig night happens.* Time wiring (schedule → venue state machine:
|
||||||
|
quiet / doors / on / done), poster → tonight's-gig discoverability, door charge through the
|
||||||
|
existing wallet/buy seam, QA gates (gig determinism, crowd caps, audio-law compliance,
|
||||||
|
no-giants inherited), the v3 tag ladder (v3.0-alpha → v3.0).
|
||||||
|
|
||||||
|
## Phasing (three slices, one venue type first)
|
||||||
|
|
||||||
|
1. **v3.0-alpha — one pub, end-to-end.** A: one venue lot + one nightly gig in the plan. C: band
|
||||||
|
room archetype. D: band + small crowd. E: one genre bed + instruments. B: lit frontage. F: it
|
||||||
|
all fires at the right hour. Vertical slice over breadth, like interiors were.
|
||||||
|
2. **v3.0-beta — the district.** 2–4 venues, full schedule, posters around town, queue outside,
|
||||||
|
walla + applause, night-readability polish lands.
|
||||||
|
3. **v3.0 — the Friday-night experience.** Door charge, identity continuity into the crowd,
|
||||||
|
money-shot tour (the queue shot John wanted), soaks, docs, tag.
|
||||||
|
|
||||||
|
## Ground rules carried forward
|
||||||
|
|
||||||
|
Deterministic from citySeed everywhere · asset-free fallback everywhere (a gig with no GLBs is
|
||||||
|
placeholder figures on a primitive stage — still a gig) · audio house law (R11) applies to gig
|
||||||
|
audio · draw/leak budgets hold on gig nights (crowd cap is a budget number, not a vibe) ·
|
||||||
|
acceptance shots with humanoids carry the human-sized line · verification in fresh browser
|
||||||
|
contexts only.
|
||||||
|
|
||||||
|
## Open questions for John (non-blocking for alpha)
|
||||||
|
|
||||||
|
1. Door charge: flat seeded $2–$10 through the wallet, or free entry for v3.0?
|
||||||
|
2. Can gig-goers "dance" (idle-variant loop) or stand-and-watch only for v3.0?
|
||||||
|
3. Band names: pure generator, or do you want a hand-written seed list mixed in (Feral Galahs
|
||||||
|
tier)?
|
||||||
85
docs/V4_REAL_MAP.md
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
# PROCITY v4 — THE REAL MAP (epoch charter)
|
||||||
|
|
||||||
|
*Fable, 2026-07-16. John's pick (R17 kickoff). Grounded in the R17 scout: 5 real AU town caches
|
||||||
|
boot through the full gate matrix — deterministic, in-budget, districts place, `?noassets` silent.
|
||||||
|
The existing CityPlan contract eats real shop data. What it does NOT yet eat is real geography:
|
||||||
|
`plan_osm` marches real shops onto synthesized parallel avenues. v4 closes that gap.*
|
||||||
|
|
||||||
|
## The vision
|
||||||
|
|
||||||
|
**Walk the actual main street of a real Australian town.** Katoomba's real high street with its
|
||||||
|
real secondhand shops in their real order, PROCITY's stylized look (this is not photorealism —
|
||||||
|
the *town* is real, the *rendering* is PROCITY), the gig district, the citizens, the dig, the
|
||||||
|
weather — everything shipped, on real bones. The default boot stays the synthetic town; real
|
||||||
|
towns are a destination you pick.
|
||||||
|
|
||||||
|
## The stages
|
||||||
|
|
||||||
|
- **v4.0-alpha (R18): REAL ROADS, one town.** Real OSM street geometry (simplified to the
|
||||||
|
CityPlan edge schema — straight segments, real topology) for `katoomba_real`, shops seated
|
||||||
|
along their real streets, everything downstream unchanged by contract. Opt-in via the town
|
||||||
|
cache (schema v2 carries `roads[]`; caches without roads keep the marched fallback).
|
||||||
|
- **v4.0-beta: the district on real bones, five towns.** All scouted towns get roads; venues,
|
||||||
|
posters, queue, tram(?), citizens proven on irregular graphs; the town selector (B) so a player
|
||||||
|
can pick a town without URL surgery.
|
||||||
|
- **v4.0: the town pack + the tour.** A curated pack (~15–25 towns, E's bbox discipline), the
|
||||||
|
release tour shot on real streets, docs, tag. Default boot still synthetic; flag-flip for real
|
||||||
|
towns is a v4.x decision AFTER soak (the v3 pattern, proven twice).
|
||||||
|
|
||||||
|
## The laws (inherited, plus two new)
|
||||||
|
|
||||||
|
Classic covenant untouched (`?classic=1` byte-identical forever) · default boot untouched through
|
||||||
|
alpha/beta (real towns opt-in) · determinism from (town, citySeed) · budgets unchanged (≤300/200k
|
||||||
|
street, ≤350 interior) · asset law (cache fetch fail-soft; `?noassets` falls back to fixtures) ·
|
||||||
|
amendment law for goldens · atomic pathspec commits · measurement over brief.
|
||||||
|
|
||||||
|
**New — the bbox law (A's R17 risk #1):** a town cache is ONE compact town (span ≤ 5 km,
|
||||||
|
validator-enforced); town *selection* discipline lives in E's pipeline, because the generator
|
||||||
|
cannot rescue an over-broad query. **New — the ODbL law:** every cache ships
|
||||||
|
`license`/`attribution` + `SOURCES.md`; raw Overpass responses are committed so re-runs never
|
||||||
|
re-fetch; only public POI/way data, never private-individual data.
|
||||||
|
|
||||||
|
## The risk list (from the R17 scout, kept current)
|
||||||
|
|
||||||
|
1. Mega-strip / over-broad bbox — mitigated by the bbox law; watch per-town.
|
||||||
|
2. Sparse towns (< 6 shops) — curation floor; `MIN_TOWN_SHOPS` enforced.
|
||||||
|
3. **NEW, the alpha's real risk: irregular geometry downstream.** B's chunks/buildings/furniture
|
||||||
|
and D's sim have only ever seen A's synthetic graphs and the marched-avenue osm towns.
|
||||||
|
Non-grid intersections, acute angles, short edges, dead ends — the alpha exists to surface
|
||||||
|
exactly these. Every failure gets written down, fixed or fenced.
|
||||||
|
4. Road simplification fidelity — too coarse reads wrong, too fine costs edges; A owns the knob.
|
||||||
|
5. Settled non-risks: facade clearance on osm towns (R17), interior types (C proved 18 real
|
||||||
|
`shop=` values degrade cleanly), plan-side determinism (E's pipeline is byte-identical re-run).
|
||||||
|
|
||||||
|
### Retired-or-carried at the v4.0 close (ROUND22 ledger #6 — Lane A, line by line)
|
||||||
|
|
||||||
|
Verdicts against the **full 23-town pack** (1,210 shops), not against the 5-town sample the list was
|
||||||
|
written for. "Retired" means a law now enforces it and the pack proved the law holds with headroom.
|
||||||
|
|
||||||
|
1. **RETIRED — enforced, with headroom.** `MAX_TOWN_SPAN_M` (5 km) is validator-enforced, so a breach
|
||||||
|
fails loudly at load, not silently in the plan. Widest in the pack: **geelong at 3,829 m — 77% of the
|
||||||
|
ceiling**; no town came close to the law. Mega-strips are now a *pipeline curation* question (E's), and
|
||||||
|
the generator's fence is the backstop it was designed to be.
|
||||||
|
2. **RETIRED — the floor never bound.** `MIN_TOWN_SHOPS` is 6; the thinnest town in the pack is
|
||||||
|
**darwin at 12 — 2× the floor**. No cache was rejected for sparsity. (Whether a 12-shop town *reads*
|
||||||
|
alive is a different question and a live one — it's D's thin-tail spot-check this round, not a
|
||||||
|
generator risk.)
|
||||||
|
3. **RETIRED on the plan side; CARRIED for the runtime lanes.** This was the alpha's real risk and it
|
||||||
|
paid off exactly as intended — the irregular graphs surfaced fragmentation (R19), capacity (R20) and
|
||||||
|
venue stranding (R21), each fixed-or-fenced and written down. At 23-town scale A's side needed **no
|
||||||
|
new hardening**: 1 connected component and 3 venues on every town, all invariants green, fitzroy's 160
|
||||||
|
shops included. **Carried** only where it hasn't been measured yet: C's door-soak, D's fitzroy audit
|
||||||
|
and F's matrix are this round's verifies — the plan being green is not the same as the town rendering
|
||||||
|
and simulating green.
|
||||||
|
4. **RETIRED — the knob is at a measured setting.** Junction-protected Douglas–Peucker settled it
|
||||||
|
(katoomba 105 → 11 components; cull/bridge → 1 component at 100% main-net, and now **1 component on
|
||||||
|
all 23**). The fidelity/edge-count trade has a floor that holds across the pack; it is no longer an
|
||||||
|
open question, and any future move re-pins goldens under the amendment law.
|
||||||
|
5. **Unchanged** — still settled, now on 23 towns rather than 5.
|
||||||
|
6. **NEW, CARRIED — thin-graph seating capacity.** The pack's honest tail: **6.4% of shops don't seat**
|
||||||
|
(77 of 1,210), all counted by cause, dominated by overflow where a town has more shops than its road
|
||||||
|
graph has frontage. Outliers: **daylesford 19%** (32 shops on 171 roads — the thinnest graph in the
|
||||||
|
pack by 2×) and **fitzroy 13%**. `RGAP` is already 0.5 m and `NODE_CLEAR` 3 m; the only lever left is
|
||||||
|
spilling overflow onto a *different* street, which buys a few percent by putting a shop on a road it
|
||||||
|
isn't on. Ruling stands from R20: **drop and count beats teleport.** Carried into v4.x as a known,
|
||||||
|
measured, per-cause-attributed tail — not a defect, and not hidden.
|
||||||
132
docs/V5_REAL_SHOP.md
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
# PROCITY v5 — THE REAL SHOP (epoch charter)
|
||||||
|
|
||||||
|
*Fable (m3, integrator), 2026-07-16. John's pick at the v4 retro. Grounded in: the Lane G
|
||||||
|
marriage (merged, ruled, G2a atlas contract reviewed by C/E in R22), dealgod's 1.77M real items
|
||||||
|
with photos, thriftgod's census already driving `newtown_godverse`, and the v2-era dig/wallet/
|
||||||
|
stock machinery that has been waiting for this data since it shipped.*
|
||||||
|
|
||||||
|
## The vision
|
||||||
|
|
||||||
|
**v4 made the towns real; v5 makes the shops real.** Walk Fitzroy's actual street into an actual
|
||||||
|
op shop, riffle a crate, and the records in it are that shop's real stock — real covers, real
|
||||||
|
titles, real prices. Buy one and it's gone, because it was one-of-a-kind in the real world.
|
||||||
|
Thriftgod is the brain and the bank; PROCITY is the body.
|
||||||
|
|
||||||
|
## The tier ladder (the epoch's load-bearing idea)
|
||||||
|
|
||||||
|
Stock is a DATA TIER, like assets — each tier fully playable, each degrading softly to the one
|
||||||
|
below. This is the offline law made structural:
|
||||||
|
|
||||||
|
| tier | source | flag state | deterministic? |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 0 · parody | seeded canvas sleeves (v1) | any boot, always works | yes — the QA baseline |
|
||||||
|
| 1 · static real | per-shop atlases baked from dealgod (G2a) | `?stock=real`, no server | yes — atlases are files |
|
||||||
|
| 2 · live GODVERSE | thriftgod server: live stock, sold-means-gone (G3) | server reachable | **no — by design** |
|
||||||
|
|
||||||
|
The seeded-everything law is NOT weakened: the *world* (geometry, layout, venues, citizens)
|
||||||
|
stays seeded and byte-identical; stock *contents* are inventory data riding the asset law.
|
||||||
|
QA gates run on tiers 0–1; tier 2 gets its own smoke class (reachability, fail-soft, latency)
|
||||||
|
that never gates a tag on server state.
|
||||||
|
|
||||||
|
## The stages
|
||||||
|
|
||||||
|
- **v5.0-alpha: THE FIRST REAL CRATE.** One real shop end-to-end — a Newtown record shop in
|
||||||
|
`newtown_godverse`, static atlas (G2a), riffle real covers, buy, wallet debits, every tier
|
||||||
|
degrades clean. Plus the **G3 design doc ratified** (both integrators + John) before any
|
||||||
|
server code exists.
|
||||||
|
- **v5.0-beta: THE REAL SHOPPING TOWN.** Every hero shop in 1–2 godverse towns carries real
|
||||||
|
static stock; godverse coverage grows where thriftgod's census is dense (fitzroy is the
|
||||||
|
prize); pricing from thriftgod's `mint()` profiles baked into the atlases; the dig UX polished
|
||||||
|
for real items (title/price cards are real data).
|
||||||
|
- **v5.0: THE LIVE TIER.** Thriftgod's server behind tier 2 per the ratified G3 design:
|
||||||
|
live per-shop stock, sold-means-gone, reservation semantics — with the offline law proven by
|
||||||
|
the gate (kill the server mid-session; the game degrades to tier 1 without a stutter). Tour,
|
||||||
|
docs freeze, tag.
|
||||||
|
|
||||||
|
## The laws
|
||||||
|
|
||||||
|
Everything inherited (classic covenant `0x3fa36874` forever · determinism for the world ·
|
||||||
|
budgets · amendment law · measurement-over-brief · drops counted · fresh-context verification),
|
||||||
|
plus this epoch's own:
|
||||||
|
|
||||||
|
1. **The offline law (non-negotiable, from the standing G3 ruling): the server enriches, never
|
||||||
|
gates.** Full offline play at tier 0 and tier 1, always. No boot path, gate, or tag may
|
||||||
|
depend on a reachable server.
|
||||||
|
2. **The determinism boundary:** world = seeded, frozen, gated. Stock = data tiers. Tier 2 is
|
||||||
|
openly non-deterministic and therefore never inside a golden or a byte-identical assert.
|
||||||
|
3. **The licence law (extends E's provenance spine):** item photos are the stores' own product
|
||||||
|
shots — 🟢 for in-house play; **flagged before any public/commercial release** (G's own
|
||||||
|
note, now law). No private-individual data, ever. Every atlas carries provenance.
|
||||||
|
4. **The write-back law:** game purchases NEVER mutate real thriftgod inventory without an
|
||||||
|
explicit, John-ratified reservation design in the G3 doc. Until then tier 2 is read-live,
|
||||||
|
sell-sandboxed. (Whether "sold in game" can mark real stock is John's call to make in the
|
||||||
|
design review, not a default.)
|
||||||
|
5. **The two-studio law:** G owns `pipeline/godverse_*`, `web/assets/stock_godverse/**`,
|
||||||
|
godverse caches, and the server side; A–F own their lanes; cross-repo contracts publish
|
||||||
|
FIRST; both integrators sign the G3 design. **Merge amendment:** when a round's brief names
|
||||||
|
a G deliverable as gating, G's branch merges mid-round after Fable-m3 review (G's file
|
||||||
|
namespace is disjoint, so pathspec safety holds); otherwise boundaries as before.
|
||||||
|
|
||||||
|
## The risk list (kept current, v4-style)
|
||||||
|
|
||||||
|
1. **Latency/stutter at tier 2** — server calls must be async enrichment; the dig must never
|
||||||
|
block on the network. The G3 doc owns the caching design.
|
||||||
|
2. **Atlas budgets at real density** — a mecca hero shop can hold hundreds of real items; C's
|
||||||
|
interior ceilings are the law and the atlas contract must respect them (pagination/crate
|
||||||
|
rotation rather than bigger atlases).
|
||||||
|
3. **Census↔OSM identity** — matching thriftgod shops to godverse lots is fuzzy (names drift,
|
||||||
|
shops move). Mismatches must be counted and fail-soft to tier 0, never mis-stocked.
|
||||||
|
4. **Sold-means-gone semantics** — race conditions, reservations, and the write-back question.
|
||||||
|
All design-doc territory; nothing ships before ratification.
|
||||||
|
5. **Settled non-risks:** the dig/wallet/stockAdapter machinery (shipped v2, soaked 16 rounds);
|
||||||
|
static-atlas determinism (E's byte-identical pipeline discipline); the fail-soft ladder
|
||||||
|
(proven pattern from `?noassets` day one).
|
||||||
|
|
||||||
|
### Retired-or-carried at the v5.0 close (ROUND27 ledger #6 — Lane A, line by line)
|
||||||
|
|
||||||
|
Verdicts against what shipped, not against the plan. Written in **wave 2**, so tier-2 items that F's reader
|
||||||
|
and kill-the-server gate will decide are **carried, not pre-retired** — the gate retires them, not this doc.
|
||||||
|
|
||||||
|
1. **CARRIED — and it is this round's defining gate, not a footnote.** The only risk here the epoch has not
|
||||||
|
yet measured. The reader (#2) and the kill-the-server gate (#3) don't exist as I write this; **nothing
|
||||||
|
retires this line except that gate going green** — boot server-up (live fields consumed), kill mid-session
|
||||||
|
(degrade to tier 1, zero console errors, frame-time measured across the failure), boot server-down (pure
|
||||||
|
tier 1). Until then the charter's offline law is a promise, and the whole point of #3 is that a promise
|
||||||
|
isn't a gate. *(If tier 2 proves bigger than one round, F holds and this line simply carries — four holds
|
||||||
|
have proven nobody forces an epoch close.)*
|
||||||
|
2. **RETIRED — the remedy the risk named is the remedy that shipped.** It predicted "pagination/crate
|
||||||
|
rotation rather than bigger atlases", and that is exactly what happened: the mecca case is served by the
|
||||||
|
**crate-rotation menu**, not by growing the sheet. Measured across all **15 keyed atlases**: max **2
|
||||||
|
atlases/shop** against C's hard cap of 2; the 14 mint shops sit at **16 items / 1 atlas / 1024²** and only
|
||||||
|
the one real POS shop earns 2048² (**120 items, 2 atlases, 1.2 MB**) under C §7.2's 60+ item rule. E's
|
||||||
|
validator enforces the ceilings from the committed files alone. Total: 344 items.
|
||||||
|
3. **RETIRED — the risk was designed out, not solved.** It assumed a **fuzzy-matching** problem ("names
|
||||||
|
drift, shops move") and demanded mismatches be counted. There is no matcher: identity is an **explicit
|
||||||
|
opaque key** (`godverseShopId`) that G emits, so there is nothing to mis-match. The predicted failure
|
||||||
|
couldn't occur in the predicted form — and the case that would have broken any name-matcher proves it:
|
||||||
|
**Monster Robot Party is in neither the census nor OSM**, so no matching strategy could ever have found
|
||||||
|
it; it is keyed by injection. Mis-stocking is fenced structurally instead: duplicate ids **error**,
|
||||||
|
uniqueness runs over defined ids, and the **orphaned-atlas gate** fails if a stocked shop isn't seated.
|
||||||
|
4. **SPLIT — the write half RETIRED by ruling, the read half CARRIED to F's #4.** *Write-back:* retired, and
|
||||||
|
retired properly — John's **A-then-B** ruling is recorded as **mechanism, not config**: `/reserve` and
|
||||||
|
`/buy` are **ABSENT, not stubbed**. Race conditions and reservation semantics cannot occur because the
|
||||||
|
verbs do not exist; "prove the absence" is a stronger guarantee than any sandbox flag, since a sandbox a
|
||||||
|
config typo can flip isn't a sandbox. *(Option B's reservation upgrade is chartered as v5.x, behind a
|
||||||
|
hold budget — it re-opens this line deliberately, when it is built.)* *Read (`gone`, real→game):* carried
|
||||||
|
— F's #4 asserts both directions this round.
|
||||||
|
5. **Unchanged, and extended by what the epoch settled:** static-atlas determinism proved in practice
|
||||||
|
(byte-identical re-bakes, including G's index-only sku re-emit that moved no `.webp` bytes); the
|
||||||
|
fail-soft ladder proved on every rung; and the **id-space fences** (`sku_`/`mint_`, plan-id vs
|
||||||
|
godverse-id) now have case law behind them rather than good intentions.
|
||||||
|
6. **NEW, CARRIED — the orphaned atlas.** The lift legitimately drops shops (overlap-resolve, counted since
|
||||||
|
R19); dropping one that carries a **committed atlas** means real stock keyed to a shop that isn't in the
|
||||||
|
town. Gated in `selfcheck` since R25 and **currently green** — and the gate armed itself this round
|
||||||
|
without anyone touching it: it SKIPped on `newtown_godverse` while no atlas keyed there, and **began
|
||||||
|
binding the moment G's mint atlases landed**. Live near-miss on the books: `redhill_godverse`'s lift drops
|
||||||
|
keyed shop **2286 "Empire Revival"**; G stocked its numeric neighbour **2287** instead. Carried because
|
||||||
|
the hazard grows with every shop stocked — **stocking 2286 would orphan it today.**
|
||||||
|
7. **NEW, CARRIED — "real" is one shop wide.** 14 of 15 keyed atlases are **mint**; exactly one is a POS
|
||||||
|
snapshot, because exactly one shop has a POS. That is honest and marked (`sourcing`), not a defect — but
|
||||||
|
the epoch's headline claim rests on **one** shop, and any future claim of scale needs either more POS
|
||||||
|
shops (v5.x: "if monsterrobot ever gets siblings") or continued care that `sourcing` stays legible to
|
||||||
|
humans, not just gates.
|
||||||
98
docs/V6_LIVING_STREET.md
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
# PROCITY v6 — THE LIVING STREET (epoch charter — DRAFT for John)
|
||||||
|
|
||||||
|
*Fable (m3), 2026-07-17. John's idea, from a browser walk down the strip: the crowd is
|
||||||
|
believable but narrow — one walk, one idle, one sit, everyone jaywalks, nobody owns the street.
|
||||||
|
Status: DRAFT — v5.0 (THE LIVE CRATE) is still closing; this charters the v6 conversation the
|
||||||
|
retro was already going to open. Applies to PROCITY first and 90sDJsim by depot (the Lane-G
|
||||||
|
two-studio pattern, proven).*
|
||||||
|
|
||||||
|
## The vision
|
||||||
|
|
||||||
|
The street stops being 24 clones who walk anywhere and becomes a town of *people*: personas
|
||||||
|
with their own gaits, errands and manners; footpaths used like footpaths; the odd car easing
|
||||||
|
down the main street; a busker outside the record shop doing a move John actually performed in
|
||||||
|
his actual shop. **Every asset generated, rigged, animated and shipped on our own machines —
|
||||||
|
zero cloud, zero subscriptions, everything owned.**
|
||||||
|
|
||||||
|
## The stack (measured today, not aspirational)
|
||||||
|
|
||||||
|
| piece | state | what it gives us |
|
||||||
|
|---|---|---|
|
||||||
|
| **mixamo-fetch** (tailnet source box) | **34 clips cached, only 3 wired** (walk/idle/sit) | the free stash, already in hand — lean, look-around, phone, talk, carry… |
|
||||||
|
| **MOCAPGOD** (ultra) | Lanes 0/A/B **DONE**: phone video → HSMR/SKEL → BVH, MPS-verified, ~1.6× realtime, mixamorig name-map shipped | film John → skeleton motion. Lanes C (retarget) + D (clip factory) remain |
|
||||||
|
| **MIRPAMO** (both Macs) | repo live: **a local offline Mixamo** — autorig (landmark → mixamorig fit → bone-heat weights) + retarget → `character@clip.glb` | new rigged BODIES from any mesh + clip retargeting, no Adobe |
|
||||||
|
| **MODELBEAST** (m3ultra :8777) | flux_local (Apache, free) · **hunyuan3d_mlx local** · trellis_mac · sf3d · mflux_image_edit · seedvr2 | concept art → 3D meshes → texture/outfit variants, $0 |
|
||||||
|
| **3GOD depot** | live, no-Draco law, two-studio consumption proven (Lane G) | one publish, every game (PROCITY · 90sDJsim · festifun) consumes |
|
||||||
|
| **PROCITY runtime** | ped fleet (19 rigged, `_canon` mixamorig), sit.glb clip pattern proven (R16), persona hooks half-exist (loiter/dance/bench/patronage) | the body the assets walk into |
|
||||||
|
| **A's street exports** | `roadWidth/vergeBand/poleOffset` per edge — **shipped R15, barely consumed** | the footpath law is one D constraint away, not a new system |
|
||||||
|
|
||||||
|
**MOCAPGOD Lane C and MIRPAMO's retarget are the same machinery** — an integration decision
|
||||||
|
for Fable-ultra before either builds it twice.
|
||||||
|
|
||||||
|
## THE SPIKES (do these FIRST — cheap, each kills the biggest unknown in one thread)
|
||||||
|
|
||||||
|
The pipeline test John asked for, as three end-to-end spikes. No epoch work until these run.
|
||||||
|
|
||||||
|
1. **SPIKE 1 — the free stash (motion, zero generation).** ~~Wire 2 of the 34 cached clips
|
||||||
|
(`lean`, `look-around`)~~ **AMENDED R28: `lean` does not exist in the stash** — E measured
|
||||||
|
the 34 instead of trusting the wishlist (the brief's premise was half wrong). `look.glb` is
|
||||||
|
shipped + depot-verified (R28); D's wiring closes in R29 wave 0. The lean moves to Spike
|
||||||
|
2's shot list — John films a wall-lean. *Proves:* the clip bank expands cheaply, and
|
||||||
|
wishlists are not inventories. Risk: ~zero (confirmed).
|
||||||
|
2. **SPIKE 2 — the full mocap loop (the one that matters).** John films ONE move on a phone —
|
||||||
|
**the crate-riffle, at Monster Robot** (the exact clip D's notes flagged "bespoke, won't be
|
||||||
|
on Mixamo" two epochs ago) → MOCAPGOD ingest → BVH → retarget (MOCAPGOD-C or MIRPAMO —
|
||||||
|
the integration call) → clip GLB → depot → **a ped riffles a crate on Musgrave Road,
|
||||||
|
in-game**. *Proves:* film-yourself-to-NPC works end to end. Needs: MOCAPGOD Lane C/D
|
||||||
|
sessions on ultra + John's test vids in the queue (it's empty — that's the ask).
|
||||||
|
3. **SPIKE 3 — a generated body (the stretch).** flux_local concept ("older shopper with
|
||||||
|
trolley, 90s AU high street") → hunyuan3d_mlx mesh → **MIRPAMO autorig** → walk retarget →
|
||||||
|
in-game past the no-giants gate. *Proves (or honestly fails):* autorig quality on generated
|
||||||
|
meshes — the least-proven link, which is exactly why it's a spike and not a plan line.
|
||||||
|
|
||||||
|
## The epoch tracks (after v5.0 tags + the retro + the spikes report)
|
||||||
|
|
||||||
|
1. **CLIPS** — wire the 34; fetch more free categories; the MOCAPGOD bespoke set (riffle,
|
||||||
|
till-operation, busker strum, lean-on-wall, trolley-push, phone-check). Licence rail: the
|
||||||
|
HSMR/SKEL NC-risk (MOCAPGOD Decision 1) means mocap-derived clips stay **in-house** until
|
||||||
|
reviewed — same discipline as the item photos, same flag line.
|
||||||
|
2. **PERSONAS (D)** — a seeded archetype layer per citizen: {body variant, clip set, speed
|
||||||
|
band, loiter/dance/bench biases, errand routes (pick a shop, walk there, dwell, move on —
|
||||||
|
patronage formalized into visible purpose)}. The persona is DATA riding the existing sim;
|
||||||
|
determinism per citizen id; identity signatures unchanged.
|
||||||
|
3. **STREET DISCIPLINE (D + B + A)** — the footpath law: peds walk the `vergeBand`, cross at
|
||||||
|
nodes (zebra-preferred on real roads — B's crossings already render); **occasional cars**:
|
||||||
|
seeded parked cars (instanced, furniture pattern) + 1–3 movers on the mains (the tram
|
||||||
|
pattern: deterministic path-followers, ~2 draws each, stated budgets). v0 non-interacting:
|
||||||
|
cars don't yield, peds don't jaywalk — both true by construction because each stays in its
|
||||||
|
band.
|
||||||
|
4. **BODIES (E + MIRPAMO)** — outfit/texture variety on the existing fleet first (
|
||||||
|
mflux_image_edit reskins — free variety, zero rig risk), MIRPAMO-autorigged newcomers
|
||||||
|
second, per Spike 3's verdict.
|
||||||
|
|
||||||
|
## Laws (inherited + this epoch's)
|
||||||
|
|
||||||
|
Everything behind **`?streetlife=1`** (cars possibly split as `?traffic=1` — F's call at
|
||||||
|
wiring), default-OFF until soaked, `?classic=1` byte-identical as ever · budgets stated per
|
||||||
|
system (cars and new rigs are draw/tri line items) · determinism from citySeed + citizen id ·
|
||||||
|
asset law (every new clip/body rides the manifest + depot + `?noassets` fail-soft) · licence
|
||||||
|
law (LICENSES.md per clip source: Mixamo royalty-free / MOCAPGOD in-house-NC-flagged /
|
||||||
|
generated CC0-equiv) · vacuous-gate law (a persona gate proves a persona DID something, not
|
||||||
|
that one exists) · measurement over brief.
|
||||||
|
|
||||||
|
## Sequencing (the honest order)
|
||||||
|
|
||||||
|
1. **Now:** v5.0 closes per ROUND27 — nothing here touches it.
|
||||||
|
2. **In parallel, zero collision (different repos):** MOCAPGOD Lanes C/D run on ultra ·
|
||||||
|
John drops 2–3 test vids in the MOCAPGOD queue · a MIRPAMO autorig smoke on one
|
||||||
|
hunyuan3d_mlx mesh.
|
||||||
|
3. **After the v5 retro:** the spikes run as v6 wave 1; this charter ratifies (or amends)
|
||||||
|
on their results; the tracks become rounds.
|
||||||
|
|
||||||
|
## John — the asks that unblock the spikes
|
||||||
|
|
||||||
|
1. **Film the test vids** (Spike 2 blocks on an empty MOCAPGOD queue): the capture protocol
|
||||||
|
is `docs/CAPTURE.md` in MOCAPGOD — the crate-riffle plus any two more moves you fancy.
|
||||||
|
2. **The MOCAPGOD C-vs-MIRPAMO integration call** is Fable-ultra's to make — nudge that
|
||||||
|
session when you're next on the M1.
|
||||||
|
3. Nothing else — the free stash (Spike 1) needs no new material at all.
|
||||||
106
docs/V7_THE_GAME.md
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
# PROCITY v7 — THE GAME (epoch charter — **RATIFIED by John, 2026-07-17**)
|
||||||
|
|
||||||
|
*(Ratified as drafted; the fantasy stands. R30 opens the epoch — v7.0-alpha wave 1.)*
|
||||||
|
|
||||||
|
*Fable (m3), 2026-07-17. From THE_FRONTIER's headline gap: the world is built, the game isn't.
|
||||||
|
v7 is the smallest set of systems that makes a stranger play twice. Design principle: every
|
||||||
|
system below rides machinery that ALREADY EXISTS — nothing here invents a new world layer.*
|
||||||
|
|
||||||
|
## The fantasy, one line
|
||||||
|
|
||||||
|
**You're a crate-digger in 90s Australia: build the collection, learn the prices, work the
|
||||||
|
towns — and be home in time for the gig.**
|
||||||
|
|
||||||
|
## The loop
|
||||||
|
|
||||||
|
Wake (save point) → plan the day (shops shut per their real hours; the gig starts at night —
|
||||||
|
the clock finally matters) → dig shops, spot the gems (priced below the guide), spend scarce
|
||||||
|
cash → sell the flips (keeper today; the auction tomorrow) → sleep = save, the day advances,
|
||||||
|
stock rotates → the collection grows → the wantlist points at another town → travel costs a
|
||||||
|
day. Repeat, richer and one gem wiser.
|
||||||
|
|
||||||
|
## The systems (each with its existing foundation)
|
||||||
|
|
||||||
|
1. **PERSISTENCE — the foundation stone.** Save = THE DELTA ONLY: cash, owned items, town, day
|
||||||
|
number, wantlist, stats. The world is never saved — it regenerates from seed (the
|
||||||
|
seeded-everything law finally pays its biggest dividend: saves are tiny and can't corrupt
|
||||||
|
the world). localStorage, schema-versioned, JSON export/import for moving machines.
|
||||||
|
*Foundation: item identity is already stable — parody items are seeded, real/mint carry
|
||||||
|
skus (the R25/R26 sku fences were built for exactly this, before we knew it).*
|
||||||
|
2. **THE COLLECTION.** Your crate, as a UI: cover, title, artist, price paid, where and what
|
||||||
|
day you found it. The trophy case of the whole game. (A walkable home-room shelf is v7.x —
|
||||||
|
UI first.) *Foundation: the atlas indexes already carry every field the card needs.*
|
||||||
|
3. **THE GUIDE + THE GEM (the thrill loop).** An in-game price guide — thriftgod's BIBLE
|
||||||
|
concept — gives a VALUE BAND per item (imperfect, like the real 90s guide books). Shops
|
||||||
|
price with seeded variance around it → a **gem** is an item priced under its band. Skill =
|
||||||
|
spotting them. *Foundation: mint() gem/price bands exist as data and drive nothing; real
|
||||||
|
items have real dealgod prices; the dig card already shows asking price.*
|
||||||
|
4. **SELLING — where cash comes back.** v7.0-alpha: sell to a keeper at the counter (below
|
||||||
|
guide, always — the no-money-pump law). v7.0: **GodBay** — list an item, a SEEDED offline
|
||||||
|
auction resolves NEXT DAY (the come-back-tomorrow hook; thriftgod's auction brain, G's
|
||||||
|
lane, offline tier first per the proven tier-ladder pattern — a live tier is v7.x and
|
||||||
|
rides the G3 rails).
|
||||||
|
5. **THE DAY.** Sleep advances the day; **stock rotates per (shop, day)** — seeded, so day 3
|
||||||
|
in Bendigo is the same for every player on that seed (parody/mint rotate; the real shop's
|
||||||
|
real stock stays real); gigs already run a 7-night week — `day % 7` plugs straight in;
|
||||||
|
hours already exist — now closing time costs you something. *Foundation: the segment spine,
|
||||||
|
`isOpen`, and `plan.gigs` are all sitting there waiting.*
|
||||||
|
6. **THE WANTLIST + TRAVEL.** Mark items/artists you're hunting; seeded rumors surface hints
|
||||||
|
("saw one in Castlemaine" — a keeper line, a gig-crowd mutter: D's citizens finally gossip
|
||||||
|
for a reason); **travel costs a day** → 23 towns become a strategy map instead of a
|
||||||
|
dropdown. *Foundation: the selector, the pack, the personas.*
|
||||||
|
7. **THE FIRST FIVE MINUTES.** You arrive with $50 and a want. Three toast-beats: dig, buy,
|
||||||
|
sell. A real splash. *(Smallest system, biggest stranger-facing payoff.)*
|
||||||
|
|
||||||
|
## The stages
|
||||||
|
|
||||||
|
- **v7.0-alpha — THE SAVE AND THE CRATE:** persistence + collection UI + sell-to-keeper +
|
||||||
|
sleep/day cycle + stock rotation. ONE town plays as a complete (small) game.
|
||||||
|
- **v7.0-beta — THE THRILL:** the guide bands + gem variance + wantlist + rumors + travel
|
||||||
|
costs a day. The 23 towns become the map.
|
||||||
|
- **v7.0 — THE AUCTION:** GodBay offline tier, onboarding, the tour ("day one to first gem"
|
||||||
|
as the money shot), docs freeze, tag.
|
||||||
|
|
||||||
|
## The laws (new ones this epoch earns)
|
||||||
|
|
||||||
|
1. **The delta law:** a save NEVER contains world state — only player deltas. A corrupted
|
||||||
|
save can cost you your stuff, never the town.
|
||||||
|
2. **The no-pump law:** every sell price < the buy-side guide band, structurally. The economy
|
||||||
|
gets a GATE (an agent plays N seeded days and asserts net-worth growth is bounded by finds,
|
||||||
|
not arbitrage loops).
|
||||||
|
3. **Game money is game money:** the write-back law extends — no bridge to real money, ever,
|
||||||
|
in either direction.
|
||||||
|
4. **Classic stays pure:** `?classic=1` boots the v2 town with NO game layer (no save, no
|
||||||
|
HUD additions) — the covenant is untouched. The game layer defaults ON for normal boots
|
||||||
|
(the v3.1 flip precedent) with `?game=0` as the opt-out.
|
||||||
|
5. **Rotation is seeded:** stock rotation draws from (citySeed, shopId, day) — deterministic,
|
||||||
|
gate-checkable, same for everyone on a seed.
|
||||||
|
|
||||||
|
## Lane map (first cut)
|
||||||
|
|
||||||
|
**F** the save core + gates (delta law, no-pump gate, save/load determinism) · **B** the UI
|
||||||
|
surfaces (collection, wantlist, day clock, splash — hud.js territory) · **C** the counter
|
||||||
|
sell interaction + dig-card guide hints (+ the home room, v7.x) · **A** the day/rotation
|
||||||
|
seeding contracts (new sub-streams, goldens untouched — rotation rides runtime, not plan
|
||||||
|
gen... A verifies that boundary) · **E** guide-band data baked into packs/atlases (mint
|
||||||
|
bands, real prices) · **D** rumor lines through keepers/citizens · **G** GodBay (the auction
|
||||||
|
brain, offline seeded tier) + real guide prices from dealgod.
|
||||||
|
|
||||||
|
## Risks (named now, v4-style)
|
||||||
|
|
||||||
|
1. **Economy balance** — pumps, dumps, degenerate flips. The no-pump gate is the fence; a
|
||||||
|
playtest-bot (an agent that greedily arbitrages for N days) is the adversarial verifier.
|
||||||
|
2. **Save schema drift** — versioned from day one, migration-or-reject, never silent.
|
||||||
|
3. **Item identity across re-emits** — a re-baked atlas must not orphan owned items (the sku
|
||||||
|
IS the identity; E's validator grows an owned-item stability check).
|
||||||
|
4. **Scope gravity** — the game wants everything (housing! romance! a phone!). The stage
|
||||||
|
gates are the fence: alpha ships ONE town playing well before beta touches 23.
|
||||||
|
5. **v6 collision — none by design:** v7-alpha is systems/UI (F/B/C-heavy); v6's spikes are
|
||||||
|
clips/ultra (E/D/G-heavy). They interleave; Fable sequences the rounds.
|
||||||
|
|
||||||
|
## Sequencing
|
||||||
|
|
||||||
|
v7.0-alpha can start **now** — it's unblocked (v6 waits on footage; the two don't collide).
|
||||||
|
Recommended: R30 = v7.0-alpha wave 1 (F save core + B collection UI + C sell counter contracts
|
||||||
|
first). John ratifies this charter (or amends the fantasy — it's your shop, your era, your
|
||||||
|
game) and says go.
|
||||||
@ -4,31 +4,44 @@ Owned by Lane F (each lane also drops its own acceptance screenshots here).
|
|||||||
|
|
||||||
```
|
```
|
||||||
docs/shots/
|
docs/shots/
|
||||||
laneA/ … A: 5 seeds rendered in map.html (spine, arcade, market, milkbars)
|
laneA/ … laneE/ … per-lane acceptance screenshots (each lane owns its dir)
|
||||||
laneB/ … B: 3 fixed street cameras (P-key shot harness)
|
v1_tour/ … F: the v1 beauty shots (tools/shots.py) — frozen devlog set
|
||||||
laneC/ … C: 9 types × 5 archetypes interior grid
|
v2_tour/ … F: the v2 tour (tools/v2_tour.py) — the CURRENT canonical set (see below)
|
||||||
laneD/ … D: near / mid tiers + busy main street
|
before/ … F: last known-good capture, for side-by-side regression
|
||||||
laneE/ … E: skin contact sheets (new skins vs originals)
|
references/ … real-world reference photos named per shot
|
||||||
v1_tour/ … F: the 10 beauty shots (see tools/shots.py TOUR) — the devlog set
|
v2_contact.html … generated contact sheet for the v2 tour
|
||||||
before/ … F: last known-good capture, for side-by-side regression
|
|
||||||
references/ … real-world reference photos named per shot (street_noon.jpg, arcade.png, …)
|
|
||||||
contact.html … generated by tools/shots.py — this-run | before | reference
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Capture (Lane F, once Lane B's shell + `window.DBG` hook land)
|
## v2 tour — stable filenames (do not rename; the v3 site links these)
|
||||||
|
|
||||||
```
|
`cd web && python3 -m http.server 8130` then `tools/.venv/bin/python tools/v2_tour.py`
|
||||||
cd web && python3 -m http.server 8130 # terminal 1
|
→ 1280×720 PNGs in `v2_tour/` + `docs/shots/v2_contact.html`.
|
||||||
python3 -m venv tools/.venv # once
|
|
||||||
tools/.venv/bin/pip install playwright
|
|
||||||
tools/.venv/bin/python -m playwright install chromium
|
|
||||||
tools/.venv/bin/python tools/shots.py v1_tour --seed 20261990 # terminal 2
|
|
||||||
open docs/shots/contact.html
|
|
||||||
```
|
|
||||||
|
|
||||||
Before a risky change, snapshot the good state into `before/`
|
| file | what it documents |
|
||||||
(`tools/.venv/bin/python tools/shots.py before`), then re-run and eyeball `contact.html`.
|
|---|---|
|
||||||
|
| `street_noon.png` | main strip at midday |
|
||||||
|
| `crossroads_busy.png` | busiest intersection, peds about |
|
||||||
|
| `shopfront_detail.png` | verandah + sign + window up close |
|
||||||
|
| `patronage_door.png` | streamed peds at an open shop's door (Lane D patronage) |
|
||||||
|
| `rain_verandah.png` | rain outside a lit `?winmap` video-shop window |
|
||||||
|
| `rain_street.png` | wet strip, rain down the street |
|
||||||
|
| `tram_stop.png` | the v2 tram paused at a shelter (`?tram=1`) |
|
||||||
|
| `night_crowd.png` | **VIDEO REGAL glowing amid CLOSED neighbours at night** — the §3.5 hours story (clean-night boot). **NB:** no visible street crowd — see the note below. |
|
||||||
|
| `browsers.png` | **an occupied shop's interior life** — a browser at the rack + the keeper at the counter, both human-sized (R10 giant-rig fix). *figures human-sized vs door/fittings: yes.* |
|
||||||
|
| `dig_real.png` | riffling a real-cover record bin (`?dig=1&stock=real`) |
|
||||||
|
| `katoomba_main.png` | plan-agnostic main strip (`?plansrc=osm&town=katoomba`) |
|
||||||
|
|
||||||
The v1 tour list (LANE_F_INTEGRATION §Shots): main-street noon, arcade, market square,
|
### Note on `night_crowd` (Lane F, R10)
|
||||||
milkbar dusk, night neon, warehouse fringe, residential collar, record interior, opshop
|
Re-framed to the tighter, readable "one lit shop in a closed town" composition. It does **not** show a
|
||||||
interior, busy crossroads. Adjust `TOUR` in `tools/shots.py` as the town evolves.
|
standing street crowd, and that is not a framing bug: Lane D's night patronage parks ducked-in patrons
|
||||||
|
*hidden inside* the shop (they become the interior browsers seen in `browsers.png`), and seed 20261990's
|
||||||
|
video shop sits on a low-traffic corner, so none gather visibly outside (tested: 0 patronage / 0 visible
|
||||||
|
at night). The town's night life is the interior presence + the neon-vs-CLOSED mood. A visible outdoor
|
||||||
|
night queue would need a new Lane-D mechanic (loiter-outside-the-openLate-shop) — parked for v3. The
|
||||||
|
~20 fps in night captures is a **headless-chromium software-GL artifact** (the scene is light, ~128
|
||||||
|
draws / 65k tris); on real hardware the night scene is not a perf concern.
|
||||||
|
|
||||||
|
## Regression workflow
|
||||||
|
Before a risky change, snapshot the good state into `before/`, then re-run `v2_tour.py` and eyeball
|
||||||
|
`v2_contact.html`. **Process rule (R10): any shot containing a humanoid gets an explicit
|
||||||
|
"figures human-sized relative to doors/fittings: yes" line in the progress note — actually look.**
|
||||||
|
|||||||
BIN
docs/shots/laneA/osm-katoomba.png
Normal file
|
After Width: | Height: | Size: 68 KiB |
BIN
docs/shots/laneB/r9_rain_verandah.png
Normal file
|
After Width: | Height: | Size: 201 KiB |
BIN
docs/shots/laneB/r9_tram_dayloop.png
Normal file
|
After Width: | Height: | Size: 795 KiB |
BIN
docs/shots/laneB/r9_tram_night.png
Normal file
|
After Width: | Height: | Size: 251 KiB |
BIN
docs/shots/laneB/r9_tram_stop.png
Normal file
|
After Width: | Height: | Size: 889 KiB |
BIN
docs/shots/laneB/weather_rain_day.png
Normal file
|
After Width: | Height: | Size: 761 KiB |
BIN
docs/shots/laneB/weather_rain_night_winmap.png
Normal file
|
After Width: | Height: | Size: 119 KiB |
BIN
docs/shots/laneC/browse_buy_r9.jpg
Normal file
|
After Width: | Height: | Size: 169 KiB |
BIN
docs/shots/laneC/buyloop_r8.jpg
Normal file
|
After Width: | Height: | Size: 160 KiB |
BIN
docs/shots/laneC/rsl_gig_r13.jpg
Normal file
|
After Width: | Height: | Size: 602 KiB |
BIN
docs/shots/laneD/r10_browsers_fixed.jpg
Normal file
|
After Width: | Height: | Size: 779 KiB |
BIN
docs/shots/laneD/r12_gig_night.jpg
Normal file
|
After Width: | Height: | Size: 306 KiB |
BIN
docs/shots/laneD/r9_browsers_in_shop.jpg
Normal file
|
After Width: | Height: | Size: 866 KiB |
BIN
docs/shots/laneE/audio_spectrograms.png
Normal file
|
After Width: | Height: | Size: 838 KiB |
BIN
docs/shots/laneE/gig_beds_spectrograms.png
Normal file
|
After Width: | Height: | Size: 618 KiB |
BIN
docs/shots/laneE/sit_clip.png
Normal file
|
After Width: | Height: | Size: 92 KiB |
BIN
docs/shots/laneF/r12_v3alpha_gig.png
Normal file
|
After Width: | Height: | Size: 1.0 MiB |
BIN
docs/shots/laneF/r13_v3beta_gig_interior.png
Normal file
|
After Width: | Height: | Size: 965 KiB |
BIN
docs/shots/laneF/r13_v3beta_venue_night.png
Normal file
|
After Width: | Height: | Size: 333 KiB |
BIN
docs/shots/laneF/r8_katoomba.png
Normal file
|
After Width: | Height: | Size: 851 KiB |
BIN
docs/shots/laneF/r8_rain_street.png
Normal file
|
After Width: | Height: | Size: 834 KiB |
1
docs/shots/release_v3/contact.html
Normal file
@ -0,0 +1 @@
|
|||||||
|
<!doctype html><meta charset="utf-8"><title>PROCITY v3.0 — release tour</title><style>body{background:#14110c;color:#f4efe6;font:14px/1.4 -apple-system,Segoe UI,Roboto,sans-serif;margin:24px}h1{font-weight:700}.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(420px,1fr));gap:18px}figure{margin:0;background:#1d1810;border-radius:10px;overflow:hidden}img{width:100%;display:block}figcaption{padding:8px 12px;color:#cdc4b2}b{color:#ffd9a0}</style><h1>PROCITY v3.0 — release money-shot tour <small>· seed 20261990 · settled measurements</small></h1><div class="grid"><figure><img src="venue_night.png" alt="venue_night" loading="lazy"><figcaption><b>venue_night</b><br>121 draws · 51,899 tris · queue 2 · 14 posters</figcaption></figure><figure><img src="district_posters.png" alt="district_posters" loading="lazy"><figcaption><b>district_posters</b><br>186 draws · 134,068 tris · queue 2 · 14 posters</figcaption></figure><figure><img src="queue_night.png" alt="queue_night" loading="lazy"><figcaption><b>queue_night</b><br>121 draws · 61,738 tris · queue 2 · 14 posters</figcaption></figure><figure><img src="gig_interior.png" alt="gig_interior" loading="lazy"><figcaption><b>gig_interior</b><br>The Exchange Hotel · Screaming Utes · 4-piece · crowd 8 (2 from roster) · 56 draws · 35,216→182,657 tris (pre-load→settled)</figcaption></figure><figure><img src="rain_verandah.png" alt="rain_verandah" loading="lazy"><figcaption><b>rain_verandah</b><br>116 draws · 51,643 tris</figcaption></figure><figure><img src="tram_stop.png" alt="tram_stop" loading="lazy"><figcaption><b>tram_stop</b><br>105 draws · 172,187 tris</figcaption></figure></div>
|
||||||
BIN
docs/shots/release_v3/district_posters.png
Normal file
|
After Width: | Height: | Size: 218 KiB |
BIN
docs/shots/release_v3/gig_interior.png
Normal file
|
After Width: | Height: | Size: 946 KiB |
BIN
docs/shots/release_v3/queue_night.png
Normal file
|
After Width: | Height: | Size: 357 KiB |
BIN
docs/shots/release_v3/rain_verandah.png
Normal file
|
After Width: | Height: | Size: 200 KiB |
BIN
docs/shots/release_v3/tram_stop.png
Normal file
|
After Width: | Height: | Size: 860 KiB |
BIN
docs/shots/release_v3/venue_night.png
Normal file
|
After Width: | Height: | Size: 330 KiB |
1
docs/shots/release_v3_1/contact.html
Normal file
@ -0,0 +1 @@
|
|||||||
|
<!doctype html><meta charset="utf-8"><title>PROCITY v3.1 — release tour</title><style>body{background:#14110c;color:#f4efe6;font:14px/1.4 -apple-system,Segoe UI,Roboto,sans-serif;margin:24px}h1{font-weight:700}.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(420px,1fr));gap:18px}figure{margin:0;background:#1d1810;border-radius:10px;overflow:hidden}img{width:100%;display:block}figcaption{padding:8px 12px;color:#cdc4b2}b{color:#ffd9a0}</style><h1>PROCITY v3.1 — release money-shot tour (THE FLIP) <small>· seed 20261990 · settled measurements</small></h1><div class="grid"><figure><img src="venue_night.png" alt="venue_night" loading="lazy"><figcaption><b>venue_night</b><br>124 draws · 57,669 tris · queue 2 · 15 posters</figcaption></figure><figure><img src="district_posters.png" alt="district_posters" loading="lazy"><figcaption><b>district_posters</b><br>189 draws · 139,838 tris · queue 2 · 15 posters</figcaption></figure><figure><img src="queue_night.png" alt="queue_night" loading="lazy"><figcaption><b>queue_night</b><br>122 draws · 62,763 tris · queue 2 · 15 posters</figcaption></figure><figure><img src="tram_stop.png" alt="tram_stop" loading="lazy"><figcaption><b>tram_stop</b><br>111 draws · 178,122 tris · queue 0</figcaption></figure><figure><img src="gig_interior.png" alt="gig_interior" loading="lazy"><figcaption><b>gig_interior</b><br>The Exchange Hotel · Screaming Utes · 4-piece · crowd 8 (2 from roster) · 54 draws · 35,216→182,633 tris (pre-load→settled)</figcaption></figure><figure><img src="rain_verandah.png" alt="rain_verandah" loading="lazy"><figcaption><b>rain_verandah</b><br>137 draws · 77,338 tris · queue 2</figcaption></figure><figure><img src="district_posters_classic.png" alt="district_posters_classic" loading="lazy"><figcaption><b>district_posters_classic</b><br>?classic=1 — the town before v3 (no gig layer, no pools)</figcaption></figure></div>
|
||||||
BIN
docs/shots/release_v3_1/district_posters.png
Normal file
|
After Width: | Height: | Size: 219 KiB |
BIN
docs/shots/release_v3_1/district_posters_classic.png
Normal file
|
After Width: | Height: | Size: 216 KiB |
BIN
docs/shots/release_v3_1/gig_interior.png
Normal file
|
After Width: | Height: | Size: 944 KiB |
BIN
docs/shots/release_v3_1/queue_night.png
Normal file
|
After Width: | Height: | Size: 361 KiB |
BIN
docs/shots/release_v3_1/rain_verandah.png
Normal file
|
After Width: | Height: | Size: 203 KiB |
BIN
docs/shots/release_v3_1/tram_stop.png
Normal file
|
After Width: | Height: | Size: 843 KiB |
BIN
docs/shots/release_v3_1/venue_night.png
Normal file
|
After Width: | Height: | Size: 334 KiB |
BIN
docs/shots/release_v4/classic_covenant.png
Normal file
|
After Width: | Height: | Size: 809 KiB |
1
docs/shots/release_v4/contact.html
Normal file
@ -0,0 +1 @@
|
|||||||
|
<!doctype html><meta charset="utf-8"><title>PROCITY v4.0 — the release tour</title><style>body{background:#14110c;color:#f4efe6;font:14px/1.4 -apple-system,Segoe UI,Roboto,sans-serif;margin:24px}.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(460px,1fr));gap:18px}figure{margin:0;background:#1d1810;border-radius:10px;overflow:hidden}img{width:100%;display:block}figcaption{padding:9px 13px;color:#cdc4b2}b{color:#ffd9a0}</style><h1>PROCITY v4.0 — THE REAL MAP <small>· 23 real Australian towns · seed 20261990 · settled</small></h1><div class="grid"><figure><img src="fremantle_hero.png" alt="fremantle_hero" loading="lazy"><figcaption><b>fremantle_hero</b><br>the hero — Fremantle, real streets, golden hour · Fremantle · 79 shops / 1056 edges · retail heart: 10 shops/60m · 126 draws · 77,322 tris</figcaption></figure><figure><img src="fitzroy_mecca.png" alt="fitzroy_mecca" loading="lazy"><figcaption><b>fitzroy_mecca</b><br>the mecca — Fitzroy, the densest town in the pack · Fitzroy · 139 shops / 2408 edges · retail heart: 7 shops/60m · 166 draws · 95,824 tris</figcaption></figure><figure><img src="toowoomba_thintail.png" alt="toowoomba_thintail" loading="lazy"><figcaption><b>toowoomba_thintail</b><br>the thin tail — Toowoomba: the floor does NOT hold here, and this is the honest frame (12 shops over a 1,459-edge graph = 0.008 shops/edge vs the heroes' 0.06–0.08; it reads as road, not town). Filed for v4.x · Toowoomba · 12 shops / 1459 edges · retail heart: 1 shops/60m · 78 draws · 78,496 tris</figcaption></figure><figure><img src="synthetic_control.png" alt="synthetic_control" loading="lazy"><figcaption><b>synthetic_control</b><br>the control — the procedural town, unchanged · Boolarra Heads · 493 shops / 22 edges · 180 draws · 29,158 tris</figcaption></figure><figure><img src="classic_covenant.png" alt="classic_covenant" loading="lazy"><figcaption><b>classic_covenant</b><br>?classic=1 — the v2 covenant, byte-identical forever · Boolarra Heads · 493 shops / 22 edges · 164 draws · 26,472 tris</figcaption></figure></div>
|
||||||
BIN
docs/shots/release_v4/fitzroy_mecca.png
Normal file
|
After Width: | Height: | Size: 778 KiB |
BIN
docs/shots/release_v4/fremantle_hero.png
Normal file
|
After Width: | Height: | Size: 662 KiB |
BIN
docs/shots/release_v4/synthetic_control.png
Normal file
|
After Width: | Height: | Size: 809 KiB |