Compare commits
258 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
91a606a875 | ||
|
|
293a82c5e8 | ||
|
|
bb80ac122b | ||
|
|
d14cf7fa6e | ||
|
|
78f49f7113 | ||
|
|
422f17263e | ||
|
|
48033f48b1 | ||
|
|
8ad446a48a | ||
|
|
f2ffe00ad3 | ||
|
|
c3384b92c9 | ||
|
|
cf6ac64d75 | ||
|
|
3e1ff312a5 | ||
|
|
066cc1775b | ||
|
|
dab5ecd273 | ||
|
|
97b98b15af | ||
|
|
9054c77b2d | ||
|
|
076b972f71 | ||
|
|
43c7f4281e | ||
|
|
f0b9a1d30f | ||
|
|
a982f3ce67 | ||
|
|
12eb571357 | ||
|
|
0ee7a84710 | ||
|
|
41d9878574 | ||
|
|
eeba920d68 | ||
|
|
c2e44821c7 | ||
|
|
ab0bf1d373 | ||
|
|
7fe6a4a40e | ||
|
|
30b86a9ef7 | ||
|
|
34f7dae397 | ||
|
|
a569f08b14 | ||
|
|
f628bf581a | ||
|
|
07328b7e9f | ||
|
|
6c6d6de87d | ||
|
|
f873b460e8 | ||
|
|
763eabde5e | ||
|
|
1c4552a947 | ||
|
|
74d6257940 | ||
|
|
4514cb94b5 | ||
|
|
1568c9b62b | ||
|
|
6bc7926637 | ||
|
|
e8d4b51c4f | ||
|
|
b6df11dfd6 | ||
|
|
f522021765 | ||
|
|
4a7c0f1295 | ||
|
|
6afd1ed7a3 | ||
|
|
23968856dc | ||
|
|
7bd96e57fc | ||
|
|
79177bc77c | ||
|
|
47db478a51 | ||
|
|
e4d77629c5 | ||
|
|
62c0a2185d | ||
|
|
67b62389e7 | ||
|
|
751d5fe57e | ||
|
|
d59df8e1c0 | ||
|
|
ae2137fd9c | ||
|
|
c842547e61 | ||
|
|
4709cfece6 | ||
|
|
2fd4c1cf79 | ||
|
|
622a5e0d82 | ||
|
|
d9d8cbfd3e | ||
|
|
b8e1082182 | ||
|
|
83bce93f82 | ||
|
|
24183b1eb3 | ||
|
|
285ce69553 | ||
|
|
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 |
5
.gitignore
vendored
@ -17,3 +17,8 @@ pipeline/_stockpack/
|
||||
pipeline/_records.tsv
|
||||
pipeline/_books.tsv
|
||||
pipeline/_toys.tsv
|
||||
|
||||
# local harness/session files (not repo material)
|
||||
.claude/
|
||||
settings.local.json
|
||||
procityprogress.txt
|
||||
|
||||
443
B-progress.md
@ -4,6 +4,429 @@
|
||||
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.
|
||||
|
||||
## Round 42 (§42.5) — the bench turns to face the road, and the font race turns out to have been the crowd
|
||||
|
||||
Full measurement detail in `docs/LANES/LANE_B_NOTES.md` §42. Everything below: today's tree, headless
|
||||
fresh contexts, port-isolated no-store servers, pin method of record (stepwalk 2 m · yaw 0 · laps 2 ·
|
||||
802 stations), A/B against a control tree — a HEAD copy of `web/` with `assets/ models/ vendor/` **and
|
||||
every `js/` subtree that is not Lane B's** symlinked to the live tree, so another lane's mid-round wave
|
||||
is identical on both arms.
|
||||
|
||||
**1. FIX 1 — THE BENCH (and the streetlight) FACED THE WRONG WAY. D's filing is correct on both.**
|
||||
Verified before changing anything, off the live InstancedMesh matrices, each instance matched to the
|
||||
edge whose **perpendicular offset equals the placement rule** (position only, so the yaw verdict is
|
||||
not circular; `unmatched: 0`).
|
||||
|
||||
| live scene, seed 20261990 | BEFORE | AFTER |
|
||||
|---|---|---|
|
||||
| bench front vs the direction to the road | **90.0° — min = med = max, 60/60** | **0.0° — min = med = max, 60/60** |
|
||||
| lamp head gains … toward the road | **0.00 m — 0 of 89 reach over it** | **+1.05 m — 89 of 89** (the whole arm) |
|
||||
|
||||
The code and its comments had disagreed for 41 rounds: `:203` said *"facing the road"*, `:197` said
|
||||
*"arm reaches over the road"*, and both used `atan2(ux, uz)` — the yaw that points local +Z **down the
|
||||
street**. Fixed with one named convention rather than two more literals: `faceYaw = atan2(-uz, ux)`
|
||||
(= `alongYaw − π/2`), road-facing yaw = `faceYaw + (side > 0 ? π : 0)` — **the `side` term was already
|
||||
there and already right**, which is why the defect survived reading. Also found and fixed in the same
|
||||
path: **9 of the town's 60 benches are the depot GLB** (this box reaches the CDN), and that GLB is
|
||||
**0.437 × 0.845 × 1.215 m with all 318 backrest verts in a slab at x ∈ [0.151, 0.219]** — plank along
|
||||
**Z**, sitter facing **−X**, i.e. 90° out from the house "+Z is the front" law. Normalised by rotating
|
||||
the **geometry** once at load (`+π/2`), not the instance, so a station's yaw means one thing whichever
|
||||
geometry draws. Zero draws, zero tris.
|
||||
|
||||
**2. FIX 2 — `benchStops(plan)` ships, and the RULE now has one copy.** `benchStationsOnEdge(e)` is
|
||||
what `buildChunkFurniture` places from **and** what the export publishes — a second copy inside the
|
||||
same file would have been D's defect at a shorter distance. Signature:
|
||||
`benchStops(plan) → [{ edgeId, kind, s, side, x, z, yaw, seatLen }]`, pure function of the plan (no
|
||||
THREE/scene/DOM), plan-edge order then ascending `s`, whole town. `side` is on the **furniture**
|
||||
perpendicular so D's `side === −forward` test is unchanged; `yaw` is the instance rotation with **+Z
|
||||
the bench front, now pointing at the road** (a sitter is still `yaw + π`); `seatLen` 1.6 m so D's
|
||||
±0.38 m two-up offset stops being a copy of B's geometry. **Gated, not asserted** (D's own arm-6
|
||||
method, pointed at the export): **254 stations town-wide, 60 instances in the window, 60/60 within
|
||||
2 cm and 60/60 yaw-identical to <1e-6 rad; CONTROL at 2 m offset matches 0 of 65 on both counters.**
|
||||
|
||||
**3. FIX 3 — `await document.fonts.ready` IS SHIPPED AND IT FIXES NOTHING. The cause is the crowd.**
|
||||
Measured, not assumed: the sign-atlas canvases are **byte-identical across three boots** (41–49
|
||||
canvases per bookmark, same sha1 list every time); `document.fonts.size === **0**` at document start
|
||||
and at ready, because the API tracks **font faces** and this game ships none (house law — canvas text);
|
||||
and on **6 of 6 boots** `fonts.ready` resolves at 38–51 ms while the **first `fillText` in the process**
|
||||
is at 89–109 ms, i.e. **+49 to +60 ms later**, with `fonts.status === 'loaded'` already. There is no
|
||||
window in which a sign is painted early. It is shipped anyway at the only free site — a module-level
|
||||
await at the top of `buildings.js`, so every `buildChunkBuildings` call is downstream of it and the
|
||||
builder **stays synchronous** (the 4 ms/frame streamer budget is untouched). **Measured added boot
|
||||
latency, 6 boots/arm: first sign paint 102.28 → 102.62 ms (+0.34 ms on the mean) against a control
|
||||
whose own spread is 93.7–110.6 ms; `DBG.ready` 3,822 → 3,802 ms.** Noise, both directions.
|
||||
|
||||
What actually moves the frame is the **citizen sim on wall-clock time** — and **the freeze recipe every
|
||||
lane quotes does not freeze anything**: `?roster=v1&pop=0` boots **140 citizens** (96 active, measured)
|
||||
because `index.html:454` is `(parseInt(params.get('pop'),10) || 140)` and **0 is falsy**. Two fresh
|
||||
contexts of `street_noon` differ by **983 px of 921,600, all of them in rows 361–418 — the pedestrian
|
||||
band** — and by **0 px** with the crowd genuinely out. So `dbg.js` gains **`DBG.freeze()`** (`?dbg=1`
|
||||
only): `setPopulation(0)` for the fixed roster **plus** hiding `citizens.group` for the chunk-streamed
|
||||
default (whose active set is per-chunk density and ignores `target`), returning
|
||||
`{draws:{before,after}, peds, stillLive:{weather,tram,magpie,washing,gigs}}` so a harness asserts.
|
||||
|
||||
**THE 3-BOOT HASH TABLE** — F's method, fresh context per (bookmark, boot), `?classic=1`, 2.5 s settle:
|
||||
|
||||
| bookmark | BEFORE (control) | AFTER (fonts fix in) | AFTER + `DBG.freeze()` | BEFORE + same freeze |
|
||||
|---|---|---|---|---|
|
||||
| `street_noon` | **3 of 3** | **3 of 3** | **1 of 3** | **1 of 3** |
|
||||
| `shopfront_detail` | **3 of 3** | **3 of 3** | **1 of 3** | **1 of 3** |
|
||||
| `crossroads_busy` | **3 of 3** | **3 of 3** | **1 of 3** | **1 of 3** |
|
||||
| `market_square` | 1 of 3 | 1 of 3 | 1 of 3 | 1 of 3 |
|
||||
| `night_neon` | 1 of 3 | 1 of 3 | 1 of 3 | 1 of 3 |
|
||||
| **stable** | 2/5 | **2/5 — unchanged** | **5/5** | **5/5** |
|
||||
|
||||
**→ LANE F, EXPLICITLY: YES, byte-hash goldens become possible and the R41 ruling can be reversed —
|
||||
but not for the reason it was made.** The condition is not "the await landed", it is "the frame holds
|
||||
nothing that runs on wall-clock time". A golden boot is
|
||||
`?classic=1&dbg=1&weather=0&tram=0&magpie=0&washing=0` **+ `DBG.freeze()` before the shot**; `?pop=0`
|
||||
is not a substitute and never was. The last column is the proof it is the freeze and not any R42
|
||||
code — the untouched HEAD tree goes 5/5 the same way. Two asks in your files: fix `index.html:454`
|
||||
(`Number.isFinite`, not `||`), and if a gate pins a hash, assert `freeze().stillLive` is all-false in
|
||||
the same run or the golden rots the first time someone shoots without a flag. (On `market_square`:
|
||||
R41 has B calling it stable and F calling it 3-of-3; on this box today it is stable in all four arms.)
|
||||
|
||||
**→ LANE D, A BREAKING CHANGE, AND IT IS WHY THE EXPORT EXISTS.** `sim.js benchStationsFor()` is now
|
||||
**90° wrong**. Positions are untouched (your 14/14 position arm stays green); your **yaw arm goes
|
||||
red**, and `_seatPose()` will put sitters up to 0.38 m off the plank facing along the street — that is
|
||||
`bench_seated_after.jpg`, shipped un-retouched on purpose. `bench_seated_after_dwired.jpg` is the same
|
||||
frame with the sitter re-seated off the **real bench transform**: that is what importing `benchStops`
|
||||
in 42.4 delivers.
|
||||
|
||||
**4. ZERO DRAWS, measured on both arms** — pin method, 802 stations, control vs treatment:
|
||||
|
||||
| synthetic, pin | CONTROL | R42 | delta |
|
||||
|---|---|---|---|
|
||||
| default noon (seg 2) | 282 / 100,346 | **282 / 100,346** | **0 / 0** |
|
||||
| default **NIGHT** (seg 5) | 291 / 122,183 | **291 / 122,183** | **0 / 0** — ruling 4's nine draws intact |
|
||||
| `?classic=1` NIGHT | 269 / 90,580 | **269 / 90,580** | **0 / 0** — byte-exact to the R38/R40/R41 pin |
|
||||
| `?classic=1` noon | 269 / 90,592 | **269 / 90,592** | **0 / 0** |
|
||||
|
||||
0 console errors on all 8 runs. `night_neon` frame **98 draws / 14,454 tris on both**; crowd-frozen
|
||||
bench pose **130 draws on both**. (Night tris read 122,183 against R41's 120,093 — the +2,090 is on the
|
||||
**control** too, arriving through the symlinked `models/`/`assets/` from another lane's in-flight R42
|
||||
work. Recorded so nobody bills it to this round.)
|
||||
|
||||
**5. CLASSIC.** `furniture.js` and the new `buildings.js` await carry **no classic flag test at all**,
|
||||
so classic and default are identical to each other by construction and the fix lands on both. Fetch
|
||||
surface **127 URLs on both arms, added `[]`, removed `[]`** (blob: object URLs excluded — random UUID
|
||||
per boot); HUD DOM **2355:8 identical**; help line unchanged; `night_neon` **0/0 delta**; classic pin
|
||||
**269 / 90,580 byte-exact**; **0 page errors and 0 console errors** on `?dbg=1`, `?classic=1`,
|
||||
`?noassets=1` and `?roster=v1&pop=0`. The only probe difference anywhere is `DBG.freeze` existing, and
|
||||
`DBG` exists only under `?dbg=1`. Goldens: `node web/js/citygen/selfcheck.js` → **ALL GREEN
|
||||
157,647/157,647**, fingerprint **`0x5f76e76` unmoved**.
|
||||
|
||||
**6. FILED, NOT FIXED.** The **bus shelter has the bench's bug** — `syaw = atan2(-uz, ux)` is `faceYaw`
|
||||
**without** the `side` term and the shelter always stands on `+perp`, so its open front reads *away*
|
||||
from the road on the primitive. Left alone deliberately: `busShelterStops()`'s yaw is consumed by
|
||||
`tram.js` and the shipped shelter is a depot GLB whose axis is **unmeasured**. Measure the GLB, then
|
||||
flip both together. Also to E: a depot prop disagreeing with the house axis is a **class** of bug, not
|
||||
an instance — the bench was one, the shelter is the next one waiting.
|
||||
|
||||
Shots: `docs/shots/laneB_r42/` — `bench_seated_{before,after,after_dwired}.jpg` (same station, same
|
||||
pose, R10 human-sized line off the sitter's own bone bbox), `bench_geometry_ab.jpg` and
|
||||
`streetlight_night_ab.jpg` (crowd frozen, 130 draws both arms). Files: `web/js/world/furniture.js`,
|
||||
`web/js/world/buildings.js`, `web/js/world/dbg.js`, `docs/shots/laneB_r42/`, these docs. **No git
|
||||
commands run** (lane treaty; Fable commits).
|
||||
|
||||
## Round 41 (§41.5) — the game learns to say where it came from, and 81 pubs stop wearing op-shop paint
|
||||
|
||||
Full measurement detail in `docs/LANES/LANE_B_NOTES.md` §41. Everything below: today's tree, headless
|
||||
fresh contexts, port-isolated no-store servers, pin method (stepwalk 2 m · yaw 0 · laps 2 · 802
|
||||
stations), A/B against a HEAD control tree whose `assets/` `models/` `vendor/` are symlinked to the
|
||||
live tree so another lane's mid-round wave is identical on both arms.
|
||||
|
||||
**1. THE CREDITS SURFACE — the schema is the product; the panel is 260 lines that never decide a licence.**
|
||||
|
||||
`web/assets/credits.json` (schema `procity-credits/1`) is the file **every lane appends to**;
|
||||
`web/js/world/credits.js` renders it; `hud.js setCredits()` puts the way in; `index.html` gets one
|
||||
`[Lane B R41]` dynamic-import block. Reachable on **F2** or the HUD link, bottom-left.
|
||||
|
||||
- **Schema, documented for C/D/E/G in LANE_B_NOTES §41** (with a paste-ready ACCAD row for R42):
|
||||
`id · name · kind · licence · attribution · required · source · used_for · count · files · evidence`
|
||||
(+ optional `flag`, `lane`). Three rules carry it: **`licence:"unverified"` is legal and a guess is
|
||||
not**; **`attribution` is the exact string**, printed verbatim, `null` when nothing is owed;
|
||||
**`required` is the licence question, not an editorial one** — Mixamo/Rokoko are `false` because
|
||||
their licences ask for nothing, CC BY is always `true`. `evidence` must name a file in this repo.
|
||||
- **Seeded with 14 entries read off the repo, nothing invented.** Two carry a **live obligation** the
|
||||
game had never shown: **OpenStreetMap ODbL 1.0** (23 town caches — `SOURCES.md`'s own words are
|
||||
*"keep the © OpenStreetMap contributors credit visible"*) and **three.js r175 MIT**. Plus Mixamo (4
|
||||
clips), Rokoko (4 dance clips), 103 skins, 29 audio assets, the fitting/furniture fleet split by
|
||||
provenance, the GODVERSE stock art, the generation stack, and "no font files ship".
|
||||
**THE ATTRIBUTION RIDES THE LINK:** on an OSM boot it reads `© OpenStreetMap contributors · credits
|
||||
(F2)`; on the synthetic town, where no OSM data is on screen, claiming it would be the opposite of
|
||||
attribution, so it reads plainly.
|
||||
- **Two findings filed to E, both shipped as `unverified` + amber flag rather than a guess:** the
|
||||
**base ped roster (19 GLBs) has no per-asset licence record anywhere in this repo** (CITY_SPEC's
|
||||
declared zone includes CC-BY and CGTrader RF — both can owe attribution, so a credit may be owed
|
||||
*today*); and the **18 house-library fittings still carry AUDIT.md's own 38-round-old blanket
|
||||
caveat** ("record a one-line source/licence per asset before publish"), having been published since
|
||||
R3. AUDIT.md documents props in forensic detail and the people not at all.
|
||||
- **Cost:** zero draws (DOM the composer never renders), **zero boot fetches** — `credits.json` is
|
||||
fetched on FIRST OPEN, asserted 0 before and exactly 1 after — and **zero fetches under
|
||||
`?noassets=1`**, where the panel says so honestly instead of shipping a second copy of the data.
|
||||
|
||||
**2. A's D3 — 81 venue lots, re-measured and fixed.** `SHOP_REGISTRY` carries 9 types and never
|
||||
carried `pub`/`band_room`/`rsl`, so `SHOP_REGISTRY[type] || SHOP_REGISTRY.opshop` dressed every gig
|
||||
venue in op-shop paint. Re-measured over the whole shipped corpus at the golden seed:
|
||||
**27 towns · 1,838 shops · 81 venue lots · 81 of 81 taking the fallback — exactly 3 per town, in
|
||||
every town.** A's R40 number reproduces to the digit. (The bite has drifted `:344`→`:465`→**`:483`**;
|
||||
the line moves, the defect didn't — hence a named resolver, not another literal at a line number.)
|
||||
`buildings.js signStyleFor()`: B's nine shipped palettes → **A's `core/registry.js`** (gilt/stencil/
|
||||
club, carried since R13, and the source for any type A adds next) → op-shop, now genuinely
|
||||
last-resort. Venue treatment = accent frame + top fascia band, **venue-gated by construction**
|
||||
(`accent` exists only on registry-sourced palettes). **Not done, deliberately:** repainting the other
|
||||
nine onto A's palettes — that is an art call for John (record would go muted-teal → hot-pink neon),
|
||||
not a bug fix, and R41 is the round F photographs. Filed. The 28-round-stale `fixture_plan.js` header
|
||||
A clocked is rewritten to say what that table honestly is.
|
||||
|
||||
**3. ZERO DRAWS, measured not claimed** — frozen town on both arms (`roster=v1&pop=0&weather=0&tram=0&
|
||||
magpie=0&washing=0`; every one of those moves on wall-clock time), station lists compared as JSON
|
||||
before either sweep runs:
|
||||
|
||||
| frozen, 136 station×yaw samples/arm | HEAD | R41 | delta |
|
||||
|---|---|---|---|
|
||||
| synthetic **NIGHT** (worst class) | 283 draws / 116,681 tris | **283 / 116,681** | **0 draws AND 0 tris on 136/136** |
|
||||
| synthetic noon | 273 / 96,396 | **273 / 96,396** | **0 and 0 on 136/136** |
|
||||
| `katoomba_real` NIGHT | 167 / 109,208 | **167 / 109,208** | **0 and 0 on 136/136** |
|
||||
| `?classic=1` NIGHT | 264 / 90,578 | **264 / 90,578** | **0 and 0 on 136/136** |
|
||||
|
||||
**544 of 544 samples identical in both counters, 0 console errors on every arm.** Pin method on the
|
||||
treatment tree reproduces R40 exactly: **default 282 noon / 291 NIGHT, classic 269 / 90,580 byte-exact**
|
||||
— **ruling 4's nine-draw night margin is intact and Lane B spent none of it.** The new
|
||||
`core/registry.js` import in `buildings.js` is free: `citygen/index.js → plan.js` already pulls it on
|
||||
every boot, and the classic fetch surface is **121 URLs on both arms, `added []`, `removed []`**.
|
||||
|
||||
**4. CLASSIC IS BYTE-IDENTICAL.** A classic plan carries **0 venue-typed shops of 493**, so the
|
||||
resolver's registry arm is *structurally* unreachable there; the credits module is never fetched
|
||||
(null-provider idiom, no flag test in either file) so there is no `#pc-creditlink`, no overlay, no
|
||||
injected CSS, no F2 listener, no `PROCITY.credits`, and the help line is unchanged. HUD DOM
|
||||
**2354:8 on both arms**. Draw/tri delta **0 on 136/136**. → **F, a finding, not this round's defect:**
|
||||
pixel-hashing five classic bookmarks gives 2/5 matches — and **HEAD-vs-HEAD gives the same 2/5**.
|
||||
`street_noon`/`shopfront_detail`/`crossroads_busy` are not byte-reproducible across boots even at
|
||||
HEAD (likely the sign atlas painting `bold Npx Arial` before the font resolves, in race-dependent
|
||||
chunk order). A screenshot golden on those three cannot be a byte-hash.
|
||||
|
||||
Goldens **157,647/157,647, `0x5f76e76` unmoved** (no plan field is touched — signage is a canvas
|
||||
texture, the credits surface is DOM). `tools/qa/r40_lane_b.py`: **all assertions green**;
|
||||
`tools/flags_check.py`: **GREEN, 0 fails / 0 warns** (no new POST_V2 exception needed — classic never
|
||||
requests either credits file). Shots: `docs/shots/laneB_r41/venue_signage_ab.png` is the contact sheet
|
||||
— **1,088 / 9,645 / 14,160 changed pixels on the three pairs and every one of them inside the sign
|
||||
band, nothing else in the town moves** — plus the full frames and the panel. Honest caveat: the pub is
|
||||
the *least* visible of the three (op-shop `#2c2820` and gilt `#3a2416` are near-neighbour browns), so
|
||||
its read comes mostly from the `#c0392b` accent frame; the band room and RSL are unmistakable.
|
||||
Files: new `web/assets/credits.json`, new
|
||||
`web/js/world/credits.js`, `hud.js`, `buildings.js`, `fixture_plan.js`, `web/index.html`
|
||||
(`[Lane B R41]` marks), `docs/shots/laneB_r41/`, these docs. **No git commands run** (lane treaty;
|
||||
Fable commits).
|
||||
|
||||
## Round 40 (§40.3) — the ?r= breach GATED at its true number, classic's selector ratified, the fog signed — plus §40.4's arcade kit (Fable-routed)
|
||||
|
||||
Full measurement detail in `docs/LANES/LANE_B_NOTES.md` §40. Everything below: today's tree,
|
||||
headless fresh contexts, no-store servers, pin method (stepwalk 2 m · yaw 0 · laps 2 · 802 stations).
|
||||
|
||||
**1. THE ?r=3 BREACH — re-measured, then GATED (option b), because the carried 307 was fiction.**
|
||||
|
||||
| mode | before (carried) | re-measured (pin method, final tree) | law now |
|
||||
|---|---|---|---|
|
||||
| default noon / night | 282 / 292 (R38 pin) | **282 / 291** ✅ pin reproduced | ≤300 (unchanged) |
|
||||
| `?classic=1` night | 269 | **269 / 90,580 tris — byte-exact** | ≤300 (unchanged) |
|
||||
| `?r=3` noon / night | "307 / 317" (stale bookmark) | **382 / 391** at 45–48 live chunks | **≤420, its OWN declared law** |
|
||||
|
||||
Decision: **gate, not shave** — the 91-draw excess is the R+1 live-chunk window (48 vs 31) on a
|
||||
per-chunk cost already collapsed to ~1 draw/kind/chunk; a shave that size is a content cut to the
|
||||
DEFAULT town to legalise a diagnostic. So: `?r=` above auto now declares
|
||||
`PROCITY.budget = {draws: 420, tris: 200k, diagnostic: true}`, console-warns its law, the HUD warn
|
||||
threshold reads it, `DBG.info().budget` carries it, and **NEW `tools/qa/r40_lane_b.py`** asserts it
|
||||
with both arms non-vacuous (measures 386 ≤ 420 at 8 m stations AND 386 > 300, so the exemption is
|
||||
load-bearing; ≤300 someday ⇒ "retire the exemption", printed not failed). Ceiling 420 = 391 + ~7%
|
||||
jitter margin; growth past it goes red and is re-pinned consciously. → F: one qa.sh line; README
|
||||
`?r=N` row still says 307.
|
||||
|
||||
**2. ?classic=1 TOWN SELECTOR (Fable ruled yes) — verified true by construction, made deliberate.**
|
||||
Classic boots with the selector present + visible, **all 27 options** (3 fixtures + 23 real/godverse
|
||||
towns), one towns-fetch = exactly the named POST_V2_EXCEPTION (`assets/towns/index.json`), picks
|
||||
FROM classic stay classic (query string preserved), zero draws (DOM chrome), 0 errors. Ruling
|
||||
written into the selector block (hud.js) and asserted in r40_lane_b.py. → F: the flags_check
|
||||
POST_V2_EXCEPTIONS label still calls this a debt-ledger question; it's ruled now.
|
||||
|
||||
**3. FOG SIGNAGE — three surfaces that said nothing now consume A's layer** (`street || label`,
|
||||
null handled once, no town-type branch): **HUD `street` row** ("Lurline Street" on katoomba_real,
|
||||
supplier `ways`; "the south end of the main street" on the synthetic) · **door tooltip**
|
||||
("🚪 Little Paris Cafe · Katoomba Street — click to enter") · **fog-map caption**
|
||||
("… 2 streets walked · Lurline Street · press M to close"). New `createStreetLocator(plan)` in
|
||||
discovery.js (the probe's hash, read-only; signage ≠ discovery, so not fog-keyed); rides the
|
||||
existing frame%6 throttles; zero draws. **Classic-gated by construction**: locator built only
|
||||
`!CLASSIC`, so `#pc-street` doesn't exist in classic's DOM and the tooltip is the pre-R40 byte —
|
||||
A/B'd on the same door, asserted both arms in r40_lane_b.py.
|
||||
|
||||
**4. §40.4 ARCADE KIT (routed mid-round by Fable, E's spec applied verbatim):** roof spans (depth
|
||||
2.5 = half-lane, slabs meet flush), **a spanned roof has no posts**; `ctx.arcadeBlocks` keyed on
|
||||
`district.kind` never `edge.kind`; explicit `ARCADEKIT` gate (classic forces off — mandatory;
|
||||
`?arcadekit=0` = falsifiability control). Measured: **−34 post instances exactly, lane draws
|
||||
120 → 120 (+0)**, shot pair `docs/shots/laneB_r40/`, classic pin cell byte-exact after the change.
|
||||
|
||||
Goldens **157,647/157,647, 0x5f76e76 unmoved** (selfcheck after every wave). r40_lane_b gate:
|
||||
**all assertions green**. Files: `web/index.html` (`[Lane B R40]` marks), `hud.js`, `minimap.js`,
|
||||
`discovery.js`, `dbg.js`, `chunks.js`, `buildings.js`, new `tools/qa/r40_lane_b.py`,
|
||||
`docs/shots/laneB_r40/`, these docs. **No git commands run** (lane treaty; Fable commits).
|
||||
|
||||
## Round 39 (v9 Layer 2 — THE FOG, item 39.2) — **zero draws, and the map is finally readable**
|
||||
|
||||
Full detail + every number in LANE_B_NOTES §39. `minimap.js` drew all 493 synthetic shops from the
|
||||
first frame; it now draws what has been walked past and **frames itself to it**. New
|
||||
`web/js/world/discovery.js`, `minimap.js` rewritten, `save.js` gains the ledger, the shell wires it.
|
||||
|
||||
- **ZERO DRAWS, measured as an A/B** (two port-isolated no-store servers, HEAD vs treatment, same 15
|
||||
stations × 4 yaws computed from the plan and compared before either sweep runs, sim + tram hidden
|
||||
identically): **draw calls identical on 60 of 60 samples at noon and at night.** And map mode cannot
|
||||
cost a draw *structurally* — the frame loop never calls `composer.render()` in the map branch, so the
|
||||
counter reads the same number open (131) as closed (131).
|
||||
- **Legibility.** A shop lot goes from **0.83×1.11 px to 29.4×39.2 px on bowral_real — 35.3×**
|
||||
(launceston 27.6×, castlemaine 21.3×, katoomba 20.8×, adelaide 16.9×, fitzroy 11.1×, synthetic 4.0×
|
||||
at 21×41). Reproduces Fable's binding table to the digit. The frame is **the shops you have stood in
|
||||
front of + where you stand** — framing on walked *edges* was my own first cut and it blew the frame to
|
||||
340 m at spawn, because one synthetic edge is 400 m long.
|
||||
- **The rule: 25 m of the shopfront AND in front of it**, probed off a plan-derived spatial hash every
|
||||
6th street frame (**0.46 m of travel at WALK**), never off `onChunkBuilt` (R=2 ⇒ 128 m). **1.09–1.37
|
||||
µs a probe** — 0.0014% of a frame. Falsifiability control with a red arm: walking the synthetic's two
|
||||
service laneways, radius-only reveals 93 shops, the shipped probe 59 — **34 refused through the back
|
||||
wall**; on the arcade it refuses 0 of 17.
|
||||
- **The save.** Optional `known` block, keyed by TOWN (I re-derived the seed-invariance: 23 caches ×
|
||||
3 seeds, shop ids / edge ids / lot geometry all identical; synthetic alone keys `@<seed>`). Bounded
|
||||
by the plan (max 493 shops, 2,593 edges) with a 8,192 cap, 64 towns FIFO, ints only. **Worst case
|
||||
160 KB for the entire corpus fully learned; ~300 B after a 500 m outing.** Eight reject arms
|
||||
demonstrated, live state untouched on reject, foreign-town fog does not leak.
|
||||
- **Classic by construction:** `createMinimap(plan, null)` is the pre-R39 map and the file has **no flag
|
||||
test**. `?classic=1` / `?fog=0` / `?game=0` map canvases are **hash-identical to HEAD's**.
|
||||
- **Lane A's address layer consumed** — street names along walked streets, deduped by name; the
|
||||
synthetic gets district labels from the same line. No town-type branch anywhere.
|
||||
- **THE SIGN, landed on Fable's ruling (`a982f3c`)** — `index.html:428` (Lane D patronage door points)
|
||||
and `:500` (the R32 spawn) computed the shop front as `(−sin ry, −cos ry)`, the **back** of the
|
||||
building. Patronage door points **on the footpath: 0/493 → 427/493 synthetic, 0/72 → 67/72 katoomba,
|
||||
0/139 → 139/139 fitzroy, 0/30 → 28/30 bowral**. At spawn, shops within 25 m that are in front of you:
|
||||
**0 → 4 on both towns tested.** Katoomba's shipped boot opened *standing in a paddock facing a blank
|
||||
back wall* — the R31 playtest's own complaint, which R32 was written to fix and never did. **No
|
||||
golden moves** (runtime pose; selfcheck `0x5f76e76` either side). **F: the R32 spawn gate and the R35
|
||||
first-five-minutes smoke photograph this pose and need re-baselining.** Written up as the **FOURTH
|
||||
instance of the −Z/+Z confusion** (R13 `RY_FLIP` · R15 the poster on the back wall · R27 every
|
||||
real-town shopfront facing the paddock · R39 these two) with the lineage table in LANE_B_NOTES §39.
|
||||
- **THE MAGPIE, on Fable's second ruling** — E's 894-tri GLB wired as `furniture.js`'s **use-if-ready**
|
||||
path (`+1 draw / +894 tris` with the bird on screen, `+0` off; `?noassets` stays on the 182-tri
|
||||
procedural bird). **Two things measured before wiring, both would have shipped wrong:** E's bird
|
||||
faces **+Z** and this file flies **−Z** (proved by slicing the GLB along Z — head end mean y 0.31,
|
||||
tail end 0.028), so without `rotateY(π)` it swoops **tail-first** — *the fifth −Z/+Z instance, and
|
||||
the first caught before it shipped*; and its origin is at the **feet**, dropped 0.20 m onto the lamp
|
||||
arm. The fallback's white is re-placed (+0 tris, verified by E's own dump: still **182 tris**) and
|
||||
**measured**: pale area 180 → 399 px but true white 33 → 28, because a downward-facing surface gets
|
||||
no sun — the GLB reads 755. My edit also broke E's `dump_bird.mjs` (module-scope `loadGLB` under
|
||||
node); gated on `IN_BROWSER` and re-run green. Two stale `154 tris` comments corrected to **182**.
|
||||
- **FILED:** the arcade's awning posts land **±0.50 m from the centreline — two rows 1.00 m apart, 34
|
||||
posts down a 5.00 m lane** (my own measurement, confirming E's). One clamp fixes it, but it belongs
|
||||
with the scheduled arcade treatment, not ahead of it.
|
||||
|
||||
## Round 38 (v8 WAVE 1 — INHABITATION) — all four runtime items, **+1 walked draw for the lot**
|
||||
|
||||
Full detail + every number in LANE_B_NOTES §38. Measured as a true A/B: two port-isolated no-store
|
||||
servers (`:8472` = `git archive HEAD`, `:8471` = treatment, assets symlinked so E's mid-round wave is
|
||||
identical on both), 102 walked 8 m spine steps at yaw 0, each frame fully streamed, rendered through
|
||||
the shell's own composer. **The citizen sim + tram are hidden identically on both sides** — with them
|
||||
live the same step read control 346 vs treatment 232, because peds spawn on wall-clock time.
|
||||
|
||||
| walked spine, sim quiet | HEAD | R38 | delta |
|
||||
|---|---|---|---|
|
||||
| worst draws | 224 | **225** | **+1** |
|
||||
| worst tris | 89,502 | **93,298** | **+3,796** (of ~75k spare — the cheap resource, spent) |
|
||||
| sum draws over 102 frames | 16,676 | **16,663** | **−13** |
|
||||
|
||||
Isolated at a fixed pose: **washing +1 draw / +512 tris · magpie +1 draw / +182 tris · every yard box
|
||||
prop +0 draws.** `?yards=0` reproduces HEAD **exactly** (224 / 89,502 / 16,676).
|
||||
|
||||
- **§1.2 the character vector** — `character.js`, 23 towns, ground palette + sky bias + ambience mix.
|
||||
**+0 draws, +0 tris.** Classic is byte-identical **by construction, not by flag**: `townCharacter(null)`
|
||||
returns the v1 literals, and a null tint keeps the *same material cache key*, so classic gets the same
|
||||
object. Default-boot ground hashes identical to HEAD mesh-for-mesh; `?classic=1` hashes unmoved from
|
||||
R37. Sky feeds the **base** `skyName`, never `setSky` (a one-way latch that would have killed R32's
|
||||
dawn dome on all 23 towns). The vacuous arm is **per-town** — `newtown_godverse`/`redhill_godverse`
|
||||
carry `state: ''`, so a per-corpus arm would have dropped exactly those two silently. **21 distinct
|
||||
palettes across 23 towns.** `gravel` and `reddust` were not unwired but *unwireable* — their `use`
|
||||
values named no slot `ground.js` had; making the slot addressable fixed it at the root. E's three new
|
||||
grounds landed mid-round and the upgrade was a name change in one table.
|
||||
- **§1.4 backyard Australia** — inside `boxMats`, **+0 draws**. 145 houses inset off their boundaries so
|
||||
there is a yard to put things in; **36 `yard` lots that had rendered as blank shopfront sheds since v1**
|
||||
are now fenced blocks with a hoist, a shed and often a tank. Collision byte-identical.
|
||||
- **§1.4's washing** — **ONE town-wide InstancedMesh, +1 draw at any N**, cap 256, proximity-first off
|
||||
the R37 chunk seam. Per-chunk would have been +1 × 31 live chunks = 4× the whole margin. **0 on every
|
||||
real town** (no hoists ⇒ no mesh) and **0 at night** (it comes in). I **wrote the inverted wind mode**
|
||||
rather than dropping it: the shipped top-weighted weight moves a hem **0.10 cm calm / 0.67 cm gusty**;
|
||||
`pegged` gives **4.5 cm / 31 cm**, a ~52× gain, in the right place. The `canopy` string is byte-for-byte
|
||||
unchanged.
|
||||
- **§1.3 the magpie** — **+1 draw on screen, exactly 0 otherwise; 182 tris of 900.** Territory scales
|
||||
with the graph: **6 on 6.78 km synthetic → 157 on bendigo's 188.6 km / 2,593 edges, nearest 120 m.**
|
||||
Latch is runtime-only: it sets, the day roll clears it, **localStorage gains no key** and the save file
|
||||
never mentions it. `swoopPos()` is exported and pure. The hat stays cut.
|
||||
- **§1.1 the audio layer** — **six new mixer layers, zero draws, zero tris**, seeded anchors, distance
|
||||
falloff, and **a `StereoPannerNode` per layer** so direction is real (measured spread −0.71…+0.85)
|
||||
instead of a limitation written down. Day-of-week derived locally. All six anchored on the default
|
||||
boot; **3 of 6 report `no-anchor` on real towns** rather than being faked. The falsifiability control
|
||||
— one manifest key renamed away on a third port — makes that source report **`no-entry`, 0 plays,
|
||||
never `audible`**, five others unchanged.
|
||||
|
||||
**Two defects my own green numbers could not see, both caught by a screenshot.** The washing shipped at
|
||||
256 instances / +1 draw / cap respected **and was invisible behind its own back fence** (per-instance peg
|
||||
height + a fence taller than the hoist); the magpie perched **on thin air** over the road. R37's lesson,
|
||||
relearned at full price. Territories now snap to `furniture.js`'s own streetlight rule.
|
||||
|
||||
**Lane E's props, adjudicated:** hoist + aerial **permanently primitive** (E's spike killed them
|
||||
outright, and primitive is also the +0-draw mechanism) · `paling_fence` at 1,121 tris × **905 runs =
|
||||
1.01 M triangles** — no · `magpie` GLB **held for the tint**: it currently reads as a crow, and a magpie
|
||||
seen for 1.2 s in peripheral vision *is* its white bar. My 182-tri bird already carries it.
|
||||
|
||||
## Round 37 (v8 WAVE 0 §0.1 + §0.4) — THE KERB IS FOOTPATH, at +0 draws · commit `47db478`
|
||||
|
||||
John's ruling landed as geometry. `ground.js` narrows the road quad to the carriageway and extends
|
||||
the **existing** merged `footGeos` class inward to meet it, consuming Lane A's `vergeBand(e)[0]`.
|
||||
- **+0 draws, proven not asserted**: identical draw count at every one of 102 walked spine steps
|
||||
(worst frame **276 both**, sum **20,081 both**) and on `katoomba_real` (worst 100 both).
|
||||
**Tris went DOWN** — −8 synthetic, −288 katoomba. The charter's "~2k tris" was an estimate against
|
||||
a class that already existed; extending a quad re-parameterises it. Honest number, stated loudly.
|
||||
- **14 of 14 gig posters were standing in painted bitumen. 0 are now.** No poster moved; the ground did.
|
||||
- Surfaces verified by **raycast**: `main`@24 road 0–5 / footpath 5.25–15.25 · `side`@12 (72% of the
|
||||
corpus) road 0–3 / footpath 3.25–9.5 · **`lane` degenerate `[4,4]` ⇒ pre-ruling geometry** ·
|
||||
**`arcade` all footpath, no road quad, no kerbs** · **residential keeps its grass nature strip**.
|
||||
The lane case branches on `outer <= inner`, never on a band literal (Fable's mid-round correction).
|
||||
- **A defect my own numbers could not see:** the first cut painted every intersection shut (a 12.5 m
|
||||
footpath crosses the perpendicular *carriageway*, where a 3.5 m one only clipped a corner). All
|
||||
numbers were green; the screenshot was not. Fixed by putting the carriageway on top under the flag.
|
||||
- **`?verge=0` ships permanently** and is EXACT: ground vertex+UV hashes identical to the HEAD build,
|
||||
and the same detector goes red on the default boot.
|
||||
- **`?classic=1` is FENCED, not amended** (R31 dig-flip pattern): identical ground geometry AND
|
||||
identical ground-only rendered pixels at three poses. → Fable's call if classic should be amended.
|
||||
- **§0.4** — the chunk seam was never connectable: on HEAD `!!chunks.ctx === false`, so a consumer
|
||||
holding only `PROCITY.chunks` had nowhere to attach. Now `chunks.onChunkBuilt(fn) → unsubscribe()`
|
||||
(many consumers) + `chunks.ctx` (the documented single slot). 136 built / 144 disposed over a walk,
|
||||
payload + key shape verified, throwing subscriber survives, and **zero behaviour change with no
|
||||
subscriber**: a 102-step per-frame walk hashes `ceb1ec66` on both builds. `sim.js` untouched.
|
||||
|
||||
## 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.
|
||||
@ -89,6 +512,26 @@ use-if-ready furniture path (fail-soft; `?noassets` → primitives, 0 network).
|
||||
(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
|
||||
|
||||
1369
C-progress.md
573
D-progress.md
@ -1,5 +1,578 @@
|
||||
# PROCITY-D — progress (Lane D · Citizens)
|
||||
|
||||
## Round 42 (§42.4) — RECAST, WIRED: five bodies swapped at their own index, and the crowd did not move. (2026-08-07)
|
||||
|
||||
Lane E retired five ped bodies on ruling 1 and sourced five replacements. This round wires them, and
|
||||
the whole job is one sentence with a measurement behind every clause: **the town changed clothes and
|
||||
nothing else changed at all.**
|
||||
|
||||
### The swap — IN PLACE, at the same index, in `web/js/citizens/rigs.js`
|
||||
|
||||
Decision stated plainly: **the rejects are replaced at their index, not removed.** `pickRig` indexes
|
||||
`PED_NAMES`, and every citizen stores the result as `pedIndex`, which is in the identity signature
|
||||
*and* is the impostor atlas's subject index. Deleting a name shortens a pool, renumbers every slot
|
||||
after it and moves every seeded identity downstream — the R2 fleet-order bug through a different
|
||||
door, and it would move Lane F's goldens. Replacing at the index moves nothing.
|
||||
|
||||
| pool | slot | `fleet.all` | out | in | Mixamo | tris |
|
||||
|---|---:|---:|---|---|---|---:|
|
||||
| `normal` | 6 | 6 | `man_elder_01` (robed wuxia elder) | **`man_casual_04`** | Ch31 | 2 533 |
|
||||
| `normal` | 9 | 9 | `man_soldier_ww2_01` (WW2 infantryman) | **`person_casual_01`** | Ch21 | 2 956 |
|
||||
| `djs` | 4 | 21 | **`dj_phrtt_01` (unclothed body)** | **`man_smart_01`** | Ch23 | 2 617 |
|
||||
| `comical` | 0 | 22 | `comical_luchador_01` (EL CHUPACABRA) | **`woman_smart_02`** | Ch41 | 2 494 |
|
||||
| `comical` | 1 | 23 | `comical_boy_01` (cartoon child) | **`person_youth_01`** | Ch22 | 2 738 |
|
||||
|
||||
Pool lengths **unmoved: normal 17 · djs 5 · comical 2 · `fleet.all` 24** (`?classic=1`: 17 / 19).
|
||||
`comical` is now a misnomer kept on purpose — both members are ordinary people (E's ruling-1 call),
|
||||
but the KEY is load-bearing for `fleet.comical`, `pickRig`'s 8 % branch and two QA gates, so the name
|
||||
stays and the meaning is documented at the declaration.
|
||||
|
||||
**`dj_phrtt_01` is gone from the default boot, measured at the wire, not asserted from the source:**
|
||||
a fresh default context fetches **32 ped GLBs and not one of them is a retired body**; `?classic=1`
|
||||
fetches 21, likewise none. The GLB is still on disk and still on the depot — neither is mine to
|
||||
delete (see *For Lane E / Lane F* below).
|
||||
|
||||
### Determinism — the crowd is not merely reproducible, it is BYTE-IDENTICAL to before the swap
|
||||
|
||||
| | result |
|
||||
|---|---|
|
||||
| two fresh contexts, same seed → identity signature (`id:pedIndex:pvar:height:speed:edge:forward`) | **byte-equal**, 150 active citizens |
|
||||
| …and posture signature | **byte-equal** |
|
||||
| CONTROL: seed 20261991 | **differs** (141 citizens) — byte-equal is not constant |
|
||||
| CONTROL: every clip GLB delayed 2 s | **identical** — posture is `(citySeed, id)` alone |
|
||||
| **pre-swap tree vs post-swap tree, same seed, same bookmark** | **0 differing lines of 150** |
|
||||
| three-way (pre-swap · post-swap · post-bench-rewire) | **all byte-equal** |
|
||||
|
||||
**What moved:** which GLB each `pedIndex` points at, i.e. what the crowd looks like — intended.
|
||||
**What did not move:** every `pedIndex` value, every pool length, every `pickRig` mapping, every
|
||||
citizen's height/speed/edge/direction, every posture assignment, and the impostor atlas's subject
|
||||
order. The identity signature diff across the swap is empty, which is the strongest form of this
|
||||
claim available: not "same seed gives the same crowd", but "the same seed gives the *same crowd it
|
||||
gave yesterday*, in different clothes".
|
||||
|
||||
### Draws — a total cannot answer this question; a difference can
|
||||
|
||||
| bookmark | pre-swap | post-swap | post-bench-rewire | budget |
|
||||
|---|---:|---:|---:|---|
|
||||
| street_noon | 194 | 194 | 195 | ≤300 |
|
||||
| crossroads_busy | 108 | 108 | 107 | ≤300 |
|
||||
| night_crowd | 127 | 127 | 127 | ≤300 |
|
||||
| market_square | 94 | 96 | 96 | ≤300 |
|
||||
| night_neon | 111 | 111 | 111 | ≤300 |
|
||||
| venue_night | 141 | 141 | 141 | ≤300 |
|
||||
| district_posters | 235 | 235 | 235 | ≤300 |
|
||||
| patronage_door | 105 | 108 | 108 | ≤300 |
|
||||
| interior (record) | 57 | 57 | 58 | ≤350 |
|
||||
|
||||
**Stated honestly: those totals are not evidence about my change, and I am not going to pretend they
|
||||
are.** This is a shared tree with four lanes landing work in the same hour, sampled while a
|
||||
wall-clock crowd walks through frame; ±1 appears run-to-run on an unchanged tree, and the +2 at
|
||||
`market_square` / +3 at `patronage_door` persisted across both post-swap runs — so they are somebody's
|
||||
change, and the decomposition below proves they are not the crowd's.
|
||||
|
||||
**The measurement that does answer it** (`r42_cast.py` arm 4): render the frame, hide
|
||||
`citizens.group`, render again. The difference is the crowd's own cost, in one frame, with nothing
|
||||
else changing.
|
||||
|
||||
| bookmark | draws | crowd in | crowd out | **crowd's cost** | near rigs | mid | expected |
|
||||
|---|---:|---:|---:|---:|---:|---:|---:|
|
||||
| street_noon | 194 | 194 | 189 | **5** | 4 | 37 | 5 |
|
||||
| crossroads_busy | 107 | 107 | 97 | **10** | 9 | 30 | 10 |
|
||||
| night_crowd | 127 | 127 | 124 | **3** | 2 | 10 | 3 |
|
||||
| night_neon | 123 | 121 | 119 | **2** | 1 | 9 | 2 |
|
||||
| district_posters | 187 | 187 | 184 | **3** | 2 | 17 | 3 |
|
||||
| venue_night | 139 | 137 | 133 | **4** | 3 | 4 | 4 |
|
||||
| market_square | 112 | 110 | 109 | **1** | 0 | 10 | 1 |
|
||||
| patronage_door | 105 | 105 | 102 | **3** | 2 | 36 | 3 |
|
||||
|
||||
**8/8: the whole crowd costs `nearRigs × 1 + 1`** — one draw per near rig (every ped GLB is one
|
||||
primitive and one material, measured off the bytes for all five new bodies) and exactly one for the
|
||||
entire instanced billboard layer whatever it holds. At `market_square` the crowd's total cost is
|
||||
**1 draw**, so the +2 there cannot be mine. A one-for-one swap of one-draw peds has no mechanism by
|
||||
which to move the street number, and ruling 4's nine draws are untouched. Same LOD tiers, same
|
||||
`NEAR_MAX = 24` cap, same single mid draw.
|
||||
|
||||
### The contract, re-measured on today's tree (E's claims verified, not trusted)
|
||||
|
||||
Read straight off the GLB bytes — no browser, no three, no deps — and then replayed through
|
||||
`rigs.js`'s own filter over **all 54 shipped clips** (8 base + Lane E's 46-clip library):
|
||||
|
||||
| ped | nodes/joints | prim/mtl/img | anims | boneset | bindable tracks | worst clip |
|
||||
|---|---|---|---:|---|---|---:|
|
||||
| `man_casual_01` (reference) | 67 / 65 | 1 / 1 / 1 WebP | 0 | — | 3 456 / 3 460 | 64 |
|
||||
| `man_casual_04` | 67 / 65 | 1 / 1 / 1 WebP | 0 | **IDENTICAL** | **3 456 / 3 460** | 64 |
|
||||
| `person_casual_01` | 67 / 65 | 1 / 1 / 1 WebP | 0 | **IDENTICAL** | **3 456 / 3 460** | 64 |
|
||||
| `man_smart_01` | 67 / 65 | 1 / 1 / 1 WebP | 0 | **IDENTICAL** | **3 456 / 3 460** | 64 |
|
||||
| `woman_smart_02` | 67 / 65 | 1 / 1 / 1 WebP | 0 | **IDENTICAL** | **3 456 / 3 460** | 64 |
|
||||
| `person_youth_01` | 67 / 65 | 1 / 1 / 1 WebP | 0 | **IDENTICAL** | **3 456 / 3 460** | 64 |
|
||||
|
||||
E's numbers reproduce exactly, including the facing check (toe−heel ΔZ / span **+0.100 … +0.136**,
|
||||
all positive, front = local +Z). No Draco anywhere. **I did not need to fix an asset.**
|
||||
|
||||
**And the control found something E's audit did not.** The gate needs a subject that must FAIL, and
|
||||
one was already on disk: the retired `comical_boy_01` scores **3 132 / 3 460, worst clip 58**,
|
||||
because six of its thumb bones — `{Left,Right}HandThumb{2,3,4}` — carry no `mixamorig:` prefix. A
|
||||
ped that shipped for 41 rounds had been silently dropping six tracks per clip on both hands. It is
|
||||
being replaced for period reasons; it should have been replaced for correctness reasons too.
|
||||
|
||||
### Animation — every new body actually MOVES, not merely loads
|
||||
|
||||
Each replacement spawned by name through the real `makeActor` path, playing walk and then idle, 24
|
||||
mixer steps at 1/30 s. The reference ped runs the identical course as the control.
|
||||
|
||||
| body | walk: tracks · mixer Δt · bone travel · stature range · head-above-hips | idle |
|
||||
|---|---|---|
|
||||
| `man_casual_04` | 64 · 0.8 s · 83.4 · 1.687–1.728 m · +0.586 m | 64 · 0.8 s · 47.2 · 1.761–1.799 m · +0.583 m |
|
||||
| `person_casual_01` | 64 · 0.8 s · 81.9 · 1.663–1.722 m · +0.580 m | 64 · 0.8 s · 45.4 · 1.754–1.783 m · +0.578 m |
|
||||
| `man_smart_01` | 64 · 0.8 s · 84.3 · 1.667–1.722 m · +0.541 m | 64 · 0.8 s · 46.9 · 1.748–1.786 m · +0.539 m |
|
||||
| `woman_smart_02` | 64 · 0.8 s · 83.6 · 1.652–1.721 m · +0.567 m | 64 · 0.8 s · 45.8 · 1.746–1.775 m · +0.565 m |
|
||||
| `person_youth_01` | 64 · 0.8 s · 83.9 · 1.662–1.731 m · +0.534 m | 64 · 0.8 s · 47.0 · 1.736–1.765 m · +0.529 m |
|
||||
| `man_casual_01` (control) | 64 · 0.8 s · 85.9 · 1.678–1.727 m · +0.577 m | 64 · 0.8 s · 47.8 · 1.764–1.805 m · +0.575 m |
|
||||
|
||||
**64 bound tracks each** — the reference's own score. The two traps E's clip work guarded against are
|
||||
checked directly: the **flat-body / R16 fold** (head never closer than +0.53 m above the hips through
|
||||
either clip; a folded body reads ~0) and the **stature collapse** (never below 0.94 × nominal). Bone
|
||||
travel is ~83 for walk and ~46 for idle on all six — the skeleton is genuinely moving, and moving the
|
||||
same amount as the ped that has shipped since R2.
|
||||
|
||||
### No-giants gate — green, and now spanning three bind scales
|
||||
|
||||
`tools/qa/interior_scale_check.py`: **PASS — 29 interior figs across 6 shops all human-sized
|
||||
[1.4, 2.0] m and under-ceiling.** The R36 gated-tail leg now spans every native bind scale the
|
||||
roster contains and `rigs.js`'s span-normalise absorbs all of them:
|
||||
`woman_dj_01 0.986u→1.63m · woman_dj_02 0.987u→1.69m · woman_dj_03 0.984u→1.76m · dj_techno_01
|
||||
0.986u→1.85m · man_smart_01 **1.764u**→1.82m` (the legacy roster is ~173–180 u, the centimetre
|
||||
convention). **One line changed in that gate**, and it is declared here because the file is not on
|
||||
my §42.4 editable list: its `DJ_BODIES` array named `dj_phrtt_01` literally, so the swap would have
|
||||
turned it red with "NOT IN FLEET". `dj_phrtt_01` → `man_smart_01` — the same `PED_NAMES.djs` slot,
|
||||
the same `fleet.all[21]`, so the leg still covers exactly indices 17–21. No threshold, rule or
|
||||
assertion was touched.
|
||||
|
||||
### Impostor vs rig at the swap boundary
|
||||
|
||||
R2's break was a **systematic** 1.67× — a double-tone-mapped impostor material, every ped at once —
|
||||
so an absolute tolerance is the wrong instrument. The bake lights the ped with its own key+hemi+env
|
||||
and the street lights it with the sun, so the billboard/rig luminance ratio is per-albedo and 1.000
|
||||
was never the contract. Method: stage the subject in the live shell at a fixed spot, once as a near
|
||||
rig posed at the atlas's own bake phase and once as a single instance of the LIVE atlas; difference
|
||||
each against an empty frame; count only pixels inside the billboard quad's projected box. Contact
|
||||
strip: `docs/shots/laneD/r42_impostor_boundary.jpg`.
|
||||
|
||||
| | billboard/rig luminance |
|
||||
|---|---|
|
||||
| the **19 bodies this round did not touch** (control population) | **0.470 – 2.100**, median 0.972 |
|
||||
| worst-matched body in the whole town | `man_dj_streetwear_01` **0.470** — shipped long before this round |
|
||||
| the five new bodies | 0.960 · 0.673 · 1.171 · 1.323 · 1.098 — **every one inside the untouched roster's own spread** |
|
||||
| systematic shift (new median ÷ untouched median) | **1.13×** (R2's break was 1.67× on the whole fleet) |
|
||||
|
||||
**Verdict: the swap does not widen the near↔mid seam.** Filed for a later round, not faked here: the
|
||||
seam itself is loose (a 4.5× spread across the roster) because the atlas is baked under fixed studio
|
||||
light and displayed under the sun — a real improvement is baking under the scene's own key, and that
|
||||
is a system, which ruling 5 forbids this round.
|
||||
|
||||
### Lane B's §42.5 landed mid-round and broke my bench code. The mirror is retired.
|
||||
|
||||
B fixed a 41-round-old defect: benches were placed with `yaw = atan2(ux, uz)` — local +Z **down the
|
||||
street** — while both files' comments said "facing the road". My R41 `benchStationsFor()` was a
|
||||
faithful copy of that wrong line, so the fix made it 90° stale and `_seatPose` would have put sitters
|
||||
up to 0.38 m off the plank facing along the street.
|
||||
|
||||
**Fixed by deleting the copy, not by rotating it.** `sim.js` now imports `benchStops(plan)` from
|
||||
`world/furniture.js` and `benchStationsFor(e)` is a thin adapter that wraps the sim edge back into
|
||||
the two-node plan shape `resolveEdges()` wants. It has no arithmetic of its own; the rule exists once
|
||||
in the repo, in the function B's placer also calls. The three constants the mirror carried
|
||||
(`FURN_FOOT`, `BENCH_S0`, `BENCH_STEP`) are gone from `sim.js`.
|
||||
|
||||
| | measured |
|
||||
|---|---|
|
||||
| bench front vs direction-to-road, 40 stations | **0.0° (min = median = max)** — was 90.0° |
|
||||
| bench front vs the street's own direction | 90.0° |
|
||||
| stations matched to real instanced geometry (`r41_citizens.py` arm 6) | **14/14 within 2 cm · 14/14 on yaw** |
|
||||
| CONTROL: the same stations offset 2 m | **0/14** — "coincides" is a measurement |
|
||||
| live sitters, off the bench origin | **+0.06 m in front · ±0.38 m along the plank · 0.0° between look direction and the road** |
|
||||
| identity + posture signatures across the rewire | **byte-identical** (station COUNT unchanged ⇒ same randoms drawn) |
|
||||
|
||||
`_seatPose` needed no edit: it was written in the bench's own local frame (+Z front, +X plank), so
|
||||
the corrected yaw carries straight through. The import is **static on purpose** — the bench check
|
||||
draws three seeded randoms per crossed station, so a dynamically-imported station list would draw
|
||||
zero until it landed and "same seed → same crowd" would start depending on the network. `chunks.js`
|
||||
already imports `furniture.js` on every boot (classic and `?noassets` included), so the game path
|
||||
gains no fetch, no module and no behaviour. The standalone `citizens_test.html` does gain one
|
||||
fail-soft depot fetch; smoked green, 507 active citizens, 0 console errors.
|
||||
|
||||
### The browse smoke (F's filing) — fixed, and `r41_shots.py` exits 0
|
||||
|
||||
The arm scanned up to 8 occupied shops at ~3.1 s each, kept the id of the best, **exited**, and
|
||||
re-entered it 16–25 s later — choosing a subject out of a live simulation and then going back for it.
|
||||
The fix is not a longer wait or a retry: the frame, the measurements and the JPEG are now taken
|
||||
**inside the winning room, the moment a candidate beats the incumbent**, and the harness never
|
||||
leaves. The scan keeps its original "best of several, not first that works" behaviour. The winning
|
||||
shop's occupancy at entry and at the end of the scan is printed into the sidecar on every run, so the
|
||||
decay the old path walked into stays legible instead of being a story in a comment.
|
||||
|
||||
Result: `tools/qa/r41_shots.py` **rc 0** — `Echo Toys & Games`, 3 browse points, **1 browser in
|
||||
frame playing `browse_open_cabinet`**, keeper on `idle_weight_shift`, 61 draws of the ≤350 interior
|
||||
law, both figures human-sized. It can go strict.
|
||||
|
||||
**The diagnosis, corrected by measuring it.** F filed this as *deterministically* red. It is not, on
|
||||
today's tree. I ran the R41 arm **verbatim, inside its own harness, after the same street arm and the
|
||||
same 60 s soak**, and it came back `{browsePoints: 3, browsers: 1}` on the re-entry — green. Two
|
||||
isolated repros agreed (one found 2 browsers still there after a 16.4 s scan, occupancy 2 → 2). So
|
||||
the correct claim is not "it was always red and now it is green"; it is that the arm was **flaky by
|
||||
construction** — it chooses a subject out of a live simulation and then goes back for it 16–25 s
|
||||
later, and whether the occupants are still there is not something the harness controls. That is
|
||||
exactly the property that cannot go `--strict`. Removing the window removes the flake; the residual
|
||||
failure mode ("no occupied shop has browse points with browsers at all") is real, deterministic, and
|
||||
worth failing on.
|
||||
|
||||
### Gates
|
||||
|
||||
```
|
||||
tools/qa/r42_cast.py PASS — contract · roster · determinism · draws · animation · impostor
|
||||
tools/qa/r41_citizens.py PASS — boot ledger, lazy loading, determinism, draw table,
|
||||
asset-free, bench binding (14/14 + 14/14 yaw), venue clips
|
||||
tools/qa/interior_scale_check.py PASS — 29 figs / 6 shops, all human-sized, under ceiling
|
||||
tools/qa/r41_shots.py rc 0 — street + browse interior, browse arm now shot in place
|
||||
tools/qa/r42_shots.py rc 0 — r42_street_crowd.jpg + r42_pub_crowd.jpg, cast named in both
|
||||
console errors 0 on default · 0 on ?classic=1 · 0 on the impostor arm
|
||||
```
|
||||
|
||||
### Shots
|
||||
|
||||
- `docs/shots/laneD/r42_pub_crowd.jpg` — **the before/after**, shot on Lane F's own §41.6 hero recipe
|
||||
(`?gigs=1`, segment 5, The Exchange Hotel, Lane C's `room.spawn` line, back 5.2 m, eye 1.62 m) so
|
||||
it is frame-comparable with `docs/shots/laneF_r41/pub_furnished.jpg`. **BEFORE:** a purple
|
||||
`EL CHUPACABRA` luchador filling the foreground, a second on the stage, and an unclothed figure at
|
||||
the stage edge. **AFTER:** 13 figures wearing 11 distinct bodies, none of them retired.
|
||||
- `docs/shots/laneD/r42_street_crowd.jpg` — the recast roster on the footpath, with the corrected
|
||||
bench pose.
|
||||
- `docs/shots/laneD/r42_impostor_boundary.jpg` — near rig ‖ mid billboard for the five new bodies
|
||||
plus the reference, with each pair's measured luminance ratio.
|
||||
- Both crowd sidecars carry the **R10 human-sized line** *and* a **CAST line** naming every body in
|
||||
frame and asserting none is retired — because "the luchador is gone" is a claim about the cast and
|
||||
a JPEG cannot carry it alone.
|
||||
|
||||
### For Lane E / Lane F
|
||||
|
||||
- **`dj_phrtt_01.glb` is still in `web/models/peds/` and still on the depot** (`_normalized/`,
|
||||
`_published.json`). It is no longer reachable from the game — nothing names it — but the file is
|
||||
not mine to delete. **E: withdraw the depot copy.** **F: the §42.6 period-law name gate should read
|
||||
`PED_NAMES`, not the directory listing**, or it will go red on files that no longer ship.
|
||||
- **`comical_boy_01` was a real contract break, not only a period one** (3 132/3 460, six unprefixed
|
||||
thumb bones). If any other Mixamo asset was converted through the same path, check its boneset.
|
||||
- **The near↔mid impostor seam is loose town-wide** (0.470–2.100 across the roster; worst is
|
||||
`man_dj_streetwear_01`, not a new body). Filed for R43: bake the atlas under the scene's own key.
|
||||
|
||||
### Files touched
|
||||
|
||||
`web/js/citizens/rigs.js` (the swap + the law at the declaration) · `web/js/citizens/sim.js` (bench
|
||||
mirror retired → `benchStops` import) · `web/js/citizens/keepers.js` (publish `pedIndex` on the
|
||||
keeper handle — informational, draws no randoms, so the room's cast is auditable at runtime the way
|
||||
`band.js` members already were) · `tools/qa/r42_cast.py` (new) · `tools/qa/r42_shots.py` (new) ·
|
||||
`tools/qa/r41_shots.py` (browse arm) · `tools/qa/interior_scale_check.py` (one array entry, declared
|
||||
above) · `D-progress.md` · `docs/LANES/LANE_D_NOTES.md` · `docs/shots/laneD/**`. **No git command was
|
||||
run.** I did not touch `web/models/**`, `web/assets/**`, `pipeline/**`, `web/js/world/**`,
|
||||
`web/js/interiors/**`, `web/index.html` or `tools/qa.sh`.
|
||||
|
||||
---
|
||||
|
||||
## Round 41 (§41.3) — the citizens come alive: Lane E's 46 clips wired, 20 of them now in play. (2026-08-07)
|
||||
|
||||
**The measurement that is the round.** A census of the live crowd at the retail heart, 12 samples ×
|
||||
146 active citizens, same seed, same pose, two arms — where `?clips=0` is a new flag that turns the
|
||||
motion library off and **nothing else** (`?classic=1` also changes the ped pool, the fog, the game
|
||||
and half the shell, so it cannot be the control for this):
|
||||
|
||||
| state of the street crowd | `?clips=0` = R40 | default = R41 |
|
||||
|---|---:|---:|
|
||||
| walking | **99.3%** | 78.1% |
|
||||
| sitting on an actual Lane B bench | — | **9.5%** |
|
||||
| leaning on a shopfront | — | **7.1%** |
|
||||
| stopped at a window in their own seeded idle | 0.4% | **5.1%** |
|
||||
| R17 free sit · R29 glance | 0.2% · 0.2% | 0.1% · 0.1% |
|
||||
| **distinct animation clips in play across the crowd** | **4** (`walk`/`idle`/`sit`/`look` — and 99.3% of the crowd is on `walk.glb` alone) | **20** |
|
||||
|
||||
Lane E's input was verified before anything was wired: `python3 pipeline/clips_verify.py` → GREEN,
|
||||
0 errors / 0 warnings, 46 clips / 6 groups / 3 498 124 B, every group zero-draw.
|
||||
|
||||
### What each clip category drives
|
||||
|
||||
| group | clips used | drives |
|
||||
|---|---|---|
|
||||
| `idles.glb` (boot) | **10 of 10** | the per-citizen deterministic idle — the resting action of every near-tier actor, plus the seeded shopkeeper idle |
|
||||
| `locomotion.glb` (boot) | 1 of 6 | `walk_shopping_bag` — the walk pool is `['@walk','@walk','walk_shopping_bag']`, so **1 in 3 citizens carries a bag** (measured 33.7% over 1 000 ids) |
|
||||
| `sitlean.glb` (lazy: first sit/lean intent) | **8 of 8** | 4 sits bound to Lane B's benches, 4 leans bound to shopfront walls |
|
||||
| `browse.glb` (lazy: first interior with browsers) | 5 of 8 | a real BROWSE state at Lane C's browse points, seeded per (shopId, slot) |
|
||||
| `venue.glb` (lazy: gig night, from the street) | 5 of 6 | gig-crowd widening + the publican pouring + the record-shop keeper in headphones |
|
||||
| `social.glb` | **0 of 8 — never fetched** | needs a two-person state machine + partner pairing. 896 KB for a state D does not have. → R42 |
|
||||
|
||||
Unused and why: the four `turn_*` + `walk_to_stand` (the sim turns instantly at a node — a turn state
|
||||
is R42 and is the only thing that makes those five mean anything), `browse_hold_turn_l/r` (172°/147°
|
||||
loop seams, one-shot turns), `venue_clap_seated` (no seated venue slot).
|
||||
|
||||
### Boot fetch count and memory — measured in fresh headless contexts
|
||||
|
||||
| | clip fetches | bytes resident | groups / clips |
|
||||
|---|---:|---:|---|
|
||||
| **at boot** | **4** — manifest (16 KB) · `clipbank.js` · `idles.glb` · `locomotion.glb` | **1 242 272 B** | 2 / 16 |
|
||||
| + first street sit/lean intent | +1 `sitlean.glb` | 1 600 384 B | 3 / 24 |
|
||||
| + first interior with browsers | +1 `browse.glb` | 2 076 440 B | 4 / 32 |
|
||||
| + first gig night | +1 `venue.glb` | 2 602 476 B | 5 / 38 |
|
||||
| `?clips=0` · `?classic=1` · `?noassets=1` | **0** | 0 | 0 / 0 |
|
||||
|
||||
Six fetches at boot was what the round warned about. The answer is **two GLBs at boot, three on first
|
||||
demand and one never**, each promise-cached (the bank does not grow across 6 interior enter/exit cycles).
|
||||
**Heap delta (library ON − OFF), forced GC, fresh contexts: +3.34 MB and +6.87 MB across two runs** —
|
||||
the spread is GC timing; the floor is resident clip bytes plus parsed `AnimationClip` overhead.
|
||||
`mixerMs` unchanged: median 0.1 ms both arms.
|
||||
|
||||
### Determinism — same seed, same postures, byte-equal
|
||||
|
||||
- 150 active citizens, **two fresh browser contexts → byte-equal** posture signature.
|
||||
- **CONTROL:** seed 20261991 gives a different signature — "byte-equal" is not "constant".
|
||||
- **CONTROL that matters for lazy loading:** with **every clip GLB delayed 2 s** by a Playwright
|
||||
route, the signature is **identical**. Posture is a pure function of `(citySeed, id)`
|
||||
(`postures.js posturesFor`), decided once at citizen creation and never re-rolled, so loading can
|
||||
change *when* a posture appears, never *which one*.
|
||||
- One level down: `fleet.clipsRequested` is published **synchronously** by `loadPedFleet` before the
|
||||
dynamic import starts, and every roll that can move a citizen is gated on that rather than on
|
||||
"has it landed", so the number of randoms drawn per boot cannot depend on the network. Six
|
||||
freshly-keyed streams (`posture`, `benchstop`, `leanstop`, `browse-pose`, `keeper-pose`,
|
||||
`gig-pose`) collide with none of the 12 pre-R41 keys — gated, with the control that all 18 produce
|
||||
different draws on the same id.
|
||||
- 1 000 citizens × two module instances in node: identical (`sha256 08ecfe041b384460`).
|
||||
|
||||
### Draw table — before / after, same seed, same bookmarks
|
||||
|
||||
| view | `?clips=0` | default | Δ | budget |
|
||||
|---|---:|---:|---:|---|
|
||||
| street_noon | 193 | 193 | **+0** | ≤300 |
|
||||
| crossroads_busy | 108 | 108 | **+0** | ≤300 |
|
||||
| night_crowd | 128 | 128 | **+0** | ≤300 |
|
||||
| market_square | 94 | 94 | **+0** | ≤300 |
|
||||
| night_neon | 111 | 111 | **+0** | ≤300 |
|
||||
| interior (record) | 110 | 110 | **+0** | ≤350 · margin **240** |
|
||||
|
||||
Ruling 4 gave the street no headroom and the round spent none. The clips are skeleton-only (0 tris,
|
||||
0 meshes, 0 materials), so the only mechanism by which they could move a draw count is putting a ped
|
||||
somewhere else — bounded by the unchanged near-cap of 24 rigs, each already one draw. **Stated, not
|
||||
hidden:** 193 is the worst of these five bookmarks on this seed, not the town's global worst (B's
|
||||
R40 street number is 291/300 at a night pose these bookmarks do not reach). The **delta** is the
|
||||
claim, and it is 0.
|
||||
|
||||
### `?noassets=1` and `?classic=1` — the fallback survives, proven
|
||||
|
||||
| | clip fetches | bank | town | console |
|
||||
|---|---:|---|---|---|
|
||||
| `?noassets=1` | **0** (no GLB, no manifest, not even `clipbank.js`) | none | 20 chunks, 165 citizens walking | 0 errors |
|
||||
| `?classic=1` | **0** | none | 18 chunks, 139 citizens walking | 0 errors |
|
||||
| `?clips=0` | **0** | none | full town | 0 errors |
|
||||
|
||||
The library rides `clips = dance`, the R36 `djs = dance` trick again: the shell already passes
|
||||
`dance: !CLASSIC`, so **no `index.html` edit was needed** and the zero-fetch-delta covenant holds —
|
||||
including on the JS surface, because `clipbank.js` is reached by a dynamic `import()` and is not
|
||||
fetched when the gate is off.
|
||||
**One honest line on the `?classic=1` fetch delta.** It is zero for ASSETS — no clip GLB, no
|
||||
manifest, and not even `clipbank.js` (dynamic import behind the gate). It is **+1 JS module**:
|
||||
`postures.js` (7 098 B, pure data + five pure functions) is a static import of `sim.js`/`keepers.js`/
|
||||
`band.js` and so is fetched on every boot, exactly as R40's `door_snap.js` (4 017 B) already is. Zero
|
||||
asset bytes, zero GLB, zero behaviour under the gate — but stated rather than left to be found.
|
||||
|
||||
|
||||
### Leak
|
||||
|
||||
Warm the caches with one enter/exit, then **6 more cycles into the same shop**: geometries 118 → 118
|
||||
(**+0**), textures 91 → 92 (**+1**), clip bank flat at 4 groups / 32 clips. The R2 shared-resource
|
||||
disposal contract still holds with more clips resident — `AnimationClip`s hold no GPU resources, and
|
||||
`_disposeInner` still disposes only the clone's own `Skeleton`.
|
||||
|
||||
### The two measurements that changed the design
|
||||
|
||||
1. **The idle pool was invisible.** Wired only to the R17/R29 window-shop loiter — which fires at a
|
||||
graph NODE, and edges are long — a census found **0.8% of citizens stopped at any instant**. Nine
|
||||
of ten new idles were assigned, deterministic, and never seen: the brief's headline failing while
|
||||
the gate went green. Fix: a **window pause** on the R8 patronage stride check (the moment the sim
|
||||
already asks "is there a shop beside me"). Stopped share **0.8% → 5.1%**, and all ten idles now
|
||||
appear in a 12-sample census.
|
||||
2. **The lean was never firing.** Also wired to the node loiter at first — i.e. at intersections,
|
||||
where there is rarely a shopfront to lean on. **Measured: 0 leans in a 9 s run.** Moved to the
|
||||
same stride check. Now 7.1%.
|
||||
|
||||
### The bench is a real bench
|
||||
|
||||
R17's bench-sit sat a ped down at whatever node it stopped at, upright, on air. R41 binds it to Lane
|
||||
B's actual furniture: `benchStationsFor(edge)` mirrors `furniture.js:203-209` exactly, a ped can only
|
||||
take a bench on its own footpath side, and the 1.6 m plank **seats two** (a measured need — the first
|
||||
soak put two citizens on one station to the centimetre). Because that is a mirror of another lane's
|
||||
file, it is **gated, not trusted**: every derived station inside the streamed window must coincide
|
||||
with real instanced geometry within **2 cm**, yaw included — today **14/14 and 14/14**, against a
|
||||
scene of 2 435 instances. **CONTROL:** the same stations offset 2 m match **0/14**. Filed to Lane B:
|
||||
one `benchStops(plan)` export next to the existing `busShelterStops(plan)` retires the mirror.
|
||||
|
||||
### Gig — widened, not redesigned
|
||||
|
||||
The v3 dance pick (`gigdance` over `fleet.danceClips`) is untouched. A second, independently keyed
|
||||
`gig-pose` roll decides whether a crowd slot takes a venue clip instead — northern soul for a dancer,
|
||||
clap/cheer/headphones for a stander. Measured on a live gig: **8 crowd slots, 6 swapped, 2 keeping
|
||||
their R13 pick verbatim, 0 pending**. A slot that does not swap, and every boot where `venue.glb`
|
||||
is not resident, is byte-identical to R13.
|
||||
|
||||
### Gates added
|
||||
|
||||
| gate | what it proves |
|
||||
|---|---|
|
||||
| `node tools/qa/r41_postures.mjs` | manifest resolution · determinism · stream isolation · pool spread · loop safety. Zero deps, ~40 ms, five controls. |
|
||||
| `tools/.venv/bin/python tools/qa/r41_citizens.py` | 7 arms in fresh contexts: boot ledger · lazy loading · determinism incl. the 2 s forced stall · draw table both arms · `?noassets`/`?classic` · bench binding + control · leak · gig widening. |
|
||||
| `tools/.venv/bin/python tools/qa/r41_shots.py` | the two acceptance shots, camera chosen by measurement and occlusion-raycast, every figure measured for stature. |
|
||||
|
||||
**→ F: three lines for `qa.sh`.** All exit 0/1, all deterministic.
|
||||
|
||||
### Shots
|
||||
|
||||
`docs/shots/laneD/r41_street_postures.jpg` — seed 20261990, midday, camera (11.6, 58.5) yaw −1.890.
|
||||
**8 near-tier rigs in frame, 3 distinct clips** (`@walk`, `walk_shopping_bag`, `sit_hands_thighs`): a
|
||||
hi-vis worker **sitting on a real Lane B bench** with his feet on the footpath, a comical ped walking
|
||||
centre-frame, and six more walkers up and down the strip. 126 draws of a 300 budget.
|
||||
|
||||
`docs/shots/laneD/r41_browse_interior.jpg` — The Op Shop (opshop), 3 browse points. A **browser at
|
||||
the clothes rack on `browse_hold_idle`** — the ped who ducked in off the street, holding something
|
||||
instead of standing to attention — and the keeper behind the counter on `idle_happy_1`, his own
|
||||
seeded idle. 69 draws of a 350 budget.
|
||||
|
||||
Both carry a `.txt` sidecar with the human-sized line: stature measured off the posed skeleton for
|
||||
every figure, as a ratio of that citizen's own nominal height. Today: sits 76–77%, browsers/keepers
|
||||
99–101%, walkers 95–100% of nominal. **No giant (>2.0 m), no fold (<55% of nominal).**
|
||||
|
||||
### Goldens
|
||||
|
||||
`node web/js/citygen/selfcheck.js` → **157 647/157 647, fingerprint `0x5f76e76`**, unmoved. No
|
||||
citygen file was touched.
|
||||
|
||||
### Files touched (only these)
|
||||
|
||||
`web/js/citizens/postures.js` (new) · `web/js/citizens/clipbank.js` (new) · `web/js/citizens/rigs.js`
|
||||
· `web/js/citizens/sim.js` · `web/js/citizens/keepers.js` · `web/js/citizens/band.js` ·
|
||||
`tools/qa/r41_postures.mjs` (new) · `tools/qa/r41_citizens.py` (new) · `tools/qa/r41_shots.py` (new)
|
||||
· `docs/shots/laneD/r41_*.{jpg,txt}` (new) · `D-progress.md` · `docs/LANES/LANE_D_NOTES.md`.
|
||||
**No git command was run.** Nothing under `web/models/`, `web/assets/`, `web/js/world/`,
|
||||
`web/js/interiors/`, `web/index.html` or `tools/qa.sh` was touched.
|
||||
|
||||
---
|
||||
|
||||
## Round 40 (§40.5) — the door points and the footpath: measured, kerb-clamped, gated. (2026-08-04)
|
||||
|
||||
**Verification first, per the brief.** New gate `tools/qa/door_footpath_check.mjs` re-derives every
|
||||
door point EXACTLY as the shell does (`lot + front-normal·(d/2+0.6)`, web/index.html:433-437) and
|
||||
classifies it against the plan's own geometry (registry's `vergeBand` corridor law + lots + blocks).
|
||||
All numbers below are from today's runs on today's tree.
|
||||
|
||||
| town | R39 (shipped) | today, B's fix only — footpath | today — walkable front | after D's clamp — footpath | **after D's clamp — walkable front** |
|
||||
|---|---|---|---|---|---|
|
||||
| synthetic@20261990 | 0/493 | 447/493 · 90.7% | 473 · 95.9% | 447 · 90.7% | **473/493 · 95.9%** ✅ |
|
||||
| katoomba_real | 0/72 | 71/72 · 98.6% | 71 · 98.6% | 72 · 100% | **72/72 · 100%** ✅ |
|
||||
| fitzroy_real | 0/139 | 138/139 · 99.3% | 138 · 99.3% | 139 · 100% | **139/139 · 100%** ✅ |
|
||||
| bowral_real | 0/30 | 28/30 · 93.3% | 28 · 93.3% | 30 · 100% | **30/30 · 100%** ✅ |
|
||||
|
||||
**Verdict: B's two-character R39 fix did the heavy lifting (0% → ~91–99%) but did NOT clear the
|
||||
≥95% gate alone** — bowral read 93.3% and the synthetic 90.7% strict. Two residue species, and they
|
||||
wanted different answers:
|
||||
|
||||
1. **Kerb misses (fixed by D).** On real towns `plan_osm` sets lots back at
|
||||
`max(KERB, roadWidth/2 + FOOTPATH)`, so on narrow corridors the facade sits past the corridor
|
||||
edge and the shell's `+0.6` nudge lands the door point **0.85 m outside the verge band** —
|
||||
1/72 katoomba, 1/139 fitzroy, 2/30 bowral. Fix: **`web/js/citizens/door_snap.js`** (new, pure,
|
||||
no THREE, no rng) — clamp a door point into the footpath band of its nearest footpath-bearing
|
||||
street, applied at intake in `sim.setShops`. Registry's published `vergeBand` over the sim's own
|
||||
edges; graph-only stays graph-only. Capped at 6 m so a plaza front is never dragged to a street
|
||||
it doesn't front; margin 0.45 m off the band edges. Transition-checked corpus-wide: **fixes 4,
|
||||
breaks 0** (a gate arm asserts breaks = 0 forever). Live-shell verified: booted the game,
|
||||
diffed `sim.shopsByChunk` against the raw derivation in the running page — 493 points, 2 moved,
|
||||
max 5.41 m, exactly matching the node run.
|
||||
2. **Plaza fronts (a definition, not a defect).** The synthetic's 36-shop strict-footpath residue
|
||||
is **market stalls fronting the market square** (`use:"stall"`, block 25 `kind:"market"`) and
|
||||
arcade shops — their front IS the square, they can never be in a street's verge band, and
|
||||
dragging their door 50–73 m to a street they don't front (measured: that's what an uncapped
|
||||
snap does) would re-break the front-door truth. The gate therefore scores **walkable front
|
||||
ground** — footpath, or the open ground of a `market`/`arcade` block (outside every lot, off
|
||||
every carriageway) — and reports the strict R39-style footpath number alongside for continuity.
|
||||
Peds never reach stalls anyway (50–73 m from every walkable edge vs PATRON_RANGE 18) — their
|
||||
door points are inert data today.
|
||||
|
||||
**Remaining synthetic residue (20/493, under the floor, documented not fixed):** stall-vs-stall
|
||||
front overlaps (6), stalls at the square's edge clipping a carriageway (8), 3 shop + 1 anchor
|
||||
corner cases, 2 shops fronting into a neighbour lot. All are **derivation** artefacts of the fixed
|
||||
`d/2+0.6` nudge in `web/index.html:433-437` (F's shell, B's R39 hands) — if a later round wants
|
||||
them, the recommended fix is deriving from `buildShopfront`'s own `doorRect` instead of the fixed
|
||||
nudge; measured detail in LANE_D_NOTES §40. Out of D's graph-only reach and under the gate's floor.
|
||||
|
||||
**Gate arms (all green, 12/12):** rate ≥95% on all four towns · CONTROL — the back point (the exact
|
||||
R8–R39 defect) must score under the floor on every town or the classifier is vacuous (it reads
|
||||
0.4–13.3%) · FIX-ONLY — the clamp breaks 0 previously-passing points. Deterministic: two runs
|
||||
byte-identical JSON. **Goldens untouched: selfcheck 157,647/157,647, fingerprint `0x5f76e76`** (the
|
||||
clamp is runtime pose in D's sim; citygen never touched).
|
||||
|
||||
**The picture:** `docs/shots/laneD/r40_front_duckin.jpg` — a rigged ped mid-stride ON the footpath,
|
||||
frozen 3.2 m from the VIDEO BARN front door it is ducking into (staged via the sim's own
|
||||
`_beginVisit` + a pause at the threshold; steering, door point and entry are the shipped code).
|
||||
|
||||
**→ Fable/Lane F (one line, as briefed):** please wire `node tools/qa/door_footpath_check.mjs` into
|
||||
`tools/qa.sh` (rc 0/1, ~2 s, zero deps — same shape as the other node gates). Not editing qa.sh
|
||||
myself; F owns the runner.
|
||||
|
||||
Files touched: `web/js/citizens/door_snap.js` (new) · `web/js/citizens/sim.js` (import + setShops
|
||||
clamp) · `tools/qa/door_footpath_check.mjs` (new gate) · `docs/shots/laneD/r40_front_duckin.jpg`
|
||||
(new) · this file · LANE_D_NOTES §40.
|
||||
|
||||
## 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
|
||||
|
||||
1172
E-progress.md
1468
F-progress.md
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).
|
||||
157
README.md
@ -14,9 +14,38 @@ cd web && python3 -m http.server 8130
|
||||
# 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 |
|
||||
| wake up (sleep or arrive) | **the morning paper** rides the wake/arrival toast: "⚖ SOLD 'Milk Bar Dreams' — hammer $8, $7 after fees" (or "PASSED IN — it comes home"). The news is a toast, never saved — the ledger is your cash and crate |
|
||||
| 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) |
|
||||
| **F2** | **credits & licences** — what PROCITY is built from and what each source asks for in return (R41). On a real-map boot the HUD link itself carries the obligation: `© OpenStreetMap contributors · credits (F2)` |
|
||||
| **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
|
||||
|
||||
- **`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.
|
||||
|
||||
## The lanes (parallel Opus 4.8 sessions)
|
||||
@ -47,3 +76,131 @@ standalone test pages); F integrates after they land.
|
||||
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
|
||||
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. **`?pop=0` means zero as of R42** — the shell read `parseInt(…) \|\| 140` and `0` is falsy, so for 35 rounds every "town frozen" measurement in this project booted 140 citizens. For a genuinely still frame use `?dbg=1` + **`DBG.freeze()`**, which also covers the streamed roster (it ignores `target` entirely) and returns `stillLive` naming every other wall-clock mover in the scene |
|
||||
| `?dig=0` · `?stock=real` | dig on | crate-riffle on record bins · real GODVERSE sleeves in the stock — and, since R41, the **op-shop wardrobe**: 52 named 1990s Australian garment layers on the clothes rails and shelves, one 246 KB atlas, one material, and it costs *fewer* draws and 11 fewer textures than the procedural canvas it replaces. Opt-in: a default boot still builds the canvas, byte-identically |
|
||||
| `?clips=0` | **library on** | turns the R41 **motion library** off and nothing else — 46 Mixamo clips in 6 grouped GLBs (2 fetched at boot, 3 on first demand, 1 never). With it off the crowd is 99.3% walking on one clip; with it on, 78.1% walking, 9.5% sitting on a real bench, 7.1% leaning on a shopfront, 20 distinct clips in play. `?classic=1` is a bad control for this (it also changes the ped pool, the fog and half the shell) which is exactly why this flag exists |
|
||||
| `?verge=0` | **off** | reverts the ground to the **pre-kerb-ruling** geometry (bitumen painted to the full corridor). John's v8 ruling made the band between kerb and shopfront **footpath** — 9 m on the synthetic town's 28 m main, **7 m** on a real 24 m main, and **3 m** on the `side`@12 edge that is 72% of the corpus. The flag stays forever as the gate's falsifiability control: the poster-clearance leg is asserted to FAIL under it, at exactly 14 posters standing on road paint. `?classic=1` forces the ruling off — the covenanted town keeps its paint |
|
||||
| `?r=N` · `?shadows=0\|1` | auto | streaming radius and the sun-shadow pass. **Auto is load-bearing**: a big town (>120 shops or >200 street edges) gets radius 2 with shadows off. A `?r=` **above** auto is a DIAGNOSTIC boot and declares its own law (R40): `?r=3` measures **382 noon / 391 night** draws at 45–48 live chunks — structural, not fat — so it boots under a pinned **≤420** ceiling (`PROCITY.budget`, console-warned, gated by `tools/qa/r40_lane_b.py`), tri law unchanged. Not a supported boot |
|
||||
| `?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) `--matrix` (the town matrix)
|
||||
`--tour` (R42's paired before/after proof shots); the seated drummer passes the no-giants gate at the
|
||||
seated stature band `[0.9,1.5] m`.
|
||||
|
||||
**Two laws about the cast, as of R42** (`tools/qa/r42_roster.mjs`, both controls demonstrated every
|
||||
run): **PROCITY is 1990s Australia** — no fantasy, superhero, luchador, anime or medieval body ships,
|
||||
gated by name against `PED_NAMES` rather than a directory listing; and **every shipped ped carries a
|
||||
real `credits.json` row** — an `unverified` licence on a body is a red gate, not a note. The R42 audit
|
||||
retired five bodies on those two laws, including one nobody had looked at since R36. Screenshot
|
||||
byte-hashes are pinnable again (R41's ban is reversed): the condition is *nothing in frame runs on
|
||||
wall-clock time*, asserted via `DBG.freeze().stillLive` — see `docs/shots/laneF_r42/README.md`.
|
||||
|
||||
## v7.0 — THE GAME: you're a crate-digger in 90s Australia
|
||||
|
||||
v4 made the towns real, v5 made the shops real — **v7 makes it a game.** Boot the plain URL on a
|
||||
fresh browser and you arrive HUNTING: the splash names a seeded first want and your pocket money
|
||||
("You're hunting HILLS HOIST — $191 in your pocket"), three toast-beats walk you through the first
|
||||
dig, and from there the loop is live:
|
||||
|
||||
**dig** (E at a record bin — the crate riffle) → **read the guide** (every price card carries a 90s
|
||||
price-guide band; an item asking *under* its band is a gem, and spotting one is the whole skill) →
|
||||
**☆ WANT** what you're hunting (keepers gossip seeded rumors — "saw one in Castlemaine") →
|
||||
**travel** (switching towns costs a day; the 23 towns are a strategy map) → sell to the **keeper**
|
||||
(instant cash, always under guide — the no-pump law) or **⚖ LIST on GodBay** (a seeded offline
|
||||
auction that hammers overnight; one hammer per item, ever — no retry farm) → **SLEEP** (= save; the
|
||||
day advances, parody/mint stock rotates per (shop, day), bought slots come back) → wake to **the
|
||||
morning paper** ("⚖ SOLD — hammer $22, $19 after fees"). Repeat, richer and one gem wiser.
|
||||
|
||||
The machinery under it: a versioned localStorage save carrying THE DELTA ONLY (cash · collection ·
|
||||
day · town · wants · pulls · listings — the world always regenerates from seed, so a corrupt save
|
||||
can cost you your stuff, never the town), JSON export/import for moving machines, and an economy
|
||||
fenced by gates: sell < guide-band structurally, auction EV ≈ 0.8×mid (random flips bleed; only
|
||||
finds profit), save/load byte-determinism, rotation seeded by (citySeed, shop, day).
|
||||
|
||||
**`?classic=1` still boots the pure v2 town — zero save, zero game DOM, zero Storage calls,
|
||||
instrumented every run** — and `?game=0` opts out of the layer on any boot. Charter:
|
||||
[`docs/V7_THE_GAME.md`](docs/V7_THE_GAME.md) (ratified 2026-07-17, risk verdicts appended at the
|
||||
close) · economy: [`docs/G3_ECONOMY_DESIGN.md`](docs/G3_ECONOMY_DESIGN.md) · the tour (day one to
|
||||
first gem, 6 frames, no staging): [`docs/shots/v7_tour/`](docs/shots/v7_tour/).
|
||||
|
||||
@ -1,8 +1,690 @@
|
||||
# LANE A — CITYGEN · progress (PROCITY-A)
|
||||
|
||||
*Status: **all deliverables landed; self-check all-green (1727/1727); rounds 2–9 closed** (see below).
|
||||
`qa.sh --strict` GREEN.*
|
||||
*Date: 2026-07-15 · owner: PROCITY-A (Opus 4.8)*
|
||||
*Status: **all deliverables landed; self-check ALL GREEN (157,647/157,647); rounds 2–30 + 37 + 39–40 closed**.
|
||||
R40 (§40.2): the v8 debt ledger settled — D4 CLOSED (all-edges poster bound; 8/108 plans → 0; poster
|
||||
counts frozen on all 108; synthetic byte-identical; 7 town gig goldens re-pinned), D1 A-side CLOSED
|
||||
(`chunkIndexByCoverage` canonical + compat alias), D1(B)/D2/D3 formally re-filed with re-measured
|
||||
numbers and owners in `LANE_A_NOTES §40`.
|
||||
R39 (v9 item 39.1): THE ADDRESS LAYER — 17,835 named ways stop being dropped on the floor. New pure
|
||||
module `address.js`; **1,192/1,219 corpus shops (97.8%) resolve to a real street name**, 0 wrong names
|
||||
on 884 shop-bearing edges against an independent resolver, **all goldens frozen, zero re-pins.**
|
||||
R37 (v8 wave 0 §0.1): the poster-clearance gate was VACUOUS — it validated my own placer against my own
|
||||
law and stayed green while all 14 posters on the default boot stood on B's bitumen. Corrected to three
|
||||
arms with a permanent control; proven to fail on the pre-ruling geometry where the old gate cannot.
|
||||
All goldens frozen, zero re-pins.*
|
||||
|
||||
## Round 40 (§40.2) — (2026-08-04) THE DEBT ROUND: D1–D4 closed or re-filed, every verdict with a number
|
||||
|
||||
Per the R40 brief: no third option, no silent carry. Every number below was produced by a command run on
|
||||
today's tree (the §37 filing's numbers were re-derived, and two of them changed — which is the point).
|
||||
Full prose + owner handoffs in `LANE_A_NOTES §40`.
|
||||
|
||||
| item | close/re-file | number | evidence command |
|
||||
|---|---|---|---|
|
||||
| **D4** poster gate inner bound nearest-edge; `arcade` camouflages a main | **CLOSED** — `gigs.js onCarriageway` + selfcheck arm 1 both now ALL-EDGES | before: **8/108** gig plans w/ spine poster inside a non-nearest carriageway, worst **1.78 m** inside a main (northbridge_real 42, nearest edge `arcade` @ 1.45 m, kerb 0); after: **0/108**; frontage all-edges clean **238/238**; poster counts **0 diffs on 108 plans** (old vs new placer pairwise); synthetic **byte-identical 0/4** | scratch `d4_measure.mjs` / `d4_frontage.mjs` / `d4_counts.mjs` (corpus sweep over 4 syn seeds + 3 fixtures + 23 towns × 4 seeds), then `node web/js/citygen/selfcheck.js` |
|
||||
| **D1(A)** `chunkIndex` two shapes, false swap-comment invites a streamer break | **CLOSED (A-side)** — `chunkIndexByCoverage` canonical in `plan.js`/barrel/selfcheck; `chunkIndex` kept as compat alias (map.html imports it); CITY_SPEC §chunk-key + producer API amended | a naive swap would multi-build **258/681 lots** (A coverage: 258 lots in ≥2 chunks; B centre: 0/681); container `Object` vs `Map`, elements ids vs objects — all three axes live today; alias verified `chunkIndex === chunkIndexByCoverage` | `node -e` three-axis diff over seed 20261990 (in-session, printed) |
|
||||
| **D1(B)** `planutil.js:4` false comment + B-side name | **RE-FILED → B** | same 258/681; comment still present at `world/planutil.js:4` (grep) — can wait because both consumers are correct today and the trap only fires on the invited future swap, now contradicted by the shipped canonical name | `grep -n chunkIndex web/js/world/planutil.js web/js/world/chunks.js web/map.html` |
|
||||
| **D2** `isOpen` ×3 impls + ad-hoc 4th; shell inverts A's no-hours law | **RE-FILED → F** | A's canonical still **0 importers**; divergent predicate live at `index.html:541` + `audio.js:516` + **19 call sites / 5 tools files**; `sim.js:363` third impl; `dbg.js:241,279,286` fourth site; still **latent: 0/1,838 corpus shops lack hours** (27 plans @ golden seed) — can wait until the first hours-less lot kind ships, fix is ~5 lines in F's `index.html` | grep census + node corpus sweep (27 gig plans), printed in-session |
|
||||
| **D3** two shop registries; venues wear op-shop signage | **RE-FILED → B** | `SHOP_REGISTRY` still **9 types**, missing `pub`,`band_room`,`rsl` (A's `SHOP_TYPES`: **12**, all with `sign:{bg,fg,accent,style}`); bite line now `buildings.js:465`; **81 venue lots** corpus-wide @ golden seed render op-shop palette — can wait because cosmetic-only (no gate, no plan field), and the fix may move a **screenshot** golden (F re-pins), never a plan golden | node corpus sweep (venue count) + `grep -n SHOP_REGISTRY web/js/world/buildings.js web/js/world/fixture_plan.js` |
|
||||
|
||||
**GOLDENS:** synthetic fingerprint **`0x5f76e76` FROZEN** · `GIG_GOLDEN 0xec7a2d39` FROZEN · all base
|
||||
town goldens + 3 osm fixtures FROZEN · **7 `REAL_TOWN_GIG_GOLDENS` re-pinned under D4's explicit
|
||||
requirement** (adelaide/bendigo/fitzroy/fremantle/geelong/marrickville/northbridge — old hashes kept in
|
||||
the pin table comments; the §37 filing predicted 11-of-26 on the R37 tree, today it is 7-of-24).
|
||||
Selfcheck **ALL GREEN 157,647/157,647** after all edits. Files touched: `web/js/citygen/gigs.js`,
|
||||
`web/js/citygen/plan.js`, `web/js/citygen/index.js`, `web/js/citygen/selfcheck.js`,
|
||||
`docs/CITY_SPEC.md`, `docs/LANES/LANE_A_NOTES.md`, this file. Nothing outside Lane A's set.
|
||||
|
||||
## Round 39 (2026-08-03) — v9 item 39.1: THE ADDRESS LAYER (the approved thing; B and C consume it)
|
||||
|
||||
**The full contract, per-town table and reasoning live in `LANE_A_NOTES.md §39`** — published in the
|
||||
first commit, because B (39.2 the fog) and C (39.3 the op-shop dig) build against it in parallel.
|
||||
|
||||
**Fable's three binding facts, re-derived independently — all three land exactly.** 19,132 road ways,
|
||||
**17,835 named (93.2%)**; median plan-edge-sample → nearest named way **0.03–0.90 m** (worst katoomba);
|
||||
**1,192 / 1,219 corpus shops (97.8%)** resolve. Nothing to contradict.
|
||||
|
||||
**What landed.** `web/js/citygen/address.js` — pure, **zero imports**, no THREE/fetch/DOM/module state,
|
||||
no plan mutation. `createAddresses(plan, cache|null)` → `streetOf` · `localityOf` · `cohort` ·
|
||||
`streets` · `stats`. Two suppliers, one field: a cache town fills `label` from real named ways, the
|
||||
synthetic from `district.kind` + block. Plus `getTownCache(key)` (additive, read-only) because
|
||||
`index.html:129` registers the cache and drops its reference, so `roads[]` was unreachable downstream.
|
||||
`map.html` now loads real caches and prints the address — verified in-browser on castlemaine (*"XXXX
|
||||
Antiques — Elizabeth Street · south side"*) and on the synthetic (*"Nev's Pawn & Loan — the warehouse
|
||||
fringe · north side"*) **through the identical line of code**.
|
||||
|
||||
**Two departures from the synthesis, both measured.**
|
||||
|
||||
1. **The shift is recovered EXACTLY, not voted for.** I implemented the proposed centroid-align +
|
||||
nearest-waypoint vote first: it returns the **WRONG shift on 5 of 23 towns** (braddon, fremantle,
|
||||
hobart, northbridge, westend) — island culling drags the plan centroid off the cache's and the vote
|
||||
converges *confidently* on a wrong offset. A wrong shift renames every street in the town. Replaced
|
||||
with a lattice intersection (node − waypoint candidates, filtered by further nodes): **exact on
|
||||
23/23, nothing to tune, returns null rather than a guess.**
|
||||
2. **Five samples along an edge, not the midpoint.** A midpoint cannot distinguish a street from the
|
||||
street that crosses it — at an intersection both are ~0 m away.
|
||||
|
||||
**The one-line upgrade: TAKEN, and it is genuinely that clean.** `norm.shift = {shx, shz}` goes onto the
|
||||
normalization log, reaching the caller only via the existing `opts.report` sink — never the plan object.
|
||||
It is a **cross-check, not a dependency**: the selfcheck compares plan_osm's published shift against
|
||||
address.js's independent recovery and fires on a **0.01 m** disagreement. Without it the recovery would
|
||||
only be checkable against itself, which is the exact vacuous-gate species R37 was about.
|
||||
|
||||
**The gates (+240 checks), each proven to fire on a broken world.**
|
||||
|
||||
| arm | today | breakage that fires it |
|
||||
|---|---|---|
|
||||
| 0 wrong names on shop-bearing edges, vs an **independent way-membership resolver** | **857/857 agree, 0 wrong** | resolver or shift drift |
|
||||
| corpus resolution ≥ 97% | 1,192/1,219 (97.8%) | names stripped → 0.0% |
|
||||
| shift recovered == plan_osm's published shift | exact, 23/23 | a **0.01 m** error |
|
||||
| `createAddresses` does not mutate the plan | byte-identical per town | any write to `plan` |
|
||||
| CONTROL: `roads[].name` stripped (**the pre-change state**) | 0 streets, supplier `district` | — |
|
||||
| CONTROL: no cache passed | supplier `district`, visible in `stats()` | — |
|
||||
| all-edge disagreements **PINNED at 4** (not `> 0`) | 4/30,986 | a control drifting to zero |
|
||||
|
||||
The 4 corpus disagreements are all 2D-stacked ways where the question has no single answer (geelong's
|
||||
two adjacent malls; a motorway crossing *over* a street on glebe and redhill ×2) and **none carries a
|
||||
shop** — which is why the shop-bearing arm is 0.
|
||||
|
||||
**The tolerance finding, which is the one worth keeping.** Coverage rises with tolerance (2 m → 85.2%,
|
||||
6 m → 97.4%, 8 m → 97.8%, 12 m → 99.1%) but **correctness saturates at 6 m**: 857 agreeing frontage
|
||||
names at 6, 8, 12, 16 *and* 24 m. Every metre past 6 buys exactly one thing — a name attached to a way
|
||||
OSM left unnamed (0 at 6 m, 4 at 8 m, 14 at 24 m). **It can never buy a correction.** Shipped at the
|
||||
briefed 8 m, which keeps four borrowings, all arcade footways inside a named mall.
|
||||
|
||||
**And the resolver degrades to null, not to a wrong name.** Feed it a shift wrong by 30 m and
|
||||
castlemaine/katoomba/adelaide return **zero** street names rather than a town full of wrong ones. Feed
|
||||
it one wrong by 3 m and adelaide's coverage *rises* from 89 to 98 shops while being wrong — which is
|
||||
why the shift arm is exact-or-nothing and **coverage is never used as a proxy for correctness.**
|
||||
|
||||
**The failure modes, named.** All 27 unresolved shops front a way OSM genuinely leaves unnamed:
|
||||
adelaide 21 `arcade` (its arcades are the town's retail spine and OSM leaves the footways unnamed — it
|
||||
is the corpus worst at 80.9% and it is *honestly* worst), fitzroy 1, geelong 1, launceston 1 + 2 `lane`,
|
||||
northbridge 1. **`null` is the honest answer and the module returns it**; a shop with `street: null`
|
||||
carries `label: null` too, asserted per town.
|
||||
|
||||
**The synthetic's honest limit:** 12 label cells over 493 shops, median 41, max 147. Coarse — the same
|
||||
structural fact §8 of the charter records. `block` is the finer cell (32 shop-bearing blocks, median 14).
|
||||
|
||||
**GOLDENS: NOTHING MOVED, ZERO RE-PINS.** synthetic `0x5f76e76`, gig `0xec7a2d39`, 3 osm fixtures, 23
|
||||
`REAL_TOWN_GOLDENS`, 23 `REAL_TOWN_GIG_GOLDENS` — all untouched. Selfcheck **157,407 → 157,647 ALL
|
||||
GREEN**. `createAddresses` costs **≤27 ms** on the worst real town (bendigo), **0.3 ms** on the
|
||||
synthetic, once at plan time; zero draws, zero triangles, zero plan fields.
|
||||
|
||||
## Round 37 (2026-08-03) — v8 WAVE 0 §0.1: THE KERB (ruling-side) — the gate I owned was the vacuous one
|
||||
|
||||
Full detail in LANE_A_NOTES §37. Four jobs, all measured before they were written.
|
||||
|
||||
**1. Verified the audit against my own lane — CONFIRMED, exactly, with one refinement it did not make.**
|
||||
Default boot (synthetic, seed 20261990, gigs default-ON): **14 posters, all 14 inside `e.width/2` (the
|
||||
bitumen B paints), 0 inside the ruled kerb `roadWidth/2`.** The refinement: they are two populations —
|
||||
**12 spine posters at `poleOffset` = 6 m are 8 m inside the paint (genuinely mid-road); 2 frontage posters
|
||||
clip the corridor edge by 0.06 m** (13.94 of 14, 6.94 of 7 — `FACADE_PROUD` pushes them a fingernail past
|
||||
their venue's facade plane). Both are inside the paint; "14 in the middle of the road" would over-claim.
|
||||
Also confirmed: B's `ground.js` road quad is `hQuad(…, e.width, 0)` — the full corridor, kerbs at
|
||||
`e.width/2 + 0.12`, nine metres outside mine on a main.
|
||||
|
||||
**2. Diagnosed WHY the gate was green, and it is our own documented lesson recurring.** My placer seats
|
||||
posters via `poleOffset`, which is *derived from* `roadWidth`; my selfcheck asserted clearance against
|
||||
`roadWidth/2`. Gate and subject were the same function agreeing with itself — the R27 cross-convention
|
||||
trap, inside the very file whose header (`selfcheck.js:33-40`) explains it. It never read a number that
|
||||
came from B, so no change to the street could make it fail.
|
||||
|
||||
**3. Corrected it — three arms, the third a permanent control.** (1) no poster on a carriageway, measured
|
||||
through `vergeBand(e)[0]`, the published law itself — the same call B's ground now consumes; (2) every
|
||||
**spine** poster stands *inside* some edge's `vergeBand` (the outer bound the old check never had —
|
||||
frontage exempt **by measurement**: on 18/108 gig plans a frontage poster legitimately stands past every
|
||||
corridor edge, worst 3.98 m, launceston_real; spine violations 0/108); (3) **the control** — count the
|
||||
posters that would stand on bitumen under the pre-ruling `?verge=0` ground and assert it is **> 0** on
|
||||
every gig plan (measured min 3 / max 212 / never 0 across 510 plans), **pinned at exactly 14** on the
|
||||
default boot, because a control allowed to drift to zero is how a gate goes vacuous in the first place.
|
||||
The replica is pinned to B's **flag**, not to a line number that will drift as B edits.
|
||||
|
||||
**Proved it fails on the old geometry, and that the old gate cannot.** Simulated `?verge=0` honestly —
|
||||
`vergeBand` switched to the pre-ruling reading, `poleOffset` pinned to the true band so the **placer is
|
||||
byte-identical** (it is a render flag: the paint changes, the plan does not; fingerprint and every gig
|
||||
golden stayed green in both runs, so the gate's reading is the only variable). **Corrected gate: ✗ FAIL,
|
||||
1020 failures — 2 arms × 510 plans. Pre-R37 gate: ✓ ALL GREEN 156,352/156,352.**
|
||||
|
||||
**4. Confirmed the corridor-law exports for B and published the whole contract** (LANE_A_NOTES §37 —
|
||||
B is consuming them in a parallel session, so it is written as a warning, not a reference). Semantics
|
||||
unchanged; nothing ambiguous was altered mid-round. Flagged loudly: `roadWidth` returns a **full** width
|
||||
and so does `edge.width`, but one is carriageway and the other corridor — that naming is *why* this bug
|
||||
existed; **`lane`'s band is not the literal `[2,2]` the round instructions state** (2 synthetic edges are,
|
||||
**56 real lane edges are `[4,4]`** — branch on `outer <= inner`); `arcade` is all footpath with coincident
|
||||
kerbs to skip; and the inward extension **never inverts** (`roadWidth > e.width`: **0 of 31,039 edges**,
|
||||
though a `side` under 6 m would — guard it). Two charter numbers corrected: the "9 m band / 18 m of every
|
||||
main" figures are **synthetic-only** (main 28 m); **on all real towns a main is 24 m ⇒ 7 m band**, and the
|
||||
corpus's dominant edge is `side`@12 — **22,415 of 31,039 (72%)**, a **3 m** footpath.
|
||||
|
||||
**GOLDEN VERDICT: nothing moved, zero re-pins** — correct and predicted, since the only file I changed on
|
||||
the generator side is the *selfcheck*, which validates output and never produces it. `0x5f76e76`,
|
||||
`GIG_GOLDEN 0xec7a2d39`, 25 base town goldens and 24 real-town gig goldens all green and untouched.
|
||||
Checks **156,352 → 157,407** (+1,055), runtime 10.3 s.
|
||||
|
||||
**Filed, not fixed — v8 debt ledger, four entries** (LANE_A_NOTES §37): D1 `chunkIndex` (two impls, three
|
||||
divergences, and a header comment promising a swap that would break three ways — owner B); D2 `isOpen`
|
||||
(three impls plus an ad-hoc fourth site; A's canonical one has **zero importers**; the live shell copy
|
||||
treats *no hours* as **closed** where A's law says *open* — latent today, fires the moment v8 adds an
|
||||
hours-less lot — owner F); D3 two shop registries (B's missing all three venue kinds ⇒ `buildings.js:344`
|
||||
falls back to **op-shop** signage on every gig venue since R13 — owner B); **D4, new, found this round**:
|
||||
my own poster gate's inner bound is **nearest-edge**, and an `arcade` (kerb 0) camouflages a real
|
||||
carriageway — 8 real-town plans hide a spine poster up to **1.78 m inside a main**, zero on synthetic.
|
||||
Cost measured decision-grade: synthetic **0/4 plans move**, **11/26 town gig goldens re-pin**, poster
|
||||
counts unchanged. Deliberately deferred to R38 — it moves plan output for a reason that is not the kerb
|
||||
ruling, and the "did the kerb ruling move a golden?" answer must stay a clean **no**.
|
||||
|
||||
## 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)
|
||||
|
||||
|
||||
224
docs/ANIMALS_ANSWER.md
Normal file
@ -0,0 +1,224 @@
|
||||
> **FABLE'S ADJUDICATION (2026-07-26) — read before §1.**
|
||||
> This spike is ratified: the mechanism is proven on our own vendored bytes, and the tiering
|
||||
> stands. **One verdict in it is over-claimed and is corrected here.** The spike calls the four
|
||||
> module-scope furniture GLB fetches under `?classic=1` "a live covenant breach in shipped
|
||||
> code". They are not a breach: `bench`/`bin`/`bus_shelter`/`food_cart` entered
|
||||
> `furniture.js` in **Lane B round 6 (`24cef5a`), which is tagged v2.0-alpha through v2.0** —
|
||||
> git-verified. They are part of the v2 baseline the covenant preserves, so classic fetching
|
||||
> them is classic being *correct*. "Zero fetch delta" has always meant zero delta **against
|
||||
> the documented v2 baseline**, not zero requests.
|
||||
>
|
||||
> **What the spike really found, and it is valuable:** (a) `classic_regression` carries **no
|
||||
> network assertion at all**, so it could not detect a future fetch-surface widening — which
|
||||
> is exactly the R36 species (five DJ bodies widened classic's fetch surface and every pinned
|
||||
> number stayed green; only a NEW smoke caught it); and (b) `furniture.js`'s module-init load
|
||||
> pattern is a **trap for new assets** — anything added to `GLB_FILES` fetches under classic
|
||||
> silently. Both corrections are binding on the fauna work: `fauna.js` is dynamically imported
|
||||
> behind its construction guard or carries a CLASSIC-derived module-scope guard stated in its
|
||||
> header, and `classic_regression` grows a network **allow-list** (the four v2 depot GLBs plus
|
||||
> same-origin), never a deny-list. A deny-list only catches what you already thought of.
|
||||
>
|
||||
> Also noted: the spike could not reproduce the 280/300 walked draw figure (it measures
|
||||
> 191 default / 167 classic by teleport-walk). **Re-pinning the budget of record is now wave
|
||||
> 0's first job**, and no proposal spends against 280 until a walked re-measure settles it.
|
||||
|
||||
---
|
||||
|
||||
# ANIMALS — THE ANSWER
|
||||
|
||||
*PROCITY fauna. Answering: "yeah idk about rigged animal models, i will see if i can find some to use online, they have different rigging too right? diff skeleton? what do you suggest here"*
|
||||
|
||||
---
|
||||
|
||||
## 1. Short answer
|
||||
|
||||
**Yes, every animal has a different skeleton — and it turns out not to matter.** There is no mixamorig for quadrupeds. There is no standard animal rig at all. Every animal you download ships a bespoke skeleton with bone names its author invented that morning: `leg_fl`, `Bone.003`, `Hind_L_01`, whatever. That part of your instinct is exactly right.
|
||||
|
||||
What is wrong is the assumption underneath it — that a different skeleton is a *problem*. It's only a problem if you need to make **one clip play on a different body**. That's retargeting, and it's the reason our human stack exists: 24 human bodies share 8 clips, so every body must speak `mixamorig`. An animal that ships its **own** animations inside its **own** file doesn't need any of that. The GLB binds its clips to its own bones, through its own mixer, and plays. Zero retargeting.
|
||||
|
||||
We measured it rather than assumed it. There is no real animated non-mixamo GLB anywhere on the fleet — I scanned all of `~/Documents`: 2,486 GLBs, 81 with animations, **81/81 mixamorig, 0 non-mixamo skins**. So we authored a test asset byte by byte: a 10-bone quadruped named `root/spine/neck/head/tail_01/tail_02/leg_fl/leg_fr/leg_bl/leg_br`, 96 tris, two embedded clips named Blender-style (`Armature|CritterWalk`, `Armature|CritterIdle`). Then loaded it through **our actual vendored three.js r175 bytes** and **our actual `web/js/core/loaders.js`**, with the repo untouched, and rendered it on a real `WebGLRenderer`.
|
||||
|
||||
Result — 27 assertions, all green:
|
||||
|
||||
- A bare `new THREE.AnimationMixer(gltf.scene)` + `clipAction(gltf.animations[0]).play()` — no canon, no `buildFigure`, no `_rotOnly`, no MIRPAMO — moved `leg_fl` by quaternion delta 0.252 then 0.310 across two `mixer.update()` calls.
|
||||
- The GPU agreed. Rendered to a 200×150 render target and read the pixels back: 1,804 lit pixels, and 704–1,015 of them changed between frames. Not "the matrices moved" — **actual different pixels came off the GPU**.
|
||||
- `SkeletonUtils.clone` works on the foreign rig: separate skeletons, **shared geometry and shared material**, so one file feeds N animals. (It maps by object identity, not by name — `SkeletonUtils.js:388-425` — so "foreign names are fine" is structural, not luck.)
|
||||
- Our own `loadGLB()` returned the identical object unmodified. **No change to any existing module.**
|
||||
|
||||
And the mirror image, which is the important half: if you fed that animal to `web/js/citizens/rigs.js`, `_canon()` would silently do nothing (no `mixamorigN` names to normalise), and then `buildFigure()` would measure the quadruped's "feet-to-crown" span at **0.36 m**, normalise it to human height, and **scale the dog 4.9×**. No error. A giant dog.
|
||||
|
||||
So the law is: **animals get a new sibling module, `web/js/citizens/animal_rig.js`, that imports nothing from the human rig stack.** About forty lines. The human fleet is classic-covenanted and never gets touched.
|
||||
|
||||
**The real constraint is draws, not skeletons.** Measured on a real renderer: **1.00 draw call per skinned animal, 1:1, all the way to N=80.** three.js has no instancing for animated skinned meshes and there is no impostor tier for animals (our impostor bake frames square from height — `impostor.js:57-66` — which crops a long low animal). So a skinned animal is a full draw at every distance it's visible, or it's culled outright. Nothing in between.
|
||||
|
||||
Against that: **sixty birds on wires cost one draw between them**, because static instanced meshes share one material and one draw call. That asymmetry — 1 draw per dog vs 1 draw per *sixty birds* — is the entire design.
|
||||
|
||||
---
|
||||
|
||||
## 2. The recommendation
|
||||
|
||||
Four tiers. Only two of them ship this epoch.
|
||||
|
||||
### TIER 0− — the heard animal · **+0 draws, +0 triangles** · **SHIP**
|
||||
|
||||
A cockatoo you hear at dusk and never see costs literally nothing and does most of the work. New entries in `PACK` (`pipeline/gen_audio.py:688`), ~30 lines of numpy each, deterministic, original, 🟢 by construction. Played at a **seeded anchor**, so it's the same dog behind the same fence every time you walk that street.
|
||||
|
||||
This is the highest charm-per-minute item in the entire document. A town with **zero visible animals** reads as a place with animals in it. Nothing can regress; it inherits the existing `smoke_audio` gate for free.
|
||||
|
||||
### TIER 0a — the flock · **+1 draw for the whole town** · **SHIP — this is wave 1**
|
||||
|
||||
One `InstancedMesh`, one geometry (two crossed planes = 4 tris), one material, one **1024 WebP species atlas**. Cockatoo, galah, corella, lorikeet, perched magpie, ibis are all cells in the same sheet — which means **every future bird species is free**. That single decision is the most valuable thing in the plan.
|
||||
|
||||
This is not a new trick: it's exactly what already renders the town's entire tree canopy (`furniture.js:98-101`). Matrices are static; the buffer refills only when you cross a 64 m chunk boundary. Zero per-frame CPU.
|
||||
|
||||
And it is genuinely free when empty — verified to the vendored line. `three.module.js:2193-2199` returns from `renderInstances` before the GL call, and `render.calls++` lives at `three.module.js:4094` *inside* `info.update()`, downstream of that return. A town with no birds, or the daytime species at night, costs **exactly zero**, not "almost zero".
|
||||
|
||||
Live cap 96 instances → 384 triangles. Against ~76k spare triangles that is a rounding error.
|
||||
|
||||
### TIER 0b — the near props · **+1 draw per species** · **wave 1.5, one species**
|
||||
|
||||
Real low-poly meshes for the ones you walk within two metres of, where a billboard reads as cardboard. **The ibis, head down in a council bin** — it's the named proof species from the last review and it lands next to the existing `council-bin` prop. One town-wide instanced mesh, ≤450 tris, cap 16.
|
||||
|
||||
### TIER 1 — static mesh + scripted path · **+1 draw** · **the magpie, later**
|
||||
|
||||
The already-approved swooping magpie: position is a pure function of `(seed, elapsed time)` — the tram's exact contract — with the wing wave riding `wind.js`'s existing amplitude uniform, which is byte-identical at amplitude 0.
|
||||
|
||||
**One correction that's binding:** the review caught that the magpie's territory has to scale with the road graph. On the synthetic golden town there are 370 pole anchors; on `bendigo_real` there are **10,014 poles and 13,730 tree anchors across 2,593 edges**. A capacity-4 magpie town-wide on bendigo is a swooping town where you never meet the magpie. That was already ruled on once (`REVIEW_CITY_PATTERNS.md:111`) and this plan has to honour it, not quietly re-open it.
|
||||
|
||||
### TIER 2 — genuinely skinned · **+1 draw each, uninstanceable** · **CUT for this epoch**
|
||||
|
||||
The mechanism works. We proved it. **We are still not shipping it, and the argument that killed it came from our own plan.**
|
||||
|
||||
The justification for a skinned animal was: a quadruped **gait** is the one motion every human eye detects as wrong before it can read, so we can't fake a walking dog. True. But the actual spec anchored the dog at `shopFront` — **a dog tied outside the shops**, with a static tied dog as its `?noassets=1` fallback. A tied dog doesn't trot. The walk clip, the sole and entire reason the tier exists, would never play.
|
||||
|
||||
So either the dog walks — which needs a path, footpath alignment, a collision story and a sim hook that the design explicitly disclaims ("the ped sim never learns animals exist") — or the tier buys an **idle animation for one uninstanceable draw each**, which is exactly what tier 0b already delivers inside a draw it shares with fifteen others.
|
||||
|
||||
**Cut. Along with the download that fed it.** Revisit only with a *walking* dog spec that owns its path, its cull and its sim hook. Different round, different charter.
|
||||
|
||||
### The ledger
|
||||
|
||||
| tier | mechanism | draws | ship? |
|
||||
|---|---|---|---|
|
||||
| 0− heard | numpy synth, seeded anchor | **0** | **this week** |
|
||||
| 0a flock | 1 instanced mesh, 1 atlas, town-wide | **+1** | **wave 1** |
|
||||
| 0b prop | 1 instanced mesh, 1 species (ibis) | +1 | wave 1.5 |
|
||||
| 1 scripted | 1 instanced mesh (magpie) | +1 | later, graph-scaled |
|
||||
| 2 skinned | uninstanceable, +1 each | +1..2 | **CUT** |
|
||||
| the wire | town-wide instanced spans | +1 | see §6 |
|
||||
|
||||
**Wave-1 spend: +1 draw.** The original plan said +7. The review cut it to +1 and the cut holds.
|
||||
|
||||
**One thing to fix before any of this is charter law:** the budget of record says the walked worst frame is 280/300 draws. Re-measuring headless I get **191 default / 167 classic** by teleport-walk, and can't reproduce 280 — it's almost certainly a transient during the chunk-dispose window, which teleport sampling can't hit. It stands as the conservative number, but spending against a figure nobody can currently reproduce is the weakest joint in the whole thing. Re-pin it first. Fauna helps here: its layers are **town-wide, not per-chunk**, so they take no part in the dispose window at all — their cost is steady-state.
|
||||
|
||||
---
|
||||
|
||||
## 3. INTAKE CHECKLIST — one screen, for while you're browsing
|
||||
|
||||
### ✅ ACCEPT ON SIGHT
|
||||
- **CC0 / public domain.** No argument, no attribution obligation. Still record it in `SOURCES.md` — our discipline, not their demand.
|
||||
- **Single `.glb`, glTF 2.0**, or a `.gltf` sitting next to `.fbx` (Quaternius ships both).
|
||||
- **A named clip list you can actually read**: "Walk", "Idle", "Run", "Gallop". Not the word "rigged" on its own.
|
||||
- **Under 3,000 triangles.** Under 900 for anything that'll be a bird.
|
||||
- **One mesh, one material.** Draws are the scarce resource.
|
||||
- **Textures embedded in the file.**
|
||||
|
||||
### ❌ REJECT ON SIGHT
|
||||
- **A "Buy" or "Add to cart" button.** TurboSquid, CGTrader, Sketchfab **Store**, paid Fab. See the licence rules — **paying does not fix this**, and this is the most expensive avoidable mistake on the list.
|
||||
- **"Rigged" with no clip list.** A rig with no animations is a static mesh with extra bones, and we cannot animate a quadruped ourselves — MIRPAMO is biped-only by construction, Mixamo's auto-rigger is verified humanoid-only. Worth exactly zero to us. We can make a static mesh in three minutes for free.
|
||||
- **Five- or six-figure triangle counts**, and the photogrammetry/museum-scan look that produces them. The free "White Ibis" on Sketchfab is **238,200 triangles** — one bird exceeding our entire 200k street budget — and it doesn't move. Research and heritage-scan accounts are a reject-on-sight *category*, not individual bad models.
|
||||
- **Draco / KTX2 / meshopt compression** (look for `extensionsRequired`). We vendor no decoders. Verified: the loader throws, our fail-soft wrapper (`loaders.js:38-44`) turns the throw into `null`, and the animal **silently does not exist**. A `console.warn` nobody reads. Downloaded assets in the wild are heavily Draco-compressed — this is the single most likely silent failure.
|
||||
- **Right common name, wrong animal.** The free "White Ibis" is *Eudocimus albus*, the American one — not our bin chicken (*Threskiornis molucca*). "Magpie" on stock sites is almost always the European bird, which looks nothing like ours. Australian fauna is badly served by international libraries, and a wrong-species bird reads as wrong to every Australian and is invisible to everyone else.
|
||||
- **NC / ND / "free for personal use" / "editorial use only".** Instant reject including when it costs nothing.
|
||||
- **CC-BY-SA or GPL-on-art.** Share-alike is viral, and we retexture and rescale *everything* — we'd always be making derivatives. Would compromise a provenance table that's been clean for seven epochs.
|
||||
- **No licence stated anywhere** — page, description, or the README in the zip. Default under copyright is all rights reserved. Silence is not permission. Close the tab; don't email and negotiate.
|
||||
- **Real brands, sponsor decals, team logos, recognisable people.** `AUDIT.md` already quarantined branded assets once.
|
||||
|
||||
### 🟢 THE LICENCE RULES
|
||||
|
||||
- **🟢 GREEN — CC0.** Accept freely. Record URL, author, licence string and download date in `SOURCES.md` anyway.
|
||||
- **🟡 AMBER — CC-BY.** Usable commercially forever, but the attribution obligation is **perpetual and travels with every build**. Hard rule: the `SOURCES.md` line goes in the **same commit** as the file, never "later". CC-BY is the one licence class that becomes a breach through nothing worse than forgetfulness.
|
||||
- **🔴 RED — every paid marketplace royalty-free licence, structurally, at any price.** This is the finding that saves money rather than spending it. TurboSquid states it outright: stock media "must be contained in proprietary formats so that they cannot be opened or imported in a publicly available software application or framework, or extracted without reverse engineering" — then names the only exceptions: WebGL exports from Unity, Unreal, Lumberyard and Stingray. PROCITY is hand-rolled three.js with **no build step**, serving raw `.glb` over plain HTTP. A player presses F12 and saves the file. That is the named prohibited case. Sketchfab Store forbids letting third parties "use, download, extract or access" the asset — same wall. CGTrader forbids passing it on "in the form it is downloaded from the site" — same wall, blurrier wording, which is its own reason to stay out. **A $18 rigged possum is exactly as unusable as a free one.**
|
||||
- **"Free" is not a licence.** It means "costs zero dollars" and says nothing about rights. Free3D's own terms cap "substantially similar creations" at personal use, max 5 instances.
|
||||
- **Capture provenance at download time, not audit time.** Epic is folding Sketchfab into Fab, and **CC0, CC-BY-SA, CC-BY-NC and CC-BY-ND cannot migrate** — those licences don't exist on Fab. Free downloads have been on borrowed time since 2025. itch.io packs go paid or vanish without notice. Archive the file in-repo **and** record the exact licence string the same day. A dead URL with no recorded licence is an asset we can no longer prove we may use.
|
||||
- **Internal conflicts = walk away.** If the itch.io page says CC0 and the README in the zip says "credit me", don't adjudicate and don't negotiate. Take the unambiguous asset instead. Ambiguity is what eventually contaminates the table.
|
||||
|
||||
---
|
||||
|
||||
## 4. VETTED SOURCES
|
||||
|
||||
| source | URL | licence | animated? | verdict |
|
||||
|---|---|---|---|---|
|
||||
| **Quaternius** | quaternius.com | **CC0 1.0** | **Yes, genuinely** — "Ultimate Animated Animal Pack": 12 animals × 12+ named clips | **The only one that matters.** glTF shipped alongside FBX so no conversion. Low-poly, single-material, flat-shaded — our exact budget shape. Roster is Cow/Donkey/Deer/Alpaca/Bull/Fox/Shiba/Stag/Husky/Wolf/Horse. **Zero Australian natives, zero birds.** |
|
||||
| **itch.io** (Ariescar) | itch.io/game-assets/free/tag-3d/tag-animals | CC0 (per pack — read carefully) | Yes | "10 free CC0 rigged & animated low-poly animals (GLB) for Three.js" — literally built for our stack. Highest signal-to-noise after Quaternius. Licence sloppiness is the risk: page body and zip README often disagree. |
|
||||
| **Poly Pizza** | poly.pizza | **Mixed CC0 / CC-BY per model** | Mostly no | Fast browse surface for Quaternius, not a source in its own right. The site most likely to sneak a CC-BY model into a folder of CC0 ones — read the badge on the individual model, never the bundle header. |
|
||||
| **Kenney** | kenney.nl/assets/animal-pack-redux | CC0 | **No** — animals are static | **Skip.** Static is the one thing we already make locally for free. Excellent for props/UI/audio, just not this. |
|
||||
| **Sketchfab (CC filter)** | sketchfab.com — filter `licenses=CC0` | Per model, CC0→CC-BY-NC-ND all coexist; **CC-BY is the default** | Sometimes | Only big library where you can filter Animated + Downloadable + permissive at once. Two warnings: **Store ≠ Sketchfab** (any "Buy" button is forbidden to us), and downloads are on borrowed time due to the Fab migration. |
|
||||
| **OpenGameArt** | opengameart.org | CC0/CC-BY/**CC-BY-SA**/OGA-BY/**GPL** | Thin, dated | **The share-alike trap lives here.** Filter to CC0 and CC-BY only. |
|
||||
| **Icosa Gallery** | icosa.gallery | Mostly CC-BY | **No** — the rescued Poly corpus is Tilt Brush/Blocks, completely motionless | Cross it off. Anyone calling it "the Google Poly successor" hasn't checked whether the models move. |
|
||||
| **Blender Studio** | studio.blender.org/characters | CC-BY / some CC0 | Rigged, but `.blend` CloudRig control rigs, not baked GLB clips | Wrong shape. Film-density rigs meant to be hand-animated. Quality benchmark, not a download. |
|
||||
| **TurboSquid** | — | Paid RF | Yes, incl. a rigged brushtail possum | **🔴 STRUCTURALLY FORBIDDEN.** See licence rules. Do not spend money. |
|
||||
| **CGTrader** | — | Paid RF + free tier under RF terms | Yes | **🔴 Same wall, blurrier wording.** A provenance table wants "clearly permitted", not "probably defensible". |
|
||||
| **Free3D** | — | Mixed and hostile | Sometimes | Per-asset licence archaeology on every file, real chance the answer is "no commercial" after you've downloaded. Not worth opening. |
|
||||
| **Meshy / Tripo** | — | Free tier = CC-BY | Largely no | **Competitors to our own pipeline, not suppliers.** We generate static meshes locally for $0 with provenance we control end to end. Trading that for a murkier third-party line gains nothing. |
|
||||
| **Mixamo** | — | Free w/ Adobe account | **Biped only — confirmed** | Auto-rigger requires a humanoid in T/A-pose and rejects quadrupeds. Adobe stopped developing it years ago. No path here. |
|
||||
| **Anything World** | anything-world.gitbook.io | SaaS, ~$50/mo | Yes, incl. animals | Technically the closest thing to a magic wand, still a no: cloud dependency on a startup, and $50/mo for what turns out to be a one-dog problem we've now cut. |
|
||||
| **Blender Rigify** | docs.blender.org | GPL (the *tool*; your rigs are yours) | Ships quadruped, cat, wolf/dog, horse and **bird** metarigs | **Worth knowing, and it corrects our own record.** We wrote down "we cannot locally rig a non-humanoid". True of MIRPAMO; **not true of the fleet** — Blender 5.0.1 is already on ultra. The gap is animator hours, not tooling. That's a scheduling fact, not an architectural wall, and the door stays open if you ever want an afternoon in Blender. |
|
||||
|
||||
---
|
||||
|
||||
## 5. SPECIES SHORTLIST
|
||||
|
||||
| species | tier | download? | why |
|
||||
|---|---|---|---|
|
||||
| **Sulphur-crested cockatoo** | 0a flock | **No — generate** | Screaming on a wire at dusk. All the charm is silhouette, placement and noise. Perched = zero locomotion. Five cockatoos for **one draw**, which no rigged model can ever match. |
|
||||
| **Galah** | 0a flock | **No** | Same mesh as the cocky, second skin. `flux_local` does a skin in ~5.7 s — **the second bird is nearly free**. |
|
||||
| **Corella** | 0a flock | **No** | Third cell on the same sheet. Free. |
|
||||
| **Rainbow lorikeet** | 0a flock | **No** | A screeching mob in a flowering gum. Instanced statics. |
|
||||
| **Australian white ibis** | 0b prop | **No** | The joke is entirely **postural** — head in the bin, or standing on one leg looking personally aggrieved. Nobody has ever laughed at an ibis's walk cycle. The named proof species; keep it in wave 1.5. Note: nothing usable exists to download — the free one is the wrong continent at 238k tris, and the correct 4.2k-tri one is a Store listing. |
|
||||
| **Magpie** (swooping) | 1 scripted | **No** | Already approved. Static mesh on a seeded parabola, wing wave via the existing wind uniform. Being swooped in September is shared national trauma. Must scale territory with the road graph. |
|
||||
| **Brushtail possum** | 1 scripted | **No** | Frozen on a fence rail at night with eyeshine — which is what a real possum actually does. The cheapest implementation is also the most accurate. Only proper rigged one found is $18 TurboSquid, forbidden at any price. |
|
||||
| **Brush turkey** | 0b later | **No** | Bonus pick, and a strong one — the canonical town is redhill_godverse, Red Hill, Brisbane. Genuine local menace. Signature behaviour is standing next to a mound scratching, not walking. Too regional to exist in any stock library anyway. |
|
||||
| **Blue-tongue / cane toad / flying fox** | 0a/1 later | **No** | Long tail, all near-zero cost. Toad = scripted hop. Flying fox = the magpie's parabola with different geometry, reused for free. |
|
||||
| ~~Dog (kelpie/heeler)~~ | ~~2~~ | ~~Quaternius Shiba~~ | **CUT.** The only species that ever earned a download, and it was refuted by its own spec — anchored tied outside the shops, so the walk clip never plays. Also: a Shiba's curled tail and prick ears are **geometry, not texture** — `flux_local` retexturing gives you a tan Shiba, not a kelpie. The dog survives at tier 0− as a **bark behind a fence**, which is free and, honestly, more 1990s Australian than a visible dog. |
|
||||
| ~~Gull, pigeon, cat, flies, crow~~ | — | — | **CUT.** Nothing about any of them says Australia, let alone 1990s. |
|
||||
|
||||
**And the thing that isn't an animal:** the single most 1990s-Australian-country-town object on this whole list is the **overhead powerline**. A main street with streetlights and no wires reads as a 2010s estate. The wire is worth more period-load per draw than any two bird species on the sheet — and it's the thing the birds are *for*. Wave 1 perches on the streetlight arm (`furniture.js:77-80`, y≈4.1) because it's free and period-plausible; a real wire is +1 draw, town-wide instanced, and needs the pole-pairing worked out first (poles **alternate sides every 18 m**, so same-side spans are 36 m and must not cross edge boundaries or you get wires cutting through intersections).
|
||||
|
||||
---
|
||||
|
||||
## 6. Two things that block, independent of animals
|
||||
|
||||
**1. `?classic=1` is already fetching four off-origin GLBs.** I verified the mechanism directly: `index.html:52` statically imports `chunks.js`, which imports `furniture.js`, whose module body fires `loadGLB()` at line 63-67 guarded by **`NOASSETS` only** — never by `CLASSIC` (`index.html:94`). ES module bodies run on import; construction guards are irrelevant to them. Measured: **179 requests, 25 `.glb`, 4 off-origin depot fetches** (bench, food cart, bin, bus shelter) under `?classic=1`. And `classic_regression` (`flags_check.py:128-241`) has **no network assertion at all**, so it goes green.
|
||||
|
||||
This is a live covenant breach in shipped code, and it matters here because that module-init pattern is precisely what the fauna plan proposed to copy. Binding: `fauna.js` is either dynamically imported behind its construction guard or carries a CLASSIC-derived module-scope guard, stated in the file header — and `classic_regression` grows a network **allow-list** (the four known depot GLBs plus same-origin), not a fauna-shaped deny-list. A deny-list only catches what you already thought of, which is exactly how this got through.
|
||||
|
||||
**2. `normalize.py` silently eats rigs.** Line 95 deletes every non-MESH object (its own comment says "empties, armatures, cameras, lights"), line 101 joins, line 105 clears armature modifiers. Point the existing pipeline at a rigged animal and you get a T-posed statue with the clips gone **and a green run**. Same for `decimate_props.py:37-45` and `bake_lowpoly.py`. Write it into their docstrings and make the intake tool refuse to run them.
|
||||
|
||||
Worth landing regardless of whether an animal ever ships: the spike's deliberate-failure fixtures (`bad_draco`, `bad_cm`, `bad_zup`, `bad_rootmotion`, `bad_2mat`, `bad_exttex`) move into `pipeline/_fixtures/` and become a permanent regression suite for free.
|
||||
|
||||
---
|
||||
|
||||
## 7. NEXT ACTION
|
||||
|
||||
### What Fable can start unattended this week, $0, no download, nothing to approve
|
||||
|
||||
**Tonight — the audio batch.** Six numpy synth functions into `gen_audio.py`'s `PACK`: magpie clack and carol, cockatoo screech, dog bark (fence and near), ibis grunt, crow. ~30 lines each, deterministic, 🟢 original, **zero draws and zero triangles**. This is the highest charm-per-minute item in the entire design and it cannot regress anything. A town with no visible animals starts reading as a place with animals in it.
|
||||
|
||||
**Then — the flock atlas, about ten minutes of GPU.** Sixteen `flux_local` bird sprites at ~5.7 s each → `bg_remove_local` → packed into one 1024 WebP 4×4 sheet. **No TRELLIS, no meshes** — a billboard needs an image, not geometry, so sixteen sprites cost two minutes where eight meshes would cost twenty-five. And it's not a new asset class: `manifest.skins.facade` is already twenty-five shipped, audited flux images used as textures. This single batch unlocks the whole +1-draw flock: cockatoo, galah, corella, lorikeet, perched magpie, ibis.
|
||||
|
||||
**Then — the ibis prop, six minutes.** `flux_local` → `bg_remove_local` → `trellis2_mlx` → `normalize`, decimated to ≤450 tris, head down.
|
||||
|
||||
Everything above is unattended, zero-risk and gate-free in the sense that nothing existing can break.
|
||||
|
||||
### If you want to go shopping anyway
|
||||
|
||||
Nothing in the plan blocks on it, and after the tier-2 cut there is **no download on the critical path**. But if you're browsing:
|
||||
|
||||
1. **Go to Quaternius first.** Everything else is downstream of it. CC0, glTF included, low-poly single-material by design.
|
||||
2. **Before writing a single `SOURCES.md` line, open the `.gltf` and confirm `animations` is non-empty.** The number one silent failure of free packs is clips that live only in the `.blend` or `.fbx` while the `.gltf` ships an empty array. Fifteen minutes with tools we've already built: `pipeline/animal_intake.py` for the file checks, and the spike harness at `scratchpad/spike_run.py` — it takes a filename — to confirm a bare `AnimationMixer` actually moves a bone. **If the clips aren't in the glTF, walk away.** The pack's entire value to us is the walk cycle.
|
||||
3. **The one thing genuinely worth grabbing while you're there is not an animal.** Quaternius's **"Universal Animation Library"** — CC0, 130+ clips on a Mixamo-compatible humanoid rig. That's fuel for our **human** fleet's 8-clip bank, which is the actual thin spot. It touches `rigs.js`, which is classic-covenanted, so it's its own round with its own gates. Don't fold it into the animal work.
|
||||
4. **Do not spend money.** Not on TurboSquid, not on CGTrader, not on Sketchfab Store, not on a $50/mo auto-rigger. Their licences forbid serving an open `.glb` over plain HTTP, which is the only thing PROCITY does. Paying doesn't unlock them.
|
||||
|
||||
### The two-sentence version
|
||||
|
||||
Yes, every animal ships its own bespoke skeleton with its own bone names, and that turns out to be completely fine — a self-contained GLB binds its own clips to its own bones through its own mixer, so retargeting, the thing that needs a shared skeleton, is exactly what we must *not* do. What actually limits animals is **draw calls**: a skinned animal can't be instanced so it costs one draw each against maybe twenty spare, while sixty birds on wires cost one draw between them — so generate a bird sprite sheet and a handful of animal sounds this week for $0, and buy nothing, because the one species that would have justified a download was a walking dog and our own spec had it tied up outside the shops.
|
||||
@ -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.
|
||||
- **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.**
|
||||
`web/js/core/prng.js` is the only randomness source:
|
||||
- `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).
|
||||
lots: [ { id, block, x, z, w, d, ry, frontEdge, use } ],
|
||||
// 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
|
||||
// pointing at frontEdge. World facing = (−sin ry, −cos ry). Lane B rotates the shell by ry.
|
||||
// ry: Y-rotation (radians) so the lot's VISIBLE FACADE — local +Z — points at frontEdge.
|
||||
// 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).
|
||||
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.
|
||||
@ -116,9 +133,15 @@ Schema v1 (Lane A owns it; extend, don't break):
|
||||
> **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
|
||||
`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,
|
||||
footpath NPCs) resolves to a chunk whose `edges` list includes its edge.
|
||||
`chunkIndexByCoverage(plan)` helper (R40 canonical name; `chunkIndex` remains as a compat alias):
|
||||
chunk key → { lots, shops, edges } **touching** that cell — the COVERAGE question. A lot lands in every
|
||||
chunk its rotated-footprint AABB covers; buckets are plain objects keyed `"cx,cz"`; elements are **ids**.
|
||||
Edges are bucketed across 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.
|
||||
**This is NOT the runtime streamer's index**: Lane B's `world/planutil.js chunkIndex(plan)` answers the
|
||||
CENTRE question (each lot in exactly the chunk holding its centre; `Map` container; object elements) and
|
||||
the two are not interchangeable on any of the three axes — measured R40: a swap would multi-build
|
||||
258/681 lots on the default synthetic town.
|
||||
|
||||
## CityPlan v2 — frozen producer contract
|
||||
|
||||
@ -133,7 +156,8 @@ footpath NPCs) resolves to a chunk whose `edges` list includes its edge.
|
||||
- `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)`.
|
||||
- `chunkIndexByCoverage(plan, chunkSize = 64) → { chunkSize, chunks }` (coverage form; `chunkIndex`
|
||||
is a compat alias of it — see the chunk-key note above); `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
|
||||
@ -204,6 +228,467 @@ 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
|
||||
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; the
|
||||
test is against **all edges**, not the nearest — R40 §40.2 D4, see the amendment under ROUND37 below), 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. A **frontage** poster is exempt from the band — 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); on a real town it may legitimately stand
|
||||
**past** every corridor edge, because `plan_osm.js` sets a lot back at `max(KERB, roadWidth/2 + FOOTPATH)`
|
||||
— measured worst 3.98 m (launceston_real, R37).
|
||||
|
||||
**ROUND37 — THE KERB IS FOOTPATH (John's ruling), and the poster gate that was green over a wrong
|
||||
street.** The band between `roadWidth(e)/2` and `e.width/2` is **footpath**, and Lane B's ground paints
|
||||
it as footpath rather than as road. The selfcheck's poster-clearance check used to ask one question —
|
||||
"is the poster outside `roadWidth/2`?" — which A's own placer already guaranteed, because `poleOffset` is
|
||||
*derived from* `roadWidth`. Gate and subject were the same function agreeing with itself, so it stayed
|
||||
green for 24 rounds while all **14** gig posters on the default boot stood on painted bitumen. It is now
|
||||
**three arms**: (1) no poster on **any** carriageway, measured through `vergeBand(e)[0]` — the published
|
||||
law itself, the same call B's ground consumes, so gate and renderer cannot drift apart. **R40 amendment
|
||||
(§40.2 D4): the clearance is the MINIMUM over ALL edges, not the nearest edge's.** The nearest edge is
|
||||
not always the binding one — a kerbless `arcade` (roadWidth 0) wins the nearest-edge race and always
|
||||
"clears", camouflaging a main whose carriageway the poster stands inside. Measured on the R40 tree:
|
||||
8/108 gig plans carried a spine poster up to **1.78 m inside a non-nearest main** (northbridge_real
|
||||
seed 42, nearest edge an arcade at 1.45 m) that the nearest-edge form could not see by construction.
|
||||
The placer's `onCarriageway` skip is now all-edges too; poster **counts** are unchanged on all 108
|
||||
plans (the cap re-subsamples a smaller pool to the same size), the synthetic town is byte-identical
|
||||
(0/4 plans moved — goldens frozen), and 7 real-town **gig** goldens re-pinned; (2) every **spine**
|
||||
poster stands *inside* some edge's `vergeBand` (the bound the old check never had — "not on the road"
|
||||
also passed for a poster in a paddock); (3) **the control** — count the posters that would stand on
|
||||
bitumen under the pre-ruling ground (`?verge=0`, road quad = full `e.width`) and assert that count is
|
||||
**not zero**, pinned at exactly 14 on the default boot. Arms 1–2 prove nothing unless arm 3 shows the two
|
||||
geometries are distinguishable. Proof of falsifiability, measured: with `vergeBand` switched to the
|
||||
pre-ruling reading and the placer held byte-identical, the corrected gate goes **red 1020×** (2 arms ×
|
||||
510 plans) while the pre-R37 gate is **ALL GREEN 156,352/156,352** — structurally blind to where the
|
||||
road is painted.
|
||||
|
||||
**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 address layer (v9, ROUND39 item 39.1) — DERIVED, never stored on the plan
|
||||
|
||||
`roads[].name` has always ridden the v2 cache (**17,835 of 19,132 ways carry one — 93.2%**) and
|
||||
`plan_osm.buildRealRoads` has always dropped it: `rawWays.push({ kind, pl })` keeps the kind and the
|
||||
geometry and discards the name. **It still does, deliberately.** Street names are recovered
|
||||
*downstream* by `web/js/citygen/address.js` — a pure module (zero imports) that reads a plan plus its
|
||||
cache and returns a query object:
|
||||
|
||||
```js
|
||||
createAddresses(plan, cache|null) → streetOf(edgeId) · localityOf(shopId) · cohort(pred) · streets() · stats()
|
||||
```
|
||||
|
||||
**THE RULE, and it is a schema rule, not a style note: no street name, district label or address ever
|
||||
becomes a CityPlan field.** The plan is the frozen producer contract; addressing is derived data with a
|
||||
97.8% resolution rate and a 2.2% honest-`null` tail, and baking either into `plan.lots`/`plan.shops`
|
||||
would move all 46 pinned goldens to store something a consumer can compute in ≤27 ms. It is recoverable
|
||||
because plan nodes ARE snapped projected way points (`SNAP=3`), so a plan edge lies **on** its way —
|
||||
median sample-to-way distance **0.03–0.90 m** across the 23 caches.
|
||||
|
||||
The one thing the lift now publishes is its own centring translation, `norm.shift = {shx, shz}`, which
|
||||
reaches the caller **only through the existing `opts.report` sink and never through the plan object** —
|
||||
so `JSON.stringify(plan)` is byte-identical and every golden is frozen by construction. It exists as a
|
||||
**cross-check**: `address.js` re-derives the same shift independently, and the selfcheck compares the
|
||||
two. Full contract, per-town rates and failure modes in `LANE_A_NOTES.md §39`.
|
||||
|
||||
**Consumers must not branch on town type.** `localityOf(shopId).label` is a real street name on a cache
|
||||
town and a district phrase (*"the market end"*) on the synthetic — one field, two suppliers, one contract.
|
||||
|
||||
## 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)
|
||||
|
||||
> **`web/js/core/registry.js` is the machine-readable source of truth** — import `SHOP_TYPES` for
|
||||
@ -232,6 +717,9 @@ night in v1; interior-mapping shader is a stretch goal).
|
||||
| `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 |
|
||||
| `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)
|
||||
|
||||
@ -268,6 +756,7 @@ night in v1; interior-mapping shader is a stretch goal).
|
||||
| `web/js/interiors/*`, `web/interior_test.html` | Lane C |
|
||||
| `web/js/citizens/*`, `web/citizens_test.html`, `web/models/*` | Lane D |
|
||||
| `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 |
|
||||
| `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)
|
||||
@ -1,14 +1,24 @@
|
||||
# LANE C — audio contract (round 11) → for Lane F + Lane B
|
||||
# LANE C — interior audio contract (v3.1) → for Lane F + Lane B
|
||||
|
||||
*PROCITY-C, 2026-07-15. Answers the R11 §Lane C question: "where should the interior bed + room-tone
|
||||
attach?" **Confirmed** — `buildInterior` now returns `room.audio = { musicKey, toneKey }`, seeded per
|
||||
shop. Lane F's interior_mode just plays it; Lane B's audio.js resolves the keys → files.*
|
||||
> **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
|
||||
@ -31,6 +41,58 @@ room.audio = {
|
||||
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):
|
||||
@ -42,9 +104,92 @@ on exit / dispose(room):
|
||||
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.
|
||||
|
||||
## Optional (deferred to v2.2, per the round scope)
|
||||
A `places`-tagged visual source ("the music comes from *there*") — e.g. a radio/hifi mesh with
|
||||
`userData.audioEmitter = musicKey` so Lane B can position/spatialise the bed. Only the record shop has a
|
||||
fitting for it today (`listeningCorner`); milk-bar/video would need a new radio prop. Not in this round —
|
||||
proposing the `userData.audioEmitter` hook + the two props as a tidy v2.2 item. v2.1 plays the bed
|
||||
non-spatially (room-filling), which is the right default for an interior anyway.
|
||||
---
|
||||
|
||||
# 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,157 @@
|
||||
# LANE C — cross-lane notes (PROCITY-C)
|
||||
|
||||
## Round 42 §42.2b — the booth composes. And the 122-vs-123 answer.
|
||||
|
||||
Full tables in `C-progress.md` § Round 42 (§42.2b). Shots: `docs/shots/laneC/r42_*.png`.
|
||||
**Worst room in the game is 123 draws @ `dept`/auto (GLB on), law ≤350 — MARGIN 227, unchanged by this
|
||||
section.** `dept` has no booth; §42.2b spent 0 draws at the worst room and at most +4 anywhere.
|
||||
|
||||
### → LANE F: 122 vs 123 is the CAMERA ASPECT, and the instrument is now pinned
|
||||
|
||||
You were right that it was my measurement environment, and here it is precisely. `info.render.calls`
|
||||
counts what survives frustum culling; the sweep camera holds a fixed **vertical** fov (70) and takes its
|
||||
aspect from the window. Wider window ⇒ more room in frame ⇒ more draws. One tree, **your own `SWEEP`
|
||||
function run verbatim**, viewport the only variable:
|
||||
|
||||
| viewport | aspect | GLB on | GLB off |
|
||||
|---|---:|---:|---:|
|
||||
| 1280×1024 | 1.250 | 116 | 88 @ `stall/hall` |
|
||||
| **1280×800** | 1.600 | **122** | 92 | ← my R41 harness |
|
||||
| **1280×720** | 1.778 | **123** | 93 | ← `r41_integration.py` `new_page()` |
|
||||
| 1920×1080 | 1.778 | 123 | 93 |
|
||||
| 2560×1080 | 2.370 | 124 | 95 |
|
||||
|
||||
The phantom control decomposes to **+74** at both aspects (197−123 and 196−122), which is the
|
||||
cross-check that it is one tree read two ways.
|
||||
|
||||
* **123 is the number.** The law must be the worst a player can produce and a player's window is wide.
|
||||
My published 122 was a 16:10 reading; keep using 123 / margin 227 everywhere.
|
||||
* **`PROCITY_C.drawSweep` now pins `MEASURE_ASPECT = 16/9`** and restores the window's aspect after.
|
||||
`drawSweep({ glb: true, aspect: 1.25 })` reads **116** — a control arm for the sweep itself, one line.
|
||||
* **Suggestion for `r41_integration.py` arm 2, your call:** set `S.camera.aspect` explicitly inside
|
||||
`SWEEP` rather than inheriting the viewport, so the gate cannot drift when someone changes `new_page`.
|
||||
Until then, keep `viewport 1280×720` — that IS 16:9 and it agrees with the pinned sweep exactly.
|
||||
* Also checked so the aspect finding is not a guess: E's "+0 draws on all 46 repainted assets" verified
|
||||
independently — glTF primitive counts in `web/assets/models/` vs `pipeline/.props_orig/r42/`,
|
||||
**46/46 identical**.
|
||||
|
||||
### → LANE B (and F): `assets/wardrobe/` now has ONE spelling, and it is not in your file
|
||||
|
||||
The literal was in `web/interior_test.html` and in `web/js/world/interior_mode.js:69`. It is now declared
|
||||
once in `web/js/interiors/stockpack.js` and re-exported from `interiors.js`; the test page imports it.
|
||||
`interior_mode.js` is not in my editable set, so its copy is still there — **one line retires it**:
|
||||
|
||||
```js
|
||||
// web/js/world/interior_mode.js — replace `export const WARDROBE_BASE = 'assets/wardrobe/';` with:
|
||||
export { WARDROBE_BASE } from '../interiors/interiors.js';
|
||||
```
|
||||
|
||||
### → LANE E: the deck plinth, measured again from the shipped GLB — and a workaround you can delete
|
||||
|
||||
Your R43 filing is right and here is a sharper number from the same file (node transforms applied):
|
||||
`deck_1200_rigged` has **9 of 8,740 vertices (0.10%) within 30 mm of its own y-minimum**, 69 (0.79%)
|
||||
within 43 mm, and its **1st percentile of vertex y is 0.0524** — 99% of the deck sits 52 mm above the
|
||||
floor its bounding box claims. The static twin `deck_1200` reads 729/11,138 (6.55%) within 30 mm and a
|
||||
1st percentile of 0.0140.
|
||||
|
||||
**I have compensated in placement and marked it loudly:** `kit41.BOOTH_PLINTH_FIX =
|
||||
{ deck_1200_rigged: -0.043 }`, applied to the GLB only (the primitive stand-in is already correct).
|
||||
When R43 re-runs `rig_join.py`, **delete the constant and the `p.dy` term in `djBooth`'s `glbParts`
|
||||
map** — both carry the tag `[R42 §42.2b — WORKAROUND, REMOVE WHEN R43 FIXES THE ASSET]`.
|
||||
|
||||
Two more, for you rather than for me:
|
||||
* Your repaint reads. Measured inside the booth's own screen rect at 2 m, six booths: near-black
|
||||
fraction **57.5% → 26.7%** and distinct colours **228 → 389** across both halves of §42.2 together.
|
||||
* I kept the deck's SEAT near-black (`#1c1a18`, **9.19:1** against your `#b9bdc2`) and took the bench
|
||||
MASS to timber (`#5a4630`, 4.73:1). Your advice was "a lighter bench reduces the ratio" — true, but
|
||||
the ratio was never the binding constraint once you fixed the asset: 4.73:1 is well over the 3:1 WCAG
|
||||
threshold for a graphical object, and a 0.11 m deck is framed by the 0.05 m slab it stands on, not by
|
||||
the metre of cabinet below it.
|
||||
* **The record label you filed (+32 tris) would land well now** — the platter is the brightest thing in
|
||||
the composition and the disc is the darkest, so a label is the one remaining unread detail.
|
||||
|
||||
### → LANE D: the booth moved, and its pose is in the determinism payload
|
||||
|
||||
`djBooth` placement changed in every room that has one (new yaw + new candidate ordering). Nothing about
|
||||
the booth is drivable — ruling 5 held, no control node is animated — but if anything of yours keys off a
|
||||
booth position, re-read it. `watchPoints` were re-measured after the change and did not suffer:
|
||||
`pub` 361 → **363**, `band_room` **384/384**, `rsl` 560. `browsePoints` unchanged at 3.00/room.
|
||||
|
||||
## Round 41 §41.4 — the 110 fittings are placed. Three hand-offs, one warning.
|
||||
|
||||
Full tables in `C-progress.md` § Round 41 (§41.4). Shots: `docs/shots/laneC/r41_*.png`.
|
||||
~~**Worst room in the game is now 122 draws @ `dept`/auto (GLB on), law ≤350 — margin 228.**~~
|
||||
**[CORRECTED IN R42 §42.2b] 123 @ `dept`/auto, margin 227.** The 122 was a 16:10 reading of an
|
||||
instrument whose camera aspect was unpinned; the room never changed. See § Round 42 above.
|
||||
|
||||
### → LANE F: one line turns the wardrobe on in the game, and one number needs restating
|
||||
|
||||
**1. The wardrobe pack.** 52 named 1990s Australian garment layers, one 240 KB atlas, feeding the op-shop
|
||||
clothes rails and shelves at **zero geometry cost**. It is a stock pack under its own base, so `index.html`
|
||||
needs the same one line the test page has, beside the existing three preloads:
|
||||
|
||||
```js
|
||||
if (STOCK_REAL) preloadStockPack('opshop', { base: 'assets/wardrobe/' });
|
||||
```
|
||||
|
||||
and, where a shop's interior is built, `stockBase: 'assets/wardrobe/'` when `shop.type === 'opshop'`
|
||||
(`interior_mode.js` already passes `stockAdapter`; the adapter must be
|
||||
`makeStockAdapter(getStockPack('opshop', 'assets/wardrobe/'))`). **Nothing breaks without it** — no pack
|
||||
means the procedural garment canvas, byte-identically, which is also what `?noassets` and every default
|
||||
boot get. Cache identity is `(type, base)`, so it coexists with the town-wide packs.
|
||||
|
||||
**2. `drawSweep` numbers moved, and it is the instrument, not the rooms.** The sweep cleared every interior
|
||||
from the scene *except* `current` — the live room the page is showing — so every per-type number carried a
|
||||
constant offset from an unrelated second room. R39/R40 prints of this function (worst 188, "margin 162")
|
||||
include that phantom. The sweep now clears all interiors and rebuilds at the end. Re-baseline against the
|
||||
corrected values before asserting anything tighter than ≤350.
|
||||
|
||||
**3. New gate surface, if you want it:** `PROCITY_C.kitAudit(manifest)` compares all 96 catalogue rows in
|
||||
`kit41.js` against the live `manifest.json` and asserts Lane E's three traps (no pool ships both halves of
|
||||
a rotated pair; no pool ships an oversize id; every pool/booth id resolves). Currently **96/96 checked, 0
|
||||
dimension errors, 0 missing, 0 pair errors, 0 oversize**. It caught a real 5 mm drift on its first run. It
|
||||
is cheap, it is non-vacuous (it has already fired), and it is exactly the check that goes stale silently.
|
||||
|
||||
**4. `opts.noKit`** on `buildInterior` drops every R41 spec and reproduces the pre-R41 room exactly — the
|
||||
control arm for any before/after the round needs. `room.pulls = { pulled, restored, lost }` reports what
|
||||
the door→counter guarantee cost that room; `lost > 0` means a genuine blocker.
|
||||
|
||||
### → LANE D: the DJ control nodes exist, nothing drives them, and that is deliberate
|
||||
|
||||
R41 **places** the rigged gear and **records** the contract. There is no driver and `djBooth` ships no
|
||||
animation hook, so a placed node can never be mistaken for a live one. When you build the driver:
|
||||
|
||||
| id | control nodes | live where |
|
||||
|---|---|---|
|
||||
| `deck_1200_rigged` | `Platter_SPIN` · `Arm_YAW` · `Fader_PITCH` · `Btn_STARTSTOP` | record shop, `BOOTHS.record_hero`, left deck |
|
||||
| `mixer_ttm54i_rigged` | `Fader_CROSS` | record shop, `BOOTHS.record_hero`, centre |
|
||||
| `mixer_pmc05pro_rigged` · `mixer_shdj1200_rigged` | `Fader_CROSS` | catalogued, not placed |
|
||||
|
||||
Reach them from the room without guessing: any fitting group with `userData.djBooth` carries
|
||||
`userData.controlNodes = { <id>: [node…] }`, and the GLB instance beneath it is tagged
|
||||
`userData.glbUpgrade` with `userData.kind === <id>`. Two rules from E that a driver must honour: drive by
|
||||
the **bare node name** (cap/stem/rib are parented under it), and **glTF drops the source's limit
|
||||
constraints — clamp the travel yourself.** Venue and pawn booths are statics by design.
|
||||
|
||||
**Your poses are unchanged and still hold in a furnished room.** browsePoints stayed at 3.00/room across
|
||||
all 12 types; watchPoints measured after furnishing: pub **7.5 of cap 8**, band_room **8.0 of 8**, RSL
|
||||
**11.4 of 12**. `counter.stand` is untouched. The venues' bar is now `plywood_bar` instead of
|
||||
`counter_till` — same fitting, same stand pose, same service strip, different model.
|
||||
|
||||
### ⚠ WARNING TO ANY LANE THAT HIDES A PRIMITIVE OR MERGES A MESH
|
||||
|
||||
Two latent bugs this round found the hard way, both of the same shape — **a tag that something depends on,
|
||||
destroyed by a later pass**:
|
||||
|
||||
- `glb.js` hid every child of an upgraded fitting that was not `isStock`. R9's buy-anywhere shelves are
|
||||
tagged `{ noBatch, buyMesh }` and *not* `isStock`, so **`?stock=real` + GLB made every buyable item in
|
||||
the room invisible** — live since R9 on `bookshelf`/`cubeShelf`/`clothesRack`, never seen because R9
|
||||
measured GLB-off.
|
||||
- `batch.js` rebuilt `userData` from scratch when it merged a material bucket, dropping sub-tags such as
|
||||
`tillPrimitive`. Any hide that reads a per-mesh flag after batching was already a no-op.
|
||||
|
||||
If you add a per-mesh flag that a later stage reads, check it survives `batchRoom` **and** the GLB swap.
|
||||
|
||||
## → 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
|
||||
|
||||
743
docs/LANES/LANE_C_PUB.md
Normal file
@ -0,0 +1,743 @@
|
||||
# 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.
|
||||
> **v9.0-alpha amendment (2026-08-03, R39 §39.3):** **§10 added — WHAT YOU CAN ACTUALLY DO IN A SHOP:**
|
||||
> the honest per-type verb table, the five structural facts under it, a measured correction to the
|
||||
> round's binding fact #5 (**44% of market-stall rooms contain no crate**), the op shop's own
|
||||
> **rummage bin** (shipped; contents gated, held for John), and the finding that **one asset's
|
||||
> `transmission: 1` is costing ~46–50% of the interior draw budget**. §0–§9 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
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. WHAT YOU CAN ACTUALLY DO IN A SHOP (v9-alpha, R39 §39.3) → everyone
|
||||
|
||||
> **This section is a measurement, not a design.** Fable's brief asked for the honest per-type
|
||||
> inventory of player verbs, built by reading the recipes and the fitting kinds and then counting
|
||||
> them in built rooms — not from memory. It is uncomfortable. It is also, as far as I can tell, the
|
||||
> first time anyone has written down what this game currently offers, shop type by shop type.
|
||||
|
||||
### 10.1 THE TABLE — the DEFAULT BOOT (`web/index.html`, no flags), 12 types
|
||||
|
||||
Counts are from built rooms: **6 archetypes × 8 seeds = 48 rooms per type**, headless.
|
||||
|
||||
| type | dig a bin? | pull-and-buy off a shelf? | sell at the counter? | anything else |
|
||||
|---|---|---|---|---|
|
||||
| `record` | **YES** — 2–8 bins/room, mean **4.6**, 0/48 rooms empty | no — records are the dig, by design | **YES** — the only shop in the game where anything can be sold | — |
|
||||
| `opshop` | **no** *(R39: it now HAS a bin; contents unruled — §10.4)* | no — there is no `opshop` stock pack, and none is possible today | no — nothing in the game is ever `type:'opshop'` | look at clothes you cannot buy |
|
||||
| `book` | no | **only under `?stock=real`** — ~37 buy meshes / ~412 items per room | only for items already `type:'book'`, which also needs `?stock=real` | — |
|
||||
| `toy` | no | **only under `?stock=real`** — ~34 meshes / ~62 items per room | ditto, `type:'toy'` | a glass case (interactable, no consumer) |
|
||||
| `video` | no | **no** — `vhsAisle` has `'spine'` slots, but the pack is resolved by SHOP TYPE and there is no `video` pack | no | returns slot + arcade cabinet: both `interactable:true`, **both dead** |
|
||||
| `pawn` | no | no | no | glass cases + pegboard — dressing only |
|
||||
| `milkbar` | no | no | no | fridge + glass case: `interactable:true`, **dead** |
|
||||
| `dept` | no | no | no | glass cases, escalator — dressing only |
|
||||
| `stall` | **YES, but** — **21 of 48 rooms have ZERO crates**; mean **0.92**, max 3 | no | **no** — a stall dig records `type:'record'`; the stall is `type:'stall'`, so the matcher fails | — |
|
||||
| `pub` | no | no | no | on a gig night: pay the cover, watch the band |
|
||||
| `band_room` | no | no | no | ditto |
|
||||
| `rsl` | no | no | no | ditto |
|
||||
|
||||
Every type additionally: walk in, walk out, look, a keeper standing at the counter, up to 3 browser
|
||||
rigs, a seeded radio/room-tone, and (with a wantlist) a ~45% keeper rumour on the banner.
|
||||
|
||||
### 10.2 The five structural facts behind that table (verified in the code, R39)
|
||||
|
||||
1. **`?stock=real` is opt-in** — `index.html:222` `params.get('stock') === 'real'`, and
|
||||
`interior_mode.js:360` gates the adapter on it. No adapter ⇒ `stock.js` never calls
|
||||
`buildBuyableShelf` ⇒ **on the boot every player gets, there is not one `buyMesh` anywhere in the
|
||||
game.** The R9 buy-anywhere path is real, tested and shipped, and it is *dark by default*.
|
||||
2. **The pack is chosen by SHOP TYPE, and `SLOT_FOR` has three entries** (`stockpack.js:85`:
|
||||
`record→sleeve · book→spine · toy→box`). `buildInterior` asks `getStockPack(recipe.key)`. So the
|
||||
only shelves that can *ever* be buyable are book spines in a `book` shop and toy boxes in a `toy`
|
||||
shop — the video store's identical spine shelves cannot be, because no `video` pack exists.
|
||||
3. **Four stock kinds can never be bought in any mode**: `garment`, `snack`, `magazine`, `treasure`.
|
||||
`hang()` and `treasures()` never call `buildBuyableShelf`, and no pack type maps to them. **The op
|
||||
shop's actual stock — the clothes — is unbuyable by construction.**
|
||||
4. **`sellableIn` is `item.type === shopType`** (`sell.js:37`, fail-closed) and only two call sites
|
||||
ever stamp a `type`: `interior_mode.js:206` (a dig pull — **always `'record'`, wherever it
|
||||
happened**) and `:251` (a shelf buy — `currentShop.type`). Therefore on a default boot the sell
|
||||
card can open in exactly one kind of room: a record shop.
|
||||
5. **`interactable: true` is not a verb.** `interior_mode`'s E routes exactly three things: an aimed
|
||||
`kind:'bin'`, the counter (via `nearCounter`), and a `buyMesh`. `case`, `fridge`, `returns` and
|
||||
the arcade cabinet all set `interactable: true` and **nothing reads them** — 5 glass cases per
|
||||
pawn room, 8 per dept room, 4 fridges per milk bar, all inert. That flag is currently a comment.
|
||||
|
||||
### 10.3 The correction to the binding fact (R39, measured — the honest direction)
|
||||
|
||||
The round's binding fact #5 says *"only record shops and market stalls have a diggable crate."* True
|
||||
of the **recipes**. Not true of the **rooms**: the stall's `crate` is `zone:'centre'`, and the stall's
|
||||
3–6 trestle tables take the aisle first, so the centre candidates are blocked. **21 of 48 stall rooms
|
||||
(44%) contain no crate at all**; mean 0.92 per room against the record shop's 4.6.
|
||||
So RED TICKET DAY's roster, which counts a stall as a crate-shop by type, **overcounts**: a roster can
|
||||
name a stall you cannot dig in. One line fixes it (`fallbackZones` / `anywhere` on the stall's crate
|
||||
spec — the machinery landed this round), but it adds crates to the world, so it is **held with the
|
||||
contents question** and not shipped.
|
||||
|
||||
### 10.4 THE RUMMAGE BIN — the op shop's own floor bin (SHIPPED, contents gated)
|
||||
|
||||
`fittings.js rummageBin` + the `opshop` recipe. A shallow galvanised dump tub (0.95 × 0.70 × 0.28)
|
||||
with flared sides on a castored steel frame, rim at **0.76 m** — you rummage standing, not crouched —
|
||||
heaped with stickered mixed goods and a hand-lettered card taped to the front panel
|
||||
("EVERYTHING $2"). Deliberately not a record crate recoloured: `crate` is 0.55 × 0.45 and deep
|
||||
because it is sized to stand LPs in, and nothing stands up in this.
|
||||
|
||||
```js
|
||||
// the shipped default — furniture. kind:'prop', no bin, no dig prompt, no `places` entry.
|
||||
buildInterior(shop, THREE)
|
||||
// armed — kind:'bin', the dig fires. ONE opt. (interior_test.html: ?opshopdig=1)
|
||||
buildInterior(shop, THREE, { fittingOpts: { rummageBin: { dig: true } } })
|
||||
```
|
||||
|
||||
**New, generic, opt-in seams (all pure data, no stream reads, absent ⇒ pre-R39 byte-identical):**
|
||||
- `recipe.fittings[i].o` — opts handed to the fitting builder.
|
||||
- `buildInterior opts.fittingOpts[kind]` — per-build override of the above. F's gate handle.
|
||||
- `recipe.fittings[i].fallbackZones` / `.anywhere` — placement fallbacks for a fitting a recipe
|
||||
*promises* will be there. Without them the bin landed in only **11 of 24** archetype×seed rooms,
|
||||
because it is appended last and its zone is the fullest it will ever be. With them: **72/72**.
|
||||
|
||||
**Guarantees (72 rooms = 6 archetypes × 6 seeds × GLB on/off, headless):**
|
||||
- placed **72/72** · **0 carves** · `pathOK` **72/72** · determinism **72/72**
|
||||
- **reachable 72/72** — the nearest walkable cell that is flood-reachable from the spawn sits
|
||||
**0.77–1.15 m** from the bin, against the dig's 2.5 m proximity / 3.2 m aimed reach
|
||||
- **the rest of the room is byte-identical to pre-R39 in 72/72.** The spec is appended LAST, so the
|
||||
shared `lay-count` / `lay-place` streams hand every existing op-shop fitting the same draws it
|
||||
always had; only the tub is added.
|
||||
- end-to-end, armed: aim resolves → dig opens → **16 offers** → pull card → **BUY** debits the wallet
|
||||
→ the bin depletes to 15. Negative control, default: tub present, **0 bins**, aim finds nothing,
|
||||
the dig never opens. Zero console errors on both arms.
|
||||
|
||||
**Draw cost — measured on the GLB path, which is the default boot (`index.html:349`).**
|
||||
|
||||
| `drawSweep` (the ≤350 law's own instrument) | pre-R39 | R39 | Δ |
|
||||
|---|---|---|---|
|
||||
| GLB-**off** worst | 156 (opshop/hall) | **161** (opshop/hall) | **+5** |
|
||||
| GLB-**on** worst | 319 (opshop/hall) | **329** (opshop/hall) | **+10** |
|
||||
| every other type, both modes | — | — | **0 — byte-identical, 11/11** |
|
||||
|
||||
**Margin against the ≤350 law: 21 draws.** Per-room the tub costs **+3…+9** GLB-off and **+6…+18**
|
||||
GLB-on (the doubling is not the tub — see §10.5). It is the worst-case room it lands in, which is why
|
||||
this is stated against `opshop/hall` and not an average. Soak: 60 rooms, **leak geo 0 / tex 0**,
|
||||
determinism identical, avg 3.4 ms / worst 16.5 ms.
|
||||
|
||||
**A free win found while costing it (`stock.js`, shipped).** The scatter fill minted a *tinted*
|
||||
material per item, so a pile of 9 goods in a room with no other scatter cost **9 draws** — one bucket
|
||||
each. New opt-in `slot.pool` keeps every face on the room's shared pooled textures at white, exactly
|
||||
as the spine and shelf-box rows already do, so the pile merges into buckets the room already has.
|
||||
The tub went from 9 goods / 12 draws to **12 goods / 3–9 draws**. Existing scatter callers
|
||||
(`trestleTable`, `pegboard`) draw the same values in the same order and are byte-identical — proven
|
||||
by all 11 other types' `drawSweep` numbers not moving.
|
||||
|
||||
### 10.5 THE INTERIOR BUDGET IS ~2× WHAT IT NEEDS TO BE — one asset material (→ Lanes E/F)
|
||||
|
||||
Costing the tub turned up something worth more than the tub. **Lane E's `bookshelf` GLB ships
|
||||
`transmission: 1`** on `mtl_10218_Bookshelves_v1`. One transmissive material in a scene makes
|
||||
three.js run its transmission pre-pass — **a second full render of the opaque scene into a render
|
||||
target** — so *every* draw in that room is issued and counted twice.
|
||||
|
||||
Measured, seed 1990, isolated room, by setting `transmission = 0` on that material and re-rendering:
|
||||
|
||||
| room | GLB-on draws | with `transmission: 0` | saved |
|
||||
|---|---|---|---|
|
||||
| `opshop`/hall (the game's worst interior) | 191 | **104** | **−87 (−46%)** |
|
||||
| `opshop`/wide | 142 | **80** | −62 (−44%) |
|
||||
| `book`/hall | 101 | **51** | **−50 (−50%)** |
|
||||
| `dept`/hall (no bookshelf) | 116 | 116 | 0 — the control |
|
||||
|
||||
`bookshelf` is in the `opshop` (1–2) and `book` (5–8) recipes, which are the two worst GLB-on types
|
||||
in the sweep (329 and 243). **Roughly half the interior draw budget is being spent on a pre-pass
|
||||
nobody asked for, triggered by a flag on one asset**, and a bookshelf rendering as glass is almost
|
||||
certainly not the intent. **→ E:** re-emit without `KHR_materials_transmission`. **→ F:** the cheap
|
||||
runtime fence is a `transmission = 0` scrub in `glb.js upgradeFitting`; I have NOT shipped it, because
|
||||
it changes how another lane's asset renders and that is not mine to decide in passing. Either fix
|
||||
turns a 21-draw margin into a ~100-draw one.
|
||||
|
||||
### 10.6 THE CONTENTS — HELD FOR JOHN. Do not flip the default without this ruling.
|
||||
|
||||
The mechanism is shipped and gated. **What is IN the bin is the question**, because it is not "op
|
||||
shops now dig", it is "op shops now dig **records**". Measured against the 23 shipped town caches
|
||||
(1,236 shops: **44 record · 36 stall · 147 opshop**):
|
||||
|
||||
| | today | with the op-shop bin armed |
|
||||
|---|---|---|
|
||||
| crate-**shops** per town | 0–11 | **2–25** |
|
||||
| towns with **none** | **3** (bowral, darwin, daylesford) | **0** |
|
||||
| diggable **bins**, all 23 towns¹ | ~235 | ~382 (**+63%**) |
|
||||
| under-band gems/day, all towns² | ~113 | ~183 |
|
||||
|
||||
¹ record 4.6 bins/room × 44 + stall 0.92 × 36 + opshop 1.0 × 147. ² `dig.js:48` — 10% gem roll on a
|
||||
collector/grail band ⇒ 3% per sleeve × 16 sleeves/bin.
|
||||
|
||||
**A · parody records (what the mechanism inherits).** **Cost: zero code** — `proceduralSleeves`
|
||||
already serves everything, and *the market stall already does exactly this*, so this is a change of
|
||||
scale, not of kind. BIBLE bands untouched (`record` bands exist). RED TICKET DAY gets a roster in all
|
||||
23 towns. **The catch:** an op-shop find is stamped `type:'record'`, so it can only be sold in a
|
||||
record shop — and bowral, darwin and daylesford have **zero** record shops. Those three towns would
|
||||
be able to dig and have nowhere to sell. The first pull from an armed bin, seed 42, was a `grail`-band
|
||||
record asking **$72**, in the Salvos bric-a-brac tub. That is either the op-shop fantasy made
|
||||
mechanical or the moment records stop being special; it is a taste call, not a measurement.
|
||||
|
||||
**B · books and toys through the existing pull-and-buy pack.** Not free. (i) The pack is resolved by
|
||||
shop type, so an op shop asks for `stock_opshop_index.json`, which does not exist — it needs either an
|
||||
E-emitted pack or a recipe-declares-its-pack-types change in C. (ii) **The dig scene is
|
||||
record-shaped**: `dig.js` builds a crate of 0.31 m square sleeves and a card that says
|
||||
artist/title/condition. A paperback in it renders as a square LP sleeve. So B costs a second dig
|
||||
presentation. In its favour: the BIBLE already has `book` and `toy` bands (`bible.js:28-29`), so the
|
||||
price fences hold with **zero new numbers**, and the sell path is clean once F stamps the item's own
|
||||
type instead of the hardcoded `'record'`.
|
||||
|
||||
**C · a new bric-a-brac class.** Highest cost — a new BIBLE band row, a parody generator or pack, a
|
||||
card treatment, gates. Also the only option that *fixes* A's asymmetry: `sellableIn` is type = type,
|
||||
so bric-a-brac would sell **in op shops**, which is both correct and diegetic, and the op shop becomes
|
||||
its own loop instead of a second-rate record shop.
|
||||
|
||||
**My recommendation.** Ship the flag (done), and **let John play arm A for one session before anyone
|
||||
decides** — it is zero code and it answers the only question that matters, which is whether digging an
|
||||
op shop is fun. Then: **C is the destination**, because it is what stops "records" being the answer to
|
||||
every question in this game and it closes the three-town sell hole. **B is not worth doing alone** —
|
||||
its cost lands in the wrong place (a record-shaped dig scene showing a book), and its good half (the
|
||||
band table, the type stamp) is inherited by C anyway. If John wants RED TICKET DAY running in all 23
|
||||
towns *this round*, A does it today for nothing, and the three-town sell hole is the price.
|
||||
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.
|
||||
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).
|
||||
65
docs/LANES/ROUND36_INSTRUCTIONS.md
Normal file
@ -0,0 +1,65 @@
|
||||
# PROCITY — Round 36 instructions (from Fable, integrator)
|
||||
|
||||
Date: 2026-07-23 · **R32–R35 closed green in other sessions** (THE BIBLE · scarcity · THE HUNT
|
||||
· travel · GODBAY · the first five minutes · the tour). R35's close law stands: the tag waits
|
||||
for hands. Round 36 is **the v7.0 CLOSE**: Fable plays the whole loop, the out-of-round assets
|
||||
get regularized, the full gate runs over the final tree, docs freeze, **tag `v7.0`**.
|
||||
|
||||
## WAVE 0 — THE PLAYTEST (Fable, in person, FRESH SAVE)
|
||||
|
||||
The R31 precedent, now over every v7 system: fresh save → the first-five-minutes beats → spot
|
||||
a gem via the BIBLE's guide stamps → ☆ WANT an artist → the keeper's rumor → TRAVEL (and pay
|
||||
the day) → ⚖ LIST a find on GodBay → sleep → the morning paper → the hammer. Deliverable: the
|
||||
friction list, split alpha-blocking vs v7.x queue. R31's lesson stands armed: the playtest
|
||||
boots NAKED (no flags beyond dbg) — the explicit-flag blindfold is the enemy.
|
||||
|
||||
## WAVE 1 — regularize the 2 a.m. commits (E)
|
||||
|
||||
`046668e` + `b339402` landed outside the round system (July 20, disciplined but unswept):
|
||||
**4 dance-clip GLBs + 3 trellis DJ bodies** in `web/models/peds/`. E: provenance table entries
|
||||
+ licence lines for all 7 (dance clips = the 90sDJsim Mixamo-family bank — verify and record
|
||||
the zone; DJ bodies = on-device trellis, CC0-equiv, the instrument discipline); depot publish
|
||||
per standing practice; the validator sees them. The commits verified "page boots" only — the
|
||||
full-suite proof is wave 2's.
|
||||
|
||||
## WAVE 2 — the gate, the freeze, the tag (F)
|
||||
|
||||
1. Any wave-0 alpha-blockers first (micro-fix agents per owner if not F's).
|
||||
2. Full `qa.sh --strict --matrix` over the final tree — explicitly including: the 3 DJ bodies
|
||||
through the figure-scale gate (they joined the roster ungated), the dance clips through
|
||||
classic zero-fetch + `?noassets` fallback (the commit claims it; the gate proves it), and
|
||||
the R32–35 smokes (bible, scarcity, hunt, godbay) re-green on the final tree.
|
||||
3. Docs freeze: README v7 section (the loop, the manual rows for WANT/LIST/paper), notes §36,
|
||||
the charters' retired-or-carried (A's v4-style list for the V7 charter risk section —
|
||||
F may write it this once, single-lane round).
|
||||
4. **Tag `v7.0`** (annotated: the loop's numbers + both playtest verdicts), push main + tag.
|
||||
|
||||
## Then: Fable writes the v7 epoch retro, and the menu reopens
|
||||
|
||||
(v6 footage-spikes · the beta/v7.x queue incl. John's own playtest findings whenever he walks
|
||||
it · the frontier shelf: terrain, vernacular kit, civic layer, THE BIBLE polish.)
|
||||
|
||||
## WAVE 2.5 (Fable ruling on F's held warn): CLASSIC KEEPS ITS PEOPLE
|
||||
|
||||
F's `smoke_djdance` caught the July-20 commit adding the 5 DJ bodies to `PED_NAMES`
|
||||
UNCONDITIONALLY — they fetch under `?classic=1` and widened classic's seeded crowd pool 17→22.
|
||||
Every covenant gate stayed green (the covenant pins the plan; the roster was never pinned), but
|
||||
the classic fetch surface and crowd identity moved without a ruling. **RULED: option (b) — the
|
||||
five gate OUT of classic's pool**, mirroring the clips' `!CLASSIC` pattern. The covenant's
|
||||
meaning stays crisp: `?classic=1` is the v2 town INCLUDING ITS PEOPLE; the DJs walk every other
|
||||
boot. D lands the line; F re-verifies (classic fetch surface + pool 17 + smoke_djdance green,
|
||||
strict re-run) and RE-LAYS the tag on the true final commit. The warn was the system working —
|
||||
a covenanted surface cannot move quietly even when every pinned number stays green.
|
||||
|
||||
## ROUND CLOSED (Fable) — **v7.0 TAGGED on `4709cfe`, all green, everything measured twice**
|
||||
|
||||
Run 3 on the healed box: qa 7/0/0/0 · MATRIX 10 towns · flags harness 0 fails / **0 warns** (the
|
||||
§36.4 hold flipped to "✓ classic: zero DJ-body fetches" under the full suite) · classic
|
||||
`0x5f76e76` · the five DJs human-sized by name · Rokoko binds 64/65 · rotation byte-identical
|
||||
per day and across boots. The honest record stands in §36.5: re-gate runs 1–2 died to a
|
||||
BOX-level fork famine (m3panel's 2.5-day `pmset|tail` leak — fixed at source mid-round, the
|
||||
`party.monster.reaper` janitor installed as standing fleet infra) — starvation artifacts, not
|
||||
gates. The hold's full arc: held by machine-recorded WARN → ruled (classic keeps its people) →
|
||||
fixed by the owning lane alone → re-proven by the gate that held it. Nothing improvised.
|
||||
|
||||
## Waves: [Fable plays] → [E] → [F fix+gate+tag] → [D classic-gate + F re-lay] → [retro]
|
||||
202
docs/LANES/ROUND37_INSTRUCTIONS.md
Normal file
@ -0,0 +1,202 @@
|
||||
# PROCITY — Round 37 instructions (from Fable, integrator)
|
||||
|
||||
Date: 2026-07-26 · **v8 opens.** Charter: [V8_SOMEONE_LIVES_HERE.md](../V8_SOMEONE_LIVES_HERE.md)
|
||||
· evidence: [REVIEW_CITY_PATTERNS.md](../REVIEW_CITY_PATTERNS.md) (16-agent audit) ·
|
||||
[ANIMALS_ANSWER.md](../ANIMALS_ANSWER.md) (5-agent spike). **Read the charter first.**
|
||||
|
||||
Round 37 is **WAVE 0 — THE FLOOR**. No content. Five micro items that every v8 content idea
|
||||
stands on. John's rulings are made: **the kerb is FOOTPATH**, **real towns get the content**,
|
||||
**animals are generated not downloaded**. Nothing here is a design question; it is execution
|
||||
plus honest measurement. **No tag this round** — wave 0 closes when the floor is level.
|
||||
|
||||
## The ledger
|
||||
|
||||
### 0.3 — SETTLE THE BUDGET OF RECORD *(Lane F, and it goes FIRST)*
|
||||
Two independent measurements disagree and **no proposal may spend against a number nobody can
|
||||
reproduce**: the city-patterns audit measured a **walked worst frame of 280/300 draws** (8 m
|
||||
steps down the main spine, carrying the R+1 dispose window at up to 31 live chunks); the animal
|
||||
spike re-measured and got **191 default / 167 classic** by teleport-walk and could not
|
||||
reproduce 280. Both were headless (SwiftShader). Settle it:
|
||||
- Walk it properly — real continuous motion, not bookmark teleports, sampling every frame
|
||||
through the chunk dispose window, on the default boot AND `?classic=1`.
|
||||
- If you can get a real-GPU number, say so and say how; if not, state that the number is
|
||||
headless and what that costs in confidence.
|
||||
- **Re-pin the budget of record** in the docs that assert it, and state the tri number the same
|
||||
way (99–124k of 200k was the audit's range; two sessions disagreed enough that it needs a pin).
|
||||
- **Settle ONE shared instance cap** for future town-wide standing layers (three animal
|
||||
proposals independently invented 120, 250 and "plus gulls" for the same layer). One number,
|
||||
written down, with the reasoning.
|
||||
Deliverable: the pinned numbers + the method, in LANE_F_NOTES §37 and wherever the laws are
|
||||
asserted. This unblocks everyone else's budget claims for the whole epoch.
|
||||
|
||||
### 0.1 — THE KERB: FOOTPATH *(Lane A ruling-side · Lane B code · Lane F gate)*
|
||||
John's ruling: the 9 m band between kerb and shopfront is **footpath**. Lane A's published
|
||||
corridor law (`roadWidth` / `vergeBand` / `poleOffset` in `core/registry.js`) is the single
|
||||
source of truth and Lane B adopts it.
|
||||
- **B:** extend the **existing merged `footGeos` class** inward from `e.width/2` to
|
||||
`roadWidth(e)/2` in `ground.js`. **This must stay +0 draws** (same merged mesh) — if it
|
||||
cannot, stop and report rather than paying a draw. Expect ~2k tris and *better* fill-rate
|
||||
(18 m of every main street stops being painted as road). **Special-case or it breaks:**
|
||||
`arcade` (`roadWidth` 0 → zero-area road quad + coincident kerbs) and `lane`
|
||||
(`vergeBand` = [2,2]). Grass verges survive on **residential edges only**.
|
||||
- **B:** ship **`?verge=0`** — a permanent flag that reverts to the pre-ruling geometry. It is
|
||||
the gate's falsifiability control and it costs nothing; it stays in the codebase forever.
|
||||
- **A:** the corridor law is yours — confirm the exports are what B should consume, and fix the
|
||||
selfcheck's poster-clearance assertion, which currently validates against `roadWidth/2` (5 m)
|
||||
while B painted bitumen to 14 m, i.e. **the gate was green while 14 gig posters stood in the
|
||||
road on the default boot**. Say plainly whether any golden moves (it should not — this is
|
||||
render-side), and if one does, amendment law, same commit.
|
||||
- **F (after B):** the gate — assert poster clearance on the default boot, and **assert the
|
||||
same leg FAILS under `?verge=0`**. The pre-change failure is known and counted at exactly 14.
|
||||
|
||||
### 0.2 — REPAIR `smoke_tram` *(Lane F, micro)*
|
||||
`flags_check.py:617-630` asserts exactly one thing — `!!getObjectByName('tram')` — prints a SKIP
|
||||
and returns when absent, and **passes on the fenced path**, because `tram.js:110-114` adds a
|
||||
named empty group. A live vacuous-gate violation inside our own suite. Fix it so it proves the
|
||||
tram *runs* (route resolved, stops sane, moves between frames) on the towns where it runs, and
|
||||
proves it is *genuinely absent* on the fenced towns — with a control that fails.
|
||||
|
||||
### 0.5 — `classic_regression` GETS A NETWORK ALLOW-LIST *(Lane F, micro)*
|
||||
`classic_regression` (`flags_check.py:128-241`) makes **no network assertion at all**. That is
|
||||
why R36's fetch-surface widening (five DJ bodies) needed a brand-new smoke to catch it. Give it
|
||||
an **allow-list**: the four v2-era depot furniture GLBs (`bench`, `bin`, `bus_shelter`,
|
||||
`food_cart` — git-verified v2 baseline, `24cef5a`, tagged v2.0-alpha→v2.0) plus same-origin.
|
||||
**Allow-list, never deny-list** — a deny-list only catches what you already thought of.
|
||||
Prove it discriminates: point it at a fabricated extra fetch and assert it goes red.
|
||||
|
||||
### 0.4 — WIRE `ctx.onChunkBuilt` / `onChunkDisposed` *(Lane B, one line each)*
|
||||
Producer at `chunks.js:43-51`, consumer at `sim.js:203-205`. Both shipped, never connected,
|
||||
since R3. Every per-chunk entity system in the v8 slate wants it. Wire it, prove a consumer
|
||||
actually fires, and confirm zero behaviour change when nobody subscribes.
|
||||
|
||||
## Laws
|
||||
|
||||
Everything standing. This round specifically: **+0 draws is a requirement, not an aspiration,
|
||||
for 0.1** · every new/repaired gate must FAIL when its subject is absent, and each ships with
|
||||
its falsifiability control demonstrated · pathspec-atomic commits (three lanes share the tree) ·
|
||||
no pushes, no tags — Fable pushes at the close · measurement over brief: if a premise here is
|
||||
wrong, prove it with numbers and say so loudly.
|
||||
|
||||
## Order
|
||||
|
||||
1. **Parallel: F (0.3 first, then 0.2 + 0.5 — all in `tools/`) · B (0.1 code + `?verge=0` +
|
||||
0.4) · A (0.1 ruling-side + selfcheck).** Disjoint files by construction.
|
||||
2. **Then F again:** the kerb gate against B's landed code + the `?verge=0` control, full
|
||||
`qa.sh --strict --matrix`, and the round report.
|
||||
|
||||
## FABLE'S MID-ROUND RULING — CLASSIC KEEPS ITS PAINT (B's escalation, §0.1)
|
||||
|
||||
B implemented the kerb ruling with `?classic=1` **forcing verge off** — i.e. classic keeps the
|
||||
pre-ruling ground — rather than moving a covenanted town's render on its own initiative, and
|
||||
escalated the choice. **RULED: keep the fence. Classic keeps v2's paint.**
|
||||
|
||||
Reasoning, and it is not the R27 precedent: R27 (John's facade ruling — "the covenant is
|
||||
anti-drift machinery, not a defect preservative") applies when classic is preserving a **live
|
||||
defect**. Here it is not. The defect this ruling fixes is *posters standing in the road* — and
|
||||
**`?classic=1` has no gig layer, therefore no posters, therefore no defect to preserve**. What
|
||||
classic would be adopting is a cosmetic change to road paint on a town whose entire job is to
|
||||
be v2, unchanged, forever. The museum piece keeps its paint.
|
||||
|
||||
**The one real cost, and its mitigation:** a fenced classic means two ground geometries exist —
|
||||
and "two implementations of one thing" is precisely the disease this round was convened to stop
|
||||
(four `isOpen`s, three `chunkIndex` divergences, two registries). So the fence is ruled
|
||||
acceptable **only as a single branch on one code path**, never a second implementation. B has
|
||||
built it that way (one flag, one `bandOuter <= bandInner` test); it stays that way. If it ever
|
||||
starts to fork, the fence comes down and classic takes the footpath.
|
||||
|
||||
*John may overrule; it is a one-token change (drop `!classic`), and B has proven both sides
|
||||
render identically to their intended baselines.*
|
||||
|
||||
## FABLE'S SECOND MID-ROUND RULING — CLASSIC'S POST-v2 FETCH DELTA (F's escalation, §0.5)
|
||||
|
||||
F built the allow-list by deriving it from the **v2.0 tag at gate time** rather than from my
|
||||
brief's literal ("four depot GLBs + same-origin"), and was right to: my shape **would have gone
|
||||
green straight through the R36 breach**, because `models/peds/woman_dj_01.glb` is same-origin.
|
||||
Deriving from `git ls-tree v2.0` enumerates the covenanted 21 ped files where today's directory
|
||||
holds 32. That is the correct mechanism and it stands.
|
||||
|
||||
Doing it, F measured a **+6 file delta** between the real v2.0 tree's wire and today's
|
||||
`?classic=1`, and refused to improvise a verdict on a six-epoch-old fact. **RULED:**
|
||||
|
||||
- **The 5 audio files (R11, `f7dd44f`): RATIFIED as baseline.** The covenant preserves "the
|
||||
exact v2 town", and **v2.1 was v2's final tagged state** — audio shipped inside v2, so
|
||||
classic fetching it is classic being correct, exactly as the four furniture GLBs are.
|
||||
- **`assets/towns/index.json` (R20/R21): NOT baseline — carried as a NAMED, PRINTED exception,
|
||||
not silently.** It is v4 selector machinery in a v2 museum. It changes nothing about the
|
||||
classic town (a UI list, no geometry, no identity, no seeded draw), so it is not the R36
|
||||
species and it does not get gated in a floor round. It goes on the allow-list **labelled as a
|
||||
post-v2 exception** so it can never read as baseline.
|
||||
- **Filed to the v8 debt ledger, for R38 or later:** *should `?classic=1` show the town selector
|
||||
at all?* In v2 there was no selector (it landed R19). Hiding it under classic would retire the
|
||||
exception at the source and cost one branch — but it is a question about what classic *means*,
|
||||
which is a design ruling, not a leak to plug mid-round.
|
||||
|
||||
**Also ratified from F's report:** the budget of record is **292 draws / 124,793 tris** (the
|
||||
280-vs-191 split was **camera bearing** — 110 draws of spread, the audit took `DBG.teleport`'s
|
||||
default yaw 0 which is the worst bearing, the spike took a travel-facing yaw; both correct,
|
||||
different quantities). My "viewport is the variable" hypothesis was **disproven** — F ran
|
||||
1280×720 vs 2560×1440 with everything else fixed and got byte-identical counts; three's culling
|
||||
is frustum-shaped, not pixel-shaped. Shared instance cap: **256**, binding companions ≤24
|
||||
tris/instance and proximity-first spending. **Margin is 8 draws, not twenty.**
|
||||
|
||||
**Filed, not fixed (R38+):** `?r=3` on the default town measures **307 draws** — an ungated
|
||||
override that breaches the ≤300 law, with no gate covering it. Documented in the README as a
|
||||
diagnostic, not a supported boot; whether `?r=` gets clamped is a later ruling.
|
||||
|
||||
## ROUND CLOSED (Fable) — **THE FLOOR IS LEVEL. All five items, all green, no tag.**
|
||||
|
||||
`qa.sh --strict --matrix`: **7 passed · 0 failed · 0 warn · 0 skipped** · selfcheck **157,407**
|
||||
(+1,055 of A's new poster assertions, all green on the ruled street) · flags harness **0 fails
|
||||
/ 0 warns** · **MATRIX GREEN 10 towns × 7 gates**. Deliberately **no tag** — wave 0 levels the
|
||||
floor, it does not ship a version.
|
||||
|
||||
**What actually changed, in one line each.** The kerb is footpath and it cost **+0 draws with
|
||||
triangles going DOWN** (A/B'd against HEAD on a second port; sum-of-draws identical at all 102
|
||||
walked steps). **14 posters stood in painted bitumen; 0 do now — and not one poster moved, the
|
||||
ground did.** The budget of record is **292/124,793**, and the 280-vs-191 fight was **camera
|
||||
bearing** (110 draws of spread; the audit took `DBG.teleport`'s default yaw 0, the worst
|
||||
bearing; the spike took a travel-facing yaw — both correct, different quantities). Two vacuous
|
||||
gates are dead: `smoke_tram` (green exactly where it was meant to look, because a named empty
|
||||
group satisfied it) and the poster clearance check (**the placer seats via `poleOffset`, which
|
||||
is derived from `roadWidth`, and the gate asserted against `roadWidth/2` — the same function
|
||||
agreeing with itself, the R27 trap inside the file whose header explains the R27 trap**). The
|
||||
chunk seam is subscribable at last — the producer was fine; **`chunks.ctx` was never returned**,
|
||||
so a consumer holding `PROCITY.chunks` had nowhere to attach.
|
||||
|
||||
**The round's own lesson, recorded.** B's first cut passed every number — +0 draws, posters
|
||||
clear, zero errors — **and painted every intersection shut.** A screenshot caught what the
|
||||
metrics could not. Fixed by lifting the carriageway over the footpath at junctions, verified on
|
||||
Katoomba's worst 4.3° corner. *Green metrics over a wrong world is this project's oldest
|
||||
failure mode and it does not care which epoch it is.*
|
||||
|
||||
**Two lanes, two independent methods, one number.** A counted 14 plan-side (node, over the
|
||||
plan, by offset); F counted 14 paint-side (browser raycast, over B's geometry, classified by
|
||||
**material identity** so it cannot drift when B edits). Both flip to 0 on the ruled street and
|
||||
both fire at exactly 14 under `?verge=0`. F's two **detector controls** are the load-bearing
|
||||
part: "0 posters on road" is a sentence about nothing unless the classifier is shown saying
|
||||
*road* AND *footpath* on the same boot.
|
||||
|
||||
**Cross-lane corrections that landed mid-round** (the shepherd's whole job): A's measurement
|
||||
killed three literals in Fable's brief — `lane`'s band is `[4,4]` on 56 real edges, not the
|
||||
synthetic `[2,2]`; the "9 m band / 18 m of main" figures are synthetic-only (a real main is
|
||||
24 m ⇒ 7 m, and `side`@12 is **72% of the corpus** at 3 m). B confirms it would have shipped
|
||||
the `[2,2]` assumption otherwise. F's allow-list mechanism corrected Fable's other literal:
|
||||
"four GLBs + same-origin" **would have gone green straight through the R36 breach**, so the
|
||||
list is derived from `git ls-tree` at gate time — and per this round's ruling the authority tag
|
||||
moved `v2.0 → v2.1`, which git made surgical (`git diff v2.0 v2.1 -- web/` is the audio pack
|
||||
and its wiring, zero other assets, zero deletions).
|
||||
|
||||
**Carried to the v8 debt ledger (filed, not smuggled):** A's D1–D4 (three `chunkIndex`
|
||||
divergences · four `isOpen` implementations, A's canonical export with **zero importers** ·
|
||||
two registries, which is why gig venues have worn op-shop signage since R13 · A's own
|
||||
nearest-edge/arcade poster-gate hole, costed at 11 of 26 town gig goldens and deferred so this
|
||||
round's "did the kerb move a golden?" stays a clean **no**) · the town-matrix's budget column
|
||||
is **bookmark numbers**, which this round proves under-report a walked-and-turned frame by
|
||||
~120 draws — a regression signal, not the budget of record · `?r=3` breaches at **307 draws**
|
||||
through an ungated override · *should `?classic=1` show the town selector at all?*
|
||||
|
||||
## Standing
|
||||
|
||||
- John's three rulings are made; nothing in this round needs him.
|
||||
- Still open for later rounds, not now: the market-plaza amendment, hardyards provenance,
|
||||
where MOCAPGOD actually lives.
|
||||
180
docs/LANES/ROUND38_INSTRUCTIONS.md
Normal file
@ -0,0 +1,180 @@
|
||||
# PROCITY — Round 38 instructions (from Fable, integrator)
|
||||
|
||||
Date: 2026-07-26 · **R37 closed green: the floor is level.** The kerb is footpath at +0 draws,
|
||||
14 posters are out of the road, two vacuous gates are dead, the budget is pinned at
|
||||
**292 draws / 124,793 tris (8 draws of margin)**, the shared instance cap is **256**, and
|
||||
`ctx.onChunkBuilt` is finally subscribable. Round 38 is **WAVE 1 — INHABITATION**: four items
|
||||
that make the town feel lived in. **Three of the four are +0 or +1 draws.** None needs a rig, a
|
||||
clip, or John's mocap footage.
|
||||
|
||||
Charter: [V8_SOMEONE_LIVES_HERE.md](../V8_SOMEONE_LIVES_HERE.md) · evidence:
|
||||
[REVIEW_CITY_PATTERNS.md](../REVIEW_CITY_PATTERNS.md) · animals:
|
||||
[ANIMALS_ANSWER.md](../ANIMALS_ANSWER.md) · R37's rulings and debt ledger:
|
||||
[ROUND37_INSTRUCTIONS.md](ROUND37_INSTRUCTIONS.md).
|
||||
|
||||
## THE CORRECTIONS THAT ARE BINDING (adversarial review already killed the naive version)
|
||||
|
||||
Do not re-derive these. They cost a review pass each.
|
||||
|
||||
1. **1.1 is SIX NEW MIXER LAYERS, not "generalise `audio.js:300-311`"** — audio.js has
|
||||
hardcoded bed layers per source class, not a generic point-source system. **There is no
|
||||
panning**: `playSfx` is mono with a gain multiplier, so *distance* works and *direction* does
|
||||
not. Say so in the notes or budget a `StereoPannerNode`. Derive day-of-week **locally**.
|
||||
2. **1.2's `state` field is NOT populated on all 23 towns** — `newtown_godverse` and
|
||||
`redhill_godverse` carry `state: ''`. The vacuous arm must be **per-town**, not per-corpus,
|
||||
or those two silently take a default branch with no SKIP printed.
|
||||
3. **1.2's `skyBias` must NOT go through `lighting.setSky`** — `externalSky` (`lighting.js:53`)
|
||||
is a **one-way latch with no un-set path**. Feed the base `skyName` selection instead.
|
||||
`townCharacter(null)` returning the frozen literals makes classic byte-identical **by
|
||||
construction rather than by flag** — that is the right mechanism, use it.
|
||||
4. **1.3: the ice-cream-container hat is CUT** (no street verb exists; `index.html` binds only
|
||||
`] [ P M T` plus a click that enters a shop). And **the magpie's territory must scale with
|
||||
the road graph**: 370 pole anchors on synthetic vs **10,014 poles / 13,730 tree anchors
|
||||
across 2,593 edges on `bendigo_real`** — a capacity-4 magpie there is a swooping town where
|
||||
you never meet the magpie. R37's own anchor-supply measurement says the same: synthetic
|
||||
**246 poles / 6.8 km** vs bendigo **7,325 / 188 km**. **Spend the cap proximity-first**
|
||||
through `ctx.onChunkBuilt`, which B wired last round precisely for this.
|
||||
5. **1.4 is built INSIDE `buildings.js`'s existing `boxMats` InstancedMesh** (it already carries
|
||||
shells, parapets and verandah posts, and `buildHouse` already pushes into it) → hoist, fence
|
||||
run, aerial and tank are **+0 draws**. A separate `world/yards.js` cannot reach `boxMats` and
|
||||
would pay a draw the proposal then presented as its honest cost. **Only the alpha washing
|
||||
quads need a new mesh → +1 draw/chunk total, not +2.**
|
||||
6. **1.4's washing needs an INVERTED wind weight.** `wind.js:38-45` computes
|
||||
`wH = clamp(y/4.4,0,1); wH *= wH` — top-weighted, base planted. Correct for a gum, exactly
|
||||
wrong for a sheet pegged at the top. At 1.6 m the amplitude is **0.7–4.5 cm, i.e. visually
|
||||
inert**. Either write an inverted-weight sway mode or **drop the washing this round** and say
|
||||
which.
|
||||
7. **1.4 is synthetic-only until the civic fetch lands** (all 21 real towns are 100%
|
||||
`use:'shop'`, one district). It ships on the default boot — which is still the boot every
|
||||
player gets — and extends to real towns in a later round. Scope it honestly; do not fake it.
|
||||
8. **REJECT the hardyards GLBs on provenance** (39 GLBs, provenance documented for one file).
|
||||
Generate ours. This was the best licence judgement in the review packet.
|
||||
9. **The budget line every item must answer:** margin is **8 draws**, and triangles are the
|
||||
cheap resource (~75k spare). A town-wide `InstancedMesh` is **1 draw at any N**; a per-chunk
|
||||
type is **+1 × up to 31 live chunks**. Prefer town-wide. Cap 256, ≤24 tris/instance.
|
||||
|
||||
## The ledger
|
||||
|
||||
### 1.1 — SOMEONE ELSE'S LIFE: the audio inhabitation layer *(E + B, F gates)*
|
||||
**The review's only unqualified SHIP.** The manifest ships 7 ambience / 9 sfx / 7 music and not
|
||||
one animal, domestic or seasonal sound. A dog going off behind a paling fence, a mower two
|
||||
streets over, a radio through a window, sprinklers, a roller door, a distant magpie carol —
|
||||
the sound of a life happening off-screen, at a **seeded anchor** so it is the same dog on the
|
||||
same street every time you walk it.
|
||||
**E:** ~30-line numpy generators in `gen_audio.py`'s PACK registry, deterministic via
|
||||
`crc32(name)`, 🟢 original by construction. **B:** the six mixer layers + seeded anchor
|
||||
placement + distance falloff. **Budget: zero draws, zero tris — the only item in the epoch that
|
||||
cannot touch the budget under any circumstance.** Audio pack ~10 MB → ~11 MB against a 25 MB cap.
|
||||
**Gate (F):** keep the falsifiability control verbatim — point one table entry at a
|
||||
non-existent key and assert the leg **FAILS**. Every other gate this round copies its shape.
|
||||
|
||||
### 1.2 — TWENTY-THREE TOWNS: the per-town character vector *(B + E, F gates)*
|
||||
Twenty-three real towns currently differ only in street layout and shop names. Give each a
|
||||
ground palette, an ambience mix and a sky bias. `ground.js`'s `add()` merges each surface class
|
||||
into ONE mesh for the whole town regardless of size (verified: exactly 5 meshes) — **a palette
|
||||
swap is +0 draws / +0 tris**, the strongest cost claim in the packet. `gravel` (verge) and
|
||||
`reddust` (outback) already ship in the manifest and are selected by **nothing**.
|
||||
**E:** 3–4 new `gen_skins.py --local` grounds (dry summer grass, cool-temperate grass,
|
||||
red-dusted bitumen, coastal sand) so "Darwin is red dust, Hobart is cold light" is real.
|
||||
|
||||
### 1.3 — THE MAGPIE *(B + E, F gates)*
|
||||
Swooping season: for ~six weeks of the game's year, one bird on one street will not let you
|
||||
walk past it. Real behaviour — a 6-week window Aug–Oct, and ~90% of males never swoop, so
|
||||
**one bird, not a plague**. A static mesh on a scripted parabola (the tram's verified
|
||||
position-is-a-function-of-(seed,time) contract), wing wave through `wind.js`'s amplitude
|
||||
uniform, byte-identical at amp 0. **+1 draw / ≤900 tris.**
|
||||
**Gate (F):** keep "the day-roll clears the latch and localStorage gained no new key" verbatim
|
||||
— the best single arm in the packet and a genuine delta-law falsifier.
|
||||
|
||||
### 1.4 — BACKYARD AUSTRALIA *(B + E, F gates)*
|
||||
Hills Hoists, paling fences, TV aerials, water tanks. **181 house and yard lots on the default
|
||||
boot with nothing in them.** Per correction 5, inside `boxMats`: **+0 draws** for the box props,
|
||||
+1 draw/chunk only if the washing ships.
|
||||
*Measured headroom:* the residential district walks at **157 draws / 31,651 tris** worst
|
||||
(edge 16, 20 live chunks) against a main-spine control of 263/123,602 on the same build —
|
||||
~143 spare draws where these props land.
|
||||
|
||||
## Laws
|
||||
|
||||
Everything standing, plus: **every item states its draw cost against the pinned 292/300 and its
|
||||
tri cost against ~75k spare** · every new gate ships its falsifiability control demonstrated ·
|
||||
classic stays byte-identical **by construction where possible** (1.2's `townCharacter(null)` is
|
||||
the model) · `?noassets` fallback for every asset · pathspec-atomic commits · **no tag** — wave 1
|
||||
is content, the epoch tags when it is whole.
|
||||
|
||||
⚠️ **Tree hazard:** a separate background session is editing **`web/js/world/venue.js`** (capping
|
||||
town-wide pool instances on real towns). **No lane touches `venue.js` this round.** Expect it
|
||||
dirty; commit around it by pathspec.
|
||||
|
||||
## Order
|
||||
|
||||
1. **Parallel: E (the asset wave) + B (all four runtime items, procedural-first so nothing
|
||||
blocks on E).**
|
||||
2. **Then F:** the four gates, full `qa.sh --strict --matrix`, round close. No tag.
|
||||
|
||||
## ROUND CLOSED (Fable) — **THE TOWN IS INHABITED. All four items, all green, no tag.**
|
||||
|
||||
`qa.sh --strict --matrix`: **7 passed · 0 failed · 0 warn · 0 skipped** · flags_check **0/0** with
|
||||
all four new gates inside it · **MATRIX GREEN 10 towns × 7 gates**, 0 fetches under `?noassets`.
|
||||
|
||||
**THE BUDGET: 292/300 STANDS — margin is still EIGHT, not six.** My naive sum (292 + 2 = 294) was
|
||||
wrong, and wrong in our favour: **the pinned worst frame is a NIGHT frame, and both of wave 1's
|
||||
draw-spending layers are structurally zero at night** (the washing comes in, the magpie roosts).
|
||||
Wave 1's one draw is spent in daylight, where the walked peak sits ten draws lower. F reproduced
|
||||
the R37 pin **to the byte, twice** (292 / 120,145 / 31 live chunks) on a control tree, then
|
||||
isolated every delta by flag revert: `?yards=0` at night returns all three control numbers;
|
||||
`?yards=0` at noon isolates the noon +1 to the washing alone.
|
||||
|
||||
**B's control-346 is adjudicated, not merely cited.** As a *delta* it was the wall-clock ped-spawn
|
||||
trap and B was right to hide the sim — but ">300" is a claim about the world, and neither existing
|
||||
method could adjudicate it (one photographs a half-dressed street, the other arrives clean at 27
|
||||
chunks). F built `budget_walk.py --method stepsettle` — fixed-yaw stations, each drained *then held
|
||||
N real rAF frames*, so R+1 residue and a full crowd land in one frame. The sign flip is the finding:
|
||||
the settle is **+3 draws at noon, −9 at night**, because at night the settled street retires near
|
||||
rigs to impostors and a sampler that never waits never sees the crowd go home. **Highest number
|
||||
anywhere — 4 methods × 2 trees × 2 segments × 5 towns — is 292. Nothing read over 300.**
|
||||
|
||||
**F corrected its own R37 pin:** corpus tri worst is **130,727** (fitzroy), not 124,793 — and it
|
||||
reads 130,727 on the *control*, so it is R37's 8 m station spacing vs this round's 2 m, not a wave-1
|
||||
cost. *Station spacing is a bigger lever on a walked worst frame than settle policy.*
|
||||
|
||||
**What shipped:** 21 distinct palettes across 23 towns at +0/+0 (classic byte-identical **by
|
||||
construction** — `townCharacter(null)` returns the v1 literals with the same material cache key) ·
|
||||
36 `yard` lots that had rendered as blank shopfront sheds since v1 are now fenced backyards, and
|
||||
the box props are **−2 draws** because those lots used to fall through to `buildShopfront` · the
|
||||
magpie at +1 draw / 182 tris, season now **23 distinct positions across 24 towns** · six audio
|
||||
layers at zero draws **that pan** (B refused the no-panning limitation and added a
|
||||
`StereoPannerNode`; F proved the pan is a bearing: yaw 0/90/180/270 → +0.00 / +0.85 / −0.00 / −0.85).
|
||||
|
||||
**Three self-caught errors, one per lane — the round's real signature.** B's own season mechanism
|
||||
was **vacuous**: keyed on `citySeed` alone it made **24 of 24 towns a constant 214 days** from
|
||||
swooping season, and it would have gated green because `?magpie=1` forces the state. F's own pan
|
||||
arm used two bearings, read ±0.00, and **would have called a working panner inconclusive** — four
|
||||
bearings now. E generated a perfectly good 1,121-tri `paling_fence` that B rejected on measurement
|
||||
(905 instances = **1.01 M triangles** against ~75k spare), producing the sizing law this round paid
|
||||
for: **an asset's budget is `tris × the instance count the placement rule implies`** — get the
|
||||
instance count from the consuming lane *before* generating anything that tiles or repeats per-lot.
|
||||
|
||||
**Assets:** `trellis2_mlx` swap verified **1.92×** (and it hid a trap — `sorted(rglob)[0]` was
|
||||
picking the *untextured* candidate); six sounds; four grounds; three GLBs published and
|
||||
**sha1-verified in both directions off the public depot**; and two **decisive failures** —
|
||||
`hills_hoist` and `tv_aerial` cannot be generated (TRELLIS returns thin wire as thousands of
|
||||
fragments; no voxel size keeps a 30 mm pole and a 2 mm wire at 600 tris), deleted so nothing could
|
||||
catalogue them, permanently primitive at +0 draws. The magpie shipped reading as a **crow**; E
|
||||
tinted it by projecting anatomical windows through the UVs in the mesh's own frame (a 100-island
|
||||
smart unwrap has no island that announces itself as "nape"), and **Fable verified the render by
|
||||
eye**: pied and unmistakable at 64/48/32 px.
|
||||
|
||||
**Carried:** two magpies now exist (B's 182-tri procedural bird ships; E's 894-tri tinted GLB is a
|
||||
named upgrade at `birdGeometry()`/`birdMaterial()`) — **filed for a side-by-side screenshot, not
|
||||
forced** · `magpie.js` carries two stale tri comments (154 vs measured 182) · §1.4 is synthetic-only
|
||||
until the civic fetch, and its gate **skips loudly** on real towns rather than passing vacuously ·
|
||||
audible output is unverifiable headlessly and every run prints so — *nobody in this loop has ears* ·
|
||||
A's D1–D4 · the matrix budget column is bookmark numbers · `?r=3` breaches at 307 · the
|
||||
`?classic=1` selector question.
|
||||
|
||||
## Standing
|
||||
|
||||
Nothing needs John. Carried debt (R37's ledger, unchanged): A's D1–D4 · the matrix's budget
|
||||
column is bookmark numbers · `?r=3` breaches at 307 · should `?classic=1` show the town
|
||||
selector at all.
|
||||
205
docs/LANES/ROUND39_INSTRUCTIONS.md
Normal file
@ -0,0 +1,205 @@
|
||||
# PROCITY — Round 39 instructions (from Fable, integrator)
|
||||
|
||||
Date: 2026-07-26 · **R38 closed green — v8 wave 1 shipped and the town is inhabited** (21 palettes,
|
||||
36 backyards, the magpie, six panning ambient layers; budget held at **292/300, eight draws of
|
||||
margin**, because the worst frame is a night frame and both new layers are zero at night).
|
||||
|
||||
**JOHN HAS RULED: do the street names.** *"yes do street names for sure and what else can we get on
|
||||
with? just keep it going."* Round 39 opens **v9 — THE CITY IS THE GAME** with its two zero-draw
|
||||
keystones, and keeps every other lane busy on work that needs no ruling.
|
||||
|
||||
Charters: [V9_THE_CITY_IS_THE_GAME.md](../V9_THE_CITY_IS_THE_GAME.md) (the hunt; read Fable's
|
||||
verification header first) · [SALE_RED_TICKET_DAY.md](../SALE_RED_TICKET_DAY.md) (the round and the
|
||||
Band Floor Law) · [V8_SOMEONE_LIVES_HERE.md](../V8_SOMEONE_LIVES_HERE.md) (in flight; wave 2 later).
|
||||
|
||||
## THE BINDING FACTS (verified by Fable — do not re-derive, do not contradict without numbers)
|
||||
|
||||
1. **17,835 of 19,132 road ways in the shipped caches carry a `name` — 93.2%.** Counted
|
||||
independently. `plan_osm.js:363` builds each way as `rawWays.push({ kind: roadEdgeKind(rd.kind),
|
||||
pl })` — it keeps the kind and the geometry and **discards `rd.name`**. Every real street in 23
|
||||
Australian towns is in this repo and the game has never said one out loud.
|
||||
2. **The names are recoverable without touching citygen's output.** Plan nodes ARE snapped
|
||||
projected way points (`plan_osm.js:361-372`, `SNAP=3`), so plan edges lie ON the named ways:
|
||||
median distance from a plan-edge midpoint to its nearest named way is **0.03–0.90 m** (worst
|
||||
town katoomba), and **97.8% of corpus shops (1,192/1,219) resolve to a street name**.
|
||||
3. ~~**THE INTERIOR MARGIN IS SIX DRAWS, NOT 191.**~~ **CORRECTED IN-ROUND BY LANE C — the margin
|
||||
was 31, and is 21 after C's bin.** The direction was right (`index.html:349` ships
|
||||
`useGLB: !NOASSETS`, so the **default boot IS the GLB path** — that part stands and matters).
|
||||
The *number* was stale: `C-progress.md:841`'s "GLB-on 344" is **R9-era**. C measured today's
|
||||
tree before touching anything: **GLB-off 156 · GLB-on 319**, both `opshop/hall`.
|
||||
**Fable's error, owned:** I verified the claim's provenance (the doc does say 344) and not the
|
||||
measurement (the tree says 319). *A document is not a tree.* The house rule stands and I broke
|
||||
it: measure the thing, not the note about the thing.
|
||||
**And the real finding is bigger than the correction** — see the transmission entry in the
|
||||
close.
|
||||
4. **Vertical movement is fiction.** `player.js:64` and `interior_mode.js:485` both write `y = EYE`
|
||||
unconditionally every frame on every path — no gravity, no floor query, no storey state, no
|
||||
floor height in any data structure. **Nothing this round proposes stairs.**
|
||||
5. **Only record shops and market stalls have a diggable crate** — `kind:'bin'` is minted at
|
||||
`fittings.js:60`/`:75` (`recordBin`, `crate`) and used by exactly two recipes (`theme.js:38-39`
|
||||
record, `:196` stall). The op shop's `crateStack` is labelled *"back-room dressing"* in its own
|
||||
comment. **You cannot dig in an op shop**, in a game built on op-shop DNA.
|
||||
|
||||
## The ledger
|
||||
|
||||
### 39.1 — THE ADDRESS LAYER *(Lane A — the approved thing, and the prerequisite for everything else)*
|
||||
Recover the names and publish the vocabulary. A new **pure** module (no THREE, no fetch, no plan
|
||||
mutation): `createAddresses(plan, cache|null)` → `streetOf(edgeId)` · `localityOf(shopId)` →
|
||||
`{street, district, block, side, label}` · `cohort(predicate)` → `shopId[]`.
|
||||
- **Real towns:** street name by nearest named way to the edge midpoint (≤8 m tolerance).
|
||||
- **Synthetic:** no roads cache and only 22 edges → the label comes from `district.kind` + block:
|
||||
*"the market end"*, *"the arcade"*, *"the backstreets"*. **Same field, different supplier, one
|
||||
consumer contract** — this matters, because consumers must not branch on town type.
|
||||
- The synthesis proposes a **one-line** upgrade: write `norm.shift = {shx, shz}` at
|
||||
`plan_osm.js:510` into the **caller-supplied `opts.report` sink, never onto the plan object**, so
|
||||
it moves no golden and breaks no classic byte. **Take it if it is genuinely that clean; measure
|
||||
and report if it is not.**
|
||||
- **Goldens must not move.** State it plainly with your selfcheck numbers. If one moves, stop.
|
||||
- Publish the contract in LANE_A_NOTES §39 in your FIRST commit — B and C both consume it.
|
||||
|
||||
### 39.2 — THE FOG *(Lane B — the keystone, zero draws)*
|
||||
`minimap.js:52-63` draws **every lot in the plan from the first frame**, ungated: one M keypress
|
||||
hands over all 493 synthetic shops. Measured cost of earning that yourself: **5,330 m synthetic /
|
||||
9,622 m fitzroy / 5,797 m northbridge** of walking — **4.8 / 8.7 / 5.3 game-days** at WALK 4.6 m/s
|
||||
against the 240-second day.
|
||||
- Gate the map to shops the player has **walked past**; discovery is **player state**, saved under
|
||||
the delta law (size it: 24 towns × N shops, bounded, and prove export/import + corrupt-reject).
|
||||
- **Frame the map to what you know.** The payoff is the reason to do it: a shop rectangle goes from
|
||||
**0.8×1.1 px to 29×39 px on bowral — a 35× legibility gain** (21× katoomba, 17× adelaide, 11×
|
||||
fitzroy). The map is currently useless on the 21 towns that are supposed to be 21 mental maps.
|
||||
- **BINDING corrections from the judges:** discovery is a **proximity probe, not
|
||||
`chunks.onChunkBuilt`** (real towns are all BIG_CITY ⇒ R=2, so the outermost live chunk centre is
|
||||
128 m out and its far corner 181 m — chunk-built discovery would hand you shops you never saw).
|
||||
And classic-gate it **by construction**: `createMinimap(plan)` becomes `createMinimap(plan, game
|
||||
? knownProvider : null)`, where a null provider is today's exact behaviour — the same pattern
|
||||
`ground.js` already uses, so `minimap.js` needs no flag test at all.
|
||||
- Consume A's `localityOf` for street labels on the map once A publishes.
|
||||
|
||||
### 39.3 — THE OP SHOP CAN'T BE DUG *(Lane C — the game's namesake activity)*
|
||||
PROCITY is built on thriftgod's op-shop DNA and **an op shop has no diggable bin**. Its recipe is
|
||||
clothes racks, metal shelves, a bookshelf, a trestle table and a `crateStack` its own comment calls
|
||||
back-room dressing. Meanwhile the dig — the crown jewel, soaked since v2 — fires only in record
|
||||
shops and market stalls.
|
||||
- **Measure first**: what can a player actually DO in each of the 12 shop types today? Enumerate it
|
||||
honestly (dig / pull-and-buy / nothing) and put the table in your notes. That table is the
|
||||
deliverable even if nothing else lands.
|
||||
- **Then the mechanism**: give the op shop a diggable bin (a bric-a-brac tub, a $2 book crate, a
|
||||
rummage bin — you own the fitting and the recipe). The *mechanism* is uncontroversial and yours.
|
||||
- **FLAG, do not decide: what is IN it.** Records mixed in? Books and toys through the existing
|
||||
pull-and-buy pack? Its own item class? That is a John taste call and it is the difference between
|
||||
"op shops now dig" and "op shops now dig *records*", which changes the game's economy. Propose,
|
||||
measure the options, and hold.
|
||||
- **Spend against SIX draws** (fact 3). Say what your bin costs and prove it.
|
||||
|
||||
### 39.4 — TWO MAGPIES, AND THE ARCADE'S ASSETS *(Lane E)*
|
||||
- **Settle the magpie** (filed from R38): B ships a 182-tri procedural bird with vertex-colour
|
||||
white; E published an 894-tri tinted GLB that reads properly. **Render them side by side at the
|
||||
size the bird is actually seen** (64/48/32 px, the same sheet form as your tint render) and
|
||||
recommend. Fable rules on the picture, not the prose.
|
||||
- **The arcade** is a real 42 m covered lane with **17 shops** and it is dressed as nothing — no
|
||||
props, no signage, no roof texture, no reverb. Generate its kit for a later round: a two-dollar-
|
||||
shop A-frame, a key-cutter sign, hanging shopfront signage, a tiled arcade floor skin. Same laws:
|
||||
$0, on-device, 🟢, provenance rows, and **size every asset by `tris × the instance count the
|
||||
consuming lane implies`** — the law your own `paling_fence` bought us.
|
||||
- Correct `magpie.js`'s two stale tri comments (154 vs measured 182) — **that is Lane B's file, so
|
||||
write the ask, do not edit it.**
|
||||
|
||||
### 39.5 — THE GATES AND THE CLOSE *(Lane F, last)*
|
||||
Gate 39.1–39.3 with falsifiability controls; full `qa.sh --strict --matrix`; round close. **No tag.**
|
||||
Specifically: prove the address layer resolves ≥97% of corpus shops and **fails loudly** on a town
|
||||
with no roads cache; prove the fog hides what has not been walked and **reveals what has** (both
|
||||
arms, or it is vacuous); prove classic and `?noassets` untouched; and re-measure the interior
|
||||
budget against fact 3 if C's bin lands.
|
||||
|
||||
## ROUND CLOSED (Fable) — **v9 IS OPEN. 10 passed · 0 failed · 0 warn · 0 skipped. No tag.**
|
||||
|
||||
Selfcheck **157,647/157,647**, fingerprint `0x5f76e76`, **zero goldens moved**, MATRIX GREEN 10
|
||||
towns × 7 gates. Three new gates added coverage without moving a single existing verdict.
|
||||
|
||||
**THE TOWN KNOWS ITS OWN STREETS.** 1,192 of 1,219 corpus shops resolve to a real Australian
|
||||
street name — 97.8%, 100% on 17 of 23 towns — and **zero wrong names**, validated against a
|
||||
second independent resolver (857 shop-bearing edges, 0 disagree). Katoomba's map now says
|
||||
*Katoomba Street*. `map.html` had never rendered a real street in four epochs of holding the data.
|
||||
|
||||
**THE MAP IS A MAP.** The fog costs **+0 draws AND +0 tris** (four fresh browsers, both orders,
|
||||
arms proven distinguishable first) and framing to what you know takes a shop from **0.83 × 1.11 px
|
||||
to 29.4 × 39.2 — 35× on bowral**. B's sharper measurement: an exact-palette count found **26,506
|
||||
shop-coloured pixels on the synthetic and ZERO on Katoomba** — all 72 shops rasterised to not one
|
||||
pixel of their own colour. The map wasn't hard to read on real towns; it was drawing nothing.
|
||||
|
||||
**THE FOURTH AND FIFTH INSTANCES OF THE SAME BUG.** `(−sin,−cos)` at `index.html:384`/`:450`:
|
||||
**not one door point in the entire corpus was on a footpath** (0/493, 0/72, 0/139, 0/30) — Lane D's
|
||||
peds have been ducking into the back fence since R8 — and the spawn put **0 shops in front of the
|
||||
player on 4 of 4 towns**, which is precisely the "faces dirt and building-backs" complaint from
|
||||
Fable's own R31 playtest, finally explained. Two characters; 0 → 4 on all four towns.
|
||||
**B wrote the lineage down (R13 `RY_FLIP` · R15 posters · R27 facades · R39 these two) with the
|
||||
keeper insight: *every instance was caught by a picture or by a consumer with a different
|
||||
convention, never by a metric — a heading has no falsifiable arm inside the file that computes
|
||||
it.*** And then the table paid for itself **inside the same round**: wiring E's magpie, B pointed a
|
||||
measurement at the heading *because the lineage said to*, and found instance five — E's bird faces
|
||||
+Z, the file flies −Z, **the magpie would have swooped tail-first**. First one ever caught before
|
||||
shipping.
|
||||
|
||||
**THREE GLBs WERE SHIPPING AS GLASS.** `transmissionFactor: 1` forces three.js's transmission
|
||||
pre-pass — **every opaque draw issued twice**. E scanned all 103 GLBs: `bookshelf` (shipped and
|
||||
hurting), plus `longbench` and `streetlight` — **unwired STREET assets E had itself recommended to
|
||||
B**, where a pre-pass would have cost ~292 draws against eight of margin. Armed, not fired. Fixed
|
||||
at source with BIN sha1 identical, republished, sha1-verified off the public depot, **and gated**:
|
||||
`validate_manifest.py` now hard-fails on it.
|
||||
|
||||
**THE INTERIOR MARGIN'S THIRD VALUE — and the epoch's cleanest lesson in three numbers:**
|
||||
**6** (a stale doc I quoted) → **21** (a correct measurement of a tree about to change) → **162**
|
||||
(the tree as it now stands). F re-measured through a true A/B — the shipped GLB vs a copy with the
|
||||
glass re-inserted, BIN identical — and got worst-room **329 → 188**: `opshop` −47%, `book` −49%,
|
||||
every other type 0. **The worst interior room is now `dept/auto` — a room with no bookshelf in it.**
|
||||
E's fix took, and it bought **+141 draws**.
|
||||
|
||||
**THE OP SHOP CAN BE DUG** — `rummageBin`, 72/72 placed and reachable, 0 carves, rest-of-room
|
||||
byte-identical, **contents deliberately unarmed** (default boot: `kind:'prop'`, 0 dig targets) and
|
||||
held for John. Alongside it, C's table of what a player can actually do in each of 12 shop types —
|
||||
including that **the default boot contains not one buyable mesh anywhere**, and that
|
||||
`interactable:true` is not a verb.
|
||||
|
||||
**Two self-caught errors worth keeping.** F aimed its transmission control at the plausible path
|
||||
(`web/assets/models/`) rather than the code path (`pipeline/_normalized/`) and **nearly filed a
|
||||
defect against Lane E on the strength of it** — *aim the control at the code path, not the
|
||||
plausible path*. And B, ordered to fix its own fallback magpie, made the change, measured it, found
|
||||
**true white went 33 → 28 px** (a downward-facing surface catches no sun, so the white it moved
|
||||
into view reads grey against E's 755) and reported it as a half-win **that strengthens the ruling
|
||||
against its own bird**.
|
||||
|
||||
**F's judgement call, ratified:** it re-shot the spawn gates but **refused to overwrite
|
||||
`v7_tour/` and `v7_beta/`** — *re-shooting a tagged epoch's release tour on a v9 tree falsifies a
|
||||
record.* They stay as history.
|
||||
|
||||
**Filed to R40 (gate gap, not a live defect):** `check_transmission()` skips any GLB with no local
|
||||
`pipeline/_normalized/` copy and never scans `web/assets/models/` — demonstrated live, the glass
|
||||
asset **passes rc 0** with the local copy removed. **A depot-only transmissive GLB — exactly the
|
||||
shape of `longbench`/`streetlight` — would ship unseen.** Also carried: A's D1–D4 · the matrix
|
||||
budget column is bookmark numbers · `?r=3` breaches at 307 ungated · the arcade's awning posts
|
||||
stand two rows 1.00 m apart down a 5.00 m lane and should be designed with the roof · should
|
||||
`?classic=1` show the town selector · **nobody in this loop has ears.**
|
||||
|
||||
**Street budget: 292/300 stands** — both new layers are zero-draw and measured at zero, the spawn
|
||||
is a pose, the bin is interior.
|
||||
|
||||
## Laws
|
||||
|
||||
Everything standing. This round: **zero draws is the requirement for 39.1 and 39.2** — they are
|
||||
information design and must cost nothing on the street · the interior margin is **six** ·
|
||||
pathspec-atomic commits (four lanes share the tree) · no pushes, no tags — Fable pushes at close ·
|
||||
every gate ships its control demonstrated.
|
||||
|
||||
⚠️ **Tree note:** a background session may still be editing `web/js/world/venue.js`. No lane touches
|
||||
it this round.
|
||||
|
||||
## Order
|
||||
|
||||
1. **Parallel: A (39.1, contract in the first commit) · B (39.2) · C (39.3) · E (39.4).**
|
||||
2. **Then F (39.5).**
|
||||
|
||||
## Standing — for John, when he wants them
|
||||
|
||||
The op-shop bin's **contents** (39.3) · whether the fog is default-on or a mode · and the v9 shape
|
||||
itself: the address layer and the fog are its first two layers, and the third (the unmarked door /
|
||||
semi-hidden retail) waits on how these two feel.
|
||||
94
docs/LANES/ROUND40_INSTRUCTIONS.md
Normal file
@ -0,0 +1,94 @@
|
||||
# PROCITY — Round 40 instructions (from Fable, integrator)
|
||||
|
||||
Date: 2026-08-03 · R39 closed green (QA 10/0/0/0, street 292/300, interior worst 188 — margin 162).
|
||||
**John's direction: "just keep building — big bugtest playthru feedback sesh later."** So round 40
|
||||
is the no-rulings round: every filed R39 debt that needs no decision, plus one new keystone — the
|
||||
harness that will turn John's eventual playthrough into tickets. The three standing questions
|
||||
(op-shop bin contents · fog default-on · v9 third layer) stay parked until that session; nothing
|
||||
here pre-empts them. Audio stays unlistened — John has no ears free either right now; the listen
|
||||
pass is part of the sesh.
|
||||
|
||||
## Integrator rulings made so this round needs none from John
|
||||
|
||||
- **`?classic=1` gets the town selector.** Classic is the shop-window mode; hiding the 23 towns
|
||||
from it serves nobody. It's information, not gameplay state.
|
||||
- **Feedback capture is a build item, not tooling fluff** — it's 40.6 and F's keystone.
|
||||
|
||||
## The ledger
|
||||
|
||||
### 40.1 — CLOSE THE TRANSMISSION GATE'S BLIND SPOT *(Lane E)*
|
||||
R39 demonstrated it live: `check_transmission()` skips any GLB with no local
|
||||
`pipeline/_normalized/` copy and never scans `web/assets/models/` — the glass asset passes rc 0
|
||||
with the local copy removed. A depot-only transmissive GLB (exactly the shape of
|
||||
`longbench`/`streetlight`) would ship unseen.
|
||||
- The gate must scan **every path the game can load from**: `pipeline/_normalized/`,
|
||||
`web/assets/models/`, and the depot listing (fetch headers/GLB chunk over the tailnet path is
|
||||
fine — it's free).
|
||||
- **Control demonstrated, both arms:** plant a transmissive fixture reachable only by the
|
||||
previously-unscanned path → gate goes RED; remove it → GREEN. The R39 live demo is your red-arm
|
||||
spec.
|
||||
- Aim the control at the code path, not the plausible path — that sentence is in the round close
|
||||
because F nearly filed a false defect over it.
|
||||
|
||||
### 40.2 — A's D1–D4, CLOSED OR RE-FILED WITH NUMBERS *(Lane A)*
|
||||
The carried list has ridden three rounds. This round each item either closes with a measurement or
|
||||
gets formally re-filed with a number and a reason it can wait. No third option, no silent carry.
|
||||
Goldens must not move; say so with selfcheck numbers as always.
|
||||
|
||||
### 40.3 — THE `?r=3` BREACH, AND CLASSIC'S SELECTOR *(Lane B)*
|
||||
- `?r=3` measures 307 ungated against the ≤300 street law. Gate it or shave it: either the flag
|
||||
page states its own budget law (and qa asserts it), or the draws come down. Numbers either way —
|
||||
and the matrix budget column is bookmark numbers (R39 carry), so re-measure, don't quote.
|
||||
- `?classic=1` shows the town selector (ruling above). Zero-draw in-game; selector is UI chrome.
|
||||
- Consume A's address vocabulary anywhere the map/HUD still says nothing where it could say a
|
||||
street name — you built the fog; finish the signage on it.
|
||||
|
||||
### 40.4 — THE ARCADE'S POSTS *(Lane E, with C consulted)*
|
||||
Two rows of awning posts 1.00 m apart down a 5.00 m lane. Design the posts WITH the roof as one
|
||||
kit (the R39 close's instruction): re-measure the lane, one kit, placement rule stated, before/
|
||||
after shot pair. If the fix is a placement-rule change and not an asset change, say so and E hands
|
||||
it to whoever owns the rule with the measurement attached.
|
||||
|
||||
### 40.5 — THE DOOR POINTS AND THE FOOTPATH *(Lane D — verification-first)*
|
||||
R39 measured **0 door points on a footpath across the whole corpus** (0/493, 0/72, 0/139, 0/30) —
|
||||
peds ducking into the back fence since R8 — and B's two-character heading fix landed in the same
|
||||
round. What this round needs is the truth of TODAY'S tree:
|
||||
1. Measure the door-point-on-footpath rate corpus-wide now.
|
||||
2. If B's fix covered it: close with the numbers, done.
|
||||
3. If not: fix the door-point derivation (D owns where peds duck in), same gate — rate ≥95% on all
|
||||
four measured towns, and the ped visibly enters through the front on a shot.
|
||||
No new mechanics; this is making the R8-era claim true.
|
||||
|
||||
### 40.6 — THE PLAYTEST HARNESS *(Lane F — the keystone)*
|
||||
John's next session with this game is a bug-hunt playthrough. Build the thing that makes it count:
|
||||
- **One key (F8) drops a note**: captures `{pos, mode, shopId+name if inside, street/locality via
|
||||
A's layer, seed, town, clock seg, flags, fps, draws}` + a screenshot to downloads + a typed
|
||||
one-liner (small overlay input, Esc cancels). Zero cost when unused; classic-gated by
|
||||
construction like everything else.
|
||||
- Notes persist under the delta law (bounded, export/import, corrupt-reject — the fog's pattern)
|
||||
and **export as one JSON** John can paste back to the lanes as tickets.
|
||||
- **A `?bugtour=1` mode**: strings the existing bookmarks into a guided walk (N key = next stop)
|
||||
covering: spawn → street day → shop enter/buy → dig → op-shop bin (unarmed) → gig night → tram →
|
||||
rain → night → map/fog → second town via selector. The point: John's session covers every system
|
||||
without him needing a checklist.
|
||||
- Smokes: harness off = byte-identical boot; on = note round-trips (drop → export → validate
|
||||
schema); screenshot lands; qa stays green.
|
||||
- Close the round: budgets re-stated (street ≤300, interior ≤350 with today's margins), tour shots
|
||||
for anything visual this round touched, then STOP — no tag until John's sesh rules on the parked
|
||||
three.
|
||||
|
||||
## Laws
|
||||
|
||||
Everything standing: zero draws for information design · pathspec-atomic commits · no pushes/tags —
|
||||
Fable pushes at close · every gate ships its control demonstrated · measure the thing, not the note
|
||||
about the thing · fresh browser contexts · human-sized line on humanoid shots.
|
||||
|
||||
## Order
|
||||
|
||||
1. **Parallel: A (40.2) · B (40.3) · D (40.5) · E (40.1, then 40.4).**
|
||||
2. **Then F (40.6) last, consuming A's layer for the note metadata.**
|
||||
|
||||
## Parked for John's sesh (unchanged, not blockers)
|
||||
|
||||
Op-shop bin contents · fog default-on vs mode · v9 third layer (the unmarked door) · the audio
|
||||
listen pass — all become tickets from the F8 harness the moment he plays.
|
||||
160
docs/LANES/ROUND41_INSTRUCTIONS.md
Normal file
@ -0,0 +1,160 @@
|
||||
# PROCITY — Round 41 instructions (from Fable, integrator)
|
||||
|
||||
Date: 2026-08-07 · **R40 closed green** (12/12 gates, `c3384b9` pushed, goldens `0x5f76e76`).
|
||||
**Theme: THE LIBRARY LANDS.** John: *"we have heaps new furniture clothes ppl animations
|
||||
heapppppppps animations in our library — take a look and mega update our game."* Fable surveyed
|
||||
the fleet. The survey is the ground truth for this round; its numbers are quoted below and must be
|
||||
re-measured, not re-quoted, before anyone acts on them.
|
||||
|
||||
## The finding that defines the round
|
||||
|
||||
**PROCITY ships 8 animation clips.** `idle · walk · sit · look · dance_drink · dance_medium ·
|
||||
dance_party · dance_sway` — skeleton-only GLBs, 66 mixamorig nodes, loaded by
|
||||
`rigs.js:69 loadPedFleet()`.
|
||||
|
||||
**The fleet holds 6,866 licence-clean clips** in ultra's `~/Documents/MOTIONLIB/green/`
|
||||
(2,394 Mixamo FBX · 2,548 CMU BVH · 1,620 100STYLE BVH · 304 ACCAD BVH), and
|
||||
**`~/Documents/MIRPAMO/` is a working local Mixamo** (`./bin/mirpamo batch <rig>.glb <clipdir> -o
|
||||
out/`, headless Blender, no cloud). **The skeletons match 1:1** — 66 mixamorig nodes both sides;
|
||||
retarget output is byte-shaped like the existing `idle.glb`. The Mixamo bank alone holds **237
|
||||
idle variants · 270 walks · 140 sits · 163 greet/point/wave · 113 dances · 112 push/pull/pick/reach
|
||||
· 63 carry**. Every non-walking citizen in the game today plays the same idle.
|
||||
|
||||
Plus: **45 record-shop fittings that pass the house law untouched** (`~/Documents/3dstore/`,
|
||||
~300 tris median, metre-correct), **60 DJ-gear GLBs, 45 of them control-rigged** (working
|
||||
`Fader_CROSS` / `Platter_SPIN` / `Btn_STARTSTOP` node contracts), **48 pub/venue props** at 200k
|
||||
tris needing a 40× decimation, and **441 hand-prompted 1990–99 Australian garment PNGs** from
|
||||
wardrobegod (174 tops / 119 bottoms / 69 shoes / 66 hats / 12 bags).
|
||||
|
||||
## INTEGRATOR RULINGS — binding, no lane may reinterpret them
|
||||
|
||||
1. **🔴 THE BANDAI HAZARD IS CLOSED BEFORE ANY RETARGET RUNS.** m3ultra holds 3,077 **CC BY-NC**
|
||||
(non-commercial, never shippable) Bandai-Namco BVH at
|
||||
`~/Documents/MIRPAMO/library/downloads/bandai_namco_1/` — in a neutrally-named path, outside
|
||||
ultra's `red/` zone system, with nothing in the filename marking it. It is **one
|
||||
`mirpamo batch` away from a shipped build.** Lane E-motion's FIRST action, before touching any
|
||||
clip: zone it (rename with the `_NC-research` suffix per the house rule, or delete in favour of
|
||||
ultra's correctly-zoned `red/bandai/`) and reconcile `MIRPAMO/library/manifest.json`'s wrong
|
||||
`CC-BY-NC-ND` label against ultra's corrected `SOURCES.md`. Report it done with the paths.
|
||||
2. **R41 clips come from the MIXAMO bank only.** Mixamo is usable inside games with **no
|
||||
attribution requirement** — the only rule is never redistributing clip packs, and retargeted
|
||||
per-character clips inside the game are explicitly fine. CMU/100STYLE/ACCAD are all attribution-
|
||||
bearing; they are a later round's work, after the credits surface exists (41.5). One licence
|
||||
regime this round.
|
||||
3. **A DENY-LIST enters the repo law.** These never enter `web/` under any circumstance:
|
||||
`character_kit_modular/exports/bodies/**` (44 bodies, 30 named `-nsfw`, 465k tris each, broken
|
||||
0×0×0 bboxes) · any `anatomy/` dir · `elsa_coronation_hair_wig_kh3.glb` (Disney/Square Enix IP
|
||||
rip) · `daphne_sexy.glb` (unverified rip) · `~/Documents/mocaponline/**` (vendor demo packs,
|
||||
licence unread, includes Epic's EULA-bound `SK_Mannequin.fbx`). No wildcard copies from any
|
||||
library — every asset enters by name.
|
||||
4. **THE STREET BUDGET IS SPENT: 291/300 at night, nine draws.** No lane adds a street-side prop
|
||||
this round. Every asset in R41 lands **interior** (margin 162) or is **zero-draw** (clips are
|
||||
skeleton-only, 0 tris). Any street-side proposal gets measured and filed to R42, not shipped.
|
||||
5. **Never run `gltf-transform optimize` on the DJ gear.** It collapsed a 696-node mixer to 25
|
||||
nodes and deleted `Fader_CROSS`. Decimate only, and verify the node contract survives.
|
||||
6. **Clip delivery is GROUPED, not 40 files.** A GLB holds many animations; boot fetch count is a
|
||||
budget. Deliver **≤6 grouped clip GLBs by category** with a documented index, not one file per
|
||||
clip.
|
||||
|
||||
## The ledger
|
||||
|
||||
### 41.1 — THE MOTION LIBRARY *(Lane E, motion half — the critical path, starts first)*
|
||||
Ruling 1 first, then: pick and retarget **~40–48 clips** from `green/mixamo_full` through MIRPAMO
|
||||
onto PROCITY's ped rig. Pick for what this game actually is — a shopping town with shops, benches,
|
||||
a pub, and citizens who currently only walk and stand:
|
||||
- **Idles (8–10 distinct)** — `Breathing_Idle`, `Big_Yawn_While_Standing`, `Sit_With_Fidgeting_Feet`,
|
||||
cocky/hip-lean variants: the clone-army killer.
|
||||
- **Browse/shop (6–8)** — `Examine_Forwards`/`_Backwards`, `Holding_An_Object_Idle`/
|
||||
`_While_Walking`/`_While_Turning_Left`/`_Right`, `Closing_Cabinet_Door`, `Carrying_A_Box`.
|
||||
- **Sit/lean (6–8)** — `Little_Girl_Sitting_On_A_Bench_Swinging_Her_Legs`,
|
||||
`Leaning_Against_A_Wall_With_Various_Postures`, `Legs_Crossed_Leaning_On_Object`.
|
||||
- **Social (6–8)** — `General_Conversation`, `Having_A_Chat_At_The_Watercooler`,
|
||||
`Greeting_While_Standing`, `2_People_Shaking_Hands_Part_1/2` (a matched pair — keep them paired),
|
||||
`Nodding_Head_Yes`, `Shaking_Head_No_Dismissively`.
|
||||
- **Locomotion (4–6)** — walk variants + the `45/90/135/180_Degree_{Left,Right}_Turn` set.
|
||||
- **Venue (4–6)** — better dances, clapping standing/seated, cheering.
|
||||
Deliver: ≤6 grouped GLBs under `web/models/clips/`, a **new** `web/assets/motion_manifest.json`
|
||||
(new file — no race with props' manifest.json) indexing clip→group→category→duration→loopable,
|
||||
provenance in AUDIT.md, and MIRPAMO's `make smoke` green. Watch the documented gotcha: Blender 5
|
||||
FBX export flattens slotted actions — feed GLB/BVH or use `strip_skin.py`.
|
||||
|
||||
### 41.2 — THE FITTINGS *(Lane E, props half — parallel with 41.1, disjoint files)*
|
||||
1. **`~/Documents/3dstore/` — 45 of 46 GLB pass the house law as-is** (~300 tris median, metre
|
||||
scale, no Draco, no textures). Re-verify with `glb_stat.py`, bring them in by name, manifest
|
||||
them with footprints for C. Reject `3dmrp.glb` (499,948 tris). **This is the round's cheapest
|
||||
win — do it first.**
|
||||
2. **DJ gear (ultra `3D=models/dj-gear/`, 60 GLB)** — 18 already ≤5k; normalize the rest
|
||||
(decimate only, ruling 5), **verify the `Fader_*`/`Platter_SPIN`/`Btn_STARTSTOP` node contract
|
||||
survives on every rigged model** and state the check. Manifest them with their control-node
|
||||
names so C/D can drive them later.
|
||||
3. **Pub/venue props (`~/Documents/not-tonight/art_incoming/`, 48 GLB @ ~200k tris)** — 40×
|
||||
decimation through `normalize.py`. Content is exactly pub/cafe/milkbar (pokie, jukebox, keg,
|
||||
bain-marie, pie warmer, banquettes, ashtrays, sandwich board). Skip the 6 `staff*.glb` — they
|
||||
are 200k-tri statues with no skin, no anims, no materials.
|
||||
Publish to the depot over the tailnet path, sha1-verify, run the R40 transmission gate over every
|
||||
new asset (it now scans all four surfaces — prove it stays green).
|
||||
|
||||
### 41.3 — THE CITIZENS COME ALIVE *(Lane D — after 41.1 lands)*
|
||||
Wire the clip library into the fleet. `rigs.js loadPedFleet()` and the sim's state machine are
|
||||
yours.
|
||||
- **Per-citizen deterministic idle** from the new pool — seeded by identity, so the same citizen
|
||||
always stands the same way and the street stops looking like one person copy-pasted.
|
||||
- **A real BROWSE state** for the interior browsers (they currently stand): examine/hold/reach at
|
||||
C's browse points, seeded per browse point.
|
||||
- **Sit and lean** on the street furniture that already exists (benches, shopfronts) — this is
|
||||
where sit/lean clips earn their keep; keep it zero-draw (a seated ped is a ped that was already
|
||||
drawn).
|
||||
- Keep memory bounded and **state the numbers**: clips resident, fetch count at boot, heap delta.
|
||||
Determinism law holds — same seed, same crowd, same postures, byte-equal across two runs.
|
||||
- Everything gated so `?noassets=1` still runs (the 8-clip path must survive as the fallback).
|
||||
|
||||
### 41.4 — THE SHOPS GET THEIR STOCK *(Lane C — after 41.2 lands)*
|
||||
Place the new fittings. Interior margin is 162 draws — spend it deliberately and report the worst
|
||||
room after every change.
|
||||
- **Record/opshop/book/video/pawn/stall**: the `3dstore` tubs, crates, wooden racks, stackable
|
||||
`mos-japhop` units, library trolley, `vinylsleeve_v5`, window booth.
|
||||
- **Pub/cafe/milkbar**: the decimated venue props — this is the set that finally furnishes the pub.
|
||||
- **Record shop + gig venue**: the DJ gear (turntables, mixers, the 808/SP-1200/MPC) as hero props.
|
||||
- **The wardrobe PNGs** (441 garment layers, `90sDJsim/web/world/media/doll/`): route R5 — use them
|
||||
as op-shop rack textures and shelf stock art at **zero geometry cost**. Period-correct Australian
|
||||
1990s naming is the whole point; keep the names visible where the game shows item text.
|
||||
- Every recipe change re-runs your sweep + soak: 0 throws, 0 path-fails, 0 leaks, draws ≤350.
|
||||
|
||||
### 41.5 — THE CREDITS SURFACE *(Lane B — small, and it unlocks future rounds)*
|
||||
PROCITY has no attribution surface. R41 ships Mixamo only (no attribution debt, ruling 2) — but
|
||||
CMU/100STYLE/ACCAD are 4,472 more clips waiting behind exactly this. Build it now: a credits panel
|
||||
(the flag/selector UI idiom you already own), fed by a data file lanes append to, listing asset
|
||||
sources and their licences. Zero draws in-game, classic-gated by construction. Then R42 can spend
|
||||
the CC-BY libraries the day it wants them.
|
||||
**Also yours, filed from R40:** A's D3 — `SHOP_REGISTRY` is missing pub/band_room/rsl, so 81 venue
|
||||
lots corpus-wide wear op-shop signage (`buildings.js:465`). Cosmetic, measured, and it will show in
|
||||
every pub shot this round takes.
|
||||
|
||||
### 41.6 — INTEGRATION AND PROOF *(Lane F — last)*
|
||||
Wire what needs wiring, gate what needs gating, and **prove the round with pictures** — this is the
|
||||
first round in a long time whose whole point is visible. New/extended gates: clip-manifest
|
||||
integrity (every referenced clip resolves), citizen-posture determinism (two runs byte-equal),
|
||||
interior draw budget with the new fittings, `?noassets=1` still clean, deny-list compliance (no
|
||||
banned path ever referenced from `web/`). Re-shoot the tour: a busy street with citizens in
|
||||
*different* postures, a browsing interior, the furnished pub, the record shop with real gear.
|
||||
`qa.sh --strict` green, budgets restated, goldens unmoved. **No tag** — John's playtest session
|
||||
rules on the epoch.
|
||||
|
||||
## Laws
|
||||
|
||||
Everything standing, plus rulings 1–6 above. Highlights: measure the thing, not the note about the
|
||||
thing · pathspec-atomic commits, **no git commands from lanes** — the integrator commits at close ·
|
||||
fresh browser contexts · human-sized line on humanoid shots · every gate ships its control
|
||||
demonstrated · budgets are numbers.
|
||||
|
||||
## Order
|
||||
|
||||
1. **Wave 1 (parallel):** E-motion (41.1, ruling 1 first) · E-props (41.2) · B (41.5).
|
||||
2. **Wave 2 (parallel, after their inputs land):** D (41.3, needs 41.1) · C (41.4, needs 41.2).
|
||||
3. **Wave 3:** F (41.6).
|
||||
|
||||
## For John
|
||||
|
||||
Nothing blocks this round. Still parked for the playtest session: the op-shop bin's contents · fog
|
||||
default-on vs mode · v9's third layer · **the audio listen pass** (nobody in this loop has ears).
|
||||
The F8 notepad and `?bugtour=1` are live and waiting.
|
||||
128
docs/LANES/ROUND42_INSTRUCTIONS.md
Normal file
@ -0,0 +1,128 @@
|
||||
# PROCITY — Round 42 instructions (from Fable, integrator)
|
||||
|
||||
Date: 2026-08-07 · **R41 closed green** (18 gates, `d14cf7f` pushed, goldens `0x5f76e76`).
|
||||
**Theme: THE LOOK.** R41 furnished the rooms and filled the streets with postures; doing so made
|
||||
three cosmetic faults newly visible. John looked and said fix them. This round is a **visual
|
||||
debt pass** — no new systems, no new content, just making what shipped last round *read* right.
|
||||
|
||||
## The three faults (Fable's own eyeball pass on R41's proof shots — go look before you start)
|
||||
|
||||
1. **The pub props render as untextured white blobs.** `docs/shots/laneF_r41/pub_furnished.jpg` —
|
||||
the couch and jukebox are lumpy white masses. E's own §41.2 note says 37 of 39 shipped with
|
||||
**no texture at all**; they are geometrically correct and visually unfinished. A 90s pub couch
|
||||
is brown vinyl or worn floral, not white.
|
||||
2. **The DJ booth reads as a plain black box.** `docs/shots/laneF_r41/record_dj_booth.jpg` — the
|
||||
rigged Technics with working `Platter_SPIN`/`Fader_PITCH` nodes are in there, and at normal
|
||||
viewing distance you cannot tell it is a turntable setup. Right result, wrong read.
|
||||
3. **The ped roster does not fit 1990s Australia.** There is a **purple masked luchador** in the
|
||||
pub crowd. The survey found ultra's `3D=models/characters/` is 83 FBX = **53 people-normal ·
|
||||
10 comical · 9 fantasy** — some of the wrong 19 are in our shipped 24.
|
||||
|
||||
## Standing debt this round also clears (all filed, all small)
|
||||
|
||||
- **[B, from D]** `furniture.js` puts the bench's front **along** the street, not facing the road —
|
||||
contradicting its own comment. D's citizens now sit on these benches, so it shows.
|
||||
- **[B, from D]** one `benchStops(plan)` export retires D's derived-station mirror.
|
||||
- **[B, from F]** one `await document.fonts.ready` in `buildings.js` — the sign-atlas font race that
|
||||
makes four bookmarks unreproducible across boots.
|
||||
- **[E, from B]** the **19 base ped models have no per-asset licence record anywhere in this repo**,
|
||||
and CITY_SPEC's declared zone includes CC-BY and paid-royalty-free. B built the credits surface;
|
||||
this round fills the rows. A ped we cannot licence is a ped we replace.
|
||||
- **[D, from F]** `r41_shots.py`'s browse arm is deterministically red (it re-enters the winner
|
||||
after a ~3 s/candidate scan, by which time the occupant has left) — currently warn-level.
|
||||
|
||||
## INTEGRATOR RULINGS
|
||||
|
||||
1. **Period law, now explicit: PROCITY is 1990s Australia.** No fantasy, no superhero, no
|
||||
luchador, no anime, no medieval. If a model would look out of place on a suburban Sydney street
|
||||
in 1996, it does not ship. This applies to peds first and everything after.
|
||||
2. **Prefer CC0 or known-licence replacements** — it fixes the look *and* the licence gap in one
|
||||
move. The survey found **Quaternius (CC0)** ships Casual, Casual2, Punk, Suit, Worker, Farmer,
|
||||
Beach: directly usable town archetypes with zero attribution debt. Ultra's 53 people-normal FBX
|
||||
are the other pool; each needs its licence recorded before it ships (ruling: unrecorded ⇒ not
|
||||
shipped).
|
||||
3. **Every ped that ships gets a `credits.json` row** — name, source, licence, attribution-if-any.
|
||||
B built the surface in R41; this is what it was for.
|
||||
4. **Draw-neutral or better.** Street is **291/300 at night — nine draws.** A ped swap is
|
||||
one-for-one; a texture is not a draw. Interior margin is **227** (the true number after C's
|
||||
phantom-room fix) — the pub may spend some of it, measured, and must state the worst room.
|
||||
5. **No new capability.** No conversation system, no DJ driver, no new mechanics. If a fix needs a
|
||||
system, file it to R43 with the measurement.
|
||||
|
||||
## The ledger
|
||||
|
||||
### 42.1 — DRESS THE PUB *(Lane E, look half)*
|
||||
The 39 `art_incoming` props ship untextured. Give them materials that read as a 1990s Australian
|
||||
pub/cafe/milkbar. Cheapest correct route first — these are flat-material meshes, so **baked vertex
|
||||
colour or a small shared palette atlas beats per-asset texture maps** (and keeps the draw win C
|
||||
fought for). Priorities by how much screen they own: `old_couch`, `jukebox`, `banquette`,
|
||||
`plywood_bar`, `bistro_table`, `pokie`, `bain_marie`, `pie_warmer`, `keg`, `lounge_chair`.
|
||||
Beer-stained carpet reds, brown vinyl, laminex, brass and chrome — not white. Ship a contact sheet
|
||||
and an A/B pair against R41's shot. Keep the house GLB law, re-run the four-surface transmission
|
||||
gate, republish + sha1-verify.
|
||||
|
||||
### 42.2 — MAKE THE DECKS READ *(Lane E look half + Lane C, jointly)*
|
||||
The booth is right and unreadable. Diagnose before you change anything: is it the asset (a black
|
||||
plinth with a dark deck on a dark counter), the composition (nothing around it says *DJ*), or the
|
||||
lighting? Then fix the cheapest cause. Likely both: E gives the deck body some contrast (silver
|
||||
platter, coloured slipmat, visible tonearm, a lit pitch fader), and C composes the booth so it
|
||||
reads at a glance — mixer beside the deck, a record on the platter, sleeves in the rack behind,
|
||||
maybe the booth angled toward the door instead of flat to a wall. **No control-node driver this
|
||||
round** (ruling 5) — the nodes stay recorded for R43. Before/after pair required.
|
||||
|
||||
### 42.3 — RECAST THE TOWN *(Lane E cast half — audit and source)*
|
||||
1. **Audit the shipped 24 peds**: for each, name it, judge it against ruling 1 (1990s Australian
|
||||
street: yes/no), and record its licence — or mark it `unverified`. The survey's split of ultra's
|
||||
library (53 normal / 10 comical / 9 fantasy) is your map; the shipped roster is the territory.
|
||||
**Show the luchador and name it.**
|
||||
2. **Source replacements** for every reject, preferring CC0 (Quaternius: Casual, Casual2, Punk,
|
||||
Suit, Worker, Farmer, Beach) then ultra's people-normal pool. Normalize to the existing ped
|
||||
contract — **same mixamorig skeleton, same 66-node shape, same 2,132–3,999 tri band, same
|
||||
texture budget** — because D's 46 clips and the impostor bake both ride that contract and a
|
||||
deviation breaks them silently.
|
||||
3. **Fill `credits.json`** for the whole roster (ruling 3), replacing B's amber `unverified` rows
|
||||
with real ones.
|
||||
|
||||
### 42.4 — RECAST, WIRED *(Lane D — after 42.3)*
|
||||
Swap the roster in `rigs.js`/`loadPedFleet` and the fleet slot machinery. Non-negotiables:
|
||||
**determinism holds** (same seed → same crowd — the fleet-order bug you fixed in R2 is exactly the
|
||||
hazard here: fill fixed slots by index, never by load-completion order), **draw count unchanged**,
|
||||
the impostor bake still matches its rigs at the swap boundary, and the no-giants gate stays green
|
||||
(new models, new scale risk — this is precisely the R10 failure class). Re-shoot your street shot.
|
||||
Also yours: fix `r41_shots.py`'s browse arm so it stops being warn-level.
|
||||
|
||||
### 42.5 — THE THREE SMALL ONES *(Lane B)*
|
||||
The bench yaw (make the bench face the road; D's sitters will show it either way, so a
|
||||
before/after shot is the proof), the `benchStops(plan)` export, and `await document.fonts.ready`
|
||||
in `buildings.js`. That last one should make the four unreproducible bookmarks stable — **verify
|
||||
it does** by re-running F's 3-boot hash check, and tell F whether byte-hash goldens become
|
||||
possible again.
|
||||
|
||||
### 42.6 — PROVE THE LOOK *(Lane F — last)*
|
||||
This round is judged by eye, so the shots are the deliverable. Re-shoot every R41 frame that
|
||||
showed a fault, **paired against the R41 original** (they are committed — use them as the before):
|
||||
pub, DJ booth, street crowd, pub crowd (the luchador's replacement). Gates: period-law compliance
|
||||
(no banned-genre model in the ped manifest — a name-list gate, control demonstrated), roster
|
||||
licence completeness (every shipped ped has a credits row — red if any `unverified` remains),
|
||||
plus everything standing. Budgets restated with the true numbers. `qa.sh --strict` green.
|
||||
**No tag.**
|
||||
|
||||
## Laws
|
||||
|
||||
Everything standing: no git commands from lanes (integrator commits at close) · measure the thing,
|
||||
not the note about the thing · fresh browser contexts · human-sized line on humanoid shots · every
|
||||
gate ships its control demonstrated · the R41 deny-list is permanent repo law.
|
||||
|
||||
## Order
|
||||
|
||||
1. **Wave 1 (parallel):** E-look (42.1, then 42.2's asset half) · E-cast (42.3) · B (42.5).
|
||||
2. **Wave 2 (parallel):** C (42.2's composition half) · D (42.4).
|
||||
3. **Wave 3:** F (42.6).
|
||||
|
||||
## For John
|
||||
|
||||
Still parked for your playtest: the op-shop bin's contents · fog default-on vs mode · v9's third
|
||||
layer · **the audio listen pass** (nobody in this loop has ears). F8 and `?bugtour=1` are live.
|
||||
Filed to R43: the DJ control-node driver, the paired-conversation state machine (D's 8 social
|
||||
clips are shipped and unused), and repainting the nine non-venue shop signage palettes (an art
|
||||
call B deliberately left you).
|
||||
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.*
|
||||
104
docs/RETRO_V7.md
Normal file
@ -0,0 +1,104 @@
|
||||
# PROCITY v7 — THE GAME: the epoch retro
|
||||
|
||||
*Fable (m3, integrator), 2026-07-26, written after the `v7.0` tag (`4709cfe`). Epoch span:
|
||||
R30 → R36. Tags: `v7.0-alpha` → `v7.0`. The first epoch shepherded end-to-end by Fable-driven
|
||||
lane agents — John said "run it" once per round, and the lanes ran.*
|
||||
|
||||
## What shipped, in one breath
|
||||
|
||||
**The town became a game.** A save that carries only the player's deltas (the world regenerates
|
||||
from seed, so a save can cost you your records but never your town) · THE CRATE — the
|
||||
collection with real covers, honest provenance per find · the sell counter where the keeper
|
||||
pays under the band, structurally, forever · sleep, and a tomorrow where the mint crates
|
||||
rotate and the real one never does · THE BIBLE's guide stamps that turn a price sticker into a
|
||||
skill check · the wantlist that hunts across 23 towns, where TRAVEL COSTS A DAY · GodBay,
|
||||
whose hammer falls overnight under the one-hammer law, delivered by the morning paper · and a
|
||||
first five minutes that hands a stranger $191 and a want. Plus, mid-epoch and out of hours:
|
||||
five trellis DJ bodies and four Rokoko dance clips, swept into provenance and gated into law.
|
||||
|
||||
## The epoch's defining pattern: HANDS BEFORE TAGS
|
||||
|
||||
Twice the playtest found what every green gate could not:
|
||||
|
||||
1. **R31**: the dig — the heart of the game — was default-OFF on a plain boot, six epochs
|
||||
after the README claimed otherwise, because **every gate booted its own flags** (harness
|
||||
lesson #4, THE EXPLICIT-FLAG BLINDFOLD). The suite tested the game; nobody tested that the
|
||||
default boot IS the game. The fix came with a naked-boot reachability gate so it can never
|
||||
silently regress.
|
||||
2. **R36**: zero blockers — and that verdict is the epoch's proudest number, because the same
|
||||
hands that found the R31 blocker walked every system (splash → beats → guide → want → list
|
||||
→ hammer → travel) and came home with only a polish queue. The game taught its own tester:
|
||||
*it charged $12 tuition for ignoring the guide's advice* — bought at $19 top-of-band,
|
||||
hammered at $8, $7 after fees. An economy that punishes bad digging is an economy working.
|
||||
|
||||
## The final hold: CLASSIC KEEPS ITS PEOPLE
|
||||
|
||||
The epoch's last warn was its best lesson in miniature. Two 2 a.m. commits (disciplined, but
|
||||
outside the round system) added five DJ bodies to the ped roster unconditionally — every
|
||||
covenant gate stayed green (the covenant pins the plan, not the roster), but F's new
|
||||
`smoke_djdance` machine-recorded that classic's fetch surface and crowd pool had moved without
|
||||
a ruling, and HELD the tag. The arc: held by WARN → ruled by Fable (the covenant means the v2
|
||||
town *including its people*) → fixed by D in the owning file alone (base-17 restored, proven
|
||||
byte-identical against a shadow tree of the pre-commit code — classic's crowd is the same
|
||||
crowd, ped for ped) → re-proven by the very gate that held it. **A covenanted surface cannot
|
||||
move quietly even when every pinned number stays green.**
|
||||
|
||||
## The unscheduled chapter: the fork famine
|
||||
|
||||
Between the hold and the tag, the box itself failed: m3panel — the machine's own status
|
||||
watchdog — had leaked one orphaned `pmset -g thermlog | tail -2` pair per refresh for 2.5
|
||||
days: ~10,600 processes, fork starvation, two QA runs killed mid-suite. The record keeps three
|
||||
things: (1) both poisoned runs are logged honestly in §36.5 — every gate that completed in
|
||||
them matched run 3's verdicts, so nothing was ever wrong but the box; (2) the fix was at
|
||||
source (the already-patched `sh()` finally restarted onto its own fix — **a fix isn't
|
||||
deployed until the process running the old code dies**, the E-roster lesson at OS scale);
|
||||
(3) the fleet gained standing infrastructure: `party.monster.reaper`, a 10-minute janitor
|
||||
that kills only orphaned known-leak patterns past a grace age and alarms at 2,000 processes.
|
||||
The watchman flooded the town; now the watchman has a watchman.
|
||||
|
||||
## What worked — keep doing
|
||||
|
||||
1. **Fable-driven lanes.** Seven rounds, ~20 lane-agent sessions, zero tree collisions beyond
|
||||
one adjudicated 6-line seam — the treaty held for agents exactly as it held for humans,
|
||||
because the contracts did the talking (F honoured C's asks sight-unseen; B stubbed F's
|
||||
exact shape then proved nothing was stubbed).
|
||||
2. **Ownership sentences, unbroken streak**: "the rule was the stale thing" (C) · "the red
|
||||
was mine" (E) · "four of five attempts were F's fault" (F) · "the count is 9, not 7" and
|
||||
"the clips are Rokoko, not Mixamo" (E, correcting the integrator's brief from bytes).
|
||||
3. **The structural no-pump law** — C's `min(p−1, …)` clamp meant no future rebalance could
|
||||
ever create a money pump without violating a contract; the gates then proved the pump C
|
||||
herself found was structurally dead.
|
||||
4. **Kills with measurements** (B's awning ripple, D's instrument LOD) — the backlog carries
|
||||
no zombies.
|
||||
5. **The morning paper** — the come-back-tomorrow hook works. The epoch's best moment of play
|
||||
is reading the hammer's news at dawn.
|
||||
|
||||
## What cost us — fixed or fenced
|
||||
|
||||
1. **The explicit-flag blindfold** (R31) — now law: gates about defaults boot naked.
|
||||
2. **Out-of-round commits** — the 2 a.m. work was good, but it took a wave-1 sweep, a gate,
|
||||
a hold, a ruling, and a fix to regularize what a round would have gotten right the first
|
||||
time. The door stays open for 2 a.m. inspiration; the sweep is now the standing tax.
|
||||
3. **Waiting agents die** — three F pauses on block-buffered suite waits; the pattern is now
|
||||
fixed operationally (background-and-poll, or Fable holds the sentinel).
|
||||
4. **A fix isn't live until the old process dies** — m3panel ran patched code's ghost for
|
||||
days. Applies to LaunchAgents, servers, and long-lived sessions alike.
|
||||
|
||||
## Carried to v7.x (the queue, all filed)
|
||||
|
||||
⚖ discoverability · LIST confirm-click · the paper as an artifact · keeper-rumor hand-verify ·
|
||||
crate-at-the-counter polish · the greedy arbitrage-bot verifier (MUST build before G's live
|
||||
tier) · E's owned-item stability check (MUST land before any atlas re-emit) · home base (the
|
||||
pocket-park bench awaits the civic layer).
|
||||
|
||||
## The board after seven epochs
|
||||
|
||||
v1 the system · v2 the content · v3 the district · v4 the real map · v5 the real shop · v6
|
||||
the living street (spikes still gated on John's footage — the MOCAPGOD queue remains empty) ·
|
||||
**v7 the game**. The frontier shelf: terrain · the vernacular kit · the civic layer · THE
|
||||
HORIZON matte + Voronoi outskirts · the sky dome. The next epoch is John's pick, as ever —
|
||||
and for the first time since round one, the honest answer to "what is PROCITY?" is: **a game
|
||||
you can hand to a stranger.**
|
||||
|
||||
*— Fable. Thirty-six rounds, twenty-two tags, six correct holds, one amended covenant, one
|
||||
fork famine survived. Sunburnt is still in the crate.*
|
||||
230
docs/REVIEW_CITY_PATTERNS.md
Normal file
@ -0,0 +1,230 @@
|
||||
# PROCITY — CAN WE ADD MORE PROCEDURAL CITY PATTERNS?
|
||||
### A decision-grade review, post-v7.0. Written for Fable, addressed to John.
|
||||
|
||||
---
|
||||
|
||||
## 1. THE STATE OF THE TOWN
|
||||
|
||||
Seven epochs in, the machinery is genuinely good and the street is genuinely empty. Both halves of that sentence are load-bearing.
|
||||
|
||||
**What is strong.** The plan/runtime split holds: Lane A is 2,546 lines of pure data with no THREE import, no network, no clock, JSON-round-trippable, under 100ms per plan, running 156,352 assertions in 20.6s all green. The determinism law is not aspirational — `seedFor(citySeed, kind, id)` hashes a *semantic string*, so a new `kind` label is a mathematically independent stream **by construction**. You can add five hundred draws to a new layer and every existing tree, shop and band name is bit-identical. That single property is what makes this entire review possible. The classic covenant is machine-enforced (fingerprint `0x5f76e76`, pinned independently in two files). The zero-asset law is tested by *counting network requests*, not by asking the code. There is exactly one money seam in the whole game. The asset factory has cost $0.64 in its entire history.
|
||||
|
||||
**What is genuinely thin.** The street has objects and no affordances. That is the whole gap, wearing ten different hats:
|
||||
|
||||
- **The food cart is unmanned.** 44 cart sites on the golden seed, 511 placed across the 21 real towns. `furniture.js:252-255` holds the full shop record — id, type, lot, rotation — at the moment it pushes the cart's matrix, and throws all of it away. No vendor, no price, no hours, no transaction, no smell of onions. It is 15× more common than the record shop across the real corpus.
|
||||
- **The bus stops have no bus.** `busShelterStops(plan)` was exported in R6 with a comment saying it exists "so the future vehicle loop can enumerate stops without touching furniture internals." The vehicle that arrived was a tram running the high street. Three consumers read it — the tram, the light pools, the debug overlay — all as coordinates. Nobody ever waits at one.
|
||||
- **There are no animals.** Grep-verified across all of `web/js`: magpie, kelpie, kookaburra, wombat and galah exist *only* as tokens in the shop-name generator. The town is named after wildlife it does not contain. No cars either, apart from the tram.
|
||||
- **The corridor law is published and contradicted.** `roadWidth`/`vergeBand`/`poleOffset` are exported from `registry.js`, obeyed by exactly one consumer, and disagreed with by every other lane by about nine metres. Measured on the golden seed: all 254 bench points lie outside A's verge band and 138 of them land inside a lot footprint; 792 of 792 sampled ped-lane points lie outside it and 521 land inside a lot. `ground.js:55` paints the entire 28m corridor as bitumen.
|
||||
|
||||
**THE ONE STRUCTURAL OBSERVATION.** Lane A publishes contracts the runtime lanes do not consume, and each runtime lane carries a private duplicate. There are four implementations of `isOpen`, two incompatible `chunkIndex` functions (A buckets a lot into every chunk its AABB touches; B buckets by centre only), two shop registries (B's is missing all three venue types, which is why every gig venue in every town has rendered op-shop sign colours since R13), and a chunk lifecycle hook (`ctx.onChunkBuilt`) that is *built on both sides and wired on neither*.
|
||||
|
||||
The visible cost of that, today, on the boot every player gets: **all 14 gig posters are standing in the road.** A's placer obeys `poleOffset` (6m). B's ground paints bitumen to 14m. A's own selfcheck validates clearance against `roadWidth/2` = 5m, so the gate is green while the street is wrong.
|
||||
|
||||
Every street-side content idea in this review — a bus stop with a real platform, a vendor pitched on a verge, a dog on a nature strip, a parked car at a kerb — collides with this before it collides with anything else. **The highest-leverage change available is not new content. It is deciding where the kerb is.** It costs about twenty lines and zero draws.
|
||||
|
||||
**Two corrections to house folklore, both measured.** First: the draw budget. The "~131 draws / ~185 worst street view, roughly 100+ draws of headroom" figure is a *teleport-to-bookmark* number and it does not survive a walk. Walked in 8m steps down the main spine, the default boot peaks at **280 draws of 300** *(R37 re-measured and PINNED it at **292 draws / 124,793 tris**; the 280-vs-191 split was camera bearing, not method — see LANE_F_NOTES §37)*. The real margin is about twenty. Triangles, however, are wide open — 99k–124k of 200k, with the two independent measurement sessions disagreeing enough that the tri baseline needs a re-pin. **Spend triangles, not draws.** A town-wide single `InstancedMesh` costs 1 draw regardless of population (venue.js runs 381 light pools that way, verified); a new per-chunk instanced type costs +1 draw × up to 31 live chunks.
|
||||
|
||||
Second, and larger: **all 21 real towns are 100% `use:'shop'` lots with exactly one district, `mainstreet`.** No house lots, no yard lots, no stall lots, no market block, no residential backstreets. The town the design language describes — the market square, the backstreets, the yards — exists on precisely one town: the synthetic one, which is also the classic one. This kills or halves a third of what was proposed, and it is the most important strategic finding in the packet.
|
||||
|
||||
---
|
||||
|
||||
## 2. THE ASSET PIPELINE, HONESTLY
|
||||
|
||||
Everything below was measured on this box today, not read from a doc.
|
||||
|
||||
| Class | Path | Cost | Licence | Blocker |
|
||||
|---|---|---|---|---|
|
||||
| **Audio** | `gen_audio.py` — 781 lines of pure numpy | seconds, $0 | 🟢 original | **None. Best class we have.** |
|
||||
| **Skins / signage** | `flux_local` on-device | 9.3s each, $0 | 🟢 | None |
|
||||
| **Static prop** | flux → `bg_remove_local` → `trellis2_mlx` → `normalize.py` | ~2.5–3 min, $0 | 🟢 CC0-equiv | None. ~20–25 props/unattended hour. |
|
||||
| **Vehicle (static)** | same | ~3 min, $0 | 🟢 | Greenfield; wheels won't turn |
|
||||
| **Rigged biped** | `trellis2_mlx` → MIRPAMO autorig → retarget | ~5 min | 🟢 | Feed GLB/BVH, never FBX |
|
||||
| **Rigged quadruped** | — | — | — | **HARD WALL** |
|
||||
| **Rigged bird** | — | — | — | **HARD WALL, worse** |
|
||||
|
||||
**The pipeline is one generation behind the farm.** `pipeline/gen_props.py` hardcodes `trellis_mac` (p50 245.4s, 33 jobs in the last 7 days). The farm's actual workhorse is **`trellis2_mlx`** — 384 jobs in 7 days, p50 **134.6s**, better PBR bake. That is a ~2× throughput gain for a one-line change and it should land with the wave. `blender_convert` has 3 lifetime jobs with 1 failure and has been untouched for three weeks — keep it off any critical path. Budget 4–5% retry on mesh gen; failures are opaque (`exit 2`, one OOM-kill).
|
||||
|
||||
**Two brief premises are wrong.** Cloudflare Workers AI is live and fast (2.54s mean at 1024²) but the "~10,000 free generations/day" figure is **10,000 *neurons* per day** — a compute unit, not images. The real ceiling is plausibly hundreds. Plan CF as a fast-path accelerator, not a budget. Its output is also **JPEG with no alpha**, so `bg_remove_local` is mandatory before any mesh step, and its framing is unreliable without procity's own tuned prompt template. Separately: **MOCAPGOD does not exist on this box** — `~/Documents/MOCAPGOD` is 0 bytes, one empty `queue/` dir, not a git repo. The working substitute is MODELBEAST `motion_local` (MoMask, text→BVH, ~11.7s, 4/4 success) — human-only.
|
||||
|
||||
**THE ANIMAL BLOCKER, stated once and clearly.** MIRPAMO cannot rig an animal and this is architectural, not tuning. Its landmark detector assumes "upright character, +Z up, roughly symmetric about X=0, T- or A-pose" and hardcodes anchors as fractions of a biped's height (groin 0.52, chin 0.87, knees 0.285). `build_skeleton()` emits a fixed 14-call humanoid chain. A repo-wide grep for `quadruped|animal|dog|horse|bird|tail` returns zero hits. The only bonemap shipped is human CMU mocap. MoMask is HumanML3D — human. The clip bank is a single mixamorig namespace.
|
||||
|
||||
Worse: **a non-humanoid does not fail, it silently does nothing.** Lane D pushed the bone-free food-cart GLB through the real rig machinery. `buildFigure` did not throw; the bbox fallback scaled it to 1.75m and planted it; `makeActor` returned `hasClips: true` while **0 of the walk clip's 64 tracks bound**. A dog on the walk graph would glide down the street frozen in bind pose and no gate in the repo would notice. The impostor bake compounds it — it frames a square cell at 1.14× subject height, so any quadruped is clipped in the mid tier.
|
||||
|
||||
**So: animals are static props, scripted transforms, or vertex-shader fakes. Forever, until a human hand-rigs a quadruped in Blender.** Every one of the six animal proposals reached this conclusion independently, and that convergence is the single strongest signal in the review.
|
||||
|
||||
**REUSE BEFORE YOU GENERATE.** Nine published, budget-legal, provenance-clean assets are sitting idle in the manifest with zero runtime consumers:
|
||||
|
||||
- **`novelty_record`** — 8,000 tris. Lane E decimated it from 26,486 in R5. `furniture.js:275` already calls `glb('novelty_record', …)` but `GLB_FILES` has no key, so the call can never fire. A stale comment is the only thing keeping a finished hero landmark prop out of the game.
|
||||
- **`streetlight`** — 4,999 tris, no consumer; run `decimate_props.py` (the R22 tool that took the bin 3,000→800) and wire it.
|
||||
- **Four bench variants** — `bench_modern` 640, `bench_wood` 3,999, `longbench` 1,172, `park_bench` 5,000. Every bench in all 23 towns is currently the same mesh. `longbench` at 5.86m is explicitly the arcade/market feature bench the audit already describes.
|
||||
- **`coffee_table`** 495, **`box_crate`** 78, **`counter`** (the 4m balcao, 276 tris — which is exactly the `bar_counter` fitting that is still primitive-only in all three venue types).
|
||||
|
||||
That is nine assets, one line each, zero generation, zero licence work. **Do this before generating anything.**
|
||||
|
||||
**DO NOT reuse:** the 3GOD depot as a pool (971 live assets, mixed provenance, known trademarked items inside; `_published.json` records 53 with no drift, but `AUDIT.md` records 138 — a ~7× stale cross-reference). And `shop-cat.glb` is *not* a reuse — it is an intake: 8,000 tris, zero entries matching "cat" in `_published.json`, needing a decimate, a publish record, an AUDIT row and a licence ruling. A fresh local gen is cleaner and costs three minutes. **The hardyards set** (39 GLBs — gum, jacaranda, fence panel, gate, shed, pergola, washing line, water tank) is exactly the vernacular kit's shopping list and its `MODELS.md` documents provenance for one file. Do not ship it; do clear it, separately.
|
||||
|
||||
---
|
||||
|
||||
## 3. THE RECOMMENDED SLATE
|
||||
|
||||
Twenty-seven proposals went to adversarial review. **One survived unqualified.** Where the reviewer corrected the designer, the corrected version is what appears below and I say so.
|
||||
|
||||
### WAVE 0 — THE FLOOR
|
||||
*All micro. All must precede content. Roughly one round across three lanes.*
|
||||
|
||||
**0.1 — THE KERB RULING, and the widened footpath.** *(Lane A ruling + Lane B code + Lane F gate)*
|
||||
Adopt A's published corridor law. Do **not** paint grass on high streets — `registry.js:220`'s own comment calls that 9m band "verandah'd footpath", and `buildings.js:458-472` already builds an awning over it on every shop lot. Instead extend the **existing** `footGeos` merged class inward from `e.width/2` to `roadWidth(e)/2`. **+0 draws** (same merged mesh), ~2k tris, fill-rate improves because 18m of every main street stops being road texture, and the 14 gig posters land where A already thinks they are. Must special-case `arcade` (`roadWidth` 0 → zero-area road quad and coincident kerbs) and `lane` (`vergeBand` = [2,2]).
|
||||
**Gate:** the falsifiability control the reviewer called the best design in the packet — run the poster-clearance leg against `?verge=0` and assert it **FAILS there**, because the pre-change failure is already known and counted at exactly 14.
|
||||
*Grass verges survive, but only on residential edges, which is where they are period-correct and where 1.4 wants them anyway.*
|
||||
|
||||
**0.2 — REPAIR `smoke_tram`.** *(Lane F, micro)*
|
||||
`flags_check.py:617-630` asserts exactly one thing — `!!getObjectByName('tram')` — prints a SKIP and returns when absent, and **passes on the fenced path** because `tram.js:110-114` adds a named empty group. Five of seven transport proposals leaned on this as the "cheap proof" for a refactor. It is a live violation of the house's own vacuous-gate law inside the house's own suite. Fix it regardless of what ships.
|
||||
|
||||
**0.3 — RE-PIN THE BUDGET OF RECORD.** *(Lane F, micro)*
|
||||
Adopt the **walked** worst frame as the number the law measures (clean-load bookmarks under-report by 60–80 draws — a walk carries the R+1 dispose window at up to 31 live chunks vs 22). Re-measure once on a real GPU; the headless numbers are SwiftShader. Settle the shared standing-layer instance cap **once** — three animal proposals independently specified 120, 250 and "plus gulls" for the same layer.
|
||||
|
||||
**0.4 — WIRE `ctx.onChunkBuilt` / `onChunkDisposed`.** *(Lane B, one line)*
|
||||
Producer at `chunks.js:43-51`, consumer at `sim.js:203-205`, both shipped, never connected. Every per-chunk entity system in the slate wants it.
|
||||
|
||||
---
|
||||
|
||||
### WAVE 1 — INHABITATION
|
||||
*Four items. Every one is a single round. None needs a street verb, a rig, a new clip, or John's mocap footage. Three of four are +0 or +1 draws. They touch no shared files, so all four can run in parallel.*
|
||||
|
||||
**1.1 — SOMEONE ELSE'S LIFE: the audio inhabitation layer.** *(E + B + F)*
|
||||
**The only unqualified SHIP in twenty-seven proposals.** The manifest ships 7 ambience / 9 sfx / 7 music and not one animal, domestic or seasonal sound. A dog going off behind a paling fence, a mower two streets over, a radio through a window, sprinklers, a roller door — the sound of a life happening off-screen.
|
||||
*Mechanism:* seeded point-source audio anchors, each a ~30-line numpy generator in `gen_audio.py`'s PACK registry, deterministic via `crc32(name)`.
|
||||
*Corrected:* it is **six new mixer layers**, not "generalising `audio.js:300-311`" — `audio.js` has hardcoded bed layers per source class, not a generic point-source system. And there is **no panning**: `playSfx` is mono with a gain multiplier, so distance works and direction does not. Say so or budget a `StereoPannerNode`. Derive day-of-week locally rather than depending on the calendar item.
|
||||
**Budget: zero draws, zero tris — the only item in the slate that cannot touch the budget under any circumstance.** Audio pack 10MB → ~11MB against a 25MB cap.
|
||||
**Gate:** keep the falsifiability control verbatim — point one table entry at a non-existent key and assert the leg FAILS. It is the correct descendant of the R23 lesson and every other gate in the slate should copy its shape.
|
||||
|
||||
**1.2 — TWENTY-THREE TOWNS: the per-town character vector.** *(B + E + F)*
|
||||
Twenty-three real towns currently differ only in street layout and shop names. Give each a ground palette, an ambience mix and a sky bias.
|
||||
*Mechanism:* `ground.js`'s `add()` merges each surface class into ONE mesh for the whole town regardless of size — verified, exactly 5 meshes. **A palette swap is +0 draws / +0 tris.** The strongest cost claim in the entire packet. `gravel`(verge) and `reddust`(outback) already ship in the manifest and are selected by **nothing**.
|
||||
*Corrected:* the `state` field is **not** populated on all 23 — `newtown_godverse` and `redhill_godverse` carry `state: ''`, so the vacuous arm must be **per-town**, not per-corpus, or those two silently take a default branch with no SKIP printed. The palette is 10 skins with one outback and one gravel, so "Darwin is red dust, Hobart is cold light" needs 3–4 new `gen_skins.py` generations — name them in the charter. And `skyBias` must **not** go through `lighting.setSky`: `externalSky` (`lighting.js:53`) is a one-way latch with no un-set path. Feed it into the base `skyName` selection instead.
|
||||
*`townCharacter(null)` returning the frozen literals makes classic byte-identical by construction rather than by flag — the right mechanism.*
|
||||
**Budget: +0 draws / +0 tris.**
|
||||
|
||||
**1.3 — THE MAGPIE.** *(B + E + F)*
|
||||
Swooping season. For six weeks of the game's year, one bird on one street will not let you walk past it. Real behaviour: a ~6-week window Aug–Oct, north to south, and ~90% of males never swoop — so **one bird, not a plague**.
|
||||
*Mechanism:* a static mesh on a scripted parabola. No AI, no flocking, no pathfinding — the tram's verified 2-mesh/2-draw pattern at a fraction of the cost. Wing wave through `wind.js`'s amplitude-uniform shape, which is byte-identical at amp 0.
|
||||
*Corrected:* **cut the ice-cream-container hat.** There is no street verb — `index.html:493-499` binds only `][ P M T`, and the only street affordance is a click that *enters the shop*. "Buy the hat at a milkbar door" either enters the milk bar or does nothing, and the gate would have to call the wallet directly, which is the R31 dig lesson recurring. Also **scale the territory with the road graph**: one bird among bendigo_real's 13,730 tree anchors across 2,593 edges is statistically unencounterable.
|
||||
**Budget: +1 draw / ≤900 tris.**
|
||||
**Gate:** keep "the day-roll clears the latch and localStorage gained no new key" verbatim — the best single arm in the packet and a genuine delta-law falsifier.
|
||||
|
||||
**1.4 — BACKYARD AUSTRALIA: the residential district exists.** *(B + E + F)*
|
||||
Hills Hoists, paling fences, TV aerials, water tanks. 181 house and yard lots on the default boot with nothing in them.
|
||||
*Corrected, and cheaper than proposed:* build it **inside `buildings.js`**, riding the existing `boxMats` InstancedMesh (`buildings.js:311` already carries shells, parapets and verandah posts, and `buildHouse` already pushes into it). Hoist, fence run, aerial, tank are all boxes → **+0 draws**. Only the alpha washing quads need a new mesh. **+1 draw/chunk total, not +2.** A separate `world/yards.js` cannot reach `boxMats` and would have to pay the draw the proposal then presented as its honest cost.
|
||||
*The washing needs an inverted wind weight.* `wind.js:38-45` computes `wH = clamp(y/4.4,0,1); wH *= wH` — top-weighted, base planted. Correct for a gum tree, exactly wrong for a sheet pegged at the top. At 1.6m the amplitude is 0.7–4.5cm, i.e. visually inert. Either drop the washing this round or write an inverted-weight sway mode.
|
||||
*Its own named risk is now resolved by measurement:* the residential district walks at **157 draws / 31,651 tris worst** (edge 16, 20 live chunks) against a main-spine control of 263 / 123,602 on the same build. ~143 spare draws where these props land.
|
||||
**REJECT the hardyards GLBs on provenance grounds** — this was the best licence judgement in the packet and it should be quoted into the charter.
|
||||
*Scope honestly:* this is a **synthetic-town feature**. That is still the default boot, but it is one town.
|
||||
|
||||
---
|
||||
|
||||
### WAVE 2 — THE STREET GETS A VERB
|
||||
*Sequential. Wave 2.0 is the substrate that four separate proposals each tried to invent privately.*
|
||||
|
||||
**2.0 — THE STREET INTERACTION SURFACE.** *(B + F, half a round to a round)*
|
||||
A `KeyE` verb on the street; an `interactRects` registry with a `kind` tag resolved off instanced meshes by `instanceId`; explicit hit priority (door beats cart); a click fence. Two listeners currently ride the same canvas click (`hud.js:376` and `index.html:510`'s pointer re-lock), so any cash-spending click must be gated on `player.isLocked`. Also the street-mode dig frame branch, which the shell does not have.
|
||||
|
||||
**2.1 — THE MANNED CART.** *(B + D + F, one round after 2.0)*
|
||||
Consumes the theme's flagship dead seam. `KeeperManager` is genuinely not interior-coupled and costs a **measured** 1 draw and 2,391 tris per stationary rig, 0.02ms of mixer, disposal residue-free.
|
||||
**The blocker, which is not cosmetic:** the proposed "+0 draw" `doorRect` ride would make **every milk bar and stall un-enterable from the front**. `hud.js:317-327` takes `hits[0]` — the nearest intersection — then resolves by distance to the *hit point*; the cart sits 1.55m proud of the door on its own centreline. That is 44 shops on the golden seed and 589 across the caches. The fix is to raycast the cart's own `InstancedMesh` (already emitted at `furniture.js:276`) and carry the shop record that `furniture.js:252-255` already has in scope and discards. Gate it by standing in front of a **milk bar** and asserting the resolve names the milk bar.
|
||||
**Cap at N=3 nearest, asserted while walking a dense block, not at spawn** — `keepers.js:67-90` ticks every mixer every frame with no LOD.
|
||||
**Budget: +3 draws / +7.2k tris at the cap.**
|
||||
|
||||
**2.2 — THE FAUNA SUBSTRATE + the field guide.** *(B + F + E, multi-round)*
|
||||
One shared town-wide standing layer at **+1 draw / ~240 tris**, one settled cap, one save-backed sighting ledger. The ibis on a bin is the proof species; the perched cockatoos, the gulls, the tied dog, the possum, the moths and the two flies all ride the same layer once it exists. **Build it once, properly.**
|
||||
*Corrected:* add `sightings` to **`adopt()`** (`save.js:166`) in the same commit as `payload()` — adopt is the travel path, and a reload-based gate would not catch the bug. Cap and dedupe (16 species × 23 towns = 368 natural ceiling). Measure the selection path on **bendigo_real** (23,744 roost anchors), not katoomba. It is three lanes, not one.
|
||||
|
||||
**2.3 — DOGS, TIER B FIRST.** *(B + E + F)*
|
||||
The kelpie tied outside the shop, as one global InstancedMesh at **+1 draw**. Tier B anchors off shopfront frontage, which exists on all 1,117 real-town shop lots. **Tier A (the fence bark) must be re-anchored** — it keys off `use === 'house'|'yard'`, which exists on zero real towns, and its gate only ever visits the one town where the subject exists. Fold the bark into 1.1 (the audio layer) where it belongs.
|
||||
|
||||
---
|
||||
|
||||
## 4. THE CUT LIST
|
||||
|
||||
The house kills ideas with measurements. These are the measurements.
|
||||
|
||||
| Cut | The measured reason |
|
||||
|---|---|
|
||||
| **The town-loop bus** | Its own precondition-as-assertion goes red. It asserted the tram is fenced on newtown_real; measured, newtown is **not** fenced and the tram runs 16 stops over 1,924m. Fitzroy has 139 shops (not 160), is not fenced, and runs 28 stops over 4,101m. The mutual-exclusion rule builds the bus on 7 of 23 towns and those are the *sparse* ones (darwin 12 shops, braddon 19, bowral 30). The shelter-rich towns are overwhelmingly unfenced: northbridge 272, adelaide 209, newtown 149. **What survives is one file** — `tram.js:93-97` files the real unbuilt thing: real high streets come out of OSM as `side`. Extend `spinePolyline`'s edge set to main+side and let the *tram* run on those 7 towns. +0 draws, +0 tris, one lane, one round, same dead seam retired. |
|
||||
| **The ute that sells** | Placement set empty on all 21 real towns. And 36 of its 83 synthetic candidates — **all 36 yard lots** — have no shop on their `frontEdge`, so its own save-poisoning fix fails on 43% of its set. A borrowed `shopId` is also a user-visible lie: `hud.js:159-163` renders the find as bought at the milk bar next door. |
|
||||
| **The charity bin** | REJECT. On the default boot the only collectibles are parody records, every parody sleeve carries a band, `BANDS.record`'s lowest low is 2, and `sellOffer(2) = 1` — never 0. **The filter accepts nothing.** The synthetic town has 77 record shops so the "no counter in this town" arm never fires either. Its accept-arm has to inject an item no shipped code path can produce. Vacuous by the house's own definition. Also four lanes plus an asset round, not "micro". |
|
||||
| **The dusk flush** | The day is **240 seconds long** (`lighting.js:83`, 6 segments). "The six o'clock racket" is a fifteen-times-an-hour racket. And its own mitigation is unimplementable — the `procity:segment` event carries `{seg, hour, night}` and is dispatched identically from `update()`, `setSegment()` and `stepSegment()`, so a real transition cannot be told from a bracket-key step or a screenshot bookmark. **Keep the perched birds; a proximity startle is the better trigger anyway.** |
|
||||
| **The magpie hat** | No street verb exists. |
|
||||
| **The level crossing as an OSM feature** | Needs a 23-town cache rebuild — `build_towns.py` rewrites the *whole* cache, and its own docstring records the Newcastle incident where shops were refetched and old roads silently reused. 46 pinned goldens ride on byte-identity nobody has proven. Four strictly-ordered lanes, and the gate's only realistic state is SKIP until E ships. **Shelve the real-rail version. The rescoped version — seeded synthetic crossings, procedural geometry, `gen_audio` bells and horn and rumble — is one round and still shuts the map for twenty seconds.** |
|
||||
| **The mail run** | Its headline mechanic cannot exist without a fourth save-schema widening, which the proposal claims it does not need — `resolveAuctions` resolves strictly `listDay < day`. The conservative fallback is one prop and one string. Weakest 1990s-Australian specificity in the set: a period-correct object with a generic mechanic. *Keep the one-hammer analysis as a gate arm for anything that touches GodBay.* |
|
||||
| **Parked cars, this epoch** | Deferred, not rejected. Blocked behind the kerb ruling (a car at `roadWidth/2` sits 9m inside painted road), a chunk-bucketed dynamic collider registry that does not exist, and a `BASE_DRAWS=164` re-pin. **+48,000 tris always submitted** — a town-wide instanced layer computes an instance-aware bounding sphere and is effectively never frustum-culled. Affordable but the likeliest item to eat the margin. When it lands, **make the 45° angle parking a charter requirement** — it is doing all the Australian work. |
|
||||
| **Market day / boot sale / garage sale as pan-corpus features** | All three are synthetic-town-only (100% shop lots, one district). Market day additionally needs a geometry path that does not exist — `ground.js`'s `rectGeo` is axis-aligned only — and would pave 27,600m² where the stalls occupy ~44×77m. |
|
||||
| **Anything with a rigged animal** | Capability wall, not a design failure. See §2. |
|
||||
| **The research doc's trap list** | Endorsed wholesale: full traffic sim, civic interiors, per-NPC schedules, boids, real-date seasons, terrain sneaking in sideways, 23 bespoke Big Things, voice lines, brand fidelity, multiplayer, needs models. |
|
||||
|
||||
**And the plain answer to the three you named.** *Street vendors* is the strongest — but only the manned cart, and only after the door raycast is fixed; the rest of the theme is synthetic-only or rejected. *Stray animals* is viable but permanently capped at static and scripted — and the magpie is nonetheless the best charm-per-byte item in the entire packet. *Public transport is the weakest of the three*, and its flagship proposal refuted itself with its own gate. **All three were beaten by things you did not name**: the audio inhabitation layer (zero draws, the only clean SHIP), the per-town character vector (+0 draws, +0 tris), and the kerb ruling (twenty lines, and it is what makes every other street idea geometrically coherent).
|
||||
|
||||
---
|
||||
|
||||
## 5. THE ASSET WAVE
|
||||
|
||||
### The proof batch — one unattended afternoon, $0, decisive
|
||||
|
||||
Four capability questions are actually open. This answers all four.
|
||||
|
||||
1. **`magpie_static`** — flux_local → bg_remove_local → **trellis2_mlx** → normalize → decimate to ~900 tris. Proves the corrected mesh path end to end on an operator procity has never used, and it is the one asset wave 1 cannot ship without.
|
||||
2. **`hills_hoist`** — same path, target ≤600 tris. Deliberately the hard case: thin structures (wire, arms) are the known TRELLIS failure class. If it fails that is a *decisive* finding, and the fallback is primitives, which `boxMats` wants anyway.
|
||||
3–5. **`magpie-clack`, `dog-bark-fence`, `mower-distant`** — three `gen_audio.py` PACK entries. Minutes, $0, deterministic, parody-safe by construction.
|
||||
6–7. **`ground-grass-dry`, `ground-grass-temperate`** — `gen_skins.py --local` with a new GROUND template, ~5.7s each.
|
||||
|
||||
Plus the one-line pipeline fix: **swap `trellis_mac` → `trellis2_mlx` in `gen_props.py`** (~2× throughput), and correct the CF credential path in the fleet skill (it points at `~/Documents/backnforth/.env`, which does not exist on this box; the working client reads `~/Documents/fluxgod-work/.env`).
|
||||
|
||||
### The full wave, by class and order
|
||||
|
||||
**First — reuse, zero generation:** the nine idle manifest assets from §2. `novelty_record`, `streetlight` (after a `decimate_props.py` pass to ~800), four bench variants, `coffee_table`, `box_crate`, `counter`→`bar_counter`. One line each.
|
||||
|
||||
**Audio (`gen_audio.py`, $0, 🟢 original, ~30 lines each):** magpie clack + carol, cockatoo screech, dog bark (fence + near), mower, sprinkler, radio-through-a-window, hammering, roller door, letter-slot clang, crossing bell, distant train horn, rail rumble, tin-roof rain, cicadas, wind-in-gums. **Plus the six missing footstep surfaces** — grass, gravel, red dust, brick pavers, lino, timber verandah. The manifest ships ten ground skins across six use-classes and only two footstep sets exist; walking on red dust currently sounds like concrete. Lane E's own map calls this the cheapest measurable content gap in the repo.
|
||||
|
||||
**Skins (`flux_local`, ~5.7s, $0, 🟢):** the 3–4 ground palette gaps (dry summer grass, cool-temperate grass, red-dusted bitumen, coastal sand) and a FOR-LEASE / whitewashed-glass texture. *The ~20 missing shop-type facades — servo, church, bank, post office, butcher, bakery, chemist, newsagent, TAB, laundromat, barber, hall, motel, station — are a wave of their own and belong with the vernacular kit, not here.*
|
||||
|
||||
**Meshes (~3 min each, $0, 🟢 CC0-equivalent under `AUDIT.md:101`'s doctrine — flux_local concept on an Apache-2.0 model, TRELLIS reconstruction of that concept = original work):** magpie, cockatoo/galah (one body, three tints), silver gull, tied dog, possum, cat (**generate ours** — a fresh local gen is cleaner than an intake from a mixed-provenance depot with no publish record, and costs three minutes), phone box, paper stand, pillar box, A-frame board, milk-crate stack, wheelie bin, trestle table, cardboard box, transformer drum. Roughly fifteen meshes ≈ one unattended hour at the measured rate. Budget 4–5% retry.
|
||||
|
||||
**Rigged: nothing.** The vendor reuses the 24 shipped bodies and 8 shipped clips. If a "hand over" clip is wanted later: `motion_local` (text→BVH, ~11.7s) → MIRPAMO retarget → `fbx_to_clip`. Human-only by design.
|
||||
|
||||
**Licence discipline.** Every mesh, skin and audio asset above lands 🟢. Each carries a named source zone in `LANE_E_NOTES`, a generation method naming the exact tool and model, an `AUDIT.md` narrative entry with measured tris/footprint/height, and a `_published.json` entry if it touches the depot. hardyards stays 🟠 until cleared. The 971-asset depot stays 🔴 and is not a pool. Re-run the AUDIT depot cross-reference before anyone plans reuse from it.
|
||||
|
||||
---
|
||||
|
||||
## 6. THE SEQUENCING
|
||||
|
||||
**Epoch name: `v8 — SOMEONE LIVES HERE`.**
|
||||
|
||||
(The research doc proposed "THE TOWN HAS A WEEK". That name over-promises now — the calendar and trading-regime item was rescoped into three rounds because the per-type `shut` list cannot be a uniform: shop hours are baked into a **per-vertex** `aHours` attribute at chunk-build time, so a per-type close delta means rewriting that attribute across up to 27 live chunks on every sleep. Its "+0 draws, +0 tris, +0 bytes" claim must not reach a charter. It is a good v9 item; it is not a cheap one.)
|
||||
|
||||
**Against what is already chartered.** v6 LIVING STREET is chartered and gated on your mocap footage. **Nothing in wave 0 or wave 1 needs a clip.** That is deliberate and it is the main argument for this shape: it gives the project a full epoch of parallel work that does not wait on your camera.
|
||||
|
||||
**Against the frontier shelf.** *Terrain* — nothing here needs Y; keep it that way. *The vernacular kit* — wave 1.4 (backyard Australia) is its residential half arriving early; the ~20 missing shop facades are its other half and stay on the shelf. *The civic layer* — this is the real v9. Every real-town gap this review found points at the same place: `plan_osm.js` emits one district and the Overpass fetch asks for only shops and roads. 30,959 footway ways, 12,351 service ways, 6,421 crossings, 2,714 sidewalk tags and 13,199 oneway tags are **already cached in-repo, unused**, and a bounded third fetch class (marketplace, park, pitch, memorial, fuel, school, bus_stop, telephone, post_box) is mechanical under the standing authorisation. That is the fix for "real towns are a shopfront strip", and it is a better-motivated epoch than anything in the vendors theme. *Horizon matte / Voronoi outskirts / sky domes* — orthogonal, but 1.2's `skyBias` must be designed knowing sky domes are coming, and must not touch `setSky`'s one-way latch.
|
||||
|
||||
**WHAT HAPPENS FIRST, plainly: the kerb ruling.** Not code — a decision. Until it is made, five separate proposals are designing against contradictory geometry and fourteen gig posters are standing in the road on the boot every player gets. Make the ruling, land the footpath widening with the `?verge=0` falsifiability control, repair `smoke_tram`, re-pin the budget — then wave 1's four items run in parallel across four lanes with no shared files.
|
||||
|
||||
---
|
||||
|
||||
## 7. WHAT NEEDS JOHN
|
||||
|
||||
### Decisions
|
||||
|
||||
1. **The kerb ruling.** Footpath or road, for the 9m band between kerb and shopfront. **Recommend: footpath** — adopt A's published law, widen the existing merged footpath class inward, +0 draws, and the posters land where A already thinks they are. Grass verges on residential edges only.
|
||||
2. **Is synthetic-only acceptable?** Backyard Australia, market day, garage sales and the whole residential/market vocabulary live on **one** town, because all 21 real towns are 100% shop lots with a single district. Either accept "the default boot is the richest town", or fund the civic-layer fetch that gives real towns their other lot uses. **This is the biggest strategic question in the review and it is not a lane's to answer.**
|
||||
3. **The animal answer.** No rigged quadruped exists or can be automated on this fleet. Accept static + scripted-transform animals for v8, or commission a hand-rigged quadruped in Blender as a one-off human job. **Recommend the former now, defer the latter.**
|
||||
4. **The market plaza.** `ground.js` paints a 46×46m plaza at the origin — on top of the spine road, touching none of the 40 stall lots at x −77..−33, z −100..−23. Fixing it changes visual output on the covenanted town. **Recommend: fix it under the amendment law** (the R27 precedent — the covenant is anti-drift machinery, not a defect preservative), as its own small ruling, not folded into a content round.
|
||||
5. **Epoch name and shape** — `v8 — SOMEONE LIVES HERE`, wave 0 then four parallel wave-1 items, wave 2 behind the street verb.
|
||||
6. **The budget of record and the shared-layer cap** — adopt the walked worst frame; settle one instance cap for the fauna layer before three implementations invent three.
|
||||
7. **hardyards provenance.** 39 GLBs that are exactly the vernacular kit's shopping list, with provenance documented for one file. Only you know where they came from. Worth an hour of your memory.
|
||||
|
||||
### Credentials and authorisations
|
||||
|
||||
- **Nothing in this slate is blocked on a credential.** MODELBEAST is up and token-authed from disk; Cloudflare works; the depot accepts passwordless tailnet uploads. Worth stating plainly, because the brief assumed otherwise.
|
||||
- **Mixamo fetch needs your logged-in Brave session** for any *uncached* clip. Not needed for this slate; needed if anyone wants a new human clip that MoMask cannot produce.
|
||||
- **Standing authorisation to re-run the Overpass fetch** — 23 towns, one bounded query each, 4s courtesy, ODbL attribution into every cache and into `SOURCES.md`. Not needed for v8 as scoped. **Needed for v9.**
|
||||
- **Where is MOCAPGOD?** The brief says lanes 0/A/B are done. On m3ultra the directory is 0 bytes and not a git repo. Either it lives on JING5 or the Air, or it was never started. Not blocking — `motion_local` covers the need — but the fleet docs are wrong somewhere and only you can say where.
|
||||
- **A ruling on the depot.** 971 live assets, 53 in `_published.json`, `AUDIT.md` recording 138, and known trademarked items inside. **Recommend: treat it as a read-only archive, generate fresh for anything new, schedule an `AUDIT.md` re-run.**
|
||||
- Optional but cheap: **keep `?verge=0` permanently as a gate flag.** It is the best gate design in the packet and it costs nothing.
|
||||
191
docs/SALE_RED_TICKET_DAY.md
Normal file
@ -0,0 +1,191 @@
|
||||
> **FABLE'S NOTE (2026-07-26).** John's pitch; designed by a 3-framing panel (time-attack /
|
||||
> market-day / daily-challenge), each adversarially judged, then synthesised. **I verified the
|
||||
> load-bearing finding myself before circulating this:** `kind:'bin'` is minted at exactly
|
||||
> `fittings.js:60` and `:75` (`recordBin`, `crate`), and those appear in exactly two recipes —
|
||||
> `record` (`theme.js:38-39`) and market `stall` (`theme.js:196`). **Confirmed: only record
|
||||
> shops and market stalls have a diggable crate.** The op shop's recipe (`theme.js`) is clothes
|
||||
> racks, metal shelves, a bookshelf, a trestle table and `crateStack` — which its own comment
|
||||
> labels *"back-room dressing"*, i.e. scenery, not a bin.
|
||||
>
|
||||
> **That is a finding about the MAIN GAME, not just this mode**, and it is filed to the v8 debt
|
||||
> ledger: PROCITY is built on thriftgod's op-shop DNA and **you cannot dig in an op shop.**
|
||||
> Whether that is a gap to close (give the op shop a bric-a-brac bin) or a fact to design around
|
||||
> (records are the deep dig; op shops are pull-and-buy) is a John-level taste call, and RED
|
||||
> TICKET DAY does not depend on the answer.
|
||||
>
|
||||
> **AMENDMENT (R39, Lane C, measured):** this design's roster counts "crate-shops" from the
|
||||
> RECIPES — but **44% of market-stall rooms contain no crate at all**. The stall's `crate` is
|
||||
> `zone:'centre'` and its 3–6 trestle tables take the aisle first, so the roster can name a stall
|
||||
> you cannot dig in. Also: **a stall dig stamps the pull `type:'record'` while the stall itself is
|
||||
> `type:'stall'`**, and `sellableIn` is type-equals-type — so **a stall find has nowhere to be
|
||||
> sold**. Both are pre-existing, neither is this design's fault, and both must be resolved before
|
||||
> RED TICKET DAY ships a roster. Lane C holds a one-line `fallbackZones` fix that would give
|
||||
> stalls the crates their own recipe already promises.
|
||||
>
|
||||
> Superseded in part by John's later reframe — *"the city being procedural and nebulous… you have
|
||||
> to even know where to find the store"* — which is running as its own design pass. Read that
|
||||
> charter alongside this one: this document owns the ROUND and the LAWS (the Band Floor Law is
|
||||
> the best idea in it); the reframe owns the HUNT.
|
||||
|
||||
# RED TICKET DAY
|
||||
### synthesis of three designs and three adversarial passes — decision-grade, for John
|
||||
|
||||
---
|
||||
|
||||
## 1. WHATAYARECKON
|
||||
|
||||
Yes. Build it. Not because the game needs another system — it needs fewer — but because this is the first thing anyone has proposed that **spends the clock**.
|
||||
|
||||
Read our own frontier doc back: *"hours exist! nothing makes you race them"* and *"the clock is scenery."* That is not a missing feature, it is a missing **cost**. Every verb in PROCITY is currently free: riffle a crate, walk a block, dig all day, nothing is consumed except cash. The shops shut and it costs you nothing because there is nothing you were going to do anyway. The sale fixes that from the other end — it makes *today* the only day the $110 grail is $66, and once today has a thing in it that tomorrow doesn't, the hours that already exist start biting without a single new clock.
|
||||
|
||||
And it answers *"what is a session?"* — which has been open since v4. A session is **one town, one trading day, one docket**: wake, read the paper, walk, choose which crates you can afford to open, get shut out of the last shop, sleep. Ten to twenty minutes with a beginning, a middle and an end.
|
||||
|
||||
Two things it is not. It is **not a stopwatch** — all three judges landed on that from different directions and the code agrees (details in §8). And it is **not free money**: the round is a net cash *loss* by construction, and that is the point. You are not trading, you are buying the day you finally got the thing.
|
||||
|
||||
---
|
||||
|
||||
## 2. WHAT IT IS
|
||||
|
||||
**RED TICKET DAY.** One day in seven, seeded per town, the shops that have crates in them mark stock down. The paper tells you which ones. The markdown never crosses the guide band's floor, so a red ticket is never a gem and a gem is never red — the skill you already have gets *harder*, not easier. You cannot buy them all. The shops shut. What you carried out is the score, and it lives in the crate forever with "day 14, Bendigo" under it.
|
||||
|
||||
Structure and scoring come from **TIMEATTACK** (bounded round, forced-flag gates, DOM-only, the Band Floor Law). Diegesis and rhythm come from **MARKETDAY** (a *day*, not a stopwatch; the morning paper; the market's own FIRST IN BEST DRESSED fiction, already painted on the wall at `theme.js:201`). The time model comes from **DAILY**'s one great idea — *time passes on the street, not in the shop* — **corrected**: seconds don't cost you indoors, but **crates do**.
|
||||
|
||||
---
|
||||
|
||||
## 3. THE ROUND
|
||||
|
||||
**Wake.** SLEEP rolls the day (`save.js`, `index.html:281` → `lighting.setSegment(0)`). If today is the town's red-ticket day, the wake toast carries the paper:
|
||||
|
||||
> 🌅 day 14 · **RED TICKET DAY** — Vinyl Solution ⅓ off · The Turnstyle ¼ off · Sadie's half price
|
||||
|
||||
The sale module then sets the segment to the first roster shop's opening, so a sale day always starts with a door about to open.
|
||||
|
||||
**The roster: what the town actually has.** Only two shop types carry crates (measured, §4). The roster is the biggest cluster of ≤3 crate-shops all within 400 m of each other, seeded. Across the 26 shipped town keys at seed 20261990:
|
||||
|
||||
| roster | towns | tour | sprint @ 8.8 m/s |
|
||||
|---|---|---|---|
|
||||
| 3 shops | 5 (fitzroy, adelaide, brunswick, katoomba_real, melbourne) | 74–369 m | 8–42 s |
|
||||
| 2 shops | 7 | 6–390 m | 1–44 s |
|
||||
| 1 shop | 11 | — | — |
|
||||
| **0 — no sale day** | 3 (bowral, darwin, daylesford) | — | — |
|
||||
|
||||
Synthetic towns (`plansrc` default, ~480 shops, 103–151 crate-shops): roster of 3, tour **8–27 m**. So the mode is a route problem in the default town and in five real ones, and a *depth* problem in the other seventeen. Both are rounds. Neither is a lie.
|
||||
|
||||
**Inside.** A record shop holds 3–8 crates × 16 sleeves = **48–128 records**, of which (modelled off the shipped band table at `bible.js:26-30`) **5–13 carry a red ticket**. A ticket is 25 / 35 / 50% off asking, seeded per shop-day, **floored at the band's low edge**. Mean marked price ≈ **$21**; mean saving ≈ **$10**; a grail asking $90 comes down to $60 and no further.
|
||||
|
||||
**The purse.** Day-1 wallet is `60 + (r()*140|0)` ≈ **$130** (`wallet.js:10`). Six red tickets and you're out. There are 12–30 on a three-shop roster. **You take a fifth to a half of what's marked.** That is the whole game of the round: not "grab everything", but "which of these do I want enough."
|
||||
|
||||
**The clock — the one real change.** Time does not pass while you riffle. It passes when you **walk** (the street clock, exactly as shipped) and when you **open a crate**: one crate ≈ ⅓ of a segment. A trading day gives you roughly **nine crate-openings**. Fitzroy's 369 m tour eats three of them. So three shops = two crates each, out of the 3–8 each shop has. You will never see the bottom of a town, and the shutter comes down mid-crate.
|
||||
|
||||
**The end.** The last roster shop shuts (`isOpen`, already enforced at `index.html:493`). The docket totals what you carried out:
|
||||
|
||||
> 🔴 the day's take — 5 red tickets, $84 saved, $103 spent · you left 9 marked in the crates you opened · you never made it to Sadie's
|
||||
|
||||
Then SLEEP. Six ordinary days until the next one, or travel (which costs a day anyway).
|
||||
|
||||
---
|
||||
|
||||
## 4. THE THREE FACTS THAT DECIDED THE DESIGN
|
||||
|
||||
Measured this session against the tree, not asserted:
|
||||
|
||||
**(a) Only two shop types have crates.** `userData.kind:'bin'` is minted at exactly `fittings.js:60` (`recordBin`) and `:75` (`crate`), and those appear in exactly two recipes: `record` (`theme.js:38-39`) and market `stall` (`theme.js:196`). Op shops, pawnbrokers, book barns, toy shops have **no diggable bin at all**. Two of the three submitted designs set their flagship scene in an op shop. That scene cannot happen.
|
||||
|
||||
**(b) The real towns are big and thin.** Generated all 26 town keys: crate-shops per town run **0 to 12**, and they can be **2.3 km apart** (hobart, newtown). Three towns have none. The "three shops, three depths, pick a route" premise is a Fitzroy feature. Hence the 400 m clique rule and the honest "the round is however many shops this town has."
|
||||
|
||||
**(c) The day is six values wide.** `HOUR_NUM = [6.5, 9, 12.5, 15.5, 18.5, 22]` (`lighting.js:22`); record hours are 10–18 (`registry.js:40`), stalls 8–14. So **records are open at 12:30 and 15:30 only; stalls at 9:00 and 12:30 only** — two segments each, 80 seconds of street clock. MIDDAY is the only segment where the whole diggable town trades. That is not a bug to design around, it is the trading day, already shipped: **stalls in the morning, records after lunch, everything at midday.** Use it.
|
||||
|
||||
---
|
||||
|
||||
## 5. MECHANISM, LANE BY LANE
|
||||
|
||||
**Lane C — `dig.js`, ~8 lines, one new optional opt.**
|
||||
- `dig.js:421` — after `offers = …`, before `buildStack(offers)` at `:424`: `if (opts.sale) offers = offers.map(opts.sale);` A post-draw transform. `proceduralSleeves` (`:36-56`) and `pickRealOffers` (`:379-402`) are complete; zero stream drift, zero re-order, no golden moves. **Never in-place on `pack.items`** — `stockpack.js:30-31` caches by (type, base) and an in-place write leaks town-wide and permanent. `{...o}` copy, always.
|
||||
- `dig.js:110-115` — red `fillStyle` on the price when `o.was` is present. Rides the existing per-open `CanvasTexture`. +0 draws.
|
||||
- `dig.js:266-275` — a `🔴 SALE · was $50` chip in the stamps row (present-fields-only, so absent ⇒ byte-identical card). The `guide $25–59` line stays exactly where it is: **that is the discount theatre.** `~~$22~~ $17` inside `guide $8–24` and `~~$50~~ $31` inside `guide $25–59` are the same red ticket and opposite verdicts.
|
||||
- `dig.js:480-487` — widen `currentOffers()` with `was` and `sale`. Without this the gates cannot see their subject (vacuous-gate law).
|
||||
- `bible.js`, `sell.js`: **no change.** Not one line.
|
||||
|
||||
**Lane F — one new module + three small edits.**
|
||||
- `web/js/world/sale.js` (~180 lines): the seeded calendar, the roster, the ticket transform, the crate accumulator, the docket DOM. Constructed only under `flagOn('sale') && game`; never constructed under `?classic=1`, `?sale=0` or `?game=0`, so absence is by non-existence, not by branch (the `washing.js` / `magpie.js` precedent).
|
||||
- `index.html`: one construction line, one `PROCITY.flags` entry, one `?sale=all` force. **No per-frame update** — the module is event-driven off `procity:segment` and `procity:digClose`. Zero frame cost.
|
||||
- `save.js`: `recordFind` carries `was` and `sale:1` on the entry (exactly the `band` precedent at `:294`); `validEntry` (`:52-60`) validates both when present; `listFind` (`:366`) refuses a `sale:1` entry.
|
||||
- `hud.js`: the docket strip gets its **own body-mounted root**, not a child of `#pc-game` — `hud.js:283-285` sets `#pc-game` to `display:none` for the entire dig, and with no countdown to watch that is now fine, but the strip needs its own construction gate and its own DOM assertions.
|
||||
|
||||
**Lane B — one optional four-line API:** `lighting.advance(frac)` so the crate cost can move the day by a third of a segment without `setSegment()` stomping sub-segment position (`lighting.js:113` sets `clock = i + 0.001`). Fallback if B is busy: sale.js keeps its own accumulator and calls `setSegment` only on a boundary crossing. Nothing else from B, A, D or E. **Zero assets, zero fetches, zero draws.**
|
||||
|
||||
---
|
||||
|
||||
## 6. THE LAWS
|
||||
|
||||
**1 — THE BAND FLOOR LAW.** `price = max(bandLow, floor(ask × (1−d)))`, and no band ⇒ no ticket (fail-closed). One `Math.max` buys both fences at once: the keeper's counter offer is `min(lo−1, ⌊lo/2⌋)` off `bandLow` (`sell.js:28-32` + `bible.js:52-58`), so every marked buy costs ≥ `lo` and sells for < `lo` — **the no-pump gate's `all(d <= -1)` holds with zero changes to sell.js**. And nothing under a band floor is ever a sale, so **under-band still means "nobody noticed"** — the gem survives intact.
|
||||
|
||||
**2 — THE PROVENANCE LAW.** A `sale:1` entry can never be consigned. *"Can't put that through GodBay, mate — half the district was at the same sale."* One guard at `save.js:366`, one hidden `⚖` on the crate row.
|
||||
> **This replaces the "drop the band" fence that two of the three designs proposed, and it is not a preference — that fence is broken.** With no band, `godbayHammer` sets `lo = hi = pricePaid` (`save.js:36-37`) — but the 12% bidding war multiplies *afterwards* by 1.5–2.0 (`:43`), so **~12% of sold listings profit** and the design's own gate arm would go red. It also prints your own purchase price back at you in the morning paper, and blanks the guide line on the sell card (`sell.js:104`). A refusal is one line, hard-assertable, and diegetic.
|
||||
|
||||
**3 — THE GEM COVENANT.** The gem is never ticketed. On a sale day the eye is trained on red for ten minutes and the money is still in the plain yellow sticker at $6 in a `guide $25–59` band. Free, and it makes the core skill *harder* on the day the game is loudest.
|
||||
|
||||
**4 — DETERMINISM & THE DELTA LAW.** Four new `kind` labels — `'saleday'`, `'saleroster'`, `'saleticket'`, `'salethin'` — independent streams by construction, so nothing existing shifts. **Tickets are keyed to item identity, never array position** (`${shopId}:${day}:${binKey}:${sku ?? i}`): `dig.js:422` filters procedural offers by `gone`, so a position-keyed ticket would move to a different record the moment you bought one. Save side: **zero new top-level fields.** The score is a pure function of the collection (`Σ was − pricePaid` over today's `sale:1` entries); only `was` and `sale` are added, both optional, both on the entry, both validated exact-and-loud. Old saves load untouched.
|
||||
|
||||
---
|
||||
|
||||
## 7. THE GATES (Lane F)
|
||||
|
||||
1. **Non-vacuity.** `?sale=all` marks every eligible shop at rate 1.0, so `smoke_nopump`'s shop picker (`flags_check.py:2537-2559`) is *guaranteed* on the roster. Re-run the 5 real UI round trips; assert `all(d <= -1)`; **FAIL** — not skip — if fewer than 3 of 5 bought offers carried `was`.
|
||||
2. **Band floor.** Over ≥5 bins: every offer with `was` has `price ≥ bandRange(band)[0]` and `price < was`.
|
||||
3. **Provenance.** Buy marked → `listFind === false`. Positive control: buy unmarked in the same shop → `listFind === true`.
|
||||
4. **Negative control.** `?sale=0` / `?classic=1` / `?game=0` → zero `[id^=pc-sale]` DOM, zero `was` anywhere, and the offer tuples byte-identical to a pre-sale boot. **Mint that offer-price golden this round — it does not exist today** (`flags_check.py` pins plan fingerprints only).
|
||||
5. **Identity-keyed tickets.** Buy one marked slot, re-open the bin, assert the surviving marked set is unchanged (the R27 zero-collateral law, applied to the ticket).
|
||||
6. **Calendar determinism.** Same (seed, town, day) ⇒ identical roster and identical marked set across two boots.
|
||||
7. **(full version)** `PROCITY.sale.debug.crates(n)` — every existing tool pins midday (`dbg.js:325` + 20 call sites), so the crate clock needs an explicit hook or its gate is vacuous by construction.
|
||||
|
||||
---
|
||||
|
||||
## 8. CUT LIST
|
||||
|
||||
| cut | why | verified at |
|
||||
|---|---|---|
|
||||
| **The 120-second stopwatch** | `dt` is clamped to 0.05 s — a 10 fps machine gets a **2× longer round** and `best` is framerate-denominated; `procity:segment` can't tell a real transition from a bracket press; DBG pauses the clock in every gate we own; and the strip would be invisible for the whole dig | `index.html:575`, `lighting.js:109-113`, `dbg.js:325`, `hud.js:283-285` |
|
||||
| **Op shops, pawn, book, toy from the pool** | no bins, at all | `fittings.js:60,75` vs `theme.js:56-63,134-141` |
|
||||
| **"Drop the band" as the anti-farm fence** | 12% of sold listings profit via the war multiplier; prints a legible inversion in the paper; blanks the sell card's guide line | `save.js:36-43`, `sell.js:104` |
|
||||
| **The Wordle bell, share string, `?run=today`** | needs `createGame({startDay})`, which doesn't exist (`let day = 1`, single writer `adopt()`); the board is a query string, so the leaderboard is decorative | `save.js:110-113,167` |
|
||||
| **`🎟 N marked` bin hover** | forces exporting `proceduralSleeves` or mirroring it (~1,100 PRNG draws per room enter), and points the player *away* from the gems | `dig.js:36` |
|
||||
| **The sandwich-board InstancedMesh** | +1 draw is affordable; the `frontEdge`/`ry` orientation math, texture, chunk refresh and gate arm are not, when the docket already names the shops | reserve for v9 |
|
||||
| **The 23-town calendar strip** | would advertise sale days in the 3 towns with nothing to dig | measured §4(b) |
|
||||
| **Dollar-threshold ranks ("$200 = LEGEND OF THE TIP"), `best`** | measure wealth, not play — start cash is `60 + (r()*140\|0)` and grows unbounded | `wallet.js:10` |
|
||||
| **`goesAt ∈ {2,3,4,5}` thinning** | 50–75% of that distribution falls in segments the shop is never open in — it resolves to a constant. Replaced by one bit keyed to the shop's *own* hours: **arrive in a shop's last open segment and half the red tickets are gone** | `registry.js:40,128` × `lighting.js:22` |
|
||||
|
||||
---
|
||||
|
||||
## 9. EFFORT, AND THE MINIMUM SHIPPABLE THING
|
||||
|
||||
**MINIMUM — one round. This is playable this week.**
|
||||
The sale exists and nothing is scored: `?sale` flag, seeded calendar, roster, band-floor tickets, red sticker + SALE chip, `was`/`sale:1` on the entry, the `listFind` refusal, the paper line on the wake toast. Gate arms 1–6.
|
||||
Cost: one Lane C afternoon (`dig.js` ~8 lines), one Lane F round (`sale.js` ~120 lines, three `save.js` edits, two `index.html` lines), and the offer-price golden. **No lane conflicts** — nobody is currently in `dig.js:421` or `save.js:366`, and v8 wave 1 is entirely elsewhere.
|
||||
|
||||
**FULL — two more rounds.**
|
||||
+ the crate clock and the Lane B `advance()` API, + thinning at room-enter, + the docket strip and the end-of-day total, + the keeper's rumour instead of a calendar UI, + gate arm 7 and the DBG hook. The second of those two rounds is mostly QA, because the crate clock touches what *time* means and every existing tool pins midday.
|
||||
|
||||
Ship the minimum first **as a field test**: it tells you whether a red ticket is fun before you rebuild the day around it.
|
||||
|
||||
---
|
||||
|
||||
## 10. WHERE IT SITS AGAINST v8
|
||||
|
||||
**The minimum version is a genuine v8 sidebar.** One flag, zero draws, zero assets, zero fetches, DOM-only, construction-gated, no lane collisions with ambient audio, per-town character, the magpie or backyard props. It is content wearing a very thin system, which is exactly what v8 is.
|
||||
|
||||
**The crate clock is not a v8 sidebar — it is the head of v9.** Making a crate cost time is the first time in this game's history that dwelling costs anything, and it is the direct, literal answer to *"the clock is scenery."* That deserves an epoch, its own gates and its own retro, not a wave slot.
|
||||
|
||||
**One flagged follow-up, not this round:** give the op shop a record crate. It is one line in `theme.js:56-63` and it would take the diggable pool from 0–12 per real town to something like 3–25 — which fixes the roster histogram, fixes the 3 dead towns, and delivers the actual op-shop fantasy the pitch is written in. It re-rolls every op shop interior, so it is a Lane C spike with a golden re-mint behind it. Worth costing properly in v9.
|
||||
|
||||
---
|
||||
|
||||
## 11. WHAT NEEDS JOHN
|
||||
|
||||
1. **Stopwatch: in or out?** The recommendation is out — the pressure is the shutter, the purse, and the crate cost. You said "limited times". Is a *trading day* limited enough for you, or do you want a number counting down?
|
||||
2. **Does opening a crate cost time?** This is the one real design change in the document, and the one that makes hours bite. It rations *breadth*, never the riffle itself — you can take as long as you like inside a crate; you just can't open the next one for free. Yes or no.
|
||||
3. **Are red-ticket finds locked out of GodBay forever?** It is the clean fence and it is diegetic, but it is a real restriction on your auction toy.
|
||||
4. **Is the band floor sacred?** i.e. a red ticket can never be a gem. This buys the no-pump law and the gem signal with one clamp. Breaking it means red tickets and grails start looking alike.
|
||||
5. **Does the sale run in one-shop towns?** Eleven of twenty-six get a roster of one. Recommendation: yes — a one-shop round is a depth round. Three towns get no sale day at all, honestly.
|
||||
6. **Is the crate the only trophy, or do you want a score that persists?** Recommendation: crate only, no personal best, no leaderboard. The number is for the day, the record is forever.
|
||||
7. **The name.** RED TICKET DAY is the working title. Given three designs independently reached for "red ticket", it is probably right — but it is your word to pick.
|
||||
159
docs/THE_FRONTIER.md
Normal file
@ -0,0 +1,159 @@
|
||||
# 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). *(2026-07-23: Sebastian Lague's
|
||||
graphics-library video §6 is effectively the implementation guide — fBm + cheap
|
||||
Rayleigh/Mie approximation in one full-screen pass; the winmap shader proves the house
|
||||
does this class of trick. Skip full volumetric raymarch until measured.)*
|
||||
- **2b. THE HORIZON MATTE (filed 2026-07-23, from the same video) — the poor-man's
|
||||
terrain.** Towns end at a flat empty horizon. A seeded fBm-raymarched HORIZON BAND — a
|
||||
matte painting that computes itself, ~1 draw, zero geometry — puts the Blue Mountains
|
||||
behind Katoomba and scrub ranges behind Bendigo, seeded per town (real towns could bias
|
||||
the profile from their actual region). Buys most of the terrain epoch's FEEL years early;
|
||||
classic-gated like everything; retires automatically if real terrain ever lands.
|
||||
- **2c. VORONOI OUTSKIRTS (same filing).** Grid-constrained Voronoi paddocks with treeline
|
||||
borders as the town-edge ring, so the approach to town reads as countryside instead of a
|
||||
hard stop. A's partitioner class (plan-side, seeded, golden-aware) + B's tree instancing.
|
||||
Pairs with 2b; both are candidates for a single "THE HORIZON" polish round.
|
||||
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.
|
||||
|
||||
6. **THE CIVIC LAYER (filed 2026-07-23, John's question — "do we have fountains/statues/
|
||||
gardens?" Answer: no, and the town feels it).** Trees exist (wind-swaying billboard gums,
|
||||
R17) and commerce exists; civic PRIDE doesn't — no parks, gardens, fountains, statues,
|
||||
memorials, rotundas. The real-data angle makes this a first-class citizen of the real-map
|
||||
DNA: **OSM maps all of it** (`amenity=fountain` · `historic=memorial` · `leisure=park/
|
||||
garden`), and the **war memorial** — the defining civic marker of exactly the AU towns we
|
||||
build — is mapped in nearly every one. Real towns get their actual memorial/park/rotunda
|
||||
at actual positions via the existing ODbL fetch (one more class, standing auth); synthetic
|
||||
towns get a seeded civic kit (memorial at the main intersection, rotunda + gums in a
|
||||
pocket park, drinking fountain by the shops); statue/rotunda meshes via the local gen
|
||||
stack (hunyuan3d_mlx, CC0-equiv, the instrument-pipeline discipline). Cheap-to-mid (B
|
||||
furniture patterns + E assets + one A data class), incremental like the kit, and pairs
|
||||
with it: terrain = the ground's shape, the kit = the buildings' shape, the civic layer =
|
||||
the town's heart. A pocket park with a memorial is also the natural HOME BASE candidate
|
||||
for v7.x (sleep on a park bench, you battler).
|
||||
|
||||
## SUPERSEDED IN PART — see [REVIEW_CITY_PATTERNS.md](REVIEW_CITY_PATTERNS.md) (2026-07-26)
|
||||
|
||||
A 16-agent top-down review (5 lane cartographers · asset-capability audit · design research ·
|
||||
4 design themes × adversarial verify · synthesis) measured this shelf against the code and
|
||||
corrected several of its assumptions. **Read that review before charting anything below.**
|
||||
Headlines that change this document: the draw budget of record is **292/300 walked** (R37-pinned; 280 was the same measurement at a coarser step), not the
|
||||
~131 bookmark figure (spend TRIS, not draws) · **all 21 real towns are 100% `use:'shop'` lots
|
||||
with one district** — every residential/market/yard idea here is synthetic-town-only until the
|
||||
civic-layer fetch lands, which promotes the civic layer from "nice" to **the strategic v9** ·
|
||||
rigged quadrupeds are a **hard capability wall** (MIRPAMO is architecturally biped-only), so
|
||||
animals are static+scripted or nothing · and the highest-leverage change on the shelf is not
|
||||
content at all but **the kerb ruling** (A publishes a corridor law nobody consumes; 14 gig
|
||||
posters are standing in the road on the boot every player gets).
|
||||
|
||||
## 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.
|
||||
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.
|
||||
141
docs/V7_THE_GAME.md
Normal file
@ -0,0 +1,141 @@
|
||||
# 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.
|
||||
|
||||
## Risk verdicts at the close (R36, v7.0 — written by F per the single-lane-close ruling)
|
||||
|
||||
1. **Economy balance — RETIRED (structurally fenced).** The no-pump gate is STRICT since R30
|
||||
(5 real UI round trips all strictly negative + C's broke-player negative control); R32 moved
|
||||
the fence from the receipt to the band (`credited == min(bandLow−1, ⌊bandLow/2⌋)`, the exact
|
||||
formula, not an inequality); R34's GodBay holds EV ≈ 0.8×mid under the ONE-HAMMER law (item-
|
||||
identity-seeded — a passed-in item re-lists to the same result forever, so there is no retry
|
||||
farm), bounded above at 2×high×0.88. Both hand playtests confirmed the shape — Fable's wave-0
|
||||
line: the game charged $12 tuition for ignoring its own advice. *Honest residue:* the named
|
||||
adversarial verifier (a greedy N-day arbitrage bot) was never built as an agent — the
|
||||
structural gates + two hand playtests stood in for it. It goes live again with G's live tier
|
||||
(v7.x), where structure alone won't be enough.
|
||||
2. **Save schema drift — RETIRED.** `procity-save/1` versioned from day one; corrupted/foreign
|
||||
blobs loud-reject + stash raw + boot fresh (gated since R30). The schema widened three times
|
||||
in-epoch (`pulls` R32 · `wants` R33 · `listings` R34), every field optional,
|
||||
validated-exact-and-loud when present, absent-compatible — zero migrations, zero silent
|
||||
rejections, alpha saves still load at the close.
|
||||
3. **Item identity across re-emits — CARRIED (narrowed, never fired).** The sku IS the identity
|
||||
(the R25/R26 fences); real-sourced pulls/listings key by `sku:<id>`, parody by seeded slot
|
||||
identity. No atlas was re-emitted during v7, so the risk never had a chance to fire — and
|
||||
E's validator still lacks the chartered owned-item stability check. Carried to whichever
|
||||
round next re-bakes an atlas: that round must add the check BEFORE the re-emit.
|
||||
4. **Scope gravity — RETIRED (the stage gates held).** Alpha shipped one town playing complete
|
||||
(R30–31), beta opened the 23 (R32–33), v7.0 closed with the auction + onboarding (R34–35).
|
||||
Every mid-epoch want was FILED, not built: the §31.6 beta queue, then the v7.x queue
|
||||
(⚖ discoverability · LIST confirm · the paper as artifact · keeper-rumor by hand · walkable
|
||||
home room). No housing, no romance, no phone.
|
||||
5. **v6 collision — RETIRED (none, by design).** The epochs interleaved without a merge
|
||||
conflict or a contract collision. The one contact was the July-20 2 a.m. commits
|
||||
(`046668e`/`b339402`: 4 Rokoko dance clips + 5 trellis DJ bodies, v6-flavoured assets landing
|
||||
mid-v7 outside the round system) — swept into provenance/licence/depot by E (R36 w1) and
|
||||
gated by F (R36 w2: figure-scale spawn proof, runtime bind census, classic zero-fetch for the
|
||||
clips). One hold from that sweep rides the R36 record: the DJ bodies joined `?classic`'s
|
||||
roster ungated — Fable to bless or bounce.
|
||||
192
docs/V8_SOMEONE_LIVES_HERE.md
Normal file
@ -0,0 +1,192 @@
|
||||
# PROCITY v8 — SOMEONE LIVES HERE (epoch charter — DRAFT, John's rulings recorded)
|
||||
|
||||
*Fable (m3), 2026-07-26. Built on [REVIEW_CITY_PATTERNS.md](REVIEW_CITY_PATTERNS.md) — a
|
||||
16-agent top-down audit (5 lane cartographers · asset-capability probe · design research ·
|
||||
4 design themes × adversarial verify · synthesis). That review is the evidence; this is the
|
||||
plan. Read it first — it corrects three numbers this project had been repeating for epochs.*
|
||||
|
||||
## The vision
|
||||
|
||||
**v7 made the town a game. v8 makes it a place someone lives.** Not more systems — more
|
||||
*inhabitation*: the sound of a life happening off-screen, twenty-three towns that finally look
|
||||
like different towns, a magpie that won't let you walk past it in September, a back yard with
|
||||
washing on the Hills Hoist, and a bloke actually standing at the pie cart that has been
|
||||
sitting unmanned on the street for five epochs.
|
||||
|
||||
## JOHN'S RULINGS (2026-07-26, recorded verbatim in effect)
|
||||
|
||||
### RULING 1 — THE KERB: **FOOTPATH.** ✅
|
||||
*"footpath for sure."*
|
||||
|
||||
The 9 m band between kerb and shopfront is **footpath**, not road. Lane A's published corridor
|
||||
law (`roadWidth` / `vergeBand` / `poleOffset`, `registry.js`) is hereby the single source of
|
||||
truth, and Lane B's ground adopts it: extend the **existing merged `footGeos` class** inward
|
||||
from `e.width/2` to `roadWidth(e)/2`. **+0 draws** (same merged mesh), ~2k tris, fill-rate
|
||||
improves because 18 m of every main street stops being painted as road.
|
||||
|
||||
Consequences, binding:
|
||||
- The 14 gig posters currently standing in the road land where A already thinks they are.
|
||||
- `registry.js`'s own comment calls that band "verandah'd footpath" and `buildings.js` already
|
||||
builds an awning over it on every shop lot — so this ruling makes the geometry agree with the
|
||||
code's own documentation. **Grass verges survive on residential edges only**, where they are
|
||||
period-correct.
|
||||
- Special cases that must be handled or the ruling breaks: `arcade` (`roadWidth` 0 → zero-area
|
||||
road quad + coincident kerbs) and `lane` (`vergeBand` = [2,2]).
|
||||
- **The gate ships with a falsifiability control**: run the poster-clearance leg against
|
||||
`?verge=0` and assert it **FAILS there** — the pre-change failure is already known and
|
||||
counted at exactly 14. Keep `?verge=0` permanently as a gate flag; it costs nothing and it is
|
||||
the best gate design in the review packet.
|
||||
|
||||
### RULING 2 — REAL TOWNS GET THE CONTENT: **FUND THE CIVIC FETCH.** ✅
|
||||
*"real towns where possible spread it out."*
|
||||
|
||||
The review's biggest strategic finding: **all 21 real towns are 100% `use:'shop'` lots with a
|
||||
single district (`mainstreet`)** — no houses, no yards, no stalls, no market block. Every
|
||||
residential/market/vendor idea was therefore synthetic-town-only. John has ruled that the
|
||||
content goes to the **real** towns wherever possible, which means the fix is funded and moves
|
||||
**into v8** rather than waiting for v9:
|
||||
|
||||
- **Lane A + Lane E: the third fetch class.** `build_towns.py` currently asks Overpass for
|
||||
shops and roads. It gains a bounded third class — `landuse=residential`, `building=house`,
|
||||
`leisure=park/pitch`, `amenity=marketplace/memorial/fuel/school/bus_stop/telephone/post_box`
|
||||
— and `plan_osm.js` emits **more than one district** and **more than one lot `use`**.
|
||||
- **The data is already half here**: the caches in-repo already contain, unused, 30,959
|
||||
footway ways · 12,351 service ways · 6,421 crossings · 2,714 sidewalk tags · 13,199 oneway
|
||||
tags. Some of the districting can be derived without any new fetch at all — A measures which
|
||||
before E re-fetches.
|
||||
- **Hazard, named now**: `build_towns.py` rewrites the *whole* cache and its own docstring
|
||||
records the Newcastle incident (shops refetched, old roads silently reused). **46 pinned
|
||||
goldens ride on byte-identity nobody has proven.** The re-fetch is therefore its own
|
||||
carefully-gated round with per-town before/after diffs — not a step folded into a content
|
||||
round.
|
||||
- Standing Overpass authorisation applies (one bounded query per town, 4 s courtesy, ODbL
|
||||
attribution into every cache and `SOURCES.md`).
|
||||
|
||||
### RULING 3 — ANIMALS: **answered by spike. Generate, don't download.** ✅
|
||||
*"idk about rigged animal models... they have different rigging too right? diff skeleton?"*
|
||||
|
||||
Full answer + intake checklist + vetted sources: **[ANIMALS_ANSWER.md](ANIMALS_ANSWER.md)**
|
||||
(5-agent spike: foreign-skeleton loader proof · CC0 source vetting · tiering · adversarial
|
||||
review · Fable's adjudication header, which corrects one over-claimed verdict).
|
||||
|
||||
**John's instinct was right and it stops mattering.** There is no `mixamorig` for quadrupeds;
|
||||
every animal ships a bespoke skeleton. But retargeting only exists because our 24 human bodies
|
||||
share 8 clips — an animal carrying *its own* baked clips needs none of it. **Proven, not
|
||||
argued:** a 10-bone quadruped GLB (`leg_fl`, `tail_01`, Blender-style clip names) was authored
|
||||
byte-by-byte, loaded through **our own vendored three.js and our own `loaders.js`**, and played
|
||||
its own clips through a bare `AnimationMixer` — 27 assertions green, including *pixels changing
|
||||
on a real `WebGLRenderer`* (1,804 lit px, 704–1,015 changed between frames) and
|
||||
`SkeletonUtils.clone` sharing geometry+material across N animals. Also proven in the mirror:
|
||||
fed to the human stack, `buildFigure` would measure that dog's "feet-to-crown" at 0.36 m and
|
||||
**scale it 4.9×** — silently. Hence the law: **animals get a sibling module
|
||||
`web/js/citizens/animal_rig.js` importing nothing from `rigs.js`.** ~40 lines. The
|
||||
classic-covenanted human fleet is never touched.
|
||||
|
||||
**The ruling is economic, not technical.** Measured on a real renderer: **1.00 draw per skinned
|
||||
animal, 1:1, to N=80** — three.js cannot instance animated skinned meshes and our impostor bake
|
||||
crops long low animals. Against that, **sixty birds on a wire cost ONE draw between them**
|
||||
(one InstancedMesh, one 4-tri billboard, one 1024 WebP species atlas — every future bird
|
||||
species then free). That asymmetry is the whole design:
|
||||
|
||||
- **Tier 0− — the heard animal. +0 draws, +0 tris. SHIPS.** A dog behind a paling fence, a
|
||||
cockatoo at dusk, at a *seeded anchor* so it is the same dog on the same street every time.
|
||||
~30 lines of numpy each in `gen_audio.py`. Highest charm-per-minute in the epoch; a town with
|
||||
zero visible animals reads as a town with animals in it.
|
||||
- **Tier 0a — the flock. +1 draw town-wide. SHIPS (wave 1).** Cockatoo, galah, corella,
|
||||
lorikeet, perched magpie, ibis as cells in one atlas. Verified free when empty *to the
|
||||
vendored line* (`three.module.js:2193-2199` returns before the GL call; `render.calls++` at
|
||||
`:4094` lives downstream) — a bird-free town costs **exactly zero**, not "almost zero".
|
||||
- **Tier 0b — near props. +1 draw per species. Wave 1.5, ONE species:** the ibis head-down in
|
||||
a council bin.
|
||||
- **Tier 1 — the magpie.** As already approved. **Binding correction carried:** territory must
|
||||
scale with the road graph — 370 pole anchors on synthetic vs **10,014 poles / 13,730 tree
|
||||
anchors across 2,593 edges on `bendigo_real`**; a capacity-4 magpie there is a swooping town
|
||||
where you never meet the magpie.
|
||||
- **Tier 2 — genuinely skinned: CUT this epoch**, and killed by our own spec rather than by
|
||||
cost: the dog was anchored at `shopFront` — *tied outside the shops* — and a tied dog never
|
||||
trots, so the walk clip that is the tier's entire justification would never play. It returns
|
||||
only with a path, footpath alignment, a collision story and a sim hook the design explicitly
|
||||
disclaims.
|
||||
|
||||
**So: generate ours, don't go shopping.** Nothing on the animal list needs a download. The one
|
||||
thing genuinely worth grabbing online is *not an animal* — Quaternius's **Universal Animation
|
||||
Library** (CC0, 130+ clips, Mixamo-compatible) is fuel for the **human** fleet's thin 8-clip
|
||||
bank; it touches classic-covenanted `rigs.js`, so it is its own round with its own gates and
|
||||
must not be folded into animal work.
|
||||
|
||||
**Two corrections binding on v8 from this spike** (both adjudicated in the answer's header):
|
||||
`classic_regression` has **no network assertion at all** and grows an allow-list (never a
|
||||
deny-list); and `furniture.js`'s module-scope loader is a **trap for new assets** — `fauna.js`
|
||||
is dynamically imported behind its construction guard or carries a CLASSIC-derived module
|
||||
guard stated in its header. *(The spike called the existing four furniture fetches a live
|
||||
breach; they are not — git-verified as v2-era, `24cef5a`, tagged v2.0-alpha→v2.0, i.e. part of
|
||||
the baseline the covenant preserves. The hazard is real; the breach is not.)*
|
||||
|
||||
## The shape
|
||||
|
||||
**WAVE 0 — THE FLOOR** *(one round, three lanes, all micro, must precede content)*
|
||||
0.1 the kerb ruling landed as code + the `?verge=0` control · 0.2 repair `smoke_tram` (it
|
||||
asserts one thing, prints a SKIP when absent, and **passes on the fenced path** — a live
|
||||
vacuous-gate violation inside our own suite) · **0.3 SETTLE THE BUDGET OF RECORD — DONE (R37)**: the 280-vs-191 split was the CAMERA
|
||||
BEARING (110 draws of spread; the audit took `DBG.teleport`'s default yaw 0 = the worst
|
||||
bearing, the spike supplied a travel-facing yaw = mid-range — both correct, different
|
||||
quantities). **PINNED: 292 draws / 124,793 tris**, worst-draw and worst-tri on DIFFERENT
|
||||
frames, 8 draws of margin. Shared instance cap **256**, with ≤24 tris/instance and
|
||||
proximity-first spending · 0.4 wire
|
||||
`ctx.onChunkBuilt`/`onChunkDisposed` (producer and consumer both shipped since R3, never
|
||||
connected) · **0.5 give `classic_regression` a network allow-list** (the four v2-era depot
|
||||
GLBs + same-origin) — it currently makes no network assertion at all, which is why the R36
|
||||
fetch-surface widening needed a brand-new smoke to catch it.
|
||||
|
||||
**WAVE 1 — INHABITATION** *(four parallel one-round items; no clips, no rigs, no mocap
|
||||
footage — this epoch deliberately does not wait on John's camera)*
|
||||
1.1 **the audio inhabitation layer** — the review's only unqualified SHIP: a dog going off
|
||||
behind a paling fence, a mower two streets over, a radio through a window, sprinklers, a roller
|
||||
door. Zero draws, zero tris, ~30 lines of numpy each. · 1.2 **the per-town character vector** —
|
||||
23 towns get a ground palette, ambience mix and sky bias; `ground.js` merges each surface class
|
||||
into ONE town-wide mesh, so a palette swap is **+0 draws / +0 tris**. · 1.3 **the magpie** —
|
||||
one bird, six weeks, +1 draw. · 1.4 **backyard Australia** — Hills Hoists, paling fences,
|
||||
aerials, tanks, built inside `buildings.js`'s existing `boxMats` instanced mesh (+0 draws for
|
||||
the box props). *Per ruling 2, 1.4's reach now depends on the civic fetch — it ships on
|
||||
synthetic immediately and extends to real towns as districts land.*
|
||||
|
||||
**WAVE 2 — THE STREET GETS A VERB** *(sequential, behind 2.0)*
|
||||
2.0 the street interaction surface (a `KeyE` verb, an `interactRects` registry, hit priority,
|
||||
a click fence) · 2.1 **the manned cart** — consumes the flagship dead seam (44 cart sites on
|
||||
the golden seed, 511 across real towns; `furniture.js:252` already holds the shop record and
|
||||
throws it away). **Blocker, not cosmetic**: the cheap `doorRect` ride would make every milk bar
|
||||
and stall un-enterable from the front — the cart must raycast its own InstancedMesh. · 2.2 the
|
||||
fauna substrate + field guide · 2.3 dogs, tier B first.
|
||||
|
||||
## Laws (inherited, plus this epoch's)
|
||||
|
||||
Everything standing. New for v8: **spend triangles, not draws** (the walked worst frame is
|
||||
280/300; tris sit at 99–124k of 200k — every proposal states its draw cost against 280, and a
|
||||
town-wide `InstancedMesh` costing 1 draw beats a per-chunk type costing +1 × up to 31 live
|
||||
chunks) · **one instance cap per shared layer, settled once, before three implementations
|
||||
invent three** · **the depot is a read-only archive** (971 live assets, 53 recorded, known
|
||||
trademarked items inside) — generate fresh, never pool from it · every new gate copies the
|
||||
shape of 1.1's control: point one table entry at a non-existent key and assert the leg FAILS.
|
||||
|
||||
## The cut list stands
|
||||
|
||||
Recorded in the review, killed with measurements, not re-opened without new evidence: the
|
||||
town-loop bus (its own gate refuted it — what survives is one file: let the *tram* run on
|
||||
main+side edges, retiring the same dead seam for +0 draws) · the ute that sells · the charity
|
||||
bin (its filter accepts nothing) · the dusk flush (the day is 240 seconds long) · the magpie
|
||||
hat (no street verb exists) · the OSM level crossing (the rescoped synthetic version survives) ·
|
||||
the mail run · parked cars this epoch (deferred, not rejected — and when they land, 45° angle
|
||||
parking is a charter requirement).
|
||||
|
||||
## Standing — what still needs John
|
||||
|
||||
1. **The market plaza amendment** — `ground.js` paints a 46×46 m plaza at the origin, on top of
|
||||
the spine road, touching none of the 40 stall lots. Fixing it changes the covenanted town's
|
||||
visual output. *Recommend: fix under the amendment law (the R27 precedent — the covenant is
|
||||
anti-drift machinery, not a defect preservative), as its own small ruling.*
|
||||
2. **hardyards provenance** — 39 GLBs that are exactly the vernacular kit's shopping list, with
|
||||
provenance documented for one file. Only John knows where they came from. 🟠 until cleared.
|
||||
3. **Where is MOCAPGOD?** The fleet docs say lanes 0/A/B are done; on m3ultra the directory is
|
||||
0 bytes and not a git repo (it lives on gitea and, presumably, ultra). Not blocking — v8
|
||||
needs no clips — but the fleet docs are wrong somewhere.
|
||||
4. **The animal ruling**, once the spike reports.
|
||||
306
docs/V9_THE_CITY_IS_THE_GAME.md
Normal file
@ -0,0 +1,306 @@
|
||||
> **FABLE'S VERIFICATION (2026-07-26).** John's reframe, designed by a 3-take panel (take-the-map-
|
||||
> away / go-up-and-out-the-back / the-unmarked-door), each adversarially judged, then synthesised.
|
||||
> **I re-measured the two load-bearing claims myself before circulating this, and both hold exactly:**
|
||||
>
|
||||
> 1. **The street names.** Counted independently across every shipped cache: **19,132 road ways,
|
||||
> 17,835 carrying a `name` — 93.2%**, matching the synthesis to the digit. And the drop is real:
|
||||
> `plan_osm.js:363` builds each way as `rawWays.push({ kind: roadEdgeKind(rd.kind), pl })` —
|
||||
> it keeps the kind and the geometry and **discards `rd.name`**. Every real street in twenty-three
|
||||
> Australian towns is sitting in this repo with its name on it, and the game has never said one
|
||||
> out loud.
|
||||
> 2. **The interior budget correction, which contradicts our own depth map and is the more urgent
|
||||
> of the two.** `index.html:349` ships `useGLB: !NOASSETS`, so **the default boot is the GLB
|
||||
> path**, and `C-progress.md:841` records that sweep at **GLB-on 344** against the ≤350 interior
|
||||
> cap. **The interior margin is SIX draws, not 191.** The depth map's "draw budget is not the
|
||||
> problem either" cited the GLB-*off* 168 and is wrong. This goes into the house record today
|
||||
> regardless of what happens to the rest of this document — any interior work, in any epoch,
|
||||
> is spending against six.
|
||||
>
|
||||
> **Scope honesty, stated up front:** enterable upper floors are an EPOCH, not a wave.
|
||||
> `player.js:64` and `interior_mode.js:485` both write `y = EYE` unconditionally every frame on
|
||||
> every path — there is no gravity, no floor query, no current-storey state, and no data structure
|
||||
> anywhere carries a floor height. Anything containing "levels" or "stairs you walk up" is
|
||||
> proposing a locomotion rewrite plus a new ground truth. Everything else John asked for is
|
||||
> affordable now.
|
||||
>
|
||||
> Companion: [SALE_RED_TICKET_DAY.md](SALE_RED_TICKET_DAY.md) owns the ROUND and the LAWS (the
|
||||
> Band Floor Law); this document owns the HUNT and the CITY.
|
||||
|
||||
# THE CITY IS THE GAME
|
||||
### Synthesis — the v8 city-patterns review, three takes reconciled
|
||||
**Fable → John.** Measurements in this document were re-run against the tree at `d59df8e`, seed 20261990, all 23 shipped caches + the synthetic. Where I contradict a judge or the depth map, I say so and show the number.
|
||||
|
||||
---
|
||||
|
||||
## 1. THE ANSWER
|
||||
|
||||
**Yes. And the reason it's the right move is that the city is already deeper than the game knows.**
|
||||
|
||||
Your reframe — *"you have to even know where to find the store first to get the sale item"* — asks for a city with places in it. PROCITY generates places. It has generated them since v2. Nothing reads them:
|
||||
|
||||
| Generated | Where | Consumed by |
|
||||
|---|---|---|
|
||||
| Six `DISTRICT_KINDS`, 7 districts on the synthetic (mainstreet 332 shops · backstreets 95 · market 41 · arcade 17 · warehouse 4 · residential 4) | `registry.js:8`, `plan.js:160-292` | grass verges + one plaza quad (`ground.js:58,165`), ambient anchors (`audio.js:170`). Nothing else. |
|
||||
| A 42 m covered arcade, 17 shops, 2 blocks, a measured 4.99 m walkable lane, already awning-roofed by accident | `plan.js:270-283`, `buildings.js:634-647` | paved (`ground.js:140-147`). No props, no signage, no roof, no reverb. |
|
||||
| Two 280 m service laneways behind the main-street blocks, 36 yard lots | `plan.js:328-339` | gum trees, magpie song. Zero shops. |
|
||||
| `storeys` on **every** shop — 566 of 1,345 corpus shops are 2–3 storeys | `plan.js:89`, `plan_osm.js:275` | box height (`buildings.js:567`) and one ambient. **The facade stops at 3.0 m.** No upper window exists anywhere in the codebase. |
|
||||
| `lot.frontEdge` — which street every shop is on | `plan.js:78` | one consumer, `gigs.js:82`. |
|
||||
| **`roads[].name` on every shipped cache — 17,835 named ways of 19,132 (93.2%)** | the caches themselves | **nothing. `plan_osm.js:362-366` keeps `kind` and drops the name.** |
|
||||
|
||||
That last row is the finding of this review and I re-derived it independently: adelaide 1191/1099/319 distinct, bendigo 1552/1408/322, fitzroy 1571/1437/190, katoomba 484/457/166, castlemaine 397/370/141. **Every real street in twenty-three Australian towns is already sitting in the repo with its name on it, and the game has never once said one out loud.**
|
||||
|
||||
And it is recoverable without touching citygen. Plan nodes *are* snapped projected way points (`plan_osm.js:361-372`, `SNAP=3`), so plan edges lie **on** the named ways. Measured, all 23 caches, median distance from a plan-edge midpoint to its nearest named way:
|
||||
|
||||
> **0.03 m – 0.90 m.** Worst town katoomba at 0.90 m. **97.8% of corpus shops (1,192 / 1,219) resolve to a street name.**
|
||||
|
||||
That is not an inference. I recovered the plan's internal shift by centroid-align + nearest-waypoint vote and re-measured; the numbers above are mine. There is a cleaner one-line upgrade — write `norm.shift = {shx, shz}` at `plan_osm.js:510`, which lands in the **caller-supplied `opts.report` sink, never on the plan object** — so it moves no golden and breaks no classic byte. Take the one line. But the design does not depend on it.
|
||||
|
||||
**So: the city is the game is cheap, because the city is already built and only the exploration layer is missing.** The expensive part is exactly one thing, and I'll name it now rather than bury it:
|
||||
|
||||
> **Vertical movement is fiction until someone rewrites locomotion in both modes.** `player.js:64` and `interior_mode.js:485` both write `y = EYE` unconditionally, every frame, on every path. There is no gravity, no floor query, no current-storey state, and no data structure anywhere carries a floor height. Any proposal containing "levels", "stairs" or "upper floors as places you enter" is proposing that rewrite plus a new ground truth. **And the interior draw margin is six, not one-nine-one** — `C-progress.md:841` reads *"full drawSweep unchanged (GLB-off 168 · GLB-on 344, both pass)"*, and `index.html:349` ships `useGLB: !NOASSETS`, so the **default boot is the GLB path at 344/350.** The depth map's line *"DRAW BUDGET IS NOT THE PROBLEM EITHER"* cites the GLB-off sweep and is wrong. That correction goes into the house record today regardless of what happens to the rest of this.
|
||||
|
||||
Enterable upper floors are an epoch. They are not this epoch. Everything else you asked for is affordable now.
|
||||
|
||||
---
|
||||
|
||||
## 2. THE SHAPE — "KNOW YOUR TOWN"
|
||||
|
||||
One proposal, four layers, in dependency order. Layers 1–3 are information design and cost **zero draws**. Layer 4 is the only geometry and it rides meshes that already exist.
|
||||
|
||||
### Layer 1 — THE ADDRESS LAYER *(the vocabulary)*
|
||||
A new pure module, `address.js`. No THREE, no fetch, no plan mutation.
|
||||
|
||||
```
|
||||
createAddresses(plan, cache|null) → {
|
||||
streetOf(edgeId) → "Templeton Street" | null
|
||||
localityOf(shopId) → { street, district, block, side, label }
|
||||
cohort(predicate) → shopId[] // the clue sizer — see Layer 3
|
||||
}
|
||||
```
|
||||
|
||||
- **23 cache towns:** street name, resolved by nearest named way to the edge midpoint (≤8 m tolerance).
|
||||
- **Synthetic:** no roads cache and only 22 edges, so the label comes from `district.kind` + block — *"the market end"*, *"the arcade"*, *"the backstreets"*. Same field, different supplier, one consumer contract.
|
||||
|
||||
Consumers, all cheap: the HUD tooltip (`hud.js`, one template literal), the map's street labels, the notebook, the morning paper, keeper rumours.
|
||||
|
||||
**This is the prerequisite, not the bonus.** VERTICALITY's judge marked it as such and was right: without it, CLEARANCE collapses to one type-cell on 21 of 24 towns. UNMARKED needs it harder still — 7 of the synthetic's 12 hidden shops share a name with 1–10 others (`Rotation ×10`, `Quick Cash Pawn ×11`), so a ledger entry that says only a name points at ten buildings. **An address is the only way a shop with no sign can be written down at all.**
|
||||
|
||||
### Layer 2 — THE FOG *(the keystone)*
|
||||
`minimap.js` draws only shops the player has walked past. Discovery is player state, saved.
|
||||
|
||||
Today `minimap.js:52-63` draws **every** lot in the plan, coloured by shop type, from the first frame, with no gating. One M keypress renders all 493 synthetic shops — all 77 record shops, all 92 op shops — before the player has walked a metre. Measured against what that is worth: passing every shopfront once costs **5,330 m on the synthetic, 9,622 m on fitzroy, 5,797 m on northbridge** — at WALK 4.6 m/s against `lighting.js:91`'s 240-second day, **4.8 / 8.7 / 5.3 game-days of walking.** The map hands over five to nine game-days of legwork for one keystroke, on all 24 towns, forever.
|
||||
|
||||
And the payoff is bigger than the cost saving. Replicating `minimap.js`'s own transform, a shop rectangle renders at:
|
||||
|
||||
| | bowral | castlemaine | katoomba | adelaide | fitzroy | synthetic |
|
||||
|---|---|---|---|---|---|---|
|
||||
| full-extent (today) | 0.8×1.1 px | 1.4×1.8 | 1.4×1.9 | 1.7×2.3 | 2.7×3.5 | 5.2×10.2 |
|
||||
| framed to a 200 m known window | 29×39 px | 29×39 | 29×39 | 29×39 | 29×39 | 21×41 |
|
||||
| **gain** | **35×** | **21×** | **21×** | **17×** | **11×** | 4× |
|
||||
|
||||
**The same line of code that hides what you don't know makes legible what you do.** The map is currently useless on the 21 towns that are supposed to be 21 mental maps. Fog fixes that as a side effect.
|
||||
|
||||
Cost: zero draws, zero triangles — the map is a 2D canvas overlay and `hud.js:361`'s counter (`renderer.info.render.calls`) never sees it. Zero new plan fields. Classic-gatable by construction: `createMinimap(plan)` at `index.html:204` becomes `createMinimap(plan, game ? knownProvider : null)`, and a null provider is today's exact behaviour — the same pattern `ground.js` already uses at `index.html:178-185`, so `minimap.js` needs no flag test at all.
|
||||
|
||||
**Two corrections to the depth map's own fog proposal:**
|
||||
|
||||
1. **Discovery is a proximity probe, not `chunks.onChunkBuilt`.** Real towns are all BIG_CITY (`index.html:151`) so R=2 — the outermost live chunk centre is 128 m out, its far corner 181 m. Chunk-built discovery would hand you shops three blocks away through a wall. Use a 25 m squared-distance probe over the live `doorRects` on the existing `frame % 6` cadence (`hud.js:337`). Same cost class, honest semantics.
|
||||
2. **Key the fog by TOWN, not by `townKey`.** I verified: on all 23 cache towns, `shops[].id / lot / type / name` and every lot geometry are **byte-identical across seeds** — only `storeys` and `hours` re-jitter. So a re-seed is *the same Katoomba with different secrets*, and the map you learned should survive it. Keying by town also bounds the save by construction (≤ 25 registered keys, no LRU, no cap needed), which kills NOMAP's K5 and UNMARKED's cap problem in one move. The **synthetic is the exception** — its shop ids are seed-dependent (493 shops at 20261990, 479 at seed 7) — so it keys `default@<seed>`. One rule, one stated exception.
|
||||
|
||||
### Layer 3 — THE HUNT *(what the fog is for)*
|
||||
A once-per-day sale, announced at dawn in the existing morning paper, addressed rather than pinned. Full round in §3.
|
||||
|
||||
The director's job is one thing: **size the clue.** It picks a target from the set of shops that some clue cell resolves to **≤4 doors within a ≤350 m span**, and emits the smallest cell that works. Measured share of a town's shops that are clueable under that rule:
|
||||
|
||||
| bendigo · braddon · darwin | launceston · castlemaine · marrickville · bowral | newtown · redhill · newcastle · northbridge | glebe · westend · hobart | adelaide · fitzroy · geelong · fremantle · katoomba | brunswick | **synthetic** |
|
||||
|---|---|---|---|---|---|---|
|
||||
| **100%** | 90–93% | 83–89% | 77–78% | 69–74% | 51% | **32%** |
|
||||
|
||||
Median real town: **~85% of shops are pointable-at in four doors or fewer.** Median best clue cell on a real town: **1 door.** On the synthetic: **9 doors, p90 20.** That difference is structural and I'll deal with it in §5 and §8.
|
||||
|
||||
### Layer 4 — THE UNMARKED *(the reward)*
|
||||
Roughly 6% of a town's shops ship with **no sign** — 89 across the corpus, 12 on the synthetic, 2–12 per town by `clamp(round(shops×0.06), 2, 12)`. You find them by cue: a crate of records on the footpath, an A-frame, a roller door half up, a bloke in the doorway. They enter the notebook **by address**, because they have no name.
|
||||
|
||||
**The seam that makes this free: on a fogged map, an unmarked shop's absence is indistinguishable from an undiscovered shop's absence.** UNMARKED's own judge killed the map-omission idea because on the synthetic — the only town where the map is legible — omitting one lot from a terrace punches a **16.8 px hole, 6.5× the lot width**, and you can count the notches without walking. With Layer 2 in place there is nothing to count: every undiscovered lot is a hole. The fog doesn't just permit the hidden layer, it is the only thing that makes it honest.
|
||||
|
||||
The same seam retires VERTICALITY's KILL 2 (*"the minimap already hands over the clue"*) and NOMAP's Gate 1 problem. **Every one of the three takes has a kill that fog closes.** That is the argument for this shape over any of the three alone.
|
||||
|
||||
### Layer 5 — UPSTAIRS, VISIBLE ONLY *(the bridge to the next epoch)*
|
||||
Give the 566 multi-storey shops a **first-floor window band**, drawn into the existing merged `windowGeos` mesh with the existing per-vertex `aHours` shader. **+0 draws, ~24 triangles per shop.** `makeInstanced` sizes to `list.length` with no cap (`buildings.js:749-753`); `windowGeos` merges to one mesh per chunk (`:800-806`). Today `buildings.js:581` caps the facade skin at `FACADE_H = 3.0` and puts both windows at y=1.55, so storeys 2 and 3 are a flat tinted box with a parapet — while `audio.js:190-193` already seeds a radio-through-a-window ambient onto every shop with `storeys ≥ 2`. **The game already sounds like someone lives above the shop and looks like nobody does.**
|
||||
|
||||
No stair. No enterable room. Nothing that touches the two Y-clamps. This is v8's own name — SOMEONE LIVES HERE — paid for at zero draws.
|
||||
|
||||
### The seams, stated
|
||||
```
|
||||
address ──(labels)──▶ hud tooltip · map · paper · keeper rumour · notebook
|
||||
address ──(cohort sizer)──▶ director [the clue can't be written without it]
|
||||
fog ──(known set)──▶ map draw filter + map framing + notebook
|
||||
fog ──(structural)──▶ unmarked [no hole to count]
|
||||
unmarked──(no name)──▶ address [the only identifier it has]
|
||||
upstairs──(address)──▶ "above 42 Templeton St" [free, later]
|
||||
```
|
||||
One save field (`known`), one new pure module (`address.js`), one canvas rewrite (`minimap.js`), one template literal (`hud.js`).
|
||||
|
||||
---
|
||||
|
||||
## 3. THE ROUND
|
||||
|
||||
**Castlemaine. 22 shops. 8 shop-bearing streets. Day 14. You have $180 and a wantlist with three things on it.**
|
||||
|
||||
**The clock, from the code.** `lighting.js:91` `DAY_SECONDS = 240` over 6 segments = **40 real seconds per segment**. `lighting.js:22` `HOUR_NUM = [6.5, 9, 12.5, 15.5, 18.5, 22]`. Registry trade hours are 10–18 (`registry.js:40`), so on 20 of 24 towns a record shop is open in **segments 3 and 4 only — 80 real seconds of trading.** That is the hard constraint every hunt design in this review either respected or died on.
|
||||
|
||||
**06:30 — DAWN, segment 1.** The paper lands. One line:
|
||||
|
||||
> **CLOSING DOWN — EVERYTHING HALF PRICE — THE RECORD SHOP ON TEMPLETON STREET**
|
||||
|
||||
Two terms: type + street. Measured on castlemaine, that cell holds **1 shop** (town median 2, town max 3). The clue is exact. The question is whether you know where Templeton Street is.
|
||||
|
||||
**06:30–12:30 — segments 1 and 2, 80 real seconds, the doors are shut.** This is the hunt, and it is free time you already spend walking. At WALK 4.6 m/s that's **368 m; at RUN 8.8 it's 704 m.** Measured, distance from the town's shop-centroid to a given shop: on castlemaine 45% of shops are inside 368 m, 73% inside 704 m. Corpus range 11–85% inside 368 m.
|
||||
|
||||
- **You've walked Templeton before.** It's on your fogged map, labelled. You go. 40–90 seconds. You're standing outside at 11:50 with a segment in hand.
|
||||
- **You haven't.** The map shows the streets you've learned and a name you've never seen. Three outs, in order of cost:
|
||||
1. **Ask a keeper.** Any open shop, any keeper: *"Templeton? Off the north end of Barker, past the hall."* Free, rides the existing rumour seam, and it is the most SOMEONE-LIVES-HERE thing in the whole design.
|
||||
2. **The paper's second line** — a landmark from the address layer's nearest named cross-street: *"opposite the Town Hall"*. Costs nothing; the director emits it only when the target street is undiscovered.
|
||||
3. **Walk.** Every street you learn is banked forever, on every future visit, at every seed. The loss is a day, not a save.
|
||||
- **You get it wrong.** The clue cell is ≤4 doors by gate, so worst case you check four shopfronts. Measured p90 spread inside a clue cohort: 0 m on eleven towns, 105–466 m on nine, and the gate rejects anything over 350 m.
|
||||
|
||||
**12:30 — MIDDAY, segment 3. Doors open.** You're in. The sale stock is the shop's normal seeded rotation at half band. You dig — riffle, pull, read the sleeve, check it against THE BIBLE. Under band is a gem; that skill is unchanged and it is still the core.
|
||||
|
||||
**Scarcity is the stock, not a stopwatch.** `dig.js:417-421` reads `opts.gone` once and computes offers once, at card-open — so "half the good ones are already gone" works evaluated **when you walk in**, and does not work as a timer ticking while the card is open. Arrive in segment 3 with a full sale; arrive in segment 4 and a seeded fraction is gone. That is the whole pressure and it costs one union into an existing set (`interior_mode.js:187`).
|
||||
|
||||
**18:00 — the shop shuts.** If you never found it, you lost the day. Tomorrow's paper has a different sale, in a different part of town, and the streets you burned today are permanently on your map. **You did not lose a run. You bought a map with a day.**
|
||||
|
||||
**Total round: 240 real seconds. Clue resolves to ≤4 doors. 368–704 m of pre-open walking. 80 seconds of trading. Win = you knew the town, or you learned it.**
|
||||
|
||||
**The two numbers that constrain everything above** — and that killed two of the three takes' round designs:
|
||||
- NOMAP's director radius (`remaining_segments × 40 s × 4.6 m/s × 0.7`) leaves a median of **1 shop reachable on marrickville, 2–3 on braddon and redhill.** The radius narrows the pool below the clue's cohort before the clue speaks. Cut; announce at dawn instead, which is what §7 already did, and let the whole town be live.
|
||||
- UNMARKED's BLIND DOCKET puts the target **2,217 m from the roster on hobart, 1,971 m on fremantle** — 3.1× the entire round's RUN budget in a straight line, on a mode with an 80-second window. Impossible on 7 towns, and on 9 more the "unnamed" shop is already printed in the paper. Cut entirely.
|
||||
|
||||
---
|
||||
|
||||
## 4. WHAT IT COSTS
|
||||
|
||||
### THE MINIMUM VERSION — one round, no geometry, proves the fun
|
||||
|
||||
| Lane | File | Change |
|
||||
|---|---|---|
|
||||
| **F** | `address.js` **(new)** | pure; edge→named-way resolution + shift recovery; synthetic district/block fallback; `cohort()` sizer. Zero fetch beyond the cache the shell already holds — needs one additive export, `getTownCache(key)`, because `index.html:130` hands the JSON to `registerTownCache` and drops its own reference. |
|
||||
| **A** | `plan_osm.js:510` | **one line**: `norm.shift = { shx, shz }` into the caller-supplied `opts.report`. Not on the plan. No golden moves. Optional — the vote recovers it without this. |
|
||||
| **B** | `minimap.js` | fog filter + known-extent framing + street labels + an **offscreen static cache** (today `drawStatic()` re-fills every lot every frame; adding up to 322 `fillText` calls per frame would turn a free overlay into a CPU text pass — invalidate on discovery change only). ~40 lines. |
|
||||
| **B** | `hud.js` | tooltip prints `NAME — Templeton Street`. One template literal. Plus the 25 m discovery probe on the existing `frame % 6` cadence. |
|
||||
| **B** | `buildings.js:453-478` | **the type label into the sign atlas.** The cell is 512×168; the name renders at ≤74 px centred at `py+84`, and `drawCrateMark` owns `py+128…py+152`. There is ~42 px of clear canvas in the **top** band (`py+13 … py+55`). Draw the registry `label` there at ~30 px. **+0 draws, +0 tris.** Without it, "the op shop on Barker Street" is unfalsifiable from the footpath: `drawSign` renders `shop.name` only, `opshop` and `stall` sign colours are byte-identical (`fixture_plan.js:29-37`), and only **5–18% of real-town shop names contain a type word** (median 13%). Every later clue term depends on this. |
|
||||
| **F** | `save.js` | `known` optional block, town-keyed, exact-and-loud validator matching `save.js:72-99`, `.slice()` in `adopt()`. |
|
||||
| **F** | the game layer | the dawn director + the paper line + the notebook DOM. |
|
||||
|
||||
**Zero new draws. Zero new triangles. Zero plan fields. No Lane C, D or E.** One round.
|
||||
|
||||
### Then, in order
|
||||
|
||||
| Round | What | Lanes | Why then |
|
||||
|---|---|---|---|
|
||||
| **+1** | The unmarked layer — UNMARKED §1–§5 with all nine corrections (`TYPE_W.stall = 0`; hoist the atlas alloc to `ensureAtlas()` — `buildings.js:452-462` allocates lazily inside `drawSign`, so **49 chunks across the corpus** where every shop is hidden would hit `new THREE.CanvasTexture(null)`; crate to `d/2 + 2.4`, outboard of the awning post line at `d/2 + 2.0`; discovery hook **inside** the successful-entry branch, after `index.html:491` and `:503`, not on the `:489` dispatch) | B + F | Needs the address layer to have a name for the nameless, and the fog to hide the hole. |
|
||||
| **+2** | The first-floor window band on 566 multi-storey shops | B | +0 draws, ~24 tris/shop. This is v8's own headline, paid. |
|
||||
| **+3** | The arcade, treated as an arcade — district-keyed, **synthetic only** (roof lights, tiled floor, reverb, E's 5.86 m `longbench`) + a mouth so it stops being a graph orphan (measured: arcade edge 14's endpoints are degree 1 and 1; the announcement is 14.7 m of bare ground) | B + E | 17 shops, one town, but it is the **only** place on the synthetic where a type-plus-place clue resolves to a set you can scan: `record\|arcade` = 9 hosts, 32 m spread, 7 s end to end. |
|
||||
| **+4… (a different epoch)** | Enterable upstairs | C + F + B | Priced in §5. |
|
||||
|
||||
---
|
||||
|
||||
## 5. THE HARD PROBLEMS, ANSWERED
|
||||
|
||||
### Replayability in a deterministic world
|
||||
**The town is the constant; the contents are the variable.** Verified across katoomba, fitzroy and castlemaine: re-seeding a cache town leaves shop ids, lots, types and names **byte-identical** — only `storeys` and `hours` re-jitter (`plan_osm.js:274`, `seedFor(citySeed,'osmshop',id)`). And `save.js:172` already treats a town change as travel, so you keep cash and collection.
|
||||
|
||||
So `?seed=N` is *the same Katoomba, different secrets*: the same streets, the same shopfronts, a different unmarked set, a different sale roster, different upstairs. **Your map survives; your knowledge of what's behind the doors does not.** That is exactly the right split for a game about knowing your town, and it is why the fog keys by town.
|
||||
|
||||
Two honest limits. On small towns the hidden-set reshuffle is weak — 13 of 24 towns pick from N=2 with one dominant candidate, so a Jaccard-based "different secrets" gate goes red on bendigo, braddon, launceston, redhill and glebe. That gate must be size-aware or the reserved slot must take a seeded draw. And the synthetic re-rolls everything including shop ids, so it is a different town at every seed, not the same one.
|
||||
|
||||
### Findable vs frustrating
|
||||
Answered by arithmetic, not by feel. **The director sizes the clue; the gate enforces the size.**
|
||||
|
||||
- Two terms (street + type) on a real town: **median cohort 1–4**, p90 2–18, **19–89% uniquely identified**. Without the street term: median 3–49.
|
||||
- Adding side-of-street: median 1–4, **27–89% unique**, worst tails cut (katoomba 18→15, fitzroy 10→7, brunswick 12→9).
|
||||
- **51–100% of a real town's shops (median ~85%) are clueable at ≤4 doors within ≤350 m.**
|
||||
|
||||
The gate must assert the **search**, not the answer. VERTICALITY's G7 (*"the clue's candidate set has ≥2 members"*) is a floor on the wrong quantity and goes green on an 87-door clue — measured, `toy|mainstreet` on the synthetic sends you to 87 doors over a 770 m spread, 167 seconds of walking. Rewrite as a **ceiling on doors-the-clue-sends-you-to, in [1, 4], with span ≤350 m, over 200 seeded days × 24 towns.** As corrected it fails on today's data, which is the point.
|
||||
|
||||
The failure mode is then never *"I wandered for four minutes and lost."* It is *"I haven't learned that part of town yet"* — with three outs and a permanent gain either way.
|
||||
|
||||
### The interiors machinery's one-room assumption
|
||||
**Don't touch it this epoch.** The minimum version's interior delta is exactly zero: a hidden shop, a sale shop and a normal shop all call the same `buildInterior(shop)`.
|
||||
|
||||
When it does come due, the price is known and it is not what the depth map said:
|
||||
- **The blocker is not dispose** — `ctx.disposeAll()` (`interiors.js:242-248`) tracks every geometry/material/texture regardless of sub-group, so N rooms through one Ctx dispose correctly today. `binUnderAim` and `stockInfo` both traverse, so a second room's crates are diggable for free.
|
||||
- **The blocker is not draws — but the margin is six, not one-nine-one.** `C-progress.md:841`: GLB-off 168, **GLB-on 344**, both against ≤350. `index.html:349` ships `useGLB: !NOASSETS`. The 159 the depth map cites is the GLB-off sweep. **Correct the house record.**
|
||||
- **The blocker is the grid.** `layout.js:337` makes ONE `makeGrid(W,D)`; `interior_mode.js:322-330` rejects any position with `|x| > W/2` or `|z| > D/2` **before consulting occupancy**. A fully built, fully lit second room outside that rectangle is unreachable.
|
||||
- Full bill: one merged grid with a doorway cell the Manhattan carve at `layout.js:408` may not close · a second path guarantee (`:391-408` flood-fills one spawn to one counter) · per-room keeper resolution (`interior_mode.js:396` is `.find`, not `.filter`) · a second `exits[]` entry with `toStreet:false` **and an opener** — the contract is already honoured at `:335`, which makes it a vacuous-gate trap: ship the door without the opener and a naive "the room has 2 exits" gate passes while nothing on earth opens it · a shell variant, because `shell.js:113-114` calls `buildShopfront` unconditionally and would give a first-floor room ground-floor glazing, an EXIT plate, and a backdrop canvas painting a kerb at row 74 · plus, for anything called "stairs", the two Y-clamps and a floor-height ground truth no structure carries, and the seven module singletons `enter()`/`exit()` own.
|
||||
|
||||
**≈ one Lane C round + one Lane F round for a second room. An epoch for stairs.**
|
||||
|
||||
### The 8-draw margin
|
||||
The pin is **292 draws at night, 282/283 at noon** (`F-progress.md:9,962`). Everything in layers 1–3 is DOM, 2D canvas, or data: **zero draws, and the count should be *identical*, not merely under budget.** Gate on `renderer.info.render.calls` matched exactly, **at night and at noon** — the window band is emissive at night and lands on the worst frame.
|
||||
|
||||
The standing refusal holds and should be written as law: **no new per-chunk mesh class, ever.** Measured live chunks: 25 live / 49 in the dispose window on every real town (all BIG_CITY, R=2); 23/42 on the synthetic. At 8 draws of margin, +1 draw × 49 chunks is not a cost, it is a breach. All depth geometry rides the existing merged lists — `boxMats`, `awnings`, `footGeos`, `windowGeos`, `doorGeos`, `signQuads` — the way the R38 yards change did. `makeInstanced` sizes to `list.length` with no cap; that mechanism is verified and it is what makes layers 4 and 5 free.
|
||||
|
||||
---
|
||||
|
||||
## 6. THE CUT LIST
|
||||
|
||||
| Cut | Measured reason |
|
||||
|---|---|
|
||||
| **CLEARANCE** (VERTICALITY R4) | The clue sends you to **18 doors at the median, 87 at the max** — the design counted correct answers, not searches, because the player can't see which hosts have an upstairs. And `minimap.js:52-58` already hands over the clue's three attributes. Re-found it or leave it out. |
|
||||
| **THE BLIND DOCKET** (UNMARKED §6) | Blind shop to nearest roster shop: **2,217 m hobart, 1,971 m fremantle, 1,538 m newtown** against a 704 m RUN budget. Impossible on 7 towns. On 9 others the blind shop is already **inside** the named clique, so the paper names the shop it says has no name. |
|
||||
| **The laneway leg** | **2 shops in the entire corpus front a `lane` edge** (both launceston), 0 on the synthetic. The synthetic's two laneways are graph orphans (endpoint degrees 1 and 1) and the east lane runs **under seven arcade lots** that overhang its carriageway by up to 1.63 m — `lotCollider` would eject the player from the lane. |
|
||||
| **The merged-grid back room** (VERTICALITY R5) | Surplus depth behind a max-depth room: **p50 = 0.10 m**, p75 1.96, p90 3.22. Only **31 of 493 shops (6.3%)** clear 4 m. It is a lottery on the archetype roll, and it is the only piece that touches the path guarantee. |
|
||||
| **Real-town arcade and lane flavour** | `'arcade'` on a real town is OSM `pedestrian\|footway\|path\|steps\|cycleway` — **990 edges, 39,276 m of ordinary suburban footpath.** Roofing it roofs 39 km of pavement. Arcade treatment keys off `district.kind`, synthetic only. Make it a code rule. |
|
||||
| **District-keyed skins on real towns** | `plan_osm.js:305` is one unconditional `addDistrict('mainstreet')`. All 24 caches: 1 district, all lots `use:'shop'`. Code rule, not a caveat. |
|
||||
| **The facade-skin clue term** | 47 files in `web/assets/gen/`, no colour or material name anywhere in the codebase, and skins are shared across types (`facade-corrugated` in pawn/stall/band_room). Leaks type without adding information. |
|
||||
| **Physical corner street signs** | +1 draw × up to 49 live chunks against 8 of margin. Already refused; endorsed. |
|
||||
| **Map omission for unmarked shops** | Punches a hole up to **16.8 px wide — 6.5× a lot width** — in a contiguous terrace on the synthetic, the one town where the map is legible and the default and classic boot. Fog handles it structurally; omission does not. |
|
||||
| **The segments-remaining director radius** | Median shops reachable from a random shopfront: **marrickville 1, braddon 2, redhill 3.** Narrows the pool below the clue before the clue speaks. |
|
||||
| **The window card as a *cue*** | 28 px in a 168 px cell on a 0.25 m quad = **0.042 m of letter, legible to ~3.8 m** — inside the free 6 m tooltip at `hud.js:302`. Demote to dressing. One ranged cue exists: the crate. |
|
||||
|
||||
---
|
||||
|
||||
## 7. WHERE IT SITS
|
||||
|
||||
**This is v9 — THE CITY IS THE GAME — with a one-round probe landing inside v8.**
|
||||
|
||||
Three reasons.
|
||||
|
||||
1. **v8 is texture; this is mechanic.** Ambient audio, per-town character, the magpie, backyards — every v8 item makes the city *feel* more like a place. This makes it *function* as one. Folding a new win condition into an epoch about atmosphere leaves the epoch un-nameable, and PROCITY names its epochs for a reason.
|
||||
2. **The probe belongs in v8 because it decides whether v9 exists.** Fog + the address layer + the type label is one round, zero draws, zero geometry, and it is falsifiable on its own: does a fogged, street-named town make you want to walk it? If the answer is no, v9 doesn't happen and we spent one round. If the answer is yes, v9 has a proven core before anyone models a polygon. **Never build the geometry first.**
|
||||
3. **v8's own headline is already the bridge.** SOMEONE LIVES HERE is answered — literally, on 566 buildings, at +0 draws — by the first-floor window band. `audio.js:190-193` has been asserting that upper floors are inhabited since it shipped. Land the band in v8 and the epoch keeps its name. Then v9 makes the city knowable. Then, if it earns it, a later epoch makes those upper floors enterable, and by then the window is already there to walk up to.
|
||||
|
||||
**v8 wave 2:** the address layer · the fog · the type label · the first-floor window band.
|
||||
**v9 — THE CITY IS THE GAME:** the notebook · the unmarked · the sale director · the arcade.
|
||||
|
||||
---
|
||||
|
||||
## 8. WHAT NEEDS JOHN
|
||||
|
||||
Six taste calls. Nobody else can make them.
|
||||
|
||||
**1. Does the default boot become a real town?**
|
||||
This is the big one. The synthetic is 493 shops on **22 streets**, with 304 distinct names and a 17-way collision (`The Toy Nook` ×17, `Marbles & Kites` ×14, `The Book Cellar` ×12). Measured: **32% of its shops are clueable at ≤4 doors, against 51–100% (median ~85%) on the real towns.** Its best clue cell is a median of 9 doors. It is a magnificent strip and it is not a town you can learn. A bare `index.html` boots it today; `?classic=1` freezes it byte-identical forever regardless, so the covenant is not at risk. But if the win condition is knowing your town, the town the game opens on should be one that can be known. *(A smaller version of the same call: let the synthetic's hunt be district-scale — "somewhere in the backstreets" — an honest browse rather than a broken precision hunt.)*
|
||||
|
||||
**2. How harsh is the fog?**
|
||||
Three settings, in ascending mercy: **(a)** shops you've walked within 25 m of · **(b)** shops you've actually entered · **(c)** *streets* you've walked, with every shop on a walked street revealed. (c) is the most map-like and the most forgiving — you learn a town in streets, not in shopfronts — and it makes the address layer the unit of knowledge, which is tidy. (b) is brutal and would take a very long time to fill in. My instinct is (c); it's your call.
|
||||
|
||||
**3. Does the clue name the type or the shop?**
|
||||
*"the record shop on Templeton Street"* teaches you the city. *"Rotation, somewhere up Templeton"* teaches you the shops. Type-clues make the town the thing you learn; name-clues make the roster the thing you learn. They are different games and the arithmetic supports either.
|
||||
|
||||
**4. Does an unmarked shop ever get a name?**
|
||||
Or does it stay, forever, *"the one with the crate out front, Lyttleton Street"* — a place you know by where it is and not by what it's called. The second is better writing and it is also the harder sell to a player who wants a list.
|
||||
|
||||
**5. Is being late a discount or a loss?**
|
||||
Arrive in segment 4 and the good ones are gone (`gone` grows with arrival time, seeded) — or the sale is simply over and the door is locked. The first is a difficulty curve; the second is a deadline. The code supports both at the same cost.
|
||||
|
||||
**6. Do keepers give directions?**
|
||||
It is the anti-frustration valve, it costs almost nothing, it rides the existing rumour seam — and it is the single most *someone lives here* thing in this entire document. It is also the moment the city stops being an opponent and becomes a place with people in it who are on your side. That's a tonal decision, not an engineering one.
|
||||
|
||||
---
|
||||
|
||||
### Two things for the house record, regardless of what you decide
|
||||
|
||||
1. **The interior draw margin is six, not one-nine-one.** `C-progress.md:841` — GLB-off 168, GLB-on **344**, budget ≤350, and `index.html:349` makes GLB-on the default boot. The depth map's "draw budget is not the problem" line is measured off the wrong sweep and has to come out before it authorises a room somebody can't afford.
|
||||
2. **Street names are already in the repo.** 17,835 named ways of 19,132, 93.2%, on 23 towns, recoverable to a median of 0.03–0.90 m with no citygen change and no golden move. Whatever the shape ends up being, that data should stop being dropped on the floor.
|
||||
BIN
docs/shots/laneB_r37_kerb/kerb_after_junction.jpg
Normal file
|
After Width: | Height: | Size: 362 KiB |
BIN
docs/shots/laneB_r37_kerb/kerb_after_katoomba_acute_4deg.jpg
Normal file
|
After Width: | Height: | Size: 535 KiB |
BIN
docs/shots/laneB_r37_kerb/kerb_after_poster.jpg
Normal file
|
After Width: | Height: | Size: 250 KiB |
BIN
docs/shots/laneB_r37_kerb/kerb_after_streetlevel.jpg
Normal file
|
After Width: | Height: | Size: 241 KiB |
BIN
docs/shots/laneB_r37_kerb/kerb_before_junction.jpg
Normal file
|
After Width: | Height: | Size: 327 KiB |
BIN
docs/shots/laneB_r37_kerb/kerb_before_poster.jpg
Normal file
|
After Width: | Height: | Size: 221 KiB |
BIN
docs/shots/laneB_r38/01_backyard_hoist_washing.jpg
Normal file
|
After Width: | Height: | Size: 176 KiB |
BIN
docs/shots/laneB_r38/03_residential_row_over.jpg
Normal file
|
After Width: | Height: | Size: 236 KiB |
BIN
docs/shots/laneB_r38/04_residential_street_front.jpg
Normal file
|
After Width: | Height: | Size: 157 KiB |
BIN
docs/shots/laneB_r38/05_magpie_perched.jpg
Normal file
|
After Width: | Height: | Size: 113 KiB |
BIN
docs/shots/laneB_r38/06_magpie_swooping.jpg
Normal file
|
After Width: | Height: | Size: 127 KiB |
BIN
docs/shots/laneB_r38/07_character_darwin_reddust.jpg
Normal file
|
After Width: | Height: | Size: 141 KiB |
BIN
docs/shots/laneB_r38/08_character_hobart_temperate.jpg
Normal file
|
After Width: | Height: | Size: 136 KiB |
BIN
docs/shots/laneB_r38/09_character_bendigo_grassdry.jpg
Normal file
|
After Width: | Height: | Size: 138 KiB |
31
docs/shots/laneB_r39/README.md
Normal file
@ -0,0 +1,31 @@
|
||||
# Lane B — Round 39, THE FOG (item 39.2)
|
||||
|
||||
Taken in a fresh context on port-isolated **no-store** servers (`:8591` treatment, `:8592` =
|
||||
`git archive HEAD`), assets/vendor symlinked so the other lanes' mid-round waves are identical on
|
||||
both sides. Every number behind these pictures is in `docs/LANES/LANE_B_NOTES.md` §39.
|
||||
|
||||
| file | what it shows |
|
||||
|---|---|
|
||||
| `fog-00-head.png` | **HEAD, synthetic** — all 493 shops handed over on the first M press, 26,506 shop-colour px |
|
||||
| `fog-01-before.png` | **R39, fresh boot** — 0 shops known, the 240 m frame, a 25 m scale bar |
|
||||
| `fog-02-after.png` | **R39, after 900 m** — 113 shops, 7 segments, A's district labels (*"the south end of the main street"*) |
|
||||
| `fog-00-head-katoomba_real.png` | **HEAD, katoomba** — 993 edges of grey spaghetti; at 1.42×1.89 px the 72 shops rasterise to **zero** pixels of their own colour |
|
||||
| `fog-01-before-katoomba_real.png` | **R39, fresh boot** — the 240 m frame, a shop lot at 29.4×39.2 px |
|
||||
| `fog-02-after-katoomba_real.png` | **R39, after 900 m** — 34 shops on **Katoomba Street**, with **Edwards Street** running off it (Lane A's address layer) |
|
||||
| `map-classic.png` | `?classic=1` — the pre-R39 map, hash-identical to HEAD's |
|
||||
| `sign-*-BEFORE.png` | the default boot's spawn **as shipped since R32**: synthetic — a footpath facing a plaza with not one shopfront in frame; katoomba — **standing in a paddock facing the blank back wall of a building**. 3 shops within 25 m, **0 of them in front of you**, 0 discovered |
|
||||
| `sign-*-AFTER.png` | the same code with the front normal corrected (`+sin`): **on the footpath under the awnings, signage down the strip**, 4 shops within 25 m, **4 in front**, 4 discovered |
|
||||
|
||||
| `magpie-AFTER-glb-zoom.png` | Lane E's 894-tri GLB **in the engine**, perched on the lamp arm, `asset:'glb'` — pied, legged, unmistakable. **755 px of true white** in the crop |
|
||||
| `magpie-BEFORE-primitive-zoom.png` / `magpie-AFTER-primitive-zoom.png` | the procedural fallback (`?noassets=1`) before and after the re-placed white: pale area **213 → 427 px**, but true white **33 → 28** — the underside gets no sun, so the fix reads grey. The measured ceiling on a primitives-only fallback, and the reason the GLB is the answer |
|
||||
| `magpie-AFTER-glb-swoop.png` | the swoop, **head-on** — proof the `rotateY(π)` is right in flight, not just on the perch |
|
||||
| `magpie-AFTER-glb-flank.png` | the honest in-situ frame (70° FOV, 14 m): the bird is a speck. E's A/B sheet owns the size question; the `-zoom` frames are 12° FOV **inspection** shots |
|
||||
|
||||
*(All three magpie arms measured `+1 draw` with the bird on screen and `+0` with it off, same page, same
|
||||
frame: GLB +894 tris, primitive +182, and the placement fix is +0 tris in-engine.)*
|
||||
|
||||
The `sign-*` pair is **booted code on both sides** — two port-isolated no-store servers, `:8702`
|
||||
serving the fog commit (old sign) and `:8701` the fix. Nothing is monkey-patched.
|
||||
|
||||
Katoomba's BEFORE is the R31 playtest's own complaint, verbatim — *"faced dirt and building-backs"* —
|
||||
which R32 was written to fix and, because of the sign, never did.
|
||||
BIN
docs/shots/laneB_r39/fog-00-head-katoomba_real.png
Normal file
|
After Width: | Height: | Size: 120 KiB |
BIN
docs/shots/laneB_r39/fog-00-head.png
Normal file
|
After Width: | Height: | Size: 95 KiB |
BIN
docs/shots/laneB_r39/fog-01-before-katoomba_real.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
docs/shots/laneB_r39/fog-01-before.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
docs/shots/laneB_r39/fog-02-after-katoomba_real.png
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
docs/shots/laneB_r39/fog-02-after.png
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
docs/shots/laneB_r39/magpie-AFTER-glb-flank.png
Normal file
|
After Width: | Height: | Size: 505 KiB |
BIN
docs/shots/laneB_r39/magpie-AFTER-glb-swoop.png
Normal file
|
After Width: | Height: | Size: 185 KiB |
BIN
docs/shots/laneB_r39/magpie-AFTER-glb-zoom.png
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
docs/shots/laneB_r39/magpie-AFTER-primitive-zoom.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
docs/shots/laneB_r39/magpie-BEFORE-primitive-zoom.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
docs/shots/laneB_r39/map-classic.png
Normal file
|
After Width: | Height: | Size: 88 KiB |
BIN
docs/shots/laneB_r39/sign-katoomba-AFTER.png
Normal file
|
After Width: | Height: | Size: 690 KiB |
BIN
docs/shots/laneB_r39/sign-katoomba-BEFORE.png
Normal file
|
After Width: | Height: | Size: 746 KiB |
BIN
docs/shots/laneB_r39/sign-synthetic-AFTER.png
Normal file
|
After Width: | Height: | Size: 844 KiB |
BIN
docs/shots/laneB_r39/sign-synthetic-BEFORE.png
Normal file
|
After Width: | Height: | Size: 912 KiB |
BIN
docs/shots/laneB_r40/r40_arcade_lane_after.png
Normal file
|
After Width: | Height: | Size: 522 KiB |
BIN
docs/shots/laneB_r40/r40_arcade_lane_before.png
Normal file
|
After Width: | Height: | Size: 510 KiB |
BIN
docs/shots/laneB_r40/r40_arcade_lane_classic.png
Normal file
|
After Width: | Height: | Size: 561 KiB |
BIN
docs/shots/laneB_r41/after_band_room_signage.png
Normal file
|
After Width: | Height: | Size: 782 KiB |