Compare commits

...

143 Commits

Author SHA1 Message Date
m3ultra
97b98b15af Fable (integrator): ROUND 40 OPEN — the no-rulings round, and the harness for John's playthrough
John: 'just keep building — big bugtest playthru feedback sesh later.' R40 spends the
filed R39 debts that need no decision: E closes the transmission gate's demonstrated
blind spot (scan every loadable path, both control arms), A closes or re-files D1-D4
with numbers, B settles the ?r=3 307 breach and gives ?classic=1 the town selector
(ruled), E re-designs the arcade posts WITH the roof, D verification-first on the
0-door-points-on-footpaths finding. Keystone 40.6: F builds the playtest harness -
F8 drops a note (pos/shop/street/seed/clock/flags/fps/draws + screenshot + one-liner),
delta-law persistence, one-JSON export, and ?bugtour=1 strings the bookmarks into a
guided walk covering every system. The parked three (bin contents, fog default, v9
third layer) plus the audio listen pass become tickets the moment John plays. No tag
this round.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 23:41:23 +10:00
m3ultra
9054c77b2d Fable (integrator): ROUND 39 CLOSED — v9 is open. The town knows its streets, the map is a map, and the interior margin is 162
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 22:49:04 +10:00
m3ultra
076b972f71 Lane F R39 (v9 WAVE 1, CLOSE): QA GREEN 10/0/0/0 — and the interior margin is 162, not 21 and never 6
FIVE GATES, FIVE CONTROLS, each demonstrated in the same run. New F-owned tools wired into qa.sh as
Gate 8: r39_address.mjs · r39_transmission.py · r39_runtime.py · r39_shots.py (--no-r39 skips the
browser half). Lane A's selfcheck reproduces exactly: 157,647/157,647 ALL GREEN, fingerprint
0x5f76e76 — ZERO GOLDENS MOVED, in a round that added an address layer, a fog ledger, a spawn move
and a new interior fitting.

THE ROUND'S BIGGEST NUMBER IS NOT THE ONE THE LEDGER ASKED FOR. Two port-isolated no-store roots,
the same inode for every byte except one file — the shipped bookshelf GLB vs a copy with
transmissionFactor:1 re-inserted (BIN sha1 IDENTICAL, only the JSON chunk moved) — both through
interior_test.html's own drawSweep:

  GLB-on worst   329 (opshop/hall)  ->  188 (dept/auto)     -141
  GLB-off worst  162 (opshop/hall)  ->  162 (opshop/hall)   0 — the control's own control
  margin vs <=350            21     ->  162                 +141

opshop 329->174 (-47%) · book 247->125 (-49%) · every other type 0, exactly the shape Lane C
predicted from an isolated room, at sweep scale, by a harness C did not write. The worst interior
room in the game is now dept/auto — a room with no bookshelf in it. The default boot fetches these
GLBs from the public depot, so all four R39 GLBs were fetched off digalot.fyi and scanned: sha1
identical to the local tree, 0 transmissive materials, 4/4. Binding fact #3's third and final value.

39.1 ADDRESS — GREEN. 1,192/1,219 shops (97.8%), 23/23 'ways', 857/857 membership agree / 0 wrong
(and the control is asserted to have CHECKED 857 edges). No cache -> supplier 'district', 0 names,
still labels 72/72 from district+block. RED ARM: a 3 m shift error RAISES adelaide 89 -> 98 resolved
shops (Lane A's number to the digit) while INVENTING 9 names; stats().shiftCheck reads DISAGREE on
all 72 perturbations. And the honest limit: at 3 m the membership control reads 0 disagreements,
because those shops front ways OSM leaves unnamed — only the shift cross-derivation catches it.

39.2 FOG — GREEN, both arms on one boot. HIDES 4/72 at spawn, 0 of 61 distant shops known. REVEALS
2,125 m -> 58 shops. FRONT TEST: probe from 5 m BEHIND each of 59 unknown shopfronts, 0 learned.
+0 DRAWS AND +0 TRIS, four freshly launched browsers, both orders, drained, 3 reads each, with the
arms asserted distinguishable first. 8/8 corrupt saves rejected, state untouched. Null store on
classic/game=0/fog=0; game=0 and fog=0 pixel-identical, default differs.

39.3 THE SIGN — GREEN. Shops in front of the player at spawn: 4/4 · 4/4 · 4/4 · 1/1, and ZERO on all
four with the pre-R39 sign re-derived on the same tree. The live spawn is asserted equal to the +sin
derivation, so the shell is what is scored. Re-baselined WITHOUT falsifying a release record: the
v7 tour and v7-beta frames are kept as history; docs/shots/laneF_r39/ ships the new pair with its
own BEFORE. The R35 gate itself is pose-independent and was green either side.

39.4 RUMMAGE BIN — GREEN. 120 rooms: tub 60/60 both arms, kind:'bin' 0/60 default vs 60/60 armed,
0 carves, 60/60 deterministic, reachable 0.80-1.03 m by a flood fill written here. THE CONTENTS DID
NOT SHIP: on the default street boot the tub is kind:'prop', 0 dig targets, E opens nothing.

39.5 TRANSMISSION VALIDATOR — GREEN and it FIRES: hard-fails rc 1 on the fabricated glass asset AND
passes on the shipped tree.

THREE CORRECTIONS, ALL MEASURED. A's "a 30 m shift returns 0 street names" — it returns 40/12/47
wrong ones; a big shift lands you on the next street, it does not degrade to null. B's "a fresh
export has no `known` key" — true of ?fog=0, no longer of a default boot, because B's own +sin fix
means you spawn in front of four shopfronts. And my own: I first aimed the transmission control at
web/assets/models/ when validate_manifest.py reads 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.

HELD -> LANE E: check_transmission() skips any GLB with no local _normalized copy and never scans
web/assets/models/. Demonstrated with a live control — remove the local copy and the same glass
asset passes rc 0. A depot-only transmissive GLB (the exact shape of longbench and streetlight)
would ship unseen.

No tag.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 22:47:03 +10:00
m3ultra
43c7f4281e Lane B R39: §39 note — the classic fetch-delta arm on the magpie GLB, as a table
The network-log assertion behind the previous commit, written into the lane notes so F's gate can
read the numbers rather than re-derive them: default boot 1 magpie GLB request (asset 'glb', 894
tris) · ?classic=1 0 · ?magpie=0 0 · ?noassets=1 0 (asset 'primitive', 182 tris).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 21:40:42 +10:00
m3ultra
f0b9a1d30f Lane B R39 (Fable's ruling): the magpie takes E's GLB use-if-ready, and the fallback's white moves
+1 draw / +894 tris with the bird on screen, +0 with it off — measured same page, same frame, by
toggling the InstancedMesh count. ?noassets keeps the 182-tri procedural bird (+1 / +182), so this is
furniture.js's use-if-ready SECOND path, not a substitution. state.asset reports which bird is bound.

THREE THINGS MEASURED BEFORE THIS WAS SAFE, AND ALL THREE WOULD HAVE SHIPPED WRONG:
 · E's bird faces +Z and this file flies -Z (Matrix4.lookAt's -Z column is the travel direction).
   Sliced the GLB's 894 tris along Z in ten bins: +Z end mean y 0.31, half-width 0.009 (head + bill);
   -Z end mean y 0.028 (tail). Without rotateY(PI) the magpie swoops TAIL-FIRST. This is the FIFTH
   -Z/+Z instance and the first caught BEFORE it shipped — because R39's lineage table said to point
   a measurement at it. Verified in pictures: perched head-leading, and a swoop frame taken head-on.
 · Its origin is at the FEET (GLB law minY=0) while PERCH_Y is a body-centre height: dropped 0.20 m
   so it stands on the lamp arm (4.12) instead of hovering. `folded` is 1 for the GLB — it IS the perch.
 · THE FETCH IS ARMED BY createMagpie(), NOT BY THE IMPORT. index.html imports this module
   unconditionally and gates only the CONSTRUCTION, so a module-scope loadGLB (furniture.js's shape)
   would have put a brand-new GLB request on the ?classic=1 boot — a fetch delta on the one boot that
   is not allowed one. Asserted on the network log, not on the code: default 1 request (asset 'glb',
   894 tris) · ?classic=1 0 · ?magpie=0 0 · ?noassets=1 0 (asset 'primitive', 182).

AND ONE MY EDIT INTRODUCED INTO ANOTHER LANE'S TOOL: a module-scope loadGLB also fires GLTFLoader's
fetch path under node, so Lane E's pipeline/dump_bird.mjs (which imports this module unmodified) died
with "ProgressEvent is not defined". Gated on IN_BROWSER as well as ?noassets; E's tool then ran
against my edit and reported 182 triangles / 216 verts — the placement fixes are exactly +0 tris.

THE FALLBACK, FIXED AND THEN MEASURED — a half-win, stated as one. E is right that the white bars sat
3 mm ABOVE horizontal wing planes, so from below (the player is always below: perch 4.32 m, EYE 1.62)
the black wing occluded its own flash: bar y 0.048 -> 0.042. Same class of fix, my own arithmetic, for
the other two marks: the nape was BURIED IN THE HEAD (head-sphere surface at z=-0.10 is y 0.0728, the
box topped out at 0.067) so it moved behind the head to z -0.07; nape and rump thickened 0.014->0.030
and 0.02->0.034, because from the flank a 14 mm slab on a 166 mm bird is 1.3 px at 64 px. All +0 tris.
Measured in the same crop of the same pose: pale area 180 -> 399 px, dark 3,017 -> 2,755 — but TRUE
white 33 -> 28, because a downward-facing surface gets no sun. E's GLB reads 755. The fallback cannot
be made pied with primitives at zero cost; the problem is photometric as well as geometric.

Stale comments corrected: magpie.js:9 and :123 said 154 triangles; it is 182 (E's ask 1).

FILED, NOT FIXED: 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 from the plan, confirming E's). AWNING_DEPTH
2.2 is tuned for a 9 m footpath. One clamp fixes it, but it changes default-boot geometry for a
district whose whole treatment is a scheduled item, and the post line should be designed with the roof.

Lane E's transmission recall checked, not assumed: longbench, streetlight and magpie as staged in
web/assets/models/ carry no KHR_materials_transmission. My lane wires neither of the first two today.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 21:37:24 +10:00
m3ultra
a982f3ce67 Lane B R39 (Fable's ruling): THE SIGN — +sin lands; the fourth instance of the -Z/+Z confusion
index.html:428 (Lane D's patronage door points, since R8) and :500 (doorOf, the R32 cluster-pose
spawn) computed the shop front as (-sin ry, -cos ry) — the BACK of the building. buildings.js:410,
:583-597 put the facade, the door and the doorRect at local +Z; dbg.js:53 says so in words;
minimap.js has always drawn its front tick there. Two characters, both sites.

THE PATRONAGE DOOR POINT, on the footpath (registry.js's own roadWidth/vergeBand split, outside the
carriageway, outside every lot):
  synthetic     0/493 -> 427/493     median 26.22 m -> 9.40 m from the centreline
  katoomba_real  0/72  ->  67/72     19.10 -> 3.90
  fitzroy_real  0/139 -> 139/139     19.10 -> 3.90
  bowral_real    0/30  ->  28/30     21.10 -> 5.90
Not one door point in the corpus was on a footpath. Lane D's peds ducked into the back fence.

THE SPAWN, booted code both sides (:8702 the fog commit, :8701 the fix, nothing monkey-patched):
  synthetic  (25.5,-17.9) 3 shops within 25 m, 0 in front, 0 discovered
          -> (6.1,-19.8)  4 within 25 m, 4 in front, 4 discovered
  katoomba   (-219.0,-471.3) 3/0/0  ->  (-200.0,-474.4) 4/4/4
And in the picture (docs/shots/laneB_r39/sign-*): katoomba's shipped boot opens STANDING IN A
PADDOCK FACING THE BLANK BACK WALL OF A BUILDING — the R31 playtest's own complaint, the one R32 was
written to fix and, because of the sign, never did.

NO GOLDEN MOVES: SPAWN and the patronage points are runtime pose, computed in the shell from a plan
it never writes. Lane A selfcheck either side: fingerprint 0x5f76e76 -> 0x5f76e76, 157,647/157,647.
?classic=1 never enters the branch and keeps the frozen (-96, 9) pose.

-> LANE F: the R32 cluster-pose spawn gate and the R35 first-five-minutes smoke both photograph this
pose and need re-baselining. The falsifiable arm that needs no picture: at spawn, the count of shops
within 25 m that are IN FRONT of the player must be > 0 (0 on 4/4 towns before, 4 on 4/4 after).

THE LINEAGE, written down as Fable asked — this is the FOURTH instance of the same confusion:
R13 RY_FLIP (the band 180 deg off) · R15 the venue poster on the back wall (15.9 m from the facade,
0.07 m from the back) · R27 every real-town shopfront facing the paddock (dot = -1.000 on 35/35 and
72/72 lots; CITY_SPEC:71 says -Z, :335 says +Z) · R39 these two. Every time the rig/pose convention
is local -Z and the visible front is local +Z, and every time it 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.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 21:17:48 +10:00
m3ultra
12eb571357 Lane E R39 (6/n): THREE GLBs shipped as GLASS, not one — transmissionFactor 1 stripped, published, sha1-verified, and now GATED. Plus the arcade's three meshes.
Lane C measured the bookshelf's transmission halving interior draws (opshop/hall 191->104, book/hall
101->51, dept/hall 116->116 control). The cause, with vendored line numbers: three.module.js:16209
enters renderTransmissionPass(opaqueObjects, ...) whenever currentRenderList.transmissive is
non-empty, and :16433 re-renders the WHOLE OPAQUE LIST into a render target — every opaque draw
issued twice. Trigger is material.transmission > 0 (:6890), set from the glTF at GLTFLoader:1202.
And at 1.0 it is not 'a bit glassy': :494 does totalDiffuse = mix(totalDiffuse, transmitted.rgb,
material.transmission) — the diffuse term is REPLACED by the backdrop.

I SCANNED ALL 103 GLBs. Three carried it:
  procity_fit_bookshelf_01      mtl_10218_Bookshelves_v1     shipped and hurting (C's)
  procity_street_longbench_01   lambert3SG                   NOT WIRED — the 5.86 m arcade bench
                                                             the v9 charter names, on my R38
                                                             recommend list for Lane B
  procity_street_streetlight_01 Streetlight_HighResSG3       NOT WIRED — one per ~18 m of street
The two unwired ones are the worse finding: the street budget is 292/300 with EIGHT draws of margin,
and a pre-pass there costs another ~292, not eight. Armed, not fired.

FIXED SURGICALLY. pipeline/strip_transmission.py rewrites only the JSON chunk and copies BIN through,
printing sha1(BIN) before and after: BIN IDENTICAL on all three, tris/dims/mtl/img/tex unchanged.
KHR_materials_ior deliberately left (ior does not trigger the pre-pass).

PUBLISHED + VERIFIED under John's R22 standing authorization via the passwordless tailnet ingress:
6/6 HTTP 200 off the PUBLIC depot, byte counts exact, sha1 identical, depot copies re-scanned clean.
_published.json 56 -> 59.

GATED, which matters more than the fix: validate_manifest.py now HARD FAILS on transmissionFactor>0
with the three.js line numbers and the fix command in the message; glb_stat.py grew a transmission
column and a loud footer. Nobody looked for three epochs because a bookshelf is obviously not glass.

ALSO: the arcade's three meshes, published and catalogued the same round they were generated —
aframe_board 366 tris (<=16 inst), blade_sign 299 (17 inst), keycutter_sign 498 (1 inst). manifest
furniture 13->16, GLBs 36->39, validate 0 errors 0 warnings. blade_sign ships FLAGGED: its back face
carries TRELLIS-hallucinated art where the concept had no data, which is one more reason the
+0-draw perpendicular-quad answer is the recommendation and this GLB is the fallback.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 21:11:34 +10:00
m3ultra
0ee7a84710 Lane B R39 39.2: THE FOG — the map shows what you walked past, and is finally readable
minimap.js drew every lot in the plan from the first frame (493 shops on the synthetic, one M
press, against 5,330 m / 4.8 game-days of walking to earn it). It now draws what has been walked
past AND FRAMES ITSELF TO IT — a shop lot goes 0.83x1.11 px -> 29.4x39.2 px on bowral_real, 35.3x.

ZERO DRAWS, measured as an A/B: two port-isolated no-store servers, HEAD vs treatment, the same 15
stations x 4 yaws compared as JSON before either sweep runs. Draw calls IDENTICAL on 60/60 samples
at noon and at night; with the gig layer off both sides (the venue queue's async rigs were the only
noise) NIGHT is identical in draws AND triangles. Map mode cannot cost a draw structurally: the
frame loop never calls composer.render() in the map branch.

- discovery.js (new): a 25 m PROXIMITY PROBE, not onChunkBuilt (R=2 => the outermost live chunk
  corner is 181 m out), on a plan-derived spatial hash — 1.09-1.37 us a probe, every 6th street
  frame = every 0.46 m at WALK. Plus a side test: you must be in FRONT of the shopfront. 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.
- save.js: optional `known` block under the delta law, keyed by TOWN (re-derived: 23 caches x 3
  seeds, shop/edge ids and lot geometry all identical; the synthetic alone keys @<seed>). Bounded
  by the plan (max 493 shops / 2,593 edges) at 8,192, 64 towns FIFO, ints only. Whole corpus fully
  learned = 137,547 B (2.6% of quota); 482 B after a 500 m outing. Eight reject arms demonstrated.
- classic BY CONSTRUCTION: createMinimap(plan, null) is the pre-R39 map and the file has no flag
  test at all. ?classic=1 / ?fog=0 / ?game=0 map canvases are hash-identical to HEAD's.
- Lane A's address layer consumed (getTownCache + createAddresses): street names along walked
  streets, deduped; the synthetic gets district labels from the same line. No town-type branch.

FILED, NOT FIXED: index.html:384 (Lane D patronage door points) and :450 (the R32 spawn) use
(-sin ry, -cos ry) — the BACK of the building. buildings.js:410/583-597, dbg.js:53 and minimap.js
all use local +Z. Measured against each lot's own frontEdge the +sin point is 0.00 m from the kerb
and the shipped one 15.25 m the wrong side, 493/493 and 72/72 shops. At the shipped spawn 0 of the
shops within 25 m are in front of you on all four towns tested; at the corrected one, all of them.
Two characters, but it moves the default boot's opening pose mid-round — Fable's call.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 21:04:34 +10:00
m3ultra
41d9878574 Fable: own the stale-344 error (I verified a doc, not the tree — margin is 21 not 6) + amend RED TICKET DAY with C's stall findings
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 21:01:42 +10:00
m3ultra
eeba920d68 Lane C R39 §39.3: the op shop gets a bin — the verb table, and the margin was never six
THE TABLE (LANE_C_PUB §10, the deliverable): what a player can actually do in each of
the 12 shop types, counted in 48 rooms per type. `record` digs (4.6 bins/room) and is the
only room where anything can be sold. `stall` digs — in 27 of 48 rooms; the other 21 (44%)
have NO crate, a measured correction to the round's binding fact #5. The other ten types
offer looking. `?stock=real` is opt-in, so the default boot has zero `buyMesh` anywhere;
garments/snacks/magazines/treasures are unbuyable in any mode; `interactable:true` on
cases/fridges/returns/arcade is read by nothing.

THE MECHANISM: `rummageBin` — a castored galvanised dump tub, rim 0.76 m, heaped with
stickered bric-a-brac and a hand-lettered "EVERYTHING $2" card. 72 rooms: placed 72/72,
reachable 72/72 (0.77–1.15 m), 0 carves, pathOK 72/72, deterministic 72/72, and the rest
of the room byte-identical to pre-R39 72/72 (the spec is appended last). Armed end-to-end:
aim → dig → 16 offers → BUY → bin depletes. Default: 0 bins, dig never opens.

THE CONTENTS ARE NOT SHIPPED. `o.dig` defaults false, so the tub is furniture and the
economy is untouched; `opts.fittingOpts.rummageBin.dig` (or `?opshopdig=1`) arms it. Held
for John with the numbers: 44 record + 36 stall vs 147 op shops across 23 towns.

THE BUDGET: `C-progress.md:841`'s GLB-on 344 is R9-era and stale — today's tree measured
319 before I touched it, so the margin was 31, not six. The bin costs +5 GLB-off / +10
GLB-on at opshop/hall; every other type byte-identical. Margin now 21. Also: `slot.pool`
stops the scatter fill minting a material per item, so 12 goods cost less than 9 did.

FILED, NOT FIXED: Lane E's bookshelf GLB ships transmission:1, which triggers three.js's
transmission pre-pass and issues every draw in the room twice — opshop/hall 191→104,
book/hall 101→51. Roughly half the interior budget. E's asset, so E/F rule.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 20:59:42 +10:00
m3ultra
c2e44821c7 Lane E R39 (5/n): two corrections to my own R39 arithmetic, both measured
- The magpie's white, restated exactly. The wing-bar occlusion claim is the strong one and it now
  carries its footprint proof: the bar's (x 0.174-0.316, z 0.005-0.055) lies ENTIRELY inside the
  wing's (x 0-0.34, z -0.085-0.065) with the bar 3 mm above it, so from below the wing hides it.
  The nape box tops out at 0.067 against a head surface at 0.0728 (I had written 0.065, and had
  the sliver on the wrong side). And the flank argument is now the honest one: nape and rump are
  14 mm and 20 mm slabs on a 166 mm bird = 1.3 px and 2 px of white at 64 px.
- THE CLASSIC ARGUMENT FOR THE ARCADE IS INVERTED relative to R38 and I nearly carried the wrong
  one over. R38's ground palette is classic-safe BY ABSENCE (character.js returns frozen literals
  for a null town key; classic has none). The arcade district exists ONLY on the synthetic, and the
  synthetic IS the classic boot — so the kit lands squarely on the covenanted town and needs an
  EXPLICIT flag test, ground.js's VERGE shape, not the by-absence one.
- arcade_measure.mjs now also counts chunks: arcade lots span exactly 2 (0,0 and 0,1) on all five
  seeds, so a per-chunk arcade class is +1 draw in 2 chunks and +0 in the other 23.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 20:58:26 +10:00
m3ultra
ab0bf1d373 Lane E R39 (4/n): signpost E-progress.md, which stopped at R12 and has since R13
Not a backfill — a warning. The live Lane E record is docs/LANES/LANE_E_NOTES.md (cross-lane) and
pipeline/AUDIT.md (provenance + method). A progress file whose newest entry is 27 rounds old is a
trap for the next reader; say so at the top rather than quietly resurrect it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 20:55:47 +10:00
m3ultra
7fe6a4a40e Lane E R39 (3/n): the notes + the audit — the magpie recommendation with its arithmetic, the arcade's measured kit, and the sizing law promoted to a standing instruction
- LANE_E_NOTES §39: RECOMMENDATION TAKE THE GLB, on three findings in the order they decide it
  (80.5% of drawn frames are PERCHED and the perch is the pose E's asset already is; E reads pied
  at 64/48/32 px and under a 25% smear while B reads as a dark aeroplane; and B's white is
  geometrically buried — the wing bar sits 3 mm ABOVE a horizontal wing plane, so from underneath,
  which is where the player always is, the black wing occludes its own white flash).
- Three asks filed for Lane B, none of them edits: magpie.js's two stale 154-tri comments (:9, :123
  against a measured 182); the wing bar's side (y 0.048 -> ~0.042 is +0 tris and fixes ?classic and
  ?noassets whatever Fable rules); and two GROUND_FALLBACK entries so the arcade is not grey
  without assets.
- The arcade measured, with the awning-post finding: two post rows 1.00 m apart down a 5.00 m lane.
- AUDIT §Round 39 §0 makes the sizing law a STANDING INSTRUCTION with two new clauses: draws are
  counted per CLASS (town-wide InstancedMesh +1 vs per-chunk +1 x 25 live chunks), and for a SKIN
  the budget is the SLOT, not bytes (36 facade-atlas slots, 22 used, 14 free — past 36 skins.js
  silently REUSES one).
- pipeline/skin_sheet.py: wrap-preview + measured seam error, the check R38's stretched grounds
  needed before the harvest instead of after.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 20:53:55 +10:00
m3ultra
30b86a9ef7 Lane E R39 (2/n): THE ARCADE, MEASURED THEN DRESSED — 3 skins live ($0, on-device, green), and the instance count taken BEFORE generating
MEASURED FIRST (pipeline/arcade_measure.mjs, over 5 seeds, off Lane A's own generatePlan):
  the arcade is ONE 42.00 m x 5 m edge, 2 blocks, 16-17 shops (17 at the default seed), frontage
  3.00-4.94 m (mean 3.87-4.05), depth 5.1-8.0 m, 4-11 of them two-storey. Walkable 5.00 m between
  lot faces. AWNING_DEPTH 2.2 from each face means the two slabs already meet 0.60 m apart over the
  centreline — the arcade IS roofed, and the roof is an accident.
  ⚠ AND THE POSTS ARE IN THE WRONG PLACE: buildings.js:643 puts them 2.00 m out from each lot face,
  i.e. +/-0.50 m from the centreline — two rows of posts 1.00 m apart down a 5.00 m lane. No
  collider, so it walks; it just reads as a colonnade planted in its own doorway. Lane B ask filed.

SHIPPED (all riding existing shared materials or stating their draw):
  facade-shuttered   768x595 (the 1.29:1 arcade lot aspect, not the pool's 1.78:1) — the dead
                     tenant. +0 draws, +0 tris, 1 of the 14 FREE facade-atlas slots (22 of 36 used).
  ground-arcade-floor 512^2 terrazzo — +1 draw TOWN-WIDE, +0 tris (the 504 m2 lane quad moves out
                     of footGeos). use:'arcade-floor' names a slot that does not exist yet, on purpose.
  ground-arcade-roof  512^2 pressed-metal + wired glass — +1 draw in the 1-2 arcade chunks, +2 tris.

pipeline/seamless_tile.py: cut a regular-pattern skin to a whole number of its own periods instead
of feathering the joint. It has a guard, because on this pair the naive version made things WORSE:
the roof's x seam went 26.7 -> 8.3 (kept), the diffusion-drawn terrazzo floor is not periodic enough
to cut on (autocorr r=0.26) and 27.9 -> 31.9 was rejected and passed through. Recorded, not hidden.

manifest: facades 48->49, grounds 14->16. validate_manifest.py 0 errors, 0 warnings.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 20:46:43 +10:00
m3ultra
34f7dae397 Lane A R39 39.1 (close): map.html says the street name out loud; CITY_SPEC + A-progress §39
map.html (Lane A's reference tool) can now load E's REAL town caches — until this round it could
only render the three checked-in fixtures, so it could never show a real street. Adds the address
line to the panel and `label · side` to the shop tooltip.

Verified in-browser (no-store server, both suppliers, ONE line of code, no town-type branch):
· ?plansrc=osm&town=castlemaine_real → "address: ways · 841/858 edges named · 22/22 shops ·
  141 names"; tooltip "XXXX Antiques — Elizabeth Street · south side", "Maxi IGA — Forest Street ·
  north side". Matches the Node measurement shop-for-shop.
· default synthetic → "address: district · 493/493 shops labelled by district"; tooltip
  "Nev's Pawn & Loan — the warehouse fringe · north side".

CITY_SPEC: the address layer documented as DERIVED data, with the schema rule that follows from it —
no street name, district label or address ever becomes a CityPlan field. `plan_osm` still discards
`rd.name` deliberately; the names are recovered downstream because plan nodes ARE snapped projected
way points. The only thing the lift publishes is `norm.shift`, into opts.report, never the plan.

A-progress §39: the round, the two departures from the synthesis with their numbers, the gate table
with the breakage that fires each arm, and the tolerance finding (correctness saturates at 6 m).

selfcheck 157,647/157,647 ALL GREEN · fingerprint 0x5f76e76 unchanged · zero re-pins.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 20:39:59 +10:00
m3ultra
a569f08b14 Lane E R39 (1/n): THE MAGPIE, SETTLED ON A PICTURE — B's own birdGeometry() dumped vertex-for-vertex (182 tris, not 154), rendered against E's tinted GLB at 64/48/32 px, still and under motion blur
- pipeline/dump_bird.mjs: imports web/js/world/magpie.js UNMODIFIED (bare 'three' resolved to the
  repo's vendored build through a node resolve hook) and dumps birdGeometry() — so the A/B is
  against Lane B's actual bird, not a port. MEASURED 182 tris / 216 verts. --sim walks a player
  past a territory on magpie.js's own clock: drawn 54.1% of frames, and OF THOSE perched 80.5% /
  swooping 7.7% / returning 11.8%.
- pipeline/bird_to_glb.py: wraps the dump as a GLB in E's frame (head +Z, +Y up) so the identical
  render_views.py rig shoots both; --fold applies the perch pose (x x 0.42). glb_stat re-measures 182.
- pipeline/render_views.py --noemit: strips emission from BOTH candidates. normalize.py's
  emissiveFactor 0.28 is albedo-MODULATED; a vertex-coloured mesh cannot express that in glTF and
  Blender writes a flat 0.28 that lifts a black bird to grey. Off both sides or the picture lies.
- pipeline/view_sheet.py --ab: the strip as a GRID (one line per candidate, stacked) plus two
  linear motion-blur blocks at 25% and 100% of the bird's width, and a wrapped notes footer.
- docs/shots/laneE/r39_magpie_ab.png — the sheet Fable rules on.

Recommendation in LANE_E_NOTES; picture first.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 20:35:00 +10:00
m3ultra
f628bf581a Lane A R39 39.1: THE ADDRESS LAYER — 17,835 street names stop being dropped on the floor
The contract, published for B and C in LANE_A_NOTES §39 (first commit, per the half-day rule).

  createAddresses(plan, cache|null) → streetOf(edgeId) · localityOf(shopId) · cohort(pred)
                                     · streets() · stats()

New pure module web/js/citygen/address.js: ZERO imports, no THREE, no fetch, no DOM, no module
state, no plan mutation. Two suppliers, ONE consumer contract — a real town fills `label` from the
cache's named ways, the synthetic fills it from district.kind + block. Consumers must never branch
on town type; that constraint is the design.

MEASURED (Fable's 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 resolve to a street name (97.8%); 29,865/30,986 edges named (96.4%)
· all 27 unresolved shops front a way OSM genuinely leaves unnamed (adelaide 21 arcade) — null is
  the honest answer and the module returns it rather than borrowing a neighbour's name

TWO DEPARTURES FROM THE SYNTHESIS, both with numbers:
· the shift is recovered EXACTLY (node↔waypoint lattice intersection), not voted for. The proposed
  centroid-align + nearest-waypoint vote returns the WRONG shift on 5 of 23 towns (braddon,
  fremantle, hobart, northbridge, westend) — island culling drags the plan centroid off the
  cache's. A wrong shift renames every street in the town. Exact on 23/23, nothing to tune.
· the resolver samples FIVE points along an edge, not the midpoint — a midpoint cannot tell a
  street from the street that crosses it.

THE ONE-LINE UPGRADE: TAKEN. plan_osm.js writes norm.shift = {shx,shz} into the normalization log,
reaching the caller only via the existing opts.report sink — never onto the plan. It is a
cross-check, not a dependency: selfcheck compares plan_osm's published shift against address.js's
independently recovered one and fires on a 0.01 m disagreement.

GATES (+240 checks), each proven to fire on a broken world:
· 0 wrong street names on 884 shop-bearing edges, against an INDEPENDENT way-membership resolver
  (857 agree, 0 disagree). Corpus-wide 4/30,986 disagree — all 2D-stacked ways, none carrying a
  shop — PINNED at 4, not asserted > 0.
· CONTROL: with roads[].name stripped (exactly the pre-change state) the layer resolves 0 streets.
· CONTROL: no cache ⇒ supplier 'district', visible in stats(), never a silent wrong name.
· createAddresses does not mutate the plan — asserted byte-for-byte, per town.
· tolerance 8 m, and the finding that sets it: correctness SATURATES AT 6 m (857 agreeing frontage
  names at 6, 8, 12, 16 and 24 m). Every metre past 6 buys only a name borrowed from an unnamed
  way; it can never buy a correction.

Additive: getTownCache(key) — index.html registers the cache and drops its reference, so roads[]
was unreachable downstream. Read-only, no new state, no shell change.

GOLDENS: NOTHING MOVED, ZERO RE-PINS. selfcheck 157,407 → 157,647 ALL GREEN, fingerprint 0x5f76e76.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 20:32:43 +10:00
m3ultra
07328b7e9f Fable (integrator): round-39 instructions — v9 opens: THE ADDRESS LAYER + THE FOG (John's ruling), plus the op-shop dig gap and the magpie settle
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 20:13:43 +10:00
m3ultra
6c6d6de87d Fable: THE CITY IS THE GAME — John's reframe designed and judged; 17,835 named streets sit unused in the caches, and the interior margin is 6 draws not 191
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 20:06:05 +10:00
m3ultra
f873b460e8 Fable (integrator): ROUND 38 CLOSED — v8 wave 1, the town is inhabited; 292/300 stands (the worst frame is night, both new layers are zero at night)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 19:50:16 +10:00
m3ultra
763eabde5e Lane F R38 (v8 WAVE 1, CLOSE): the budget did NOT move — 292/300 stands, and four gates ship with four controls
THE RECONCILIATION. The brief's naive sum (292 + washing 1 + magpie 1 = 294) is wrong, in our
favour. The pinned worst-DRAW frame is a NIGHT frame and both of wave 1's draw-spending layers are
structurally zero at night: the washing comes in (count 0), the magpie roosts (count 0). Wave 1's
one draw is spent in daylight, where the walked peak sits ten draws lower.

Two pinned trees, assets symlinked to the live tree on BOTH sides, 802 stations at 2 m down the
main spine at yaw 0 (the worst bearing), citizen sim and tram LIVE, 1280x720:

  NIGHT worst draws   292 -> 292   (+0)     tris 120,145 -> 122,879
  NOON  worst draws   282 -> 283   (+1)     tris  99,872 -> 103,118
  ?classic=1          269 / 90,580 unmoved
  fitzroy_real        172 / 130,727 unmoved      bendigo_real 204 / 131,492 unmoved
                                                 (even with ?magpie=1 forced)

The control reproduced R37's pin TO THE BYTE, twice, on a fresh checkout. Every revert is exact:
?yards=0 at night returns all three control numbers; ?yards=0 at NOON returns 282/99,872, which
isolates the noon +1 to the washing and nothing else. MARGIN IS STILL EIGHT DRAWS.

LANE B's "control 346", answered rather than dismissed. As a delta it is the wall-clock ped-spawn
artifact R37 named, and B was right to hide the sim. But 346 > 300 is a claim about the world, and
neither stepwalk (walks the chunks in, photographs a half-dressed street) nor dwell (fills the
street, arrives clean at 27 chunks) can adjudicate it. budget_walk.py gains --method stepsettle:
the audit's fixed-yaw stations, each DRAINED then held N real rAF frames — R+1 dispose residue AND
a fully-acquired crowd in the same frame. Like for like on the same 101 stations, the settle is
worth +3 draws at noon and -9 at night, and the SIGN FLIP is the finding: at night the settled
street retires near rigs to impostors (tris 120,145 -> 100,939) — the crowd goes home, and a
sampler that never waits does not see it happen. The highest number anywhere in the reconciliation
(4 methods x 2 trees x 2 segments x 5 towns) is 292, the pin. NOTHING READ OVER 300.

A CORRECTION TO MY OWN R37 PIN: the corpus TRI worst is 130,727 on fitzroy_real, not 124,793 — and
it reads 130,727 on the CONTROL tree, so it is my R37 station spacing (12-bearing 8 m sweep) versus
this round's 2 m stations, not a wave-1 cost. Station spacing, not settle policy, is the biggest
lever on a walked worst frame. A pin is only as good as its spacing, and mine had not said so.

FOUR GATES, FOUR CONTROLS DEMONSTRATED (smoke_ambient / smoke_character / smoke_magpie /
smoke_yards, wired into flags_check.main()). A gate that cannot fail is not a gate — this round
earned the sentence twice, R37 killing two vacuous gates and B catching its own season mechanism
producing a constant that would have gated green under ?magpie=1.

  1.1 audio  — 6 sources anchored 63/8/81/32/39/57 (B's numbers exactly); dog AUDIBLE at 5.9 m with
    1 play scheduled (for a one-shot, `audible` is only published once plays>0, so one word covers
    fetch/decode/gain/schedule/counter); HOURS enforced (sprinkler audible 06:30 -> idle 12:30, same
    pose); DAY-OF-WEEK enforced (mower idle Mon -> audible Sat, same pose); PAN is a bearing (yaw
    0/90/180/270 -> +0.00 +0.85 -0.00 -0.85); ZERO DRAWS byte-identical to ?ambient=0. THE CONTROL:
    a second server whose root is web/ symlink-for-symlink except one patched manifest with
    sfx['dog-bark-fence'] renamed -> no-entry, 0 plays, never audible, other five unchanged, 0
    console errors. Printed on every run: audible OUTPUT is unverifiable headlessly. Nobody in this
    loop has ears.
  1.2 character — classic is the frozen v1 literals, all tints null, 5 ground meshes; all 23 shipped
    keys matched PER TOWN including the two carrying state:''; the control fires (unknown key ->
    matched:false); 21 distinct palettes across 23 towns (one shared palette FAILS by name); and the
    two orphans are ON THE GPU — ground-gravel.jpg bound on Castlemaine, ground-reddust.jpg on
    Darwin, read off the live material.
  1.3 magpie — the latch arm verbatim with its positive control FIRST; day-roll clears it;
    localStorage gained NO new key (baseline taken after a game.save() — the first cut blamed the
    magpie for v7's own write); the season is 23 distinct positions across 24 towns run UNFORCED,
    against the first cut's 24-of-24-at-214, newtown_real in season on day 1, 129/1000 seeds against
    a 11.5% design ratio; territory 6 -> 157 synthetic -> bendigo; +1 draw / +182 tris at ONE pose
    reused across both boots.
  1.4 yards — 181 lots, 181 hoists -> 394 items, 256 active, 0 at night; cost decomposition at one
    pose with the sim quiet: box props +0 draws (in fact -2, the 36 `yard` lots used to fall through
    to buildShopfront) with +1,936 tris, washing exactly +1 draw / +512 tris; and on real towns it
    SKIPS LOUDLY — a ⊘ SCOPE line printing 0 house/yard lots / 0 hoists / mesh never created, beside
    the synthetic 181 that makes the zero attributable.

Caught in my own work: the pan arm's first draft used two bearings and read +0.00/-0.00 because
that anchor sat on the forward axis — it would have called a working panner inconclusive. Four
bearings now. The gate's first draft failed the standard the gate exists to enforce.

SUITE: tools/qa.sh --strict --matrix GREEN on the true final tree (74d6257) — 7 passed, 0 failed,
0 warn, 0 skipped; flags_check GREEN 0/0 with all four new gates inside it; MATRIX GREEN 10 towns
x 7 gates, 0 fetches under ?noassets on all ten.

Held: nothing. Recorded not fixed: magpie.js says 154 triangles at :9/:123 and 182 at :42 (measured
182); E's three published GLBs are manifest-and-depot but not under web/assets/models (staged in
pipeline/_normalized, nothing loads them, the validator's contract covers it — not a defect).

NO TAG — wave 1 is content; the epoch tags when it is whole.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 19:48:45 +10:00
m3ultra
1c4552a947 Fable: RED TICKET DAY — the SALE mode design (3 framings, adversarially judged); the Band Floor Law, and the verified finding that only record shops and stalls have crates
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 19:27:25 +10:00
m3ultra
74d6257940 Lane E R38 s2: the three GLBs PUBLISHED + sha1-verified off the public depot; the magpie is a magpie
A. DEPOT — closed, under John's R22 standing Lane-E authorization (the authority that
   shipped sit.glb in R28 and the nine R36 sweep assets). Pushed via the passwordless
   tailnet ingress: _published.json 53 -> 56, drift check clean. Then verified the way
   this lane verifies — FETCHED BACK OFF THE PUBLIC DEPOT and hashed, not "the upload
   said 200": magpie/paling_fence/water_tank all HTTP 200, byte counts exact, sha1
   identical to local, 3/3. _r38_results.PENDING_PUBLISH.json renamed, build_manifest
   re-run (GLBs 33 -> 36), validate_manifest 0 errors / 0 warnings.

B. THE MAGPIE TINT — done, not offered. The bird shipped as an all-black crow (R38 4d
   traced the loss to trellis2_mlx's PBR bake). "Hand-tint the atlas" is not a paint job:
   the bake is a smart-UV unwrap of ~100 islands and none of them announces itself as a
   nape. So pipeline/tint_atlas.py defines the tint in the MESH's frame and projects it
   through the UVs — rasterize every UV triangle to texel->(position,normal), evaluate
   soft anatomical windows on the positions (white nape behind a black crown, shoulder
   bar on the flanks only so the dorsal midline stays black, rump + tail base stopping
   short of the black terminal band), paint keeping the bake's own luminance as shading,
   then dilate 5 texels into the UV padding so no black fringe survives bilinear/mip.
   3-D masks give one consistent soft edge across every island at once; a 2-D blur could
   not (it would bleed island-to-island through the black background).
   GEOMETRY UNTOUCHED byte for byte: still 894 tris / 1 mtl / 1 img / 512^2 WebP / no
   Draco / 0.155x0.358x0.404 m. Painted luma median 0.84 (p90 0.95) vs the bake's 0.13.
   VERIFIED IN THE GAME'S OWN RENDERER: the PUBLISHED file loaded off digalot.fyi through
   the vendored GLTFLoader + three.js — 1 mesh, 894 tris, 512x512 texture decoded (which
   also proves the PIL-written WebP decodes in a browser).
   VERDICT: it reads. Pied and unmistakable at 64 and 48 px, still black-and-white at 32.
   Honest caveat: from directly below-and-in-front it stays dark — correct, a real
   magpie's bib and belly ARE black. Render: docs/shots/laneE/r38_magpie_tint.png.
   Standing after B's 4514cb9: an UPGRADE PATH, not a dependency — B's 182-tri procedural
   bird is 4.9x lighter and the right call. Nothing probes for the GLB and nothing should.

C. CORRECTIONS FOLDED INTO THE RECORD so the next asset round does not re-learn them:
   skins are 9.7 s not 5.7 s (1024^2, 1.78x the pixels, per-pixel identical) - the reuse
   list's "one line each" holds for 5 of 9 (extractGLB keeps only the FIRST material) -
   normalize.py's collapse decimator floors at the shell count on TRELLIS output while
   bake_lowpoly.py clears it every time - the 489 s outlier at 3.8x median means
   throughput plans on a tail, not a mean - furniture.js:30's stale comment is fixed by B.
   NEW, and the one worth keeping: an asset's tri budget is tris x THE INSTANCE COUNT THE
   PLACEMENT RULE IMPLIES. paling_fence is a good 1,121-tri panel and a rejected asset —
   905 instances = 1.01M tris, ~47x over. Ask the consuming lane for the instance count
   BEFORE generating anything that tiles, edges or repeats per-lot.

New tools, all on-device and $0: tint_atlas.py, render_views.py (view sheet + --eevee +
roster-consistent --thumb), view_sheet.py (the distance strip no 256px thumbnail can make).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 19:02:06 +10:00
m3ultra
4514cb94b5 Lane B R38 (v8 WAVE 1, INHABITATION): all four runtime items — the whole round costs +1 walked draw
The town is inhabited: 23 towns finally differ, 181 back yards have a Hills Hoist with washing on it,
a magpie owns one street for six weeks of the year, and six point sources put a life off-screen.

MEASURED, walked, not bookmarked — two port-isolated no-store servers (:8472 = `git archive HEAD`,
:8471 = treatment, assets symlinked so E's mid-round wave is identical on both sides), 102 8-metre
spine steps at yaw 0, each frame fully streamed, through the shell's own composer:

  worst draws  224 -> 225   (+1)
  worst tris   89,502 -> 93,298   (+3,796 of ~75k spare)
  sum draws over 102 frames  16,676 -> 16,663   (-13)

The citizen sim and the tram are hidden identically on both sides, and that IS the measurement: with
them live the same step read control 346 vs treatment 232, because peds spawn on wall-clock time.
`?yards=0` reproduces HEAD exactly (224 / 89,502 / 16,676). Isolated at a fixed pose: washing +1 draw
/ +512 tris, magpie +1 draw / +182 tris, every yard box prop +0 draws.

§1.2 THE CHARACTER VECTOR — +0 draws, +0 tris, classic byte-identical BY CONSTRUCTION.
`townCharacter(null)` returns the frozen v1 literals and a null tint keeps the SAME material cache key,
so classic gets the same object, not a copy of it: default-boot ground hashes identical to HEAD
mesh-for-mesh, `?classic=1` hashes unmoved from R37. There is no `if (classic)` in the ground path.
The 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, and both are selected now. E's three new grounds
landed mid-round and the upgrade was a name change in one table, which is what procedural-first is for.

§1.4 BACKYARD AUSTRALIA — inside boxMats, +0 draws. Houses inset off their boundaries so there is a
yard; 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 (the lot rect is where the fence stands).

THE WASHING SHIPS, and I wrote the inverted weight rather than dropping it. The shipped top-weighted
mode moves a hem 0.10 cm calm / 0.67 cm gusty; `pegged` gives 4.5 cm / 31 cm — ~52x, and at the hem
instead of the pegs. The `canopy` string is byte-for-byte unchanged, so the gum-tree covenant argument
is untouched. It is ONE TOWN-WIDE InstancedMesh (1 draw at any N, cap 256, proximity-first off the R37
chunk seam) rather than per-chunk, which would have been +1 x 31 live chunks = 4x the whole margin.
Zero on every real town (no hoists ⇒ no mesh) and zero at night.

§1.3 THE MAGPIE — +1 draw on screen, exactly 0 otherwise; 182 tris of 900. Territory scales with the
road graph: 6 on the 6.78 km synthetic town, 157 on bendigo's 188.6 km / 2,593 edges, nearest 120 m
from spawn. The latch sets, the day roll clears it, localStorage gains no key and the save never
mentions it. `swoopPos()` is exported and pure. The hat stays cut.

MY OWN SEASON MECHANISM WAS VACUOUS AND THE SWEEP CAUGHT IT: keyed on citySeed alone it produced a
CONSTANT, because the town key is deliberately off the plan — 24 of 24 towns were 214 days from
swooping season, and it would have gated green because `?magpie=1` forces it. Re-keyed on
(citySeed, townKey): `newtown_real` is in season on arrival on the default seed, marrickville is 8 days
out, and 12.9% of 1,000 seeds put the synthetic default in season against a design ratio of 11.5%.

§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 into the notes. 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 onto shopfronts. The falsifiability
control — one manifest key renamed away, served from a third port — makes that source report
`no-entry`, 0 plays, never `audible`, with the other five unchanged and zero console errors.

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,
and a fence taller than the hoist), and the magpie perched on THIN AIR over the road. R37's lesson
relearned at full price. Fence 1.60 / hoist 2.15 measured against the object; territories now snap to
furniture.js's own streetlight rule.

LANE E'S PROPS, ADJUDICATED: hoist + aerial permanently primitive (E's spike killed them, and primitive
is also the +0-draw mechanism). `paling_fence` at 1,121 tris x 905 runs = 1.01M triangles — no. The
`magpie` GLB is HELD FOR THE TINT: it 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, at a fifth of the triangles and no fetch.
furniture.js's "novelty_record is 26.5k tris" was stale by six epochs — corrected to 8,000.

Zero fetch delta: `?noassets=1` fetches the same 23 assets, same list, on HEAD and on R38.
0 console errors on 10 boots (default, classic, yards=0, washing=0&magpie=0&ambient=0, noassets,
magpie=1, darwin, hobart, bendigo, the patched-manifest control) beyond the known PointerLock ones.

Shots: docs/shots/laneB_r38/. Detail + every number: LANE_B_NOTES §38.
2026-08-03 18:53:26 +10:00
m3ultra
1568c9b62b Lane E R38 (v8 wave 1): the asset wave — trellis2_mlx swap verified 1.92x, six sounds, four grounds, and the thin-structure answer
A. PIPELINE FIX: gen_props.py trellis_mac -> trellis2_mlx, contract read from its
   manifest.json. The two operators are NOT drop-in on output — trellis2_mlx writes
   BOTH candidate.glb (raw) and candidate_pbr.glb (baked), so the old
   sorted(rglob('*.glb'))[0] would have silently shipped untextured meshes. Added
   _pick_glb() (result.json first) + the bg_remove_local cutout stage, fail-soft.
   MEASURED: median 128.0s vs trellis_mac's 245.4s p50 = 1.92x. 5/5 first attempt,
   0 retries against the 4-5% budget. One 489s tail outlier, no error — named.

B. AUDIO (1.1) — six PACK entries, +0.86MB, ZERO draws/tris, all originalby
   construction: dog-bark-fence, mower-distant, radio-through-window, sprinkler,
   roller-door, magpie-carol. Seeded via crc32(name); beds fold seamlessly
   (wrap-jump <=0.18x internal step). Verified by spectrogram + band-energy, and
   one defect found by LOOKING: the sprinkler's spray masked its own ticks and read
   as a hose — rebalanced and re-rendered. Distance is spent in the SPECTRUM, not
   the level, because encode()'s loudnorm undoes a quiet render.

C. GROUNDS (1.2) — gen_skins.py had NO ground template; added one with its own
   outdoor style prefix (the house STYLE says "warm fluorescent", an interior cue).
   4 new 512^2 square tiles. Review's 5.7s is 9.7s here — same per-pixel, mine are
   1.78x the pixels. Found: gravel/reddust are selected by nothing partly because
   their `use` values name no slot ground.js:177 has.

D. MESHES — 3 shipped (magpie 894 tris, paling_fence 1121, water_tank 500), all
   GLB-law clean. 2 FAILED and were DELETED: hills_hoist + tv_aerial. Thin wire
   returns as thousands of disconnected fragments, so collapse floors at 250k/89k
   AND a remesh coarse enough for 600 tris erases the pole. Lane B's boxMats
   primitives cost +0 draws and are strictly better. The magpie's geometry passes
   but TRELLIS crushed its white nape/wing to black — traced to the operator's bake
   (the cutout and our decimation are both exonerated); flagged, not hidden.

E. REUSE — all 9 idle assets re-measured; the review's "one line each" holds for
   only 5. furniture.js's extractGLB keeps only the FIRST material, so park_bench/
   coffee_table/box_crate render in material #1 and bench_wood (0 materials) falls
   to grey. Nothing was regenerated that already existed.

The 3 GLBs are staged, NOT published: a depot push is outward-facing and had no
in-session authorization. Held at _r38_results.PENDING_PUBLISH.json; publish +
rename + rebuild lands them. Gate green: validate_manifest 0 errors / 0 warnings.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 18:30:33 +10:00
m3ultra
6bc7926637 Fable (integrator): round-38 instructions — v8 WAVE 1, INHABITATION (audio layer, 23-town character, the magpie, backyard Australia)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 17:49:28 +10:00
m3ultra
e8d4b51c4f Fable (integrator): ROUND 37 CLOSED — v8 wave 0, the floor is level; 14 posters out of the road at +0 draws, two vacuous gates dead, the budget pinned
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 17:43:45 +10:00
m3ultra
b6df11dfd6 Lane F R37 (v8 wave 0, CLOSE): the round gate — QA GREEN 7/0/0/0, flags harness 0 fails / 0 warns, MATRIX GREEN 10 towns x 7 gates
qa.sh --strict --matrix on the true final tree (Lane A e4d7762 + Lane B 47db478/79177bc + Fable
7bd96e5/4a7c0f1 + F 2396885/6afd1ed/f522021), port-isolated on :8975.

  SUMMARY  7 passed · 0 failed · 0 warn · 0 skipped — QA GREEN

  selfcheck   ALL GREEN 157,407/157,407 (was 156,352 — Lane A's +1,055 poster-clearance
              assertions, three arms, all green on the ruled street)
  manifest    0 errors / 0 warnings; atlas-QA 15 atlases, 15 licence lines shown; depot live
  flags       0 fails / 0 WARNS
                classic  fp 0x5f76e76 · dig INERT at the bin · gig layer wholly absent ·
                         162 draws / 16,642 tris · fetch surface CLEAN against v2.1 (125 requests:
                         68 code, 52 same-origin v2.1 assets, 4 depot, 1 NAMED EXCEPTION printed) ·
                         allow-list control fired (2 injected -> 2 named violations)
                default  four flags on · 14 gigs / 14 posters / venue geo / tram · gig golden
                         0xec7a2d39 · 131 draws / 23,382 tris @ venue block · dig REACHABLE naked
                kerb     all six arms green, control at EXACTLY 14 under ?verge=0
                tram     all arms + BOTH controls green
                R11-R36  audio, gigs, continuity, glance, the R30-35 game gates, djdance, real
                         crate, tier-2 kill-the-server: all re-green on the ruled street
  fig-scale   29 figs across 6 shops in [1.4,2.0] m incl. the R36 DJ-body leg
  matrix      GREEN, 10 towns x 7 gates; every town byte-equal on re-boot; 0 fetches under
              ?noassets across all ten; tram verdicts unchanged (fenced on bendigo, fremantle,
              katoomba_real — the same three the rebuilt smoke_tram uses for its absence arm)

One caveat kept in the notes rather than buried, so this round's central finding is not undone by
its own suite: the matrix's budget column (69-161 draws) is DBG.shot('venue_night') CLEAN-LOAD
BOOKMARK numbers, and 37.1 is the proof that a bookmark under-reports a walked-and-turned frame by
~120 draws. That column is a regression signal, not the budget of record. Making the matrix
walk-and-turn every town is a real improvement and a real cost — FILED TO THE v8 DEBT LEDGER, not
smuggled into a floor round.

NO TAG. Wave 0 levels the floor; it does not ship a version.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 17:42:08 +10:00
m3ultra
f522021765 Lane F R37 (v8 wave 0, 0.1 F-half): THE KERB GATE — poster clearance measured on the PAINT, and the ?verge=0 control fires at exactly 14
New STRICT smoke_kerb. Lane A's corrected selfcheck asks the plan-side question (is the poster's
offset inside vergeBand(e)[0]?) -- right for A's file, but it still reads no number that came from
Lane B, which is the precise failure that made the old gate vacuous. So F asks the runtime question,
of the paint: RAYCAST STRAIGHT DOWN AT EVERY RENDERED POSTER AND CLASSIFY THE TOPMOST GROUND HIT.

Classification is by MATERIAL IDENTITY -- skins.groundMat() is a cached factory, so the road mesh's
material IS the object groundMat('djsim-road') returns. No mesh names, no string matching, nothing
that drifts when B edits ground.js. Topmost rather than first, deliberately: B put the carriageway on
top at junctions (ROAD_Y 0 -> 0.04) so roads run through intersections, and what the player sees
under a poster is the top surface.

Six arms, all green on the final tree:
  subject          14 posters rendered == 14 in plan.posters, every one within 0 m of its planned
                   position, read off the real venue-posters InstancedMesh matrices. Nobody had ever
                   asserted that the poster you see is the poster A placed. Zero posters => FAIL.
  detector ctl A   the centreline of the longest main (main@28 m, 137.5 m) classifies ROAD at y 0.04
  detector ctl B   13 m off it (1 m inboard of the corridor edge) classifies FOOTPATH at y 0.02
  THE RULING       0 of 14 posters stand on road paint -- tally {djsim-footpath: 14}
  THE CONTROL      the same leg under ?verge=0 FAILS at EXACTLY 14 -- tally {djsim-road: 14}.
                   Keyed to B's flag, never to a line number, matching A's discipline.
  classic          13 m off the centreline is still ROAD under ?classic=1 and classic renders 0
                   posters -- Fable's CLASSIC KEEPS ITS PAINT ruling holds.

The two detector controls are the load-bearing part: "0 posters on road" is a sentence about nothing
unless the classifier is shown to say road AND footpath on the same boot.

TWO LANES, TWO INDEPENDENT METHODS, THE SAME NUMBER: A counted 14 plan-side by offset in node over
the plan; F counted 14 paint-side by raycast in the browser over B's geometry. Both flip to 0 on the
default boot.

Also lands Fable's ruling on classic's post-v2 fetch delta (4a7c0f1) in the mechanism. The audio pack
is RATIFIED BASELINE -- v2.1 was v2's final tagged state -- so the allow-list authority moves
v2.0 -> v2.1. Git makes that surgical rather than generous: `git diff v2.0 v2.1 -- web/` is the audio
pack, js/world/audio.js and four wiring edits; zero other assets, zero deletions. So the tag change
admits precisely the five ratified files and nothing else, and the depot GLB names survive in v2.1's
manifest unchanged (verified: same-origin baseline 47 -> 52, exceptions 2 -> 1, control still
discriminating). assets/towns/index.json is NOT baseline and now prints every run as
"EXCEPTION (NOT v2.1 baseline)" with its provenance, so it can never read as covenant.

README: the ?verge=0 row now carries A's corrected band figures rather than the charter headline --
9 m is the synthetic 28 m main only; a real main is 24 m => 7 m, and side@12, 72% of the corpus, is
3 m.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 17:30:59 +10:00
m3ultra
4a7c0f1295 Fable (integrator): budget PINNED 292/124,793 (the 280-vs-191 split was camera bearing) + ruling on classic's post-v2 fetch delta; stale numbers corrected in Fable-owned docs
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 17:22:59 +10:00
m3ultra
6afd1ed7a3 Lane F R37 (v8 wave 0, 0.3): THE BUDGET OF RECORD SETTLED — 292 draws / 124,793 tris, and the 280-vs-191 fight was about the CAMERA BEARING
Two sessions disagreed by 90 draws and neither was wrong. Worst draw per BEARING, 12 bearings x
102 stations down the same spine, one tree, one box: 0deg 284 · 30deg 248 · 60deg 208 · 90deg 177 ·
120deg 196 · 150deg 214 · 180deg 211 · 210deg 207 · 240deg 178 · 270deg 182 · 300deg 223 ·
330deg 267. 110 draws of spread from the camera direction alone, on identical positions -- and the
peak sits at yaw 0, the DEFAULT ARGUMENT of DBG.teleport(x, z, ry = 0). The city-patterns audit
took the default and landed on the worst bearing (280, reproduced at 282-292); the animal spike
supplied a yaw and landed mid-range (191, reproduced at 205-215). Correct numbers, different
quantities. Lane B's independent R37 walk (276 draws / 99,880 tris) and mine (282 / 99,878) agree
on TRIANGLES TO 2 -- the same frame through two harnesses.

THE PIN, default boot, synthetic golden seed, tree 7bd96e5, walked the spine:
  292 draws of 300 (NIGHT, 31 live chunks -- the R+1 dispose window at maximum)
  120,145 tris on synthetic; 124,793 on fitzroy_real, the corpus worst
  ?classic=1: 269 draws / 90,580 tris
  margin: EIGHT draws (the audit said "about twenty") and ~75k tris
The worst-draw frame and the worst-tri frame are NOT the same frame. Real towns are draw-cheap and
tri-dear; the synthetic town is the opposite. The audit's "99k-124k" tri range is confirmed and
pinned at its top.

Also measured: viewport is IRRELEVANT (1280x720 and 2560x1440 identical to the byte -- culling is
frustum-shaped, not pixel-shaped) · the R+1 dispose residue is worth +19 draws, not the 60-80
folklore (the rest is place and bearing) · CitizenSim's per-frame rig-acquisition budget means any
one-render-per-station sampler photographs a half-dressed street · ?r=3 measures 307 draws and
BREACHES the 300 gate through an ungated override (filed, not fixed) · ?shadows=1 costs +38 draws
and +38% tris · Lane B's kerb change independently confirmed at +0 draws / -8 tris at the worst
frame, by a harness B did not write.

THE SHARED INSTANCE CAP: 256, one InstancedMesh, town-wide. Measured +1 draw at every N from 32 to
2048, and N x tris/instance ALWAYS submitted (the instance bounding sphere spans the town). Draws
are not the constraint; triangles are. 256 x a 2-4 tri cell = 512-1,024 tris = 0.5% of budget;
0.01 ms/frame to rewrite every matrix. Binding companions: <=24 tris/instance at the cap, and spend
it PROXIMITY-FIRST -- anchor supply runs from synthetic's 246 poles over 6.8 km to bendigo_real's
7,325 over 188 km, so 256 sprayed town-wide there is one instance per 730 m.

Honest limit stated where the number is quoted: headless SwiftShader cannot change a draw or
triangle count (both are counted in JS, and the viewport control proves it), but frame TIME remains
unmeasured on any real GPU and no v8 proposal may claim a millisecond figure.

Re-pinned in LANE_F_NOTES 37; the four other docs that assert the number are flagged with what each
line should now say rather than edited in another author's file. README flags table gains ?verge=0
(Lane B's control, F's file) and a row naming the ?r=3 breach.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 17:19:24 +10:00
m3ultra
23968856dc Lane F R37 (v8 wave 0, 0.2 + 0.5): the vacuous tram gate rebuilt; classic_regression gets a git-derived network allow-list
0.2 — smoke_tram was ONE assertion (`!!getObjectByName('tram')`), a printed SKIP + early return
on absence, and it PASSED ON THE FENCED PATH because tram.js:110-114 adds a named EMPTY group.
Rebuilt STRICT, four arms + both controls demonstrated every run: RUNS (route/stops/metres, and
the body measurably moves under the SHELL'S own rAF loop) · BEHAVES (an isolated tram on a
throwaway scene, fixed dt: traverses, dwell time accounts to exactly dwells x 3.5 s, max cruising
tick <= SPEED*dt, every discontinuity bounded by 2*LANE with >=1 end reversal, and it rides
3.2 m off Lane A's OWN main-edge centreline) · GENUINELY ABSENT on the fenced katoomba_real
(fenced verdict, 0 stops, 0 group children, 0 m moved) · OFF under ?tram=0 and ?classic=1.
CONTROL A points the run-predicate at the fenced town and CONTROL B points the absence-predicate
at the running tram; both must go red or the gate fails. A missing subject now FAILS.

0.5 — classic_regression made no network assertion at all for 21 rounds, which is why R36's
fetch-surface widening needed a brand-new smoke. It now watches the wire from before the first
byte, with the allow-list DERIVED FROM THE v2.0 TAG at gate time (git ls-tree + the v2.0
manifest), never a deny-list. The four v2-era depot street GLBs and the five record-shop fittings
are BASELINE and admitted by provenance, not by exception. Same-origin is NOT a free pass -- the
R36 breach WAS same-origin, so /models/peds is enumerated by the v2.0 tree (exactly the
covenanted 21 files). Anti-vacuous: a required v2 core must be observed or the clean verdict is
refused. CONTROL, run every time: injecting the R36 breach itself (models/peds/woman_dj_01.glb)
plus a never-existed asset turns the clean verdict into exactly 2 violations.

Measured and carried, not fixed here: booting the actual v2.0 tree and diffing its fetch surface
against today's ?classic=1 shows a POST-v2.0 delta -- 5 audio files (R11 f7dd44f) and
assets/towns/index.json (R20/R21). Allowed on the list so the suite is not permanently yellow
over 26 rounds of shipped behaviour, printed every run so it cannot go invisible. Fable's to
ratify or retire.

Also: tools/qa/budget_walk.py (R37 0.3, the measurement rig -- continuous-motion walk through the
real rAF loop, bearing sweep, dwell, and a stepwalk that reproduces the audit's method) and
`flags_check.py --only` for single-gate development runs, which prints a PARTIAL banner so a
subset can never be read as a suite verdict.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 16:54:23 +10:00
m3ultra
7bd96e57fc Fable (integrator): mid-round ruling — CLASSIC KEEPS ITS PAINT (R27 doesn't apply: no gig layer under classic means no posters means no defect to preserve)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 16:45:40 +10:00
m3ultra
79177bc77c Lane B R37 notes: LANE_B_NOTES §37 + B-progress — the kerb ruling measured, the junction defect recorded, the chunk seam diagnosed
The measurement tables (+0 draws at every walked step on two towns, tris DOWN not up, the
raycast surface table, the ?verge=0 and classic hash pairs, the §0.4 hook proofs), plus
three things worth keeping:
 - the defect my own numbers could not see (every intersection painted shut; all metrics
   green, only the picture wrong) and why ?verge=0 + a screenshot caught it;
 - a full-frame pixel A/B is not an A/B while a live sim is in the frame — hiding
   everything but `ground` is what made the classic covenant measurable;
 - the market plaza reads worse under the wider footpath, and that is the STANDING item
   awaiting John's amendment, not new damage from §0.1.
2026-08-03 16:42:48 +10:00
m3ultra
47db478a51 Lane B R37 (v8 wave 0 §0.1 + §0.4): THE KERB IS FOOTPATH — +0 draws, tris DOWN; the chunk seam is subscribable at last
§0.1 — ground.js adopts Lane A's corridor law. The road quad narrows from the whole
corridor to the carriageway and the EXISTING merged footGeos class extends inward to
meet it, consuming `vergeBand(e)[0]` (not `roadWidth/2` — per A's mid-round correction,
so the geometry and A's corrected gate read the same number).

MEASURED, fresh boots, port-isolated no-store servers (:8178 = HEAD control, :8177 =
treatment), 2560x1440:
  synthetic     ground 5 draws / 224 tris -> 5 draws / 216 tris
                walked spine, 102 steps @ 8 m: worst 276 draws BOTH; sum of draws
                20,081 BOTH (identical at every step); 8 bookmarks identical draws
  katoomba_real ground 4 draws / 9,932 tris -> 4 draws / 9,644 tris
                walk 120 steps: worst 100 draws BOTH; sum 7,703 BOTH
+0 draws is proven, not asserted. Tris go DOWN (-8 / -288): the charter's "~2k tris" was
an estimate against a class that already existed. Honest number, stated loudly.

Surfaces verified by RAYCAST, not by reading the code (offset from centreline, topmost hit):
  main@28  road 0-4.75 | kerb 5 | footpath 5.25-17.5     (band [5,14])
  main@24  road 0-5    | footpath 5.25-15.25             (band [5,12], real)
  side@12  road 0-3    | footpath 3.25-9.5               (band [3,6] — 72% of the corpus)
  lane@8   road 0-4    | footpath 4.25-9   DEGENERATE band [4,4] => pre-ruling geometry
  lane@4   road 0-1.75 | kerb 2 | footpath 2.25-5.25     (synthetic, also unchanged)
  arcade   footpath 0-6.5, NO road quad, NO kerbs        (1,102 corpus edges)
  RESIDENTIAL side@14  road 0-2.75 | kerb 3 | GRASS 3.25-6.75 | footpath 7-10.5
The lane case branches on `outer <= inner`, never on a band literal.

Posters: 14 of 14 stood inside painted bitumen before; 0 of 14 after. Not one poster moved
— the ground did.

ONE DEFECT FOUND AND FIXED IN THE ROUND. The first cut painted every intersection shut:
a 12.5 m footpath crosses the perpendicular CARRIAGEWAY where a 3.5 m one only ever
clipped a far corner. Screenshotted, rejected, and fixed by putting the carriageway on
top (ROAD_Y 0 -> 0.04 under the flag): roads run through junctions, footpaths stop at the
kerb they meet. Verified on the real graph's worst 4.3-degree junction.

?verge=0 ships permanently — the gate's falsifiability control. Proven EXACT, not merely
similar: ground vertex+uv hashes identical to the HEAD build (c4c72181 / c111d968 /
1a80ebcd / 12c1241b / fd0d3211), and the same detector goes RED on the default boot.

CLASSIC IS FENCED, NOT AMENDED (the R31 dig-flip pattern). ?classic=1 forces verge off, so
the covenanted town's render does not move on a render-side ruling. Proven twice: identical
ground geometry hashes AND identical ground-only rendered pixels at three poses
(79df268c / 91564001 / a09d1e52), while the default boot differs at all three. If Fable
wants classic to take the footpath too, that is an amendment ruling and a one-token change.

§0.4 — the chunk lifecycle seam. The producer has fired since R3 and Lane D's consumer has
existed since R3, but nothing could connect them: createChunkManager never handed `ctx`
back, so a consumer holding only PROCITY.chunks had nowhere to attach (measured on HEAD:
`!!chunks.ctx === false`). Now `chunks.onChunkBuilt(fn) -> unsubscribe()` for many
consumers plus `chunks.ctx` for the original single slot. Proven live: 136 build / 144
dispose events over a 40-step walk; payload {cx,cz,buildings,furniture,data} with
key === `${cx},${cz}`; a second consumer does not clobber the first; the ctx slot fires;
unsubscribe silences; a THROWING subscriber does not take the streamer down.
ZERO behaviour change when nobody subscribes: a 102-step walk recording
[draws, tris, chunks] per step hashes ceb1ec66 on BOTH builds.

Files: web/js/world/ground.js, web/js/world/chunks.js, docs/shots/laneB_r37_kerb/*,
docs/LANES/LANE_B_NOTES.md, B-progress.md. sim.js untouched (Lane D's).
2026-08-03 16:40:43 +10:00
m3ultra
e4d77629c5 Lane A R37 (v8 wave 0 §0.1, ruling-side): the poster gate was VACUOUS — corrected to three arms with a permanent control
The audit's claim against my lane, re-measured and CONFIRMED exactly: on the default
boot (synthetic 20261990, gigs default-ON) there are 14 posters, all 14 stand inside
e.width/2 — the bitumen ground.js paints — and 0 stand inside the ruled kerb
roadWidth/2. One refinement it did not make: 12 are spine posters at poleOffset=6m,
8m inside the paint (genuinely mid-road); 2 are frontage posters clipping the corridor
edge by 0.06m (FACADE_PROUD past their venue's facade plane).

WHY the gate was green: my placer seats posters via poleOffset, which is DERIVED FROM
roadWidth, and my selfcheck asserted clearance against roadWidth/2. Gate and subject
were the same function agreeing with itself — the R27 cross-convention trap recurring
inside the very file whose header explains it. It never read a number that came from
Lane B, so no change to where the road is painted could make it fail.

The correction (selfcheck.js, districtInvariants), three arms:
  1. no poster on a carriageway — measured through vergeBand(e)[0], the published law
     itself, the same call B's ground now consumes, so gate and renderer move together
  2. every SPINE poster stands INSIDE some edge's vergeBand — the outer bound the old
     check never had ("not on the road" also passed for a poster in a paddock).
     Frontage exempt BY MEASUREMENT: 18/108 gig plans have a frontage poster past every
     corridor edge (worst 3.98m, launceston_real) via plan_osm's lot setback.
     Spine violations over the same corpus: 0/108.
  3. THE CONTROL — count posters standing on bitumen under the PRE-RULING ground
     (?verge=0, road quad = full e.width) and assert it is NOT ZERO on every gig plan
     (min 3 / max 212 / never 0 across 510 plans), pinned at EXACTLY 14 on the default
     boot. A control allowed to drift toward zero is how a gate goes vacuous.
     The replica is pinned to B's FLAG, not to a line number that will drift.

FALSIFIABILITY PROVEN. Simulated ?verge=0 honestly — vergeBand switched to the
pre-ruling reading, poleOffset pinned to the true band so the PLACER stays
byte-identical (it is a render flag: paint changes, plan does not; fingerprint and
every gig golden green in both runs, so the gate's reading is the only variable):
  corrected (R37) gate  →  FAIL, 1020 failures (2 arms x 510 plans)
  pre-R37 (HEAD) gate   →  ALL GREEN 156,352/156,352  — structurally blind

GOLDENS: NOTHING MOVED, ZERO RE-PINS. 0x5f76e76, GIG_GOLDEN 0xec7a2d39, 25 base town
goldens and 24 real-town gig goldens all untouched — correct and predicted, since the
only generator-adjacent file changed is the selfcheck, which validates output and never
produces it. 156,352 -> 157,407 checks (+1,055), 10.3s.

Corridor law CONFIRMED for Lane B, semantics unchanged, contract published in full
(LANE_A_NOTES §37) with four hazards flagged loudly while B consumes it in parallel:
roadWidth and edge.width are both FULL widths but one is carriageway and one 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; the inward
extension never inverts (0 of 31,039 edges). Two charter numbers corrected: the "9m
band / 18m of every main" figures are synthetic-only — on every real town a main is
24m => 7m band, and the corpus's dominant edge is side@12 at 22,415/31,039 (72%) with
a 3m footpath.

FILED, NOT FIXED — v8 debt ledger, four entries (LANE_A_NOTES §37): D1 chunkIndex
(two impls, THREE divergences, header comment promises a swap that would break three
ways — owner B) · D2 isOpen (three impls + 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 until v8 adds an hours-less lot — owner F) · D3 two shop registries (B's missing
all three venue kinds => buildings.js:344 renders op-shop signage on every gig venue
since R13 — owner B) · D4 NEW, mine: the 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.78m inside a main, 0 on synthetic. Cost measured decision-grade:
synthetic 0/4 plans move, 11/26 town gig goldens re-pin, poster counts unchanged.
Deferred to R38 on purpose — 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.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 16:25:04 +10:00
m3ultra
62c0a2185d Fable (integrator): round-37 instructions — v8 WAVE 0, THE FLOOR (budget re-pin first, the kerb, two gate repairs, the chunk hooks)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 16:03:31 +10:00
m3ultra
67b62389e7 Fable: the animal ruling — generate, don't download (foreign-skeleton proven on our own bytes; economics cut tier 2); charter gains the budget re-pin + classic network allow-list
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 15:30:43 +10:00
m3ultra
751d5fe57e Fable: V8 SOMEONE LIVES HERE draft charter — John's rulings recorded (kerb=footpath; real towns get the content, civic fetch funded into v8; animals pending spike)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 14:43:53 +10:00
m3ultra
d59df8e1c0 Fable: the post-v7.0 city-patterns review — 16-agent top-down audit + adversarial design pass; v8 SOMEONE LIVES HERE proposed, three folklore numbers corrected
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 14:18:04 +10:00
m3ultra
ae2137fd9c Fable (integrator): the v7 epoch retro — hands before tags, classic keeps its people, the fork famine chapter
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 00:34:18 +10:00
m3ultra
c842547e61 Fable (integrator): ROUND 36 CLOSED — v7.0 tagged on 4709cfe; the hold's arc complete; the fork-famine record kept
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 00:33:02 +10:00
m3ultra
4709cfece6 Lane F R36 w2.5 (v7.0 close, final): the hold RESOLVED — re-gate 0 fails / 0 warns on the true final tree
§36.4 held -> ruled -> RESOLVED: Fable ruled 'classic keeps its people', D fixed
it in rigs.js alone (2fd4c1c: base-17 restored, the five DJ bodies append behind
djs=dance riding the shell's existing !CLASSIC signal; classic's 129-citizen
census byte-identical to a b339402^ shadow tree). F's full-suite re-gate on the
true final tree (run 3): SUMMARY 7 passed / 0 failed / 0 warn / 0 skipped, QA
GREEN, MATRIX GREEN 10 towns x 7 gates, flags harness 0 fails / 0 WARNS — the
smoke_djdance warn flipped to '?classic=1: zero DJ-body fetches'; default keeps
all 22 bodies, the organic gig-crowd proof, and the DJ figure-scale statures
exactly. Honest record: re-gate runs 1-2 died to a box-level fork famine
(~10,600 leaked m3panel pmset|tail orphans, fixed at source) — starvation
artifacts, not gates; noted in §36.5.

The five-holds law end-to-end in one round: held by F, ruled by Fable, fixed by
the owning lane, re-proven by the gate that held it. Nothing improvised.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 00:31:22 +10:00
m3ultra
2fd4c1cf79 Lane D R36 w2.5 (Fable ruling): CLASSIC KEEPS ITS PEOPLE — the 5 DJ bodies gate out of ?classic's pool
b339402 appended the 5 trellis'd DJ bodies to PED_NAMES.normal unconditionally, so
?classic=1 fetched them and its seeded crowd pool widened 17->22 (F's held §36.4 warn).
Ruled option (b): the five gate OUT of classic, mirroring the sit/look/dance !CLASSIC
clip gates. classic is the v2 town INCLUDING ITS PEOPLE; the DJs walk every other boot.

MECHANISM (rigs.js only — zero shell edits): PED_NAMES.normal restored to the covenanted
base 17 (exact pre-July-20 list); the five move to PED_NAMES.djs; loadPedFleet gains
djs = dance (default tracks the existing dance flag, which the shell already passes as
!CLASSIC). Roster = djs ? [...normal, ...djs] : normal — the five APPEND, so no base
slot renumbers: default boot's 22-list is byte-identical to the pre-ruling list and
classic's 17-list is byte-identical to pre-b339402. pickRig maps through pool.length,
so 17 restores the exact pre-July-20 crowd mapping, not merely the width.

MEASURED (fresh contexts, port-isolated no-store servers, seed 20261990):
- ?classic=1: ZERO of the 5 DJ GLBs fetched (21 ped-GLB fetches = 17+2 bodies +
  walk+idle) · pool 17 normal / 19 all · 129-citizen census all base-17 u comical,
  zero DJ · 0 console errors.
- default: all 5 fetched · pool 22/24 · 37 DJ-bodied street peds · F's organic gig
  proof preserved (pinned seed's venue crowd includes dj_phrtt_01 + woman_dj_02) ·
  0 console errors.
- IDENTITY vs pre-July-20: classic booted on a shadow tree with b339402^ rigs.js —
  full 129-citizen (id, pedRoll, pedIndex, pedName) census BYTE-IDENTICAL to the
  gated tree's classic boot. Same crowd, ped for ped, as before July 20.

Known side effect (noted §36): citizens_test.html passes no opts -> its dev harness
shows 17+2; not a covenanted surface, {djs:true} restores it if wanted.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 23:20:52 +10:00
m3ultra
622a5e0d82 Fable: frontier 2b/2c — THE HORIZON MATTE + VORONOI OUTSKIRTS (from the Lague video review; engine path rejected, shader tricks adopted)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 23:14:31 +10:00
m3ultra
d9d8cbfd3e Lane F R36 w2 (v7.0 close): THE FREEZE — README v7 section, charter risk verdicts, notes §36
README: the v7.0 section (the loop dig->guide->want->travel->list->paper, the
delta-law machinery, the classic covenant) + the morning-paper manual row.

V7_THE_GAME.md: the five charter risks get their retired-or-carried verdicts,
v4-style, written by F under the single-lane-close ruling: economy RETIRED-
structural (the named bot verifier carried to the live tier) · schema drift
RETIRED (three optional widenings, zero migrations) · re-emit identity CARRIED
(never fired; owned-item stability check unbuilt — the next re-emit round owns
it) · scope gravity RETIRED (the stage gates held) · v6 collision RETIRED (the
2 a.m. commits were the one contact, swept R36w1 + gated R36w2).

LANE_F_NOTES §36 + F-progress: the close record — both playtests, E's three
flags closed by measurement, the DJ-roster hold, the full gate numbers
(7 passed / 0 failed / 0 warn / 0 skipped · flags 0 fails / 1 warn (deliberate)
· selfcheck 156,352/156,352 · figure-scale 29 figs incl. the 5 DJ bodies ·
MATRIX GREEN 10 towns x 7 gates · R32-35 smokes re-green on the final tree).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 23:10:34 +10:00
m3ultra
b8e1082182 Lane F R36 w2 (v7.0 close): the 2 a.m. assets gated — DJ-body figure-scale leg + smoke_djdance
interior_scale_check.py grows the DJ-body leg (E flag a): the 5 trellis bodies
(b339402) spawned BY NAME through the real browse path — bind skeletons measure
0.984-0.989 UNITS vs the legacy roster's ~173 (cm convention), rigs.js:163
span-normalise absorbs it: statures 1.64/1.71/1.81/1.87/1.89 m, all [1.4,2.0],
feet planted, rig-kind verified. The old sample's (i*7)%19 could never reach
fleet indices 17-21 — the subject is now provably exercised.

flags_check.py grows STRICT smoke_djdance (E flag b: gate on binds + NaN, never
node counts): 4 Rokoko clips bind 64/65 tracks (sole unbound mixamorigReference
— E's census reproduced at runtime); gig dancers clip-driven (identity by
duration, mixer advancing, position.y FLAT); ?classic ZERO dance-clip fetches
(positive control: default fetched 4); ?noassets procedural sway ALIVE (amp
0.067 m vs clip-driven 0.00 — the inverse pair proves the branch).

ONE WARN, deliberate (the held finding, machine-recorded): ?classic fetches the
5 DJ body GLBs — unconditional PED_NAMES.normal members (D's file), which also
widened classic's crowd pool 17->22 ungated. Held for Fable per the five-holds
law; F did not improvise in D's file during the close.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 23:09:48 +10:00
m3ultra
83bce93f82 Lane E R36 w1: the 2 a.m. commits regularized — 9 assets (not 7) swept, Rokoko zone correction, depot 44→53
046668e + b339402 provenance'd: 4 dance clips are ROKOKO free mocap (263 pack,
Mixamo-skeleton export — the brief's Mixamo guess corrected by measurement; new
licence-table zone) and there are FIVE trellis DJ bodies, not three. All 9
sha1-identical to their 90sDJsim originals; chain recorded to source FBXs.

Structural: clips mesh-free 68n/198t/52-joint skin+IBM, 0 NaN, bind 65/66 (only
mixamorigReference unbound); bodies 3998-4000 tris, 1024 webp, no Draco, ped law
PASS. FLAG -> F w2: bodies' bind-pose ~0.99 units vs roster ~175 (cm) — the
figure-scale gate's expected number; rigs.js:163 absorbs it at spawn. No file
touched, nothing fixed in place.

Depot: published per R22 standing authorization via tailnet ingress,
_published.json 44->53, all 9 fetched back from the public depot byte-identical.
validate_manifest 0/0, publish --verify 53/53 live.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 22:43:17 +10:00
m3ultra
24183b1eb3 Fable (integrator): round-36 instructions — the v7.0 close (Fable plays the full loop, E regularizes the 2am assets, F gates and tags)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 22:23:13 +10:00
m3ultra
285ce69553 Fable: THE FRONTIER gains THE CIVIC LAYER — parks/fountains/statues/memorials; OSM maps them all (the war memorial angle), synthetic gets a seeded kit
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 22:20:24 +10:00
type-two
3ef751e1e3 Fable (integrator): THE TOUR — day one to first gem, 6 frames, no staging
The v7.0 charter's money shot, driven through the REAL loop on the pinned seed: arrive hunting
(the splash mission) → the strip → the shop (rumor overhead) → THE GEM (THE VERANDAHS 'Tinny',
asking $19 against ★ RARE guide $60+ — with onboarding beat ③ visible below, walking its talk)
→ the crate → dawn day 2: '⚖ SOLD Tinny — hammer $89, $78 after fees'. Bought $19, hammered
$89: the loop, told in six frames. docs/shots/v7_tour/ + sequence README. What remains for the
v7.0 tag is deliberately NOT mine: docs freeze + John's playtest (hands before tags).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 20:18:13 +10:00
type-two
25cef5983e Fable (integrator): ROUND 35 CLOSED — the first five minutes land; every v7.0 SYSTEM is built
qa.sh --strict --matrix: 7 passed · 0 failed · 0 warn · 0 skipped, MATRIX 10 towns green. New
STRICT smoke_first_five: the fresh game hunts a roster artist and the splash names hunt + cash
· the beats walked the REAL paths (② at the crate, ③ on the buy, the send-off on the ⚖) · a
returning save gets no tutorial · a second fresh browser hunts the SAME artist (seeded, not
random) · ?game=0 shows nothing · 0 console errors. THE CROSS-ROUND CATCH: the seeded want
moved a fresh-boot invariant (wants 1, not 0) and the R33 gate went red on the FULL run while
green solo — amended to a baseline snapshot, lesson filed in §35 (a feature that moves a boot
invariant must sweep every gate that assumed it; full runs exist for exactly this). Money shot:
docs/shots/v7_beta/first_five_splash.png. Remaining for v7.0: the tour + docs freeze + THE TAG
— which waits for John's hands (the alpha precedent).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 20:13:02 +10:00
type-two
55b984e0b0 Lane C+B R35 (v7.0 close #2): THE FIRST FIVE MINUTES — arrive hunting, three beats walk the hustle
A FRESH game (day 1 · empty crate · empty auction house) seeds ONE artist want off
rng(citySeed,'firstwant',0) × the parody roster (dig.js ARTISTS exported) — the splash names
the mission before the first step ('You're hunting HILLS HOIST — $191 in your pocket'), and
the keeper rumors point at the same hunt from shop one. Three toast-beats ride EXISTING events
(zero new systems): ① find a record shop (on the go-click) → ② riffle · mind the guide · BUY
(first digOpen) → ③ sell at a counter or ⚖ LIST and sleep (first find, via wallet.onChange) →
the send-off on the first sale-or-listing (the listener removes itself). A returning save gets
its town back, not a tutorial (fresh-detection = state, no save flag — the delta law stays
clean); ?game=0 / ?classic=1 never reach the block. #pc-toast body-mounted (the #pc-game rule):
beats fire INSIDE shops, and a SLEEP taken at a counter now shows its morning paper.
2026-07-20 20:13:02 +10:00
type-two
c9e233a7e5 Fable (integrator): ROUND 34 CLOSED — GODBAY hammers, all green; v7.0 needs only the first five minutes
qa.sh --strict --matrix: 7 passed · 0 failed · 0 warn · 0 skipped, MATRIX 10 towns green. New
STRICT smoke_godbay: consigned through the real crate DOM · ONE-HAMMER determinism (a frozen
pre-sleep export slept twice lands byte-identical — the seeded gavel, measured not trusted) ·
honest bounded resolution (net $19 ≤ 2×$24×0.88) · the wake toast carried the morning paper ·
the ride to melbourne hammered a listing on arrival · ?game=0 has no crate to list from · 0
console errors. Money shots in docs/shots/v7_beta/ (the dawn paper + the ⚖ crate). The full
v7 loop now closes end to end: dig the gem → keeper cash now or the gavel tomorrow → the
wantlist points at another town → the ride costs a day and hammers your listings on arrival.
Remaining for the v7.0 tag: THE FIRST FIVE MINUTES (onboarding), the tour, docs freeze — and
John's hands on the whole thing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 19:32:06 +10:00
type-two
e528c92fed Lane B R34: ⚖ LIST in the crate · AT AUCTION section · the morning paper rides the toasts
hud.js: every crate find row gains ⚖ (mail-in consignment — very 1996); AT AUCTION section
(non-empty only) shows what hammers tomorrow; the rebuild watcher now folds listings in.
index.html: the wake toast and the travel-arrival toast carry the drained auction news —
'🌅 day 2 · ⚖ SOLD "Cane Toad Blues" — hammer $22, $19 after fees' is the come-back-tomorrow
hook paying off at dawn.
2026-07-20 19:32:06 +10:00
type-two
b048f51640 Lane F R34 (v7.0 close #1): GODBAY — the offline seeded auction, one hammer per item, ever
godbayHammer(entry, townKey): THE ONE-HAMMER LAW — seeded by ITEM IDENTITY
(godbay:<townKey>:<sku|slotId>:<dayFound>:<pricePaid>), NEVER by resolution day: a passed-in
item re-lists to the same result forever, so there is no retry farm anywhere in the economy
(the keeper's offer is a formula, the auction is one hammer per item per life). Fixed 4-draw
stream. Rolls: 15% passed in (comes home free) · hammer uniform across the item's band
(grail's open top = 2×low; unbanded alpha finds auction around pricePaid — honest, never
invented) · 12% bidding war ×1.5–2.0 · 12% commission, net floor $1. EV ≈ 0.8× band midpoint
— UNDER the asking-price EV of in-band stock: random flipping bleeds, only FINDS profit
(charter law #2 at the auction house); the keeper's half-of-low stays the instant floor, the
gavel the bounded ceiling (2×high×0.88). Band table imported from C's bible.js and PRNG from
core/prng.js — one authority each, no mirrors.
listings: optional on procity-save/1 (entry shape + listDay ≥ 1, the pulls/wants back-compat
law). listFind (identity — the entry LEAVES the crate, it cannot also sell at a counter) ·
resolveAuctions() runs in adopt() AND sleep(), so sleep, travel, and day-moving imports all
hammer what they pass · takeAuctionNews() drains the transient morning paper (NOT saved — the
ledger is cash/collection; the paper is a toast).
2026-07-20 19:32:06 +10:00
type-two
776b10b599 Fable (integrator): ROUND 33 CLOSED — THE HUNT lands, all green; the beta's last system is in
qa.sh --strict --matrix on the final tree: 7 passed · 0 failed · 0 warn · 0 skipped, MATRIX 10
towns green. New STRICT smoke_wantlist_travel: ☆→★ through the real card (the save carried
{THE VERANDAHS — 'Servo at Midnight'}) · a keeper muttered the hunted artist within 2 shops
('saw one in Castlemaine just last week') · buying the wanted sleeve cleared the hunt · the
synthetic→melbourne ride cost EXACTLY one day with $179 and the find riding along · the
same-town reboot was free (the control discriminates) · the ride home charged again (day 3) ·
?game=0 built zero want DOM and muttered nothing · 0 console errors. Matrix note: town_matrix
shares one browser across towns so travel drifts the day as it walks — safe because
determinism double-boots the SAME town (free by the same-town rule); reasoned before landing.
Money shots: docs/shots/v7_beta/rumor_and_wantlist.png (the keeper's mutter over the toy
shop's 70s wallpaper + THE WANTLIST open in the crate) + want_card.png. README manual row.
v7.0-beta systems are now ALL BUILT — what remains of v7: Esc-in-person (John's hands), then
the v7.0 close (GodBay offline auctions, THE FIRST FIVE MINUTES, the tour, the tag).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 19:01:40 +10:00
type-two
7b26e45fa0 Lane B R33: THE WANTLIST in the crate panel · the arrival toast · the build-race fix the shot found
hud.js: THE WANTLIST section in the crate (non-empty only — no header over nothing): hunted
rows, ✕ stops hunting, refresh watches wants length so an open panel follows adds/clears.
THE FIX: R32's game surface BUILT only in street update() frames — a door entered fast after
boot had no crate/bar inside at all, and the interior tickGame() refreshed a UI that never
existed. Every gate idles on the street long enough to build, so green gates missed it; the
R33 money-shot script entered faster and exposed it. tickGame() now builds-or-refreshes in
ANY mode (shots are a detector, not decoration — filed in LANE_F_NOTES §33).
index.html: game.traveled at boot → '🚌 <town> — the ride cost a day. Day N' toast (save.js
already charged the fare and re-keyed the week before the shell read game.day).
2026-07-20 19:01:40 +10:00
type-two
37773bc81d Lane F R33 (v7.0-beta wave 2): wants on the save · the keeper's rumor · TRAVEL COSTS A DAY
save.js: optional wants:[{artist?|title?|sku?}] on procity-save/1 (validated exact-and-loud,
≥1 string field per entry, cap 40, absent-when-empty — the same back-compat law as pulls).
recordWant (case-insensitive dedupe) / findWant (a want covers an item when every field the
WANT names matches) / removeWant (identity). TRAVEL in adopt(): the adopted save's town !==
this boot's townKey (town OR seed — a new seed is a new town) ⇒ day += 1, traveled = true,
prunePulls — load AND import both pay the fare; same-town adoption is FREE, so reloads and
export→import round trips keep the save-determinism gate's byte equality untouched. The 23
towns are a map you spend days crossing now, not a dropdown.
interior_mode.js: onWant/isWanted seams into dig.open; buying a matching item clears its want
('✓ off the wantlist'). THE RUMOR (charter #6 — the keeper finally gossips): seeded
xmur3('rumor:citySeed:shopId:day') → ~45% of shop entries mutter a hunted want + a real
cached town in the banner. Runtime-only stream, freshly keyed — A's rotation boundary holds;
no game / empty wantlist ⇒ the line never exists. Full persona lines stay D's for the v6
clip epoch; the banner is the keeper's voice until then.
2026-07-20 19:01:40 +10:00
type-two
ce71908bb5 Lane C R33 (v7.0-beta wave 2): ☆ WANT IT on the pull card
The wantlist grows from the card, not a search box the 90s didn't have: you pulled it, it's
dear, you'll hunt one cheaper. ☆ WANT IT → ★ ON THE WANTLIST (a stamp once wanted, not a
button). Construction-gated on opts.onWant/isWanted — ?game=0 / classic cards carry ZERO
wantlist DOM, the classic-pure pattern.
2026-07-20 19:01:40 +10:00
type-two
9aae167d64 Fable (integrator): ROUND 32 CLOSED — v7.0-beta wave 1 all green; smoke_bible + the routing amendment
qa.sh --strict --matrix on the final tree: 7 passed · 0 failed · 0 warn · 0 skipped, MATRIX 10
towns green. New STRICT gate smoke_bible: band law over EVERY bin off the digOffers surface
(80 offers/5 bins), the buy-everything ledger runs on THE GEM'S BIN so the finder-margin leg is
non-vacuous (bin1: 2 gems, grail asking $19 vs keeper $30; ledger closes to the cent — Σask
$333 → Σoffer $135, gems alone +$21, every non-gem strictly negative), scarcity measured
(emptied bin 0/16 on re-open AND across a full reload; 16 pulls in the save) with the ?game=0
negative control re-opening 16/16 (the detector discriminates). sell_routing amended from the
alpha inequality (credited < paid) to the exact formula (credited == min(basis−1, basis//2),
basis = bandLow-or-pricePaid) — the fence moved from the receipt to the band, which is what
charter law #2 always said; it passed on a banded standard item (+$4 on basis $8). _at_bin
grows an idx param (back-compatible). Money shots in docs/shots/v7_beta/ (the guide card + the
crate at the counter). LANE_F_NOTES §32 (incl. the JING5 fresh-checkout trap: gitignored
web/assets/models synced from m3ultra — also un-warned the validator's 15 cross-pack checks).
README manual: the guide/gem/scarcity rows. Remaining beta charter queued in the round doc:
wantlist + rumors · travel costs a day · Esc-in-person · then GodBay.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 18:27:19 +10:00
type-two
bb30767b62 Lane B R32 (§31.6 queue 1–3): the crate lives in shops · cluster-pose spawn · dawn reads dawn
hud.js: #pc-game rides document.body (position:fixed, z 30) instead of the mode-hidden street
root — day/cash/CRATE/SLEEP are YOURS inside a shop too (the playtest's 'crate at the counter').
C toggles the crate ANYWHERE; the surface yields to the dig/sell cards and the map (they own
the screen). tickGame() exported so the interior/map frame branches keep it live (buys update
the count while you shop); dispose removes the body-mounted wrap.
index.html: the frozen spawn (-96,9,east) was tuned for the synthetic fixture — on real towns
it faced dirt (§31.6-1). Non-classic boots now spawn ON the main-street footpath door-line
(block kind 'mainstreet', both generators; every-door fallback) at the door nearest the shop
cluster's heart, facing the LONGER run of shopfronts down the strip's dominant axis. Pure plan
math, zero draws — no stream moves; ?classic=1 keeps the frozen pose forever.
lighting.js: dawnSky opt (shell passes high-cirrus, null under classic) — the DAWN segment
swaps the dome off the golden-SUNSET default that read dusk-orange (§31.6-3), and swaps back.
setSky() is now an explicit override: weather's rain/overcast dome always wins over base+dawn.
2026-07-20 18:26:56 +10:00
type-two
75e5e06288 Lane F R32 (v7.0-beta wave 1): per-day scarcity — pulls ride procity-save/1, the bin remembers
save.js: optional pulls: string[] on the SAME schema (validated exact-and-loud when present;
absent on every alpha save — old savers drop it silently, never reject; payload omits it when
empty so the alpha byte shape survives). recordPull/pullsFor seams; pruned on sleep/import —
day-tagged keys from other days are unreachable by construction (the consumer embeds the
current day), untagged keys are real-sourced and KEPT (sold means gone, tier-2 spirit at
tier 1). Cap 600 FIFO (delta law: saves stay tiny). Collection entries gain optional band
(recordFind stamps dig + shelf buys) — C's §9.1 sell basis reads it.
interior_mode.js: pull keys ${shopId}|${binKey}${pullTag}#${slot} — pullTag @d<game.day> for
rotating crates INCLUDING day 1 (the SEED stays untagged per the day-1 convention; the ledger
doesn't — a day-1 pull must not survive into day 2's identical-looking untagged key space),
'' for real-sourced (their pulls never expire). The seed input to binSeed is UNTOUCHED — A's
rotation boundary holds. Selling an item does NOT restock the bin (pulls are independent of the
collection — the leak the §30.6 filing predicted). digOffers getter = the honest-gate surface
(index/ask/band/sku) so the bible gate asserts identity, not looks.
2026-07-20 18:26:36 +10:00
type-two
f9517f14ab Lane C R32 (v7.0-beta wave 1): THE BIBLE — bands derived, gems seeded, the cards get the guide stamp
bible.js: band ranges MIRRORED from E's pipeline thresholds (record 8/25/60 · book 5/15/40 ·
toy 6/20/50) — derived in JS off the price_band labels the packs already carry, NO pack re-emit
(charter risk #3 never opens). guideText renders a RANGE ('guide $8–24' / '$60+'), never a
verdict — the card never says gem; spotting asking-under-guide is the player's skill.
dig.js: parody sleeves roll seeded bands (25/45/22/8) + a 10% gem on collector/grail only,
asking strictly under the keeper's own bandLow offer (collector $2–9 vs $12 · grail $5–20 vs
$30) — margin by construction; fixed 9-draw stream per item; rare ≡ grail (matches real packs).
Real offers carry band off price_band — and real asking always sits INSIDE its band (the band
derives FROM the price), so with the §9.1 basis swap every real round trip stays strictly
negative: the no-pump gate holds untouched, structurally. open({gone}) omits pulled slots AFTER
the draw (R27 pick-then-omit, zero collateral; o.i survives filtering — identity preserved).
currentOffers() honest surface for F's gate. sell.js: the §9.1 basis swap EXECUTED exactly as
pre-ratified — sellOffer(sellBasis(entry)) = min(bandLow−1, max(1, floor(bandLow·0.5))) when
banded; alpha-era entries (no band) keep the pricePaid basis and sell exactly as before. The
sell card carries the guide line too.
2026-07-20 18:26:15 +10:00
m3ultra
046668e5af feat(gig): club dancers play real dance clips, not procedural sway
loadPedFleet gains dance:!CLASSIC (mirrors the sit/look gate) → fleet.danceClips
= 4 mesh-free mixamorig club dances (party/medium/drink/sway). _make picks one
per dancer off a fresh gigdance seeded stream (zero impact on existing draws /
the same-seed-same-crowd covenant), spawnRig plays it, and update() drops the
fake bounce for real-clip dancers (procedural sway stays as the classic/noassets
fallback). ?classic fetches nothing, byte-identical.

Verified: default boot fetches all 4 dance GLBs (gated load runs), both modules
parse + export clean, page boots. Same clips + _canon/_rotOnly/spawnRig proven
animating in 90sDJsim (its crowd/rigs.js is this file ported).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 02:11:55 +10:00
m3ultra
b3394027be feat(citizens): +5 trellis DJ bodies in the ped roster
Same 5 Mixamo-rigged DJ characters as 90sDJsim, converted FBX->skinned GLB +
decimated to 4k tris (<=5k ped law) via MESHGOD scripts/fbx_to_ped.py — mixamorig:
skeleton + skin preserved. Added to PED_NAMES (rigs.js). Shares the proven bind:
identical _canon/_rotOnly + the same GLBs verified animating in 90sDJsim.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 01:37:10 +10:00
m3ultra
3c5d0a1e95 Fable: THE FRONTIER gains THE VERNACULAR KIT — John's building-variety idea, source corrected (OSM/Overture footprints + parametric archetypes, never Google 3D)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 22:43:57 +10:00
m3ultra
67913f8b66 Fable: THE FRONTIER gains THE THIRD DIMENSION + TEXTURE LIFE — the noise ruling (semantic streams stay; noise only where continuous), terrain chartered v8+ with costs named
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 22:40:53 +10:00
m3ultra
bb5f36f1e0 Fable (integrator): ROUND 31 CLOSED — v7.0-alpha tagged and pushed; the playtest found what the gates couldn't
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 16:42:36 +10:00
m3ultra
2491f1c8ec Lane F R31 wave 2 (v7.0-alpha close): THE SHOT + the ledger. docs/shots/v7_alpha/crate_panel.png — the alpha's face: Monster Robot Party's real frontage, THE CRATE open with 3 real finds (real cover thumbs, paid/where/day honest), game bar day 1 · $116 · CRATE 3 · SLEEP, driven through the REAL loop with the dig param ABSENT (the R31 flip carries the shot), 0 console errors. LANE_F_NOTES §31 (the flip both ways, the two new gate legs + their negative control, harness lesson #4 — the explicit-flag blindfold, THE BETA QUEUE filed) + F-progress R31. Tag v7.0-alpha rides this commit, LOCAL ONLY — Fable reviews and pushes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 16:38:07 +10:00
m3ultra
50385c0f08 Lane F R31 wave 1 (v7.0-alpha close): THE DIG FLIP — the alpha-blocker Fable's playtest found. DIG_ON = flagOn('dig') (default-ON, ?dig=0 opt-out, ?classic=1 off — the v3.1 pattern; the v2-era opt-in survived 6 epochs past the README's 'dig on' claim because every R30 gate booted dig=1 explicitly: the explicit-flag blindfold). Fenced forever: default_boot_gate asserts dig REACHABILITY on a NAKED boot (E at a bin → digActive true), classic_regression asserts dig INERT at the bin (zero pcdg DOM, measured not assumed). PROCITY.flags gains dig. Splash de-staled ('Every door opens.' — no more '(soon)'; controls line carries E riffle/sell · C crate · SLEEP to save) + README manual row (sell at counter · C crate · SLEEP = save/advance/rotate). PROCITY_QA_PORT env override (flags_check, interior_scale_check, town_matrix) so the suite runs port-isolated instead of silently reusing whatever holds :8130. Verified both ways on :8951 (naked boot digs + closes clean; dig=0 and classic zero dig DOM, classic ZERO storage calls; noassets digs parody, 0 errors). qa.sh --strict --matrix on :8931: 7/0/0/0, flags_check 0 fails 0 warns, MATRIX 10 towns green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 16:37:02 +10:00
m3ultra
d96cbde004 Fable (integrator): round-31 instructions — the alpha close (wave 0: Fable plays it; then micro-fixes; then the tag)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 15:58:32 +10:00
m3ultra
7a7da29132 Fable (integrator): ROUND 30 CLOSED — v7.0-alpha wave 1 complete, all green, 7/0/0; the game remembers, spends, and cannot cheat
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 15:11:54 +10:00
m3ultra
2c9cb94b8e Lane F R30 wave 3 (v7.0-alpha ledger #5 + §9.4): THE GATES + the sell routing — E chain aimed-bin→dig · counter+sellable→C's sell card (removeFind-first onSell, veto-safe) · forgiving-bin · shelf; aim-priority amendment measured both ways (counter-adjacent bin sells, wrong-aim bin still digs); 3 shell seams (sellOpen unlock, sellActive guards). Five STRICT suite gates in flags_check: routing live-loop ($9 dig → +$4 sell → HUD $186·CRATE 0), save/load determinism (byte-equal state+export, plan fp unchanged, weekNight re-key), no-pump 5/5 strictly negative (Σ−$30) + C's negative control (wallet.buy invoked-and-false ⇒ recordFind unreachable, positive arm discriminates), classic purity (Storage-prototype instrument, 0 calls classic/game=0, positive control sees procity-save, plan fp game-on==off), rotation over VERTEX DATA (day 1/2/3 distinct, re-entry + cross-boot byte-identical, game=0==day1). qa.sh --strict --matrix: 7 passed · 0 failed · 0 warn · 0 skipped, MATRIX 10 towns green. NO TAG per brief.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 15:10:08 +10:00
m3ultra
5e115688ed Lane A R30 (v7.0-alpha ledger #6): THE ROTATION BOUNDARY VERIFY — HOLDS. F's day salt (207bcff) rides runtime streams only: stk-* stock-pick labels, dig binSeed, setWeekNight night-SELECTION over the plan-time week. Measured 3 ways: grep-zero over the proven citygen/** glob (8 files/2546 lines, pattern non-vacuous vs interior_mode.js); generatePlanFor consumes no day-shaped opt at any call site; day-inertness byte-measured (plan hash identical day-absent/1/37, 3 seeds x synthetic+osm, 6/6). Selfcheck ALL GREEN 156352/156352, fingerprint 0x5f76e76 frozen, all town pins green, zero re-pins.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 14:42:54 +10:00
m3ultra
cb03f07fb2 Lane B R30 (v7.0-alpha ledger #4): THE COLLECTION UI + SLEEP surface — the crate panel in hud.js (cover thumb = pack-atlas CSS crop off the sku, per-shop/town-wide bases, parody = glyph by design; where+day honest, foreign towns fall back to townKey), game bar day/$/CRATE count + SLEEP on F's §30.1 contract; construction-gated on PROCITY.game itself (classic/game=0 ⇒ zero DOM/listeners, measured); venue.js compat venueShopId = live weekNight getter (F's §30.4 filing, 116→490 across a sleep). Verified vs F's REAL committed core (nothing stubbed) on no-store :8151 snapshot; qa 6/6 2026-07-18 14:37:15 +10:00
m3ultra
f419c1a5e2 Fable (integrator): R30 wave-1/2 review — amendments ratified, the cross-lane 6 lines adjudicated, wave-3 inputs bound
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 14:29:39 +10:00
m3ultra
7b7227af73 Lane F R30 close-out: §30.5 results (7 verify legs, qa 6/6, the two harness bugs recorded) + F-progress — ledger #5 gates + sell routing held for the later F session per the brief 2026-07-18 14:27:44 +10:00
m3ultra
207bcffb54 Lane F R30 (v7.0-alpha ledgers #1+#3): THE SAVE CORE + SLEEP=SAVE=TOMORROW — save.js (procity-save/1, delta law by shape, loud reject + stash, beforeunload), game-backed wallet facade on the proven seam, buy seams record the collection (type/sku|slotId/title, C's §9 asks), day-salted stock rotation (stk-* seam in interiors/layout [marked], dig binSeed; day 1 = pre-v7, REAL never rotates), gig weekNight = (day-1)%7 (amended from day%7 with the measurement, §30.3), wake at DAWN. classic/game=0: layer never constructed, ZERO storage touches measured. Verified 7 legs green in fresh contexts; qa.sh --strict 6/6, 0 warn 2026-07-18 14:26:35 +10:00
m3ultra
369a2be23e Lane C R30 (v7.0-alpha ledger #2, implementation): sell.js — the sell counter lands. E at the bench → the mirrored price sticker; offer strictly below pricePaid by construction (8/8 scripted round trips lose money); type=type fail-closed (untyped item sells nowhere); wallet.sell = the credit mirror; game layer absent → no card, zero errors. Verified on :8144 no-store, stubbed game shape (F's core not landed); soak green avg 3.4ms · leak 0/0 · draws card-open ≡ closed 2026-07-18 14:14:14 +10:00
m3ultra
53fcd3ca1b Lane F R30 (v7.0-alpha ledger #1, THE CONTRACT FIRST): LANE_F_NOTES §30 — window.PROCITY.game published ({day,cash,collection,townKey,save,load,sleep,export,import,wallet,recordFind,removeFind}), procity-save/1 schema (the delta law by shape), the day-salt rotation law (day 1 = pre-v7, real never rotates, gig night = day%7), C's §9 asks honoured (type/title/artist + removeFind) 2026-07-18 14:09:09 +10:00
m3ultra
c8503a5a2e Lane C R30 (v7.0-alpha ledger #2, contract first): LANE_C_PUB §9 — the sell counter. Offer = min(p−1, max(1, floor(p·0.5))): the no-pump law structural, not tuned; type=type strict, fail-closed on untyped items; onSell mirrors onBuy (the proven wallet seam); collection read-only, removal via the game API 2026-07-18 14:04:59 +10:00
m3ultra
83ef8c9234 Fable (integrator): V7 RATIFIED + round-30 instructions (the save and the crate, wave 1) + README manual (measured controls)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 11:27:03 +10:00
m3ultra
e2345b544d Fable (integrator): V7 THE GAME draft charter — persistence, the collection, the gem thrill, GodBay; every system on existing rails
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 23:05:41 +10:00
m3ultra
bebaa66196 Fable: THE FRONTIER — the honest map of what is NOT figured out (John's challenge, answered in writing)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 22:58:32 +10:00
m3ultra
fe00c11d2c Fable: digalot.fyi/procity private-test deploy script — noindex/no-listing, _raw excluded, licence flag stays armed
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 21:36:18 +10:00
m3ultra
a80ad55f47 Lane F R29 wave 0: the pose gate gains its subject — v6's first clip is in the game
R28's gate had no subject and SKIPPED with its reason printed (look.glb was published, verified,
sha1-proven off the depot — and nothing bound it). D bound it in wave 0. The gate is now
permanent, and flags_check is GREEN:

  ✓ default boot: look.glb BOUND — 6.5s, 64 tracks
  ✓ the glance REACHES THE GAME — peak 46 of 396 peds glancing · 0 console errors
  ✓ ?classic=1: look.glb NEVER FETCHED — the zero-fetch-delta covenant holds
  ✓ ?classic=1: glance inert at the source — 0 of 269 peds, AND the rng stream still draws

That last line is the design, and it is D's cleverness: THE DETERMINISM IS IN THE DRAW, NOT THE
OUTCOME. D takes from a dedicated glanceRng stream always — so the seeded sequence is identical
whether or not a clip exists — and only flips the flag when fleet.lookClip is real. Under
?classic the roll still happens, the flag never sets, nothing is fetched, the covenant stays
byte-identical. A behaviour added without touching a hash.

The gate asserts the clip's ABSENCE under classic, not merely "0 glancing" — because "0
glancing" alone passes on a fleet that failed to load at all. Present-on-default + absent-under-
classic is the pair a broken fleet cannot satisfy.

F'S OWN GATE CRASHED THE SUITE FIRST: the subject check used pg.goto() on a .glb, which starts a
download, aborts the navigation, and took flags_check down before one assertion ran. A gate that
DIES looking for its subject is worse than one that skips — it reports nothing at all and takes
the suite with it. Now it reads the disk. The vacuous-gate law has a sibling: a gate must survive
its own subject being absent.

No tag — wave 0 rides Spike 2's close, gated on John's footage.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 20:15:20 +10:00
m3ultra
10025d4082 Lane D R29 (v6 Spike 1): look.glb bound and wired — peds glance, feet planted (wave 0)
The spike reaches the game. E's look.glb (mesh-free, 65 joints, 6.5s 'Look') is bound
through the fleet gate; street peds now glance around during window-shop stops. `lean`
is absent and never existed (E's R28 measurement). Goldens untouched — clips sit outside
every plan hash; the synthetic anchor stays R27's amended 0x5f76e76.

WIRED: loadPedFleet(base, {sit, look}) -> fleet.lookClip, the exact twin of the R16 sit
gate; the shell passes look:!CLASSIC so ?classic fetches NOTHING (a raw fetch would have
breached the zero-fetch-delta covenant — E flagged it, the gate is the answer).
makeActor gains lookA, played only on setLooking(true) — crossfaded (0.3s), not snapped
like sit, since idle->idle-variant reads naturally and there's no replant for a fade to
fight. sim.js gains a dedicated `glance` rng stream, rolled ALWAYS (deterministic) but
only flipped when a clip exists => classic inert at the source. Mutually exclusive with
bench-sit (sit rolls first at 35%, glance takes 40% of the rest). RigPool threads
lookClip; release snaps the glance off so no rig is pooled mid-look.

THE FINDING — my own prediction was wrong, and the measurement caught it. I wrote that a
STANDING clip needs no foot replant ("no hip descent to recover — that trap was sit's
alone"). Measured over the full loop: idle keeps the lowest bone within 4cm of the ground
(-0.04..0.00); look.glb ranges -0.009..+0.205 — the ped HOVERS up to 20.5cm, drifting
through the loop. Same trap, different coat: _rotOnly drops the Hips POSITION track for
rig-height independence, so the clip's authored weight-shift dip can't move the hips and
comes out as the FEET RISING. Sit's one-time replant can't fix it — the float varies per
frame.

FIX: plantFeet() — the per-frame twin of sit's replant. Reset to bind plant -> measure
posed feet -> drop inner by float/scale. ABSOLUTE (reset->measure->correct), never
incremental, so it cannot drift. Reproduces the authored motion exactly: the hips dip
relative to the ground instead of the feet leaving it. Scale-aware like sit's. Foot bones
cached once; cost paid only by glancing peds, on frames their mixer ran (both mixer sites
— the drummer's post-mix lean taught that coupling rule).

PROOFS: foot float 0.205m -> 0.000m (pinned); head yaw sweep span 1.735 (-0.85..+0.89) so
the glance still animates; ?classic — no look fetch, no Look action on any rig, glance
never flips (0); identity signature unchanged, no glance term; sit AND glance never
simultaneous (0); 0 NaN; goldens 0x5f76e76 untouched. A glancing ped is NOT tagged
seated/posed — standing pose at standing stature, so F's no-giants gate needs no arm.

D->F: the pose gate HAS ITS SUBJECT — un-SKIP it. Fresh ?localdepot=1 boot shows a ped
glancing on the footpath, head sweeping, feet planted.
Spike 2 readiness: plantFeet() is the general machinery the brief asked for ("data, not
new code") — riffle and wall-lean should land as clip + roll; the lean's wall-contact
placement is the only genuinely new part.
FLAGGED (A's file, A rests): selfcheck.js:301 still says "the synthetic golden above stays
0x3fa36874" while line 267 pins the amended 0x5f76e76. A stale comment, not a rule — but
the same species as the epoch's five holds, so: flagged, not touched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 18:44:07 +10:00
m3ultra
09dff6b788 Fable (integrator): round-29 instructions — the v6 spikes (wave 0 now, the rest gated on John's footage) + charter amended (lean doesn't exist)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 18:13:01 +10:00
m3ultra
cdce67c8f8 Lane F R28 (v5.1): the sweep gated and tagged — the spike never reached the game
qa.sh --strict --matrix 7 passed / 0 failed / 0 warn. selfcheck ALL GREEN 156,352/156,352 —
UNCHANGED, confirming the brief's prediction that nothing this round moves a golden. MATRIX
GREEN 10 towns x 7 gates.

F'S POSE GATE HAS NO SUBJECT — SKIPPED with the reason printed, per the vacuous-gate law. The
brief asked for "lean/look poses through the scale gate" and "leaning peds in old frames". There
are no new poses in the game:
  · `lean` DOES NOT EXIST. E measured instead of trusting the list — the stash's 34 clips have no
    lean, and find -iname "*lean*" across ultra returns nothing. Brief and kickoff both called it
    already-cached. The premise was half wrong, and E found it the only way these are ever found.
  · `look.glb` is shipped, verified, published, sha1-checked by fetching the bytes BACK off the
    public depot — AND NOTHING BINDS IT. loadPedFleet() loads walk, idle, sit. No look. E's
    handoff said it plainly ("-> D: gate it behind an opt the way rigs.js gates sit"); D's entire
    R28 commit is LANE_D_NOTES.md, 48 lines, docs only. The run order said "when E's clips land:
    D (#1 wiring)" — the log says D committed BEFORE E shipped.

Nobody was careless; the wave order was. Every check E ran was real and passed, and none of them
could see that no consumer exists. A GATE ON THE ARTIFACT CANNOT SEE THE WIRE — fifth appearance
of the R17 pattern this epoch and the cheapest yet to have missed, because the artifact was
perfect.

v5.1 SHIPS ANYWAY, AND DOES NOT CLAIM THE SPIKE. An unbound clip is inert: nothing is broken,
the game is unchanged and correct. This is not R18's "known breach", it is an unfinished item,
and holding a polish tag over an inert file would be precious. What ships is the sweep:
C's audioEmitter (its own v3.0 note had specified it wrong on both halves — "the panner was
inert"), and TWO backlog items KILLED WITH THE MEASUREMENT — B's awning ripple and D's
instrument LOD, the oldest tri item on the books. They don't leave the backlog "deferred"; they
leave it gone, with the number that killed them.

Filed for R29 (D, one opt): rigs.js gains `look` the way it gates `sit`; F passes it from the
shell. Then the pose gate has a subject and F runs it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 17:59:02 +10:00
m3ultra
488067004c Lane C R28: audioEmitter — the till rings from the counter; the panner was inert
R28 ledger #3. My own idea, parked since R7. Brief stated acceptance only, so I scoped it.

THE TRAP MY OWN v3.0 NOTE SET: it said "userData.audioEmitter = musicKey so Lane B can
position/spatialise the bed". Both halves wrong. (1) Shape: the engine holds no reference to
the room group and would traverse a scene it cannot reach — emitters belong on room.audio as
data, riding the EXISTING playInterior() call (no lifecycle, no traverse, no dispose hook).
(2) Method: "spatialise" invites a PannerNode. NOTHING in this tree sets ctx.listener (zero
hits), so a panner resolves against a listener frozen at (0,0,0) — which, because rooms are
built room-local at the origin, is the room's own CENTRE, forever. Measured, OfflineAudioContext,
real pub stage bed:
    panner, listener never set (the engine as it SHIPS): RMS 0.106905 door / 0.106905 stage
                                                          -> ratio 1.0000, bit-identical
    panner + listener tracking the player:                RMS 0.055665 / 0.500000 -> 8.98x
It plays, throws nothing, warns nothing, and never changes by one sample as you cross the room
— while failing at the only thing it exists to do. "Routed through a panner?" yes. "Audible in
a venue?" yes. "In a shop?" yes. All green, all worthless.
-> ASSERT THE DELTA (gain at door != gain at stage), NEVER THE PRESENCE. Vacuous-gate law, 4th
application — and this one had no author to blame but me.
The house idiom was already right: tram/spill/gig-spill are all hand-rolled distance-gains vs
PROCITY.camera.position. No listener, no panner.

SHIPPED (C's files only):
- room.audio.emitters {stage?, counter?} — room-local, 3dp, 18/18 same-seed deep-equal. `r`
  (reach) is the ROOM DIAGONAL, not a constant: measured diagonals 9.81-15.63m, and scaling to
  the room holds door->stage proximity in a 0.42-0.59 band from a pokey band_room to a hall pub.
  `floor` is the room-filling residue, never 0 — a PA fills a pub; what changes as you walk in
  is the BALANCE, not the presence. Swing 2.65-4.52 dB (John R20: subtle changes are great).
- THE TILL RINGS FROM THE COUNTER — audible positioning in a normal shop with NO ENGINE CHANGE,
  via B's already-public playSfx(key,{gain}): C does its own distance math, hands B a scalar.
  Measured through the real BUY button, real record shop: 0.9489 @0.60m vs 0.6436 @4.19m =
  3.37 dB; negative control (no emitters) fires at exactly 1.0. sfx-till.ogg has been on disk
  since 15 Jul and had NEVER ONCE PLAYED. Routing, not content — as the brief said.
- ?mute=1 now actually silences the crate riffle (below).

WHY THE VENUE HALF IS AN ASK: a one-shot resolves its gain once, at fire time, from a position C
already knows. A bed must be re-gained every frame inside the engine's update() loop — B's file.
Structural, not political. B has a ~12-line paste-ready diff in the house idiom; audio.js
untouched. The letter of "consulted not modified" could have been met by reaching through B's
exposed state.layers diag to rewire its graph from outside — that backdoor is strictly worse
than the edit it avoids. Refused.

TWO BUGS FOUND:
- F DROPS EMITTERS ON THE ONE NIGHT THEY MATTER. interior_mode.js:289 rebuilds the spec on a gig
  night ({musicKey: ra.gigKey, toneKey: WALLA_KEY}) and every unnamed field dies. Measured on a
  real gig-night pub: quietNight_emittersSurvive TRUE, gigNight_emittersSurvive FALSE. Silent,
  fail-soft, no error. -> F: don't just spread `ra`; pass emitters DELIBERATELY, gig-night only —
  on a quiet night the radio + room-tone genuinely ARE room-filling.
- ?mute=1 NEVER SILENCED THE RIFFLE (R5->R28). dig.js opened a SECOND, private AudioContext
  straight to destination, breaking both stated house laws (ONE AudioContext; mute forces
  silence). It survived three epochs because NO GATE ASSERTS MUTE => SILENCE: the engine returns
  its silent surface and looks honest from its own side, while the sound came from somewhere it
  never knew existed. A gate that asks the engine whether it is muted can only ever answer yes.
  Fixed via B's PUBLIC .muted getter (consulted, not modified), URL fallback when standalone.
  Measured with a positive control so the probe provably CAN see sound: live 1 ctx / 1 source;
  muted 0 / 0. Blips stay procedural on purpose — zero-fetch, deterministic, still work under
  ?noassets=1 where a sampled riffle cannot. Only mute was broken; only mute was fixed.
  -> F: assert SILENCE, not muted-state.

HARNESS HONESTY: the first till rig reported the same clean 3.37 dB — from a CONTAMINATED rig
(createDig appends a panel per call, close() doesn't remove it, so querySelector clicked trial
1's stale button every time). Tell: panelUp false on all three, and the NEGATIVE CONTROL
returning 0.6436 where it must return 1.0. The gains matched the formula exactly; I refused them
anyway and re-ran isolated (panelCount 1, panelUp true, control 1.0). Never report a number from
a rig you have watched lie — even when you like the number.

QA GREEN 6/0/0/0. Docs: LANE_C_AUDIO.md -> v3.1 (contract + both asks + every measurement).
2026-07-17 17:39:54 +10:00
m3ultra
640bdbb55d Lane E R28 (v5.1): Spike 1 — look.glb shipped; lean does not exist (ledger #1)
look.glb — DONE, published, verified. Looking_Around.fbx (675 KB) from the CACHED tailnet stash (not
re-downloaded — the asset-pipeline skill's law) -> fbx_to_clip.py -> web/models/peds/look.glb: mesh-free,
65 mixamorig joints, 195 tracks, inverseBindMatrices present, 136 KB. Structurally identical to the
sit.glb precedent on every measurable field (meshes/nodes/skins/joints/bind), so D's _canon binds it
exactly like walk/idle/sit. Depot: published to digalot.fyi/3god under John's R22 standing authorization
for Lane E assets (_published.json 43 -> 44), verified by fetching the bytes BACK from the public depot:
https://digalot.fyi/3god/a/look.glb -> HTTP 200, 139,640 bytes, sha1 identical to local.
-> D: it's ready. Gate it behind an opt the way rigs.js gates sit (loadPedFleet(base, { sit })) — a raw
fetch would breach the ?classic=1 zero-fetch-delta covenant, same as sit.glb.

`lean` DOES NOT EXIST — the spike's premise is half wrong. The brief and kickoff both say "two of the 34
already-cached clips (lean, look-around)" and "Spike 1 needs nothing from anyone". look-around is cached;
lean is not, anywhere. Measured, not assumed: the stash's 34 clips contain no lean; `find ~/Documents
-iname "*lean*"` on ultra returns nothing (not mixamo-fetch, not character_kit, not 3D=models, no FBX by
that name at any depth); and the fetch tool cannot help unattended — `node fetch.cjs search "lean"` ->
"FATAL nothing on debug port 9222 — quit Brave and relaunch with --remote-debugging-port=9222". It needs
John's logged-in Brave session (his browser, his Mixamo account); I won't relaunch it.
I did NOT substitute a lookalike. `Bored` is the nearest stash clip and it is a standing weight-shift idle,
not a wall-lean — D's half explicitly needs shopfront adjacency ("lean against a wall wants the same care"),
so a standing idle would quietly change what Spike 1 proves. Half a spike, honestly, beats a whole one that
lies. -> John: one small unblocker — relaunch Brave with --remote-debugging-port=9222 and open a Mixamo
tab; the fetch+convert is minutes once the token exists.

Instrument LOD — D killed it and D's measurement is better than mine. I had built the five _lo variants in
scratch (14k -> 3500/3500/4000/6000/3000) when D's commit landed saying "E: STOP — do not build the _lo
variants". Nothing shipped; scratch discarded. D measured the rooms an LOD must fire in: max diagonal 15.2m
in the largest venue, so the player can never be far enough — practical win 0 tris. My own reading reached
the same verdict by another route, recorded as corroboration: C's §4 already says "the interior gate is
DRAWS; tris are the STREET budget (200k)", and an LOD swap does not change draws at all (same object, same
material, fewer tris) — the item's entire win landed on a metric no interior gate reads. Two independent
measurements, one verdict. Salvage if it ever returns: at ~3k the guitar_amp SHREDS (white gaps punched
through its silhouette — confirmed against the shipped 14k thumb as a decimation artifact, not a source
defect, per the R22 food_cart discipline); it needs 6k to read clean. The other four were clean at 3-4k.

-> A: your own file's comment now contradicts its code. Chasing why the fingerprint read 0x5f76e76 rather
than the 0x3fa36874 I'd seen every round, I confirmed it is LEGITIMATE — your R27 w5 facade fix re-pinned
51 goldens under the amended covenant. But selfcheck.js:267 pins hash: 0x5f76e76 while selfcheck.js:301
still says "the synthetic golden above stays 0x3fa36874". Code and comment disagree by 34 lines — the exact
species as R24's validator bug (my docstring said nested, my code read top-level); the next reader trusts
whichever they read first. Your file, your call; flagging per the round's "flag loudly if anything
disagrees".

Gates: selfcheck ALL GREEN 156,352/156,352 · qa.sh --strict 6 passed / 0 failed. look.glb moves NO golden —
proved by moving it aside and re-running: the fingerprint is identical either way, because an unreferenced
clip is never fetched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 17:35:17 +10:00
m3ultra
999c9d653f Lane B R28 (v5.1): the awning fabric ripple — KILLED with the measurement
Ledger #2. Built it, proved it worked, measured what it was worth, killed it.
A 10x-over-driven billow — 1.4m of flap on a 0.14m canopy — cannot move 1% of
the frame. It is not the gain, it is the geometry. Nothing ships: buildings.js
and wind.js are byte-identical to HEAD.

IT WAS BUILT, AND IT WORKED. Split the stall canopies out of the rigid awning
InstancedMesh (own mesh, own subdivided box — a 4-corner box has nothing to
ripple), gave only them a cloth billow riding R17's wind.js uniforms: sin(pi u)*
sin(pi v) pin (zero at the four posts by construction), two crossed travelling
waves, per-instance phase so forty stalls don't breathe in unison. Proven by
rendered-pixel hashes, not by eye:
  cloth displaces (calm→gusty)        40f59eaf → d6d3e1f1  moves
  back to calm                        40f59eaf              identical
  uWindTime=99 at amp 0               40f59eaf              identical
  ?classic=1, pre- vs post-build      453c0154 → 453c0154   BYTE-IDENTICAL
  rest of town rides wind? (hidden)   76c8b518 → 76c8b518   still
  control: cloth back                 frame moves again
Cost at market_square, true A/B: +0 draws (98→98) / +2,160 tris. +0, not the +1
my own comment claimed — the square's chunks hold only stalls, so the rigid list
empties and makeInstanced returns null; the cloth mesh replaces it.

AND IT IS WORTH NOTHING (amp is the same multiplier as gain):
  0.02m  CALM, the default weather amp        0.05% of frame
  0.14m  STORM, the most a player ever sees   0.21%
  0.35m  2.5x over-driven                     0.33%
  0.70m  5x, absurd on a 0.14m slab           0.49%
  1.40m  10x, flapping a body-length          0.77%
At the player's actual browsing pose, storm-force: 0.00%, max delta 1. Not one
pixel.

THE REASON IS PHYSICAL — the same physics that killed the verandah in R17. A
canopy is a horizontal sheet at y2.2 seen by a 1.7m player: edge-on or from
underneath, so vertical displacement runs almost parallel to the view ray and
makes almost no pixels. You cannot see a flat horizontal sheet flex vertically
while standing under it. No gain fixes it; 10x buys 0.77% and stops reading as
cloth.

WHAT WOULD WORK (for whoever reads this in round 35): the motion must be
perpendicular to the view — VERTICAL cloth: hanging stall skirts, a flag, a
banner, an awning valance. That is an ASSET (Lane E), not a shader. B's
applyWind/applyFabricWind seam is ready the day such a mesh exists; the shader
was never the missing piece. Ledger #2 leaves the backlog forever.

TWO VACUOUS MEASUREMENTS CAUGHT ON THE WAY. (1) `!!material.onBeforeCompile` to
prove the rigid verandah untouched — every THREE.Material inherits a prototype
no-op, so it is ALWAYS truthy; the assert could never go red. Replaced with
hasOwnProperty + a behavioural hide-and-hash control. (2) A gain sweep reporting
an identical 0.21% at gains 0.4→4.0 because I never stored the shader ref, so
uFabricGain never moved — four identical numbers across a 10x range is a sweep
that proves nothing. The constancy, not the value, gave it away. Both would have
passed as green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 17:29:39 +10:00
m3ultra
afad68b114 Lane D R28 (v5.1): instrument LOD — KILLED with the measurement (ledger #4)
E: STOP — do not build the _lo variants. That work is unnecessary; the item is dead.

The oldest tri item on the books, killed under the wind-sway rule attached to the brief
("if the measured win is <20k tris in practice, KILL it with the measurement").

MEASURED, from the shipped depot GLBs: each of the five instruments is exactly 14,000
tris; band + amp on stage = 70,000. On paper a 3-4k _lo each "saves" ~52k, clearing the
20k bar easily.

But the rule says IN PRACTICE, and the brief named the reason — "interiors are
close-quarters". So I measured the rooms an LOD would have to fire in (live, all three
venue archetypes, interiorMode driven by hand since rAF is throttled):
  pub        9.7 x 9.7m, diagonal 13.7m, 12522 tris, 38 draws
  band_room  8.9 x 9.6m, diagonal 13.0m, 11699 tris, 35 draws
  rsl        12  x 9.4m, diagonal 15.2m, 15115 tris, 38 draws  (the biggest)

VERDICT: practical win = 0 tris. A player can NEVER be more than 15.2m from the band —
corner-to-corner in the largest venue, and the stage sits at one end so the real max is
shorter. An LOD is a distance trade; inside a <=15.2m room the band is never small — it
is the subject of the gig and of F's tour money shot. Any sane threshold (15-25m) never
fires; an aggressive 8m one fires in half a room at a distance where a 3-4k _lo is
visibly worse with no perceptual cover. The win isn't "small", it's STRUCTURALLY ZERO —
the distance an LOD needs does not exist in this game. Off the backlog forever.

No budget motivates it either: a venue interior is ~12-15k tris, ~85k with the band —
inside the 200k stress ceiling E's R22 tri diet restored, at 35-38 draws vs <=350.

SIDE-OBSERVATION for E (not a proposal, no breach motivates it): all five instruments
are EXACTLY 14,000 tris — a decimator target, not a measurement of the objects. A mic
stand does not need 14k. If instrument tris ever do drive a real breach, the lever is a
flat re-decimation by object complexity (mic stand -> ~1k), which beats any LOD because
it needs no distance. Filed so the next tri hunt starts from it.

Spike 1 (ledger #1) pending E's lean.glb — look.glb has landed, lean.glb not yet.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 17:21:34 +10:00
m3ultra
d9100962f6 Fable (integrator): round-28 instructions — THE SPIKE AND THE SWEEP (spike 1 + parked polish, tag v5.1)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 17:07:52 +10:00
m3ultra
e93bece0f4 Fable (integrator): the v5 epoch retro — five holds, nine vacuous gates, the covenant amendment, the case law
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 17:04:41 +10:00
m3ultra
b2d29ba112 Lane F R27 (v5.0): the covenant re-pinned, the frame that took five tries, QA GREEN
qa.sh --strict --matrix: 7 passed / 0 failed / 0 warn. selfcheck ALL GREEN 156,352/156,352 ·
MATRIX GREEN 10 towns x 7 gates · scaffold_check GREEN (F's re-pins took) · flags harness green,
carrying the kill-the-server gate and the beta crate gate.

F RE-PINNED ITS OWN FILES per A's handoff (A doesn't edit other lanes' files, correctly):
GOLDEN_HASH 0x3fa36874 -> 0x5f76e76 · GIG_GOLDEN 0xb1d48ea1 -> 0xec7a2d39 · melbourne · katoomba
· the captions. AND A CAUGHT A LIVE HAZARD IN F'S GATE: hash_ok carried `or ('3fa36874' in
r.stdout)` — a bare-string OR-clause, dead the moment the covenant moved and worse than dead,
because it would go green off a SENTENCE ABOUT the old covenant appearing in stdout. Deleted.
The check derives from GOLDEN_HASH or it doesn't exist.

JOHN'S 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 just 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. Every gate we own was green over a world of back walls, and the
only thing that ever saw it was a human standing in the street looking at a wall.

THE FRONTAGE FRAME TOOK FIVE ATTEMPTS, FOUR OF THEM F'S FAULT:
  1-3. cluster pose (the town's heart isn't this shop) / lot-anchored facing along the edge /
       lot-normal facing — three photographs of empty road. Also never winnable: the shops faced
       backwards the whole time. F was posing a camera at a wall and tuning the geometry.
  4.   B's helper — and F THREW THE RESULT AWAY. poseForShopFront RETURNS a pose; it does not
       apply one. F called it, error-checked it, and never touched the camera, so the frame was
       the spawn paddock — and F's error-check PASSED, because the call succeeded perfectly. The
       vacuous check one last time, in the last frame of the epoch.
  5.   Applied it -> MONSTER ROBOT PARTY. Sign, door, awning, a ped walking in off Musgrave Road.

The measurement that would have found the facade bug in five minutes — compare the two
conventions against each other — is the one nobody ran for nine rounds, F included, right up to
the vacuous 72/72 F nearly filed as "B is wrong".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 16:48:22 +10:00
m3ultra
c1e862cf7c Lane A R27 wave 5: THE FACADE FIX — it was every town, not just the real ones (the covenant amended)
selfcheck ALL GREEN 156,352/156,352; consistency green. 51 goldens re-pinned in this one commit per the
amendment law. scaffold_check is RED and CANNOT be green from here — the covenant hashes are duplicated in
LANE F's files; exact values handed to F in LANE_A_NOTES §R27w5 (I don't edit other lanes' files).

THE RULING'S PREMISE WAS WRONG, AND THE WRONG HALF WAS LOAD-BEARING.
Wave 5 says "synthetic follows B's canon so every golden has been green over a wrong product since R18".
Half right. Synthetic does NOT follow B's canon -- it is broken identically, and so is the marched path,
and so are all three checked-in fixtures. It is not "every real town since R18". It is EVERY TOWN, EVERY
PATH, SINCE v1. The ruling told me to measure the marched path rather than assume it was clean "because
that's how this one survived nine rounds" -- so I measured all three, and the same discipline caught the
premise itself.

MEASURED FIRST, IN B'S CANON (+Z = the facade), BEFORE TOUCHING ANYTHING:
  synthetic seed 20261990   0 toward / 681 away   (dot = -1.0000, exactly anti-parallel)
  marched katoomba/fremantle/godverse, fixtures melbourne/katoomba/silverton   0 toward, all away
  real-roads katoomba   0/72   <- B's number, reproduced
637 of 681 synthetic lots had their facade pointing at NO STREET AT ALL (44 hit a different street by
corner coincidence). Then I confirmed it with my own eyes rather than trust the arithmetic: booted
?classic=1, stood on the synthetic main street at lot 0 -> three blank grey boxes. Walked BEHIND them into
the block -> NUMBAT PLAYTHINGS, sign/door/windows/awning, facing the dirt. The classic covenant town has
been a row of back walls since v1.
After the fix, same camera, same seed, same ?classic=1: CROWN TOYS & GAMES · MARBLES & KITES · NUMBAT
PLAYTHINGS -- a strip of shopfronts facing the road.

FOUR SITES, ONE SIGN, AND EVERY ONE OF THEM SAID SO IN ITS OWN COMMENT:
  plan.js:122    synthetic strip   "facade faces back down the outward normal, to the street"  <- it didn't
  plan.js:244    market row        "the facade then faces -(outward) = +x"                     <- it didn't
  plan_osm:330   marched avenue    "facade faces +z (north) to the avenue"                     <- due south
  plan_osm:443   real roads        B's proven one-liner (B measured it, then reverted it -- A's file)
All four were atan2(outward) where the intent was atan2(-outward). The code never did what its comment
said, and CITY_SPEC:71 blessed the result by writing the SIM convention into the LOTS contract. Nobody was
wrong; the spec was. After: synthetic 681/681, marched + fixtures 100%, real-roads katoomba 72/72.

THE COVENANT MOVED -- JOHN RULED IT, I DIDN'T.
Fixing synthetic necessarily moves 0x3fa36874, frozen "forever" since R16. That is not Lane A's call, so I
stopped and asked with the evidence. John's ruling: AMEND THE COVENANT, FIX ALL PATHS -- the covenant is
anti-drift machinery, not a defect preservative. 51 pins in this commit:
  classic/synthetic  0x3fa36874 -> 0x5f76e76
  gig                0xb1d48ea1 -> 0xec7a2d39
  fixtures           melbourne 0x9c7e76b3 · katoomba 0xf3aafec8 · silverton 0x6d74c4
  23 real towns      base + gig (46)
Verified byte-identical across two fresh processes; pack-xor 0xa5058322. CITY_SPEC records the amendment as
a ONE-TIME, ruling-gated precedent -- from this hash forward the law reads as it always did.

A SECOND BUG THE FIX EXPOSED (fixed here, counted). buildRealRoads seated blocks at KERB = 4 m from the
CENTRELINE, but a `main` carriageway is 10 m wide -- its kerb is at 5 m. EVERY MAIN-STREET BUILDING HAS
STOOD 1 m INSIDE THE ROAD SINCE R18. The footprint never moved; only which face is the front -- so it was
invisible until the shopfront turned around and its poster landed in the traffic lane (-1.07 m clearance,
6 real failures). Blocks now seat behind the kerb per A's own published corridor law (roadWidth/2 +
FOOTPATH). Pack drops 6.4% -> 6.6%, counted, not hidden.

THE CROSS-CONVENTION GATE -- built to F's lesson, not to a spec line. F proved that measuring lots against
CITY_SPEC:71's own convention is SELF-AGREEMENT: 72/72 "facing" over a town of blank walls. So this gate
restates nothing. It replicates LANE B'S OWN maths from buildings.js (toWorld(lot, 0, y, d/2 + 0.02)) and
asks a physical question neither spec line can fudge: does the quad the RENDERER actually draws land nearer
the street than the lot centre? It fires if A's ry sign drifts OR if B's zf sign drifts. It would have
caught this on day one of v1.

CITY_SPEC amendments: the lots contract now states facade/door/frontage = local +Z; the SIM/rig convention
is documented separately as the different thing it is, with the explicit rule -- never assert a lot's
facing against a spec line, assert it against the geometry the renderer draws.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 16:28:20 +10:00
m3ultra
94fce8ea4b Fable (integrator): R27 wave-5 — THE SPEC RULING (facade = +Z, B's canon canonical); A one-commit fix+pins+cross-convention gate; F re-gates and tags
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 16:01:31 +10:00
m3ultra
8a1d2e91e8 Lane F R27: HOLD v5.0 — every real town's shops face away from their street
B's micro-task found it while fixing F's frame, and F's three photographs of empty road are the
evidence: it was never the pose. plan_osm.js:443 aims the lot's -Z at the road ("facade (-Z)
faces road"); buildings.js:404 draws the facade on the local +Z face. Exactly opposite. The
shopfront — sign, door, R26 crate mark — lands on the far side and the street gets a blank wall.
Since R18. v4.0 shipped on it. katoomba_real's street_noon, the bookmark the tours have shot
since R9, is 46 draws of bare road.

Root cause: CITY_SPEC contradicts itself — :71 (lots contract) says -Z, :335 (R15 posters) says
"+Z, B's canon" — and each generator implements one half. Synthetic follows B's canon and looks
right, which is why every golden passes and nobody saw it for nine rounds. The epoch's signature
species and its worst instance: not a gate failing on correct behaviour, but a gate suite GREEN
over a product that is wrong.

F VERIFIED IT AND F'S FIRST CHECK WAS VACUOUS — recorded because it nearly ended the round the
wrong way. F measured each lot's ry against CITY_SPEC:71's own convention and got 35/35 and 72/72
FACING, a clean contradiction of B. But plan_osm IMPLEMENTS :71, so testing :71's data against
:71's rule is self-agreement — it cannot fail. A self-consistent check over a self-contradicting
spec passes under either convention, which is exactly why nine rounds of checks missed this, and
why F nearly filed "B is wrong" with a green number in hand. The only test that sees it compares
the two conventions. F's own frames already had.

Not F's to fix and B was right not to either: A's file, it moves every real-town plan (36 goldens
re-pin under the amendment law), and a self-contradicting spec is Fable's to rule, not a lane's
to pick a side of. B proved the one-liner (ry = atan2(-nx,-nz) → katoomba 0/72 → 72/72, F's money
shot 4m from Musgrave Road) and reverted it — the same discipline F applied to A's sweep and E's
validator, arriving from the other direction.

Order: Fable rules the spec → A lands the line + re-pins the goldens in ONE commit → F re-gates,
re-shoots, tags. Everything else is done and green: the kill-the-server gate, the reader, the
sandbox, the interior money shot (interiors pose themselves; the crate is real either way).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 15:47:26 +10:00
m3ultra
4c2702fc58 Lane B R27 w4: poseForShopFront — and the real towns face the wrong way
The micro-task, done and verified. In doing it I found why F photographed
empty road: it is not the pose.

1. THE DELIVERABLE — DBG.poseForShopFront(sel, dist = 8)
Poses at a NAMED shop's frontage: door centred, sign band + R26 crate mark in
shot, under the verandah. clusterPose() answers "where is the town's HEART",
which is the wrong question for a named shop — Red Hill's heart doesn't contain
Monster Robot, which is exactly how F got three frames of empty road.

Keyed through ONE shop selector: F's R22/R24 resolver lived inside enterShop(),
so I extracted it rather than grow a second key scheme beside it. A selector bug
resolves the wrong shop AND looks like it worked (F's own first cut proved it),
so the id-namespace law now has exactly one home.
  poseForShopFront(31)         → Wholefood Cafe (PLAN id) + ambiguous: "31 is
                                 also the godverseShopId of Silky Oak Furnature"
  poseForShopFront('g:31')     → Silky Oak Furnature (the prefix IS the fence)
  poseForShopFront('g:999999') → {error} — never throws, never guesses
Framing proven by projection, not by eye: doorCentreNdcX = 0.0000 exactly, door
+ sign + mark inside the frustum. Derived from buildings.js, not hardcoded: door
is centred on the lot front (off = 0, unlike poseForShop's off = 1.0); sign band
= min(h,FACADE_H)-0.35 ± 0.25 ⇒ y 2.40-2.90; mark = y 2.45-2.52; camY 2.0 sits
below AWNING_Y 2.95, so a ray rising to a target ≤2.9 can never cross it — the
sign is clear BY GEOMETRY at any distance.

2. CORRECTING MY OWN R27 INTEL — I gave F two wrong numbers. I said the mark
"rides high (y≈3.3)" and the vinyl blocks 4% of it. Both wrong: the mark is at
y 2.45-2.52, and I had scanned y 3.0-3.6 — empty awning air above the sign,
which is why it read ~0. Re-measured: the novelty_record disc (top y 2.50)
reaches INTO the mark band and clips ~15% of the strip from EVERY offset;
moving the camera only changes which 15%. "Shoot head-on" survives, but for the
opposite reason to the one I gave.

3. THE FINDING — every real town's shops face away from their street, since R18.
From Musgrave Road (Monster Robot's own frontEdge 1316) the shop is a blank grey
back wall: no sign, no door, no mark. Measured:
  redhill_godverse  35 shop lots: 0 facing, 35 backwards (dot = -1.000)
  katoomba_real     72 shop lots: 0 facing, 72 backwards (dot = -1.000)
  synthetic         correct — always has been
dot = -1.000 exactly on every lot in both towns: a convention, not a defect.
Katoomba's street_noon — the bookmark the tours have shot for nine rounds — is
46 draws / 19,680 tris of bare road.

Root cause: CITY_SPEC contradicts itself and each generator implements one half.
  CITY_SPEC:71  (lots contract) "World facing = (-sin ry, -cos ry)"  ⇒ -Z facade
  CITY_SPEC:335 (posters, R15)  "the +Z street facade (B's canon)"   ⇒ +Z facade
plan_osm.js:443 obeys the first (its comment: "facade (-Z) faces road").
plan.js, buildings.js, venue.js and the R15 posters obey the second. Synthetic —
the town every golden tests — follows B's canon and looks right, which is exactly
why nine rounds of real-town work never caught it. The epoch's own signature
species: a rule meeting a case written after it.

Proven, then REVERTED: one line in A's plan_osm.js (ry = atan2(-nx,-nz)) flips
katoomba 0/72 → 72/72 facing, and turns poseForShopFront's camera from 26m out in
a paddock into 4m from Musgrave Road — the money shot F specified. Not committed:
it is A's file, it moves every real-town plan (goldens re-pin under amendment
law), and a self-contradicting spec is Fable's to rule on, not mine to pick a
side of.

This also re-reads my R22 diagnosis: I recorded "chunk-buildings 348 tris (0.2%)"
in katoomba's busiest block and credited the instancing work. 348 tris is ~29
boxes — that was not efficiency, it was the buildings being ABSENT from frame. I
read the evidence as a win for five rounds.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 15:42:50 +10:00
m3ultra
83ed848f16 Fable (integrator): R27 wave-4 — ratify the exactly-one-refusal + control-delta gates; B micro-task (frontage pose); F's tag path
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 15:23:04 +10:00
m3ultra
630042fbe1 Lane F R27: the kill-the-server gate GREEN — and two corrections to F's own gate
THE KILL-THE-SERVER GATE, permanent and green (charter law #1 made falsifiable):
  ARM 1 server-up   · gone[3] → 117 survivors · items[] 120, identity AND ORDER intact ·
                      COLLATERAL 0 · `gone` carries the atlas id verbatim (R26 fence)
  ARM 2 killed mid-session · enter 6ms vs control 6ms (Δ+0) · worst frame 204ms vs control
                      202ms (Δ+2) · room complete from files · ZERO game errors · EXACTLY 1
                      refused connection
  ARM 3 server-down boot   · pure tier 1, gone[0] · ZERO game errors · exactly 1 refusal
  SANDBOX · POST/PUT/PATCH/DELETE all 501 · a mint shop 404s
The subject exists only because G built `make-fixture`: production `gone` is EMPTY (0 sales
since 2026-07-01), so this gate would have passed vacuously forever on live data.

CORRECTION 1 — "ZERO console errors on server death" is UNACHIEVABLE, and F measured why. A
fetch to a dead host logs net::ERR_CONNECTION_REFUSED at the BROWSER level; no .catch() sees it.
To log zero the reader would have to know the server is dead WITHOUT ASKING. One refusal is the
irreducible cost of discovering death. F's replacement is strictly stronger: ZERO errors from
game code, and EXACTLY ONE refusal — which PROVES the circuit breaker. Without the breaker it is
one error per shop entry forever, and "zero" could never tell a working breaker from a broken
one because both fail it. F is the beneficiary of this change, so it is filed loudly in §27 for
Fable to ratify or overrule, not quietly relaxed.

CORRECTION 2 — F's frame assertion was weak: `< 250ms` is a number with no control. ARM 2 first
reported worst-frame 201ms and PASSED. But entering a shop rebuilds an interior, which costs the
same with or without a server — so an absolute threshold measures Lane C's room build, not the
network. The arm now walks the same shop with the breaker tripped and asserts the DELTA: 202ms
control, 204ms killed, Δ+2ms. The death cost nothing the room didn't. F told A the same thing one
round ago; a number without a control is not a measurement.

THE TOUR — 3 of 4 frames, and the 4th is FILED NOT SHIPPED. The real crate (the money shot: its
own 120 records, price stickers, the keeper), a mint crate (captioned MINT — plausible, not
real), the classic covenant. 0 page errors. The FRONTAGE frame does not land after three
attempts and F will not ship a frame that doesn't show what its caption claims: the cluster
primitive answers "where is this town's heart", which is not the question a frontage frame asks
— Red Hill's heart is 2 shops/60m and does not contain Monster Robot. Next: ask B, who owns the
frontage and shipped its mark this round. Re-deriving B's anchor in a tour script is how you get
three bad frames.

Also: the interior beauty shot came back with the HUD across it — R21's lesson, fixed on the
street frames only. Now every DOM overlay is hidden, not a guessed selector list.

NOT TAGGED. qa.sh --strict --matrix and the frontage frame remain.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 15:05:17 +10:00
m3ultra
f91d734b32 Fable (integrator): R27 wave-3 — ratify ?live opt-in + the collateral assertion; F's remaining close enumerated
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 14:39:54 +10:00
m3ultra
39cfc3d3ed Lane F R27 (v5.0): the tier-2 reader — pick-then-omit, and the server killed mid-session
#2 THE READER, built to C's §8 ruling (direct wire, no hook) and to what the waves measured,
not to the brief's letter:
  · `pack.gone` set, `price` updated IN PLACE — items[] never added to, removed from, or
    reordered, because its identity AND ORDER are the pick's stability (C §8.3). Filtering the
    array instead reshuffles the crate: C measured ONE sale changing 21 OTHER records.
  · fire-and-forget at room build, 1200ms abort, etag-cached, circuit-broken on first failure.
    The dig never blocks on the network (charter risk #1). No await anywhere near the door.
  · fires ONLY for a real-sourced shop. G's fence 404s a mint shop, and asking anyway would be
    R26's blind probe wearing a server. The manifest already knows which is which.

TIER 2 IS OPT-IN (`?live=1` / `?live=<url>`; absent → byte-identical to the beta). That is the
R26 lesson applied to a server: a default boot that probed for a GODVERSE host would ask a
question with no answer on every machine that isn'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.

PROVEN IN THE BROWSER (formal smoke class still to land):
  · sold-means-gone, via G's make-fixture (production `gone` is EMPTY — 0 sales since
    2026-07-01 — so a live assert would pass vacuously forever; G built the subject before the
    gate could be born blind): 3 sold → gone[3] → items[] 120→120 IDENTITY AND ORDER INTACT,
    117 survivors, COLLATERAL 0. C called this: both pick-then-omit and filter-the-array hide
    the sold record, so "the gone item never renders" cannot tell them apart — the obvious
    assertion is vacuous and goes green. Collateral is the one that discriminates.
  · KILL THE SERVER MID-SESSION → enter took 13.1ms, worst frame gap 14ms, the crate still
    renders its own 120 real covers, and ZERO console errors. Charter law #1 proven by
    execution instead of assertion — the thing A correctly refused to retire from the risk
    list until this gate went green.

Recorded, not hidden: after a mid-session kill the etag-cached `gone` persists for the session,
so a killed server does not resurrect sold records mid-walk. That is tier-1-plus-last-known, not
tier-1-exactly. It is the honest behaviour (a network blink must not re-stock a shop) and a
server-down BOOT is still pure tier 1 — but it is a nuance the brief's "degrades to tier 1
exactly" doesn't cover, so it goes in writing rather than in the gaps.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 14:34:42 +10:00
m3ultra
70743db47e Fable (integrator): R27 wave-2 status — F's three load-bearing inputs (pick-then-omit, make-fixture for gone[], the lifecycle switch)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 14:13:39 +10:00
m3ultra
bee7563a76 Lane E R27 (v5.0): provenance v5-final — and the roster gap the freeze caught (ledger #6)
THE BUG THE FREEZE FOUND: redhill_godverse was ON DISK but NOT IN THE ROSTER — for three rounds. The v5
provenance table needed real numbers, so I measured instead of carrying the v4 row forward. The count
didn't match: 23 caches on disk, 22 rostered. The missing one was redhill_godverse — THE TOWN THE ENTIRE
v5 EPOCH WALKS TO.

Mine, squarely. index.json is Lane E's file (write_index()). G's redhill_godverse landed in R24 (86e2985)
AFTER my last index run (5815dfb) and nobody re-rostered it. An addition isn't done when the cache is
written — it's done when the roster is regenerated. That is the exact mirror of my own R24 lesson ("a
retirement isn't done when the cache is deleted; it's done when the consumers are swept"): I wrote the
lesson for deletions and missed its reflection for additions.

WHY FOUR GREEN ROUNDS NEVER CAUGHT IT — a gate-shape lesson. Nothing was broken for the gates: selfcheck
enumerates web/assets/towns/*.json DIRECTLY (so it saw the town; A even pinned its goldens) and F's crate
smoke boots BY KEY (?town=redhill_godverse). The roster's only consumer is B's HUD selector (hud.js, my
R20 ledger #2 design). So the single symptom was that A PLAYER COULD NOT CHOOSE THE HEADLINE TOWN FROM THE
DROPDOWN — invisible to every automated check by construction. A gate that reads around the artifact under
test cannot test it: the vacuous-gate species, one layer out.
Fixed: re-ran write_index() -> 23 rostered, 23 on disk, 0 missing. Only index.json moved (every cache
byte-identical again). selfcheck ALL GREEN 156,212, golden 0x3fa36874 untouched. qa.sh --strict --matrix
7 passed / 0 failed / 0 warn.

-> F (#5, the tour): the money shot walks Musgrave Road into Monster Robot Party. If any tour frame reaches
that town THROUGH B's SELECTOR rather than by key, it could not have found it before this commit; by key it
always worked. Worth one glance at your framing path.
-> B: the dropdown now carries both godverse towns. Your `${t.state ? ', ' + t.state : ''}` handles their
empty state gracefully — nothing needed, and the defensive write is why this cost nothing cosmetically.
-> G: your godverse caches carry no `state`, so they roster as state:''. Harmless (B degrades cleanly). For
"Red Hill, QLD" in the picker, emit `state` — write_index() already prefers the cache's own field.

PROVENANCE v5-FINAL. The ⟨v5.0-FINAL⟩ rows are measured, not inherited — the town-pack row still claimed
v4's "22 towns / 1192 shops", stale since my R24 ballarat retirement and redhill build (now 21 real / 1196
shops / 299 heroes, + both godverse towns = 23 rostered). Added the two v5 art rows: the real-stock atlas
(1 crate, 120 items, Monster Robot's own POS, sku_ ids) and the mint atlases (14 crates, 224 items, seeded
from real dealgod listings, mint_ ids). These are the table's FIRST AMBER ROWS — everything before v5 was
green web-ok; real product photography is the first asset class this project ships that a human must clear
before public release.

I VERIFIED MY OWN PROSE AND IT CAUGHT ME: I first wrote the mint crates as "6 record · 6 book · 3 toy" —
that is the breakdown of all FIFTEEN crates, which includes Monster Robot's REAL record crate. The 14 mint
crates are 5 record · 6 book · 3 toy. A count attributed to the wrong population, one line from being
frozen into the release record. Every other claim in the table re-measured true (21/1196/299/8 states/23
rostered/roster==disk/72/37/120 items/224 items/both zones in-house-green).

THE LICENCE LINE'S STORY, recorded as a mechanism. The amber rows are guarded by exactly one printed line,
so the freeze says why it took two rounds to make honest: R23's glob matched 0 files and returned 0; R25's
permissive `or` fallback let the print go silent while the check still passed — the flag stopped reaching a
human on real product photos while the gate said OK. Now: one spelling, one lookup with one owner, and the
line is COUNTED (judged N => N lines, or FAIL). Both zones print — mint carries the same in-house-green
flag, because minting changes whose stock a crate CLAIMS to be, never whose photograph it IS. Frozen today
at: atlas-QA OK — 15 per-shop atlas(es) valid, 15 licence line(s) shown, 0 warning(s).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 13:59:49 +10:00
m3ultra
cedc5872fb Lane G R27 (v5.0): tier 2 — the two read endpoints, the three fences, and the gate's missing subject
THE LIVE CRATE READS. godverse_server.py: /health, /shop/<gid>/stock, /shop/<gid>/crates, plus
start|stop|status (F's kill-the-server lifecycle), setup-role, make-fixture. Stdlib + psql. First
server code of the epoch, per the ratified G3.

Live against the real POS: crate 550 -> {baked:120, live:120, gone:0}, real prices, real VG+
grading, etag stable 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, each measured:
- The credential: godverse_ro holds SELECT on inventory/crate/disc_cache and nothing else.
  UPDATE inventory -> permission denied. SELECT FROM customer -> permission denied. ONE grant
  carries both the §7 sandbox and the §9 PII fence — which is why §7 said enforce it at the
  credential, not in a code review.
- Write verbs absent: POST /reserve -> 501, POST /buy -> 501, GET either -> 404.
- Metadata only: no cover, no pixels on the wire; a mint shop's /stock is 404 (no POS to be live
  about), not an empty crate.

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 (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 — and an assert over it would pass
WITHOUT TOUCHING ITS SUBJECT, the exact species that has held four tags. So: 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 at it via
GODVERSE_POS_DB for a deterministic gate; production reads the real POS untouched. A fixture is
not a lie; a green assert over an absent subject is.

LIFECYCLE (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) -> status DOWN -> start -> live. No
admin verb was added to the API: a server whose whole point is "no write verbs" shouldn't grow a
remote kill.

THREE CORRECTIONS TO MY OWN G3 §4 (design -> as-built), all measured not reasoned:
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, and 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, this time in the doc I wrote.
2. items[] drops release_id and cover — cover edges the API toward serving pixels; release_id is
   the pressing, and 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 proof: 404 means a handler declined; 501 means no write handler exists in the process.
   -> F: assert 501.

Scoped: pathspec commit. web/assets/towns/index.json is another lane's live edit — left untouched.
Runtime pidfile moved out of the repo (it is not an artifact).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 13:56:02 +10:00
m3ultra
6960bdd279 Lane C R27 (v5.0): the tier-2 seam ruling + LANE_C_PUB v5-FROZEN (ledgers #2, #6)
SEAM RULING (§8): bless the direct wire — NO lifecycle hook. F already holds currentAdapter
(interior_mode.js:67) and passes it to dig.open() PER DIG (:111), so the live read lands, F
updates the pack, the next riffle picks it up; not-landed/slow/dead => the adapter is simply the
tier-1 pack => the dig never blocks (charter risk #1). No C lifecycle change.

BUT THE QUESTION UNDERNEATH IT IS C'S, AND THE OBVIOUS WIRING IS WRONG. pickRealOffers indexes
pack.items[(rr()*len)|0], so FILTERING THE ARRAY RESHUFFLES THE CRATE. Measured, 5 crates: one
record selling changed 21 OTHER records (6/2/2/6/5 collateral). Pick-then-omit (draw from the
stable full list, drop gone after) = 0 collateral: a crate with 3 sold shows 13, not a different
16. Selling one record must remove one record, not re-stock the shop.

THE TRAP: both forms hide the sold record — so F's planned assertion ("a real gone item never
renders") GOES GREEN ON THE WRONG WIRING. It cannot see the 21. -> F: assert on COLLATERAL (the
survivors are unchanged across a gone delta), not absence. Vacuous-gate law, third application,
and the cheapest to miss because it passes.

LANDED (dig.js): pack.gone (Set|array) omitted at PICK time. F's invariant published: update item
FIELDS in place (live price) freely; NEVER add/remove/reorder items[] — that identity+order IS the
seeded pick's stability (R25's positional-id lesson, one layer up).

DETERMINISM BOUNDARY sits exactly here: server down => byte-identical tier 1 (gate-able); server
up => the crate legitimately changes (tier 2 is non-deterministic BY DESIGN), so no gate may
assert dig determinism with the server up, and pack.gone never enters a seeded stream or golden.

SCOPED: the bin fan is decorative (a different seeded pick from the dig's offers) — a sold cover
may linger there until rebuild. That's when I'd publish a post-build hook; the epoch's claim
doesn't need it, because the crate you RIFFLE is true.

VERIFIED: pure-function measurement for the collateral claim, then end-to-end through the real
dig's public surface (pull -> Esc -> scroll -> pull): sold record gone, survivors 4/4, collateral
0, items[] untouched at 120, live in-place price shows on the next pull. (My first harness
returned the same title 4x — I never un-pulled — so its number was an artifact; re-ran it properly
rather than report a metric I'd just watched lie.)

DOCS FREEZE (§6): LANE_C_PUB is v5.0-FROZEN, whole document, with the case law at the top: three
amendments here exist because a rule of mine met a case written AFTER it, and both times the lane
building to my rule took the red. Header now states it: when a line meets a case it didn't
foresee, the line is the stale thing — amend it, don't bend the case.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 13:53:02 +10:00
m3ultra
01f398960b Lane B R27 (on-call): the R26 handshake verified against G's real manifest
R27 owes B nothing ("B/D: notes current; nothing else owed"; "B/D on-call").
This is on-call work on F's #5: the tour is specified as "Monster Robot Party's
frontage (B's mark visible)" — a claim R26 could not test, because G's manifest
did not exist and B proved the treatment against a temp manifest it scaffolded
and deleted. G shipped the real one (36d10ca). Now measured, not assumed.

Monster Robot Party is in redhill_godverse — NOT newtown_godverse, the only
town B ever tested the mark in. F's money shot is posed in a town this
treatment had never been verified against. It works.

The handshake held unmodified: G's {version, shops:[{godverseShopId, types,
sourcing}]} lands inside B's tolerant reader with zero coordination. The
tier→sourcing rename did not touch B — the mark never reads that field, so a
rename of it cannot break the footpath. A cue that reads no field cannot break
when the field moves.

Both arms proven live, the discriminating arm for the first time (R26's temp
manifest marked all 18 keyed shops, so "the manifest is the authority, not the
key" was never exercised). Redhill: 9 keyed, only 4 stocked.
  Monster Robot Party #3962749 (real)  keyed + in manifest → MARKED
  Silky Oak Furnature #31              keyed, not in manifest → BARE
  Thrift Shop and Eternity Boutique    keyed, not in manifest → BARE
Two keyed shops stand metres from a stocked one wearing no mark, because they
have no crate. The street advertises exactly the crates that exist. (#31 is
Fable's R25 id-namespace shop; the mark reads godverseShopId only, unaffected.)
Frontage 135 draws / 74,354 tris, 0 page errors, manifest prefetched at boot.

Intel for F's #5, no action owed: the novelty_record landmark (B's furniture —
a giant vinyl disc marking record shops) stands 2.5m off the frontage and is
prominent head-on. Ray-grid occlusion so F can frame without guessing:
  mark+sign   head-on 4% blocked   off-axis +4m 0%
  window      head-on 22% blocked  off-axis +4m 44%
Shoot HEAD-ON: the vinyl sits low (y~1.3), the mark rides high (y~3.3), and
stepping off-axis doubles window occlusion instead of helping. I first read the
black disc as a render bug — it isn't, it's the landmark working; recorded so
the next reader doesn't re-open it.

Notes-only; no code changed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 13:52:57 +10:00
m3ultra
839575b28d Lane A round 27 (v5.0): the docs freeze — CITY_SPEC v5 + the risk list judged (ledger #6)
Docs only. No code, no golden moved — correct: tier 2 touches no plan, no cache, no atlas bytes. selfcheck
ALL GREEN 156,212/156,212; scaffold + consistency green; classic 0x3fa36874 and gig 0xb1d48ea1 frozen. The
whole stock epoch moved zero plan goldens, which was the determinism boundary's entire promise.

CITY_SPEC v5 SECTION — written from the ARTIFACTS, not the briefs (this epoch's own lesson):
- The tier ladder: 0 parody / 1 static real / 2 live GODVERSE, each degrading softly to the rung below.
- `sourcing` is NOT `tier`, and C's ruling is the precise one — both ship in every index. `tier: 1` is the
  LADDER RUNG (a mint atlas is still a static file, so still rung 1); `sourcing: "real"|"mint"` is WHERE
  THE CONTENTS CAME FROM. Different axes. Collapsing them is exactly how a mint crate comes to read as
  real. Measured: 15 keyed atlases = 1 real + 14 mint.
- The three id-space fences, verified rather than restated (below).
- The manifest: existence DECLARED, never probed — the root fix for fail-soft-by-404 colliding with the
  zero-console-errors law. No entry, no fetch, zero 404s, no attribution exceptions. 15 entries / 15 dirs.
- Ruling 2 as law (mixed caches are the design; no gate may assert it away — mine did).
- Lane A's seam is exactly one field: godverseShopId, and `'godverseShopId' in shop` is the tier test.

THE ID FENCES ARE VERIFIED, NOT ASSERTED. 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) enter the wrong shop and pass a soft-fall
assertion vacuously on the wrong subject. So it goes in the spec as a STANDING RULE, not a fixed bug: no
code or gate may compare a bare number across the two spaces. (Within the godverse space, G's two halves —
census <=2992, dealgod store ids — are numerically disjoint by construction, which is what makes the opaque
key safe.)

THE RISK LIST, JUDGED LINE BY LINE — AND I DID NOT PRE-RETIRE ANYONE'S GATE:
1. CARRIED. Tier-2 latency/stutter is this round's defining gate and I'm writing in wave 2 — F's reader and
   kill-the-server gate do not exist yet. NOTHING retires this line except that gate going green. Retiring
   it from a doc while the code is unwritten would be the precise species this epoch spent four holds
   catching: a rule meeting a case that doesn't exist yet. The offline law is a promise until #3 makes it
   falsifiable; that's the whole point of #3.
2. RETIRED — measured, and the remedy the risk NAMED is the remedy that shipped ("crate rotation rather
   than bigger atlases"). Max 2 atlases/shop vs C's hard cap of 2; the 14 mint shops sit at 16 items / 1
   atlas / 1024²; only the one real POS shop earns 2048² (120 items, 2 atlases, 1.2 MB) under the 60+ rule.
3. RETIRED — DESIGNED OUT, not solved. The risk assumed a fuzzy-matching problem ("names drift, shops
   move"). There is no matcher: identity is an explicit opaque key G emits. The predicted failure cannot
   occur in the predicted form — and the proof is the headline shop: Monster Robot is in NEITHER the census
   NOR OSM, so no name-matching strategy could ever have found it. Mis-stocking is fenced structurally
   instead (duplicate ids error, uniqueness over defined ids, the orphaned-atlas gate).
4. SPLIT. Write-back RETIRED by John's ruling recorded as MECHANISM, not config: /reserve and /buy are
   ABSENT, not stubbed — races and reservation semantics cannot occur because the verbs don't exist, and
   "prove the absence" beats any sandbox flag a config typo could flip. (Option B re-opens this line
   deliberately at v5.x, behind a hold budget.) The `gone` read half is CARRIED to F's #4.
5. Unchanged, extended: static-atlas determinism proved in practice (G's index-only sku re-emit moved no
   .webp bytes); the fail-soft ladder proved on every rung; the id fences now have case law behind them.
6. NEW, CARRIED — the orphaned atlas, with a live near-miss on the books.
7. NEW, CARRIED — "real" is one shop wide: 14 of 15 keyed atlases are mint, because exactly one shop has a
   POS. Honest and marked, not a defect — but the epoch's headline claim rests on ONE shop, and any future
   claim of scale needs either siblings for monsterrobot or continued care that `sourcing` stays legible to
   humans and not just to gates.

THE GATE THAT ARMED ITSELF (the law working unattended, worth recording). My R25 orphaned-atlas gate SKIPped
on newtown_godverse all through R25 — "no committed atlas keys to this town", subject absent, so it said so
instead of passing quietly. The moment G's 14 mint atlases landed it STARTED BINDING ON ITS OWN and passed:
156,211 -> 156,212, and that +1 IS the gate arming itself. Nobody edited it. That is what the vacuous-gate
law buys — a gate that waits, in the open, for its subject to exist.

G: redhill_godverse's lift drops keyed shop 2286 "Empire Revival"; you stocked its numeric neighbour 2287.
Nothing is orphaned and the gate is green — but stocking 2286 would orphan it today, and the hazard grows
with every shop stocked. Carried as risk #6.

F: nothing of mine gates your tag.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 13:52:47 +10:00
m3ultra
2cc6c32c73 Fable (integrator): V6 LIVING STREET draft charter — John's NPC-variety idea, grounded in the measured local stack (MOCAPGOD/MIRPAMO/MODELBEAST), three spikes first
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 13:40:47 +10:00
m3ultra
78848ed254 Lane F R26 wave 4: the re-run — 6/1/0 → 7/0/0, QA GREEN, v5.0-beta tagged
The fence went in and the hold ended. C ruled, and both lanes titled their commit the same
thing: "my rule was the stale thing." E's one line:

    SLOT_ID_PREFIX = {"real": "sku_", "mint": "mint_"}     # sourcing-scoped, not one space

qa.sh --strict --matrix: 7 passed / 0 failed / 0 warn. selfcheck ALL GREEN 156,212/156,212 ·
MATRIX GREEN 10 towns x 7 gates · flags harness (carrying F's beta gate: 15 crates / 105 pairs /
0 shared titles, sourcing real+mint, ZERO 404s, 0 console errors) · atlas-QA OK, 15 atlases
valid, 15 LICENCE LINES SHOWN — R24's finding #4 at full scale, where one silently printed none.

The ruling is the round's keeper. Fable: prefixes are namespace fences. E's comment makes G's
judgment law: "`sku_` names a POS copy on a shelf, `mint_` names a dealgod listing standing in
for one... a prefix that lies doesn't fail a gate, it SELLS THE WRONG RECORD out of a real shop
to satisfy a minted stand-in. Never treat them as one space (same standing note as plan-ids vs
godverse-ids)."

That closing clause is F's R24 collision — plan id 31 vs godverse id 31, which walked F's own
harness into a cafe — promoted from a finding to a standing law, and re-derived independently by
G in the layer that moves money. The bug F found by entering the wrong shop is the bug G refused
to ship by selling the wrong record.

The rule bent; the id spaces didn't. That is the right direction: a rule describes an intent, and
when a legitimate case fails it, the description is what's wrong — PROVIDED somebody measured
that the intent still holds. F measured it before the ruling (non-positional, seeded, stable,
0 reuse across 15 crates), which is what made the ruling a decision instead of a guess. Holding
the tag was never about the rule being wrong; it was about who gets to say so.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 13:36:44 +10:00
m3ultra
bd6f576c00 Fable (integrator): round-27 instructions — v5.0 THE LIVE CRATE (wave 0: beta tag; then tier-2 read-live, the kill-the-server gate, the tour, the tag)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 13:27:28 +10:00
m3ultra
f2989858aa Lane E R26 w4: the mint-id fence — my rule was the stale thing (wave 4, E's one line)
THE RED WAS MINE. My R25 slot-id check demanded `sku_` on every real-stock pack — faithfully implementing
C's R25 line, which was written BEFORE MINT EXISTED. It then failed all 14 of G's mint crates, whose ids
honour its every intent: non-positional, seeded, stable, zero reuse. C's ruling names it exactly: "the rule
was the stale thing, not E's check." Fourth hold of the epoch, and the fourth to trace to a rule meeting a
case written before the case existed.

THE FIX IS A FENCE, AND IT IS STRONGER THAN THE RULE IT REPLACES — that was the thing to prove. The easy
green was to drop the prefix check. Instead the form is sourcing-scoped per C §7.2a (R26 w4):
sourcing="real" => `sku_` (a POS copy on a shelf) · sourcing="mint" => `mint_` (a dealgod listing standing
in for one). A crate wearing the other's prefix FAILS.

WHY THIS IS A MONEY BUG, NOT A NAMING BUG — G's judgment, now law. G refused to file dealgod listing ids
under `sku_` to go green the easy way. They were right: 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 A REAL RECORD out of a
real shop to satisfy a minted stand-in. The gate's error says that, in those words — the cost, not the
rule. The message also distinguishes a fence-crossing from a merely positional id, because they are
different faults and the reader shouldn't have to guess which one bit.

PROVED 7/7 — that the fence BITES, not merely that the red stopped. G's real crate (sku_...) -> PASS ·
G's mint crate (mint_...) -> PASS · mint filed under `sku_` (the exact shortcut G refused) -> FAIL, naming
the fence and the real cost · real wearing `mint_` (the reverse) -> FAIL · positional ids still FAIL in
BOTH sourcings (the old rule's whole intent, retained) · duplicate ids -> FAIL. Loosening would have passed
the first two and lost the last four; this passes the first two and keeps all four.

BOTH OLDER SUITES BROUGHT FORWARD RATHER THAN LEFT TO ROT. The R26 suite's mint fixture wore sku_ ids, so
the fence correctly rejected it — a fixture that fails for a NEW reason has stopped testing what it was
written for. Moved to the mint_ space: 17/17. R25 licence suite: 8/8, tally still firing VIA THE TALLY.
Third round running where the near-miss was a test going quietly stale around a changing contract; it is
the failure mode I now look for first.

Gates: atlas-QA OK — 15 per-shop atlas(es) valid, 15 licence line(s) shown, 0 warning(s) (1 real + 14
mint, every one showing its flag to a human) · validate_manifest exit 0 · qa.sh --strict --matrix
7 passed / 0 failed / 0 warn — QA GREEN.
-> F: your 6/1/0 is now 7/0/0. The beta is yours to tag.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 13:08:40 +10:00
m3ultra
53e83308a0 Lane C R26 w4: the mint-id fence — my rule was the stale thing (wave 4, C's one line)
F's fourth hold traced to MY §7.2a. I wrote "sku_-prefixed for real-stock packs" in R25 —
before mint existed — so it names one space where there are two. E implemented it faithfully
and its validator did exactly what I told it to: demand sku_ on 14 mint crates. The check
wasn't wrong; my rule was. (Second time a contract line of mine met a case written after it
and another lane took the red.)

AMENDED §7.2a — the id form is SOURCING-SCOPED and the prefix IS the fence:
  sourcing "real" -> sku_<POS id>            the shop's POS inventory (a physical copy)
  sourcing "mint" -> mint_<dealgod listing>  a dealgod listing (a plausible stand-in)

WHY G WAS RIGHT TO REFUSE THE EASY GREEN: filing dealgod listing ids under sku_ would have
gone green in one line and recreated the R24 collision INSIDE THE ECONOMY LAYER — where
tier-2's sold-means-gone looks up POS skus. R24's collision entered the wrong shop; the same
collision in the money layer sells the wrong record — a real copy, out of a real shop, to
satisfy a minted stand-in. A prefix that lies costs money, not a gate. Blessed as design:
disjoint namespaces; no gate, lookup or economy path may treat them as one (same standing
note as plan-ids vs godverse-ids).

VERIFIED against every crate before handing E the line: 15 crates (1 real + 14 mint), 0 fails
under the amended rule (prefix matches sourcing, ids unique within pack), and 0 ids appear in
both spaces across 344 distinct ids. The fence already holds in the data; only my wording didn't.

-> E: assert the form against `sourcing` (already read at line 178) — sku_ for real, mint_ for
mint; a crate wearing the other's prefix FAILS. Stronger than the check it replaces, not looser.
-> F: 6/1/0 should flip to 7/0/0. Doc-only: no code, no atlas bytes, no goldens.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 12:32:45 +10:00
m3ultra
0b4bc6c1f9 Fable (integrator): R26 wave 4 — the mint-id ruling (prefixes are namespace fences); C one line, E one line, F tags
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 11:49:38 +10:00
m3ultra
7ba7ab20a8 Lane F R26: the beta gate's exact tally — 6 passed / 1 failed / 0 warn
The full qa.sh --strict --matrix run confirms the hold and sizes it. Green: selfcheck ALL GREEN
156,212/156,212 · MATRIX GREEN 10 towns x 7 gates · flags harness (carrying F's beta gate:
distinctness, sourcing, zero 404s, 0 console errors) · consistency · figure-scale.

Red: the manifest validator alone — atlas-QA, 14 identical errors, all 14 mint crates, one
sourcing-blind assertion, one line. The real crate passes.

Recording the ratio because it IS the argument: the beta is finished. What stands between it
and the tag is a rule meeting a case written before the case existed — C's to rule, E's to
land, and not F's to quietly loosen on its own behalf.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 11:43:35 +10:00
m3ultra
528829eeba Lane F R26 (v5.0-beta): the manifest consumed, the tolerance retired — tag HELD on the mint-id rule
EVERY CRATE IS DIFFERENT, and F measured it rather than taking G's 15/15 on trust — from the
indexes of record, not by entering one lucky shop: 15 keyed crates, 105 pairs, 0 sharing a
title; 0 crates overlap the generic v2 pack; 0 ids reused across crates (G's sku fix holds at
scale — the R24 vacuity dead at the source, not papered over in F's gate). 4 crates in Red Hill
+ 11 in Newtown = the manifest's 15 exactly, and the manifest lists nothing not on disk.

SOURCING, NOT TIER — C was right and the gate follows C. `tier` is the charter's ladder rung and
a mint atlas is ALSO rung 1: still a file, still offline, still deterministic. What differs is
where the stock came from. 15/15 indexes declare sourcing, every one agrees with the manifest,
a mint crate can never report as real, and the gate PRINTS it per room — provenance a human
never reads isn't doing anything (R24's licence lesson, one round on).

THE 404 TOLERANCE IS DELETED, NOT TUNED. R24 probed every keyed shop and ate a 404 per
atlas-less one; F's smoke could only cope by NAMING them, and a tolerance is a confession. F
filed the real fix as a contract ask rather than inventing it; G built the manifest; F now
consumes it — the shell asks for nothing unlisted. Zero 404s because nothing absent gets asked
for, not because the gate stopped looking. AND DELETING IT IMMEDIATELY CAUGHT A LEFTOVER: F had
wired the boot preload but left a second blind probe at the door, 404ing on every
keyed-but-unfetchable shop. While the gate said "8 probes, expected", a ninth was invisible too.
A named exception starts protecting new bugs the moment the real fix exists.

F's beta gate GREEN: distinctness · sourcing (real + mint rooms) · soft fall silent · ZERO 404s ·
0 console errors, no attribution exceptions. Selfcheck ALL GREEN 156,212/156,212.

TAG HELD — atlas-QA FAIL, 14 errors, and it is not the product. E's SLOT_ID_PREFIX = "sku_"
(validate_atlas.py:73) applies to every pack; E reads `sourcing` at :178 but the slot-id check is
sourcing-blind. All 14 mint crates fail; the real crate passes. A mint crate HAS no POS sku — it
was never in that shop's till — so the rule demands what cannot exist, the same shape as R24's
sweep demanding a cafe carry a godverseShopId.

F measured the rule's intent and it holds: mint_<dealgod listing id>, non-positional, sorted by
it, seeded bake, 0 ids reused across all 15 crates. Only the literal prefix fails. And G refused
to go green the easy way for the best reason — F's own R24 collision grown up: a POS sku and a
dealgod listing id are different id spaces, and filing one under `sku_` recreates that bug
INSIDE the economy, where tier-2's sold-means-gone looks up POS skus. R24's collision entered
the wrong shop; the same collision in the money layer sells the wrong record.

F did not fix E's one line: E's file, C's contract, C's call as its author (the principle Fable
ratified when C overruled `tier`) — and F is the party that benefits, since that line is the
only thing between F and the tag. Cost of waiting: one line. Cost of F being quietly wrong: a
gate that green-lights id-space collisions in the layer that moves money.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 11:38:44 +10:00
m3ultra
a9a5ef05d8 Fable (integrator): R26 waves 1-2 complete — F's beta gate asserts C's 'sourcing' (the brief's 'tier' superseded, correctly)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 11:17:54 +10:00
m3ultra
8fd73acfa7 Lane F: the v5 README teaser — held three rounds, shipped with the tag
The teaser and the tag were one claim, so it waited until there was a tag to stand on.
v5.0-alpha is now live at e7d58ea: walk into Monster Robot Party on Musgrave Road and riffle
its own real crate — 120 real records off its real POS, real covers, titles and prices.

Documents the tier ladder as the epoch's load-bearing idea (0 parody / 1 static real / 2 live
GODVERSE, each degrading softly to the one below), the offline law (?noassets fetches zero
bytes; a missing atlas falls to parody, never another shop's crate), and the covenant that
outlived four epochs (?classic=1 → 0x3fa36874, byte-identical forever).

States the licence position where a reader will actually meet it: item photos are each shop's
own product shots, in-house green, FLAGGED before any public or commercial release; real
titles/artists ride the same amendment; and no in-game purchase touches real inventory —
sell-sandboxed by ruling, with the reservation tier chartered for v5.x, not assumed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 10:41:50 +10:00
m3ultra
36d10ca4ae Lane G R26 (v5.0-beta): the mint baker, the crate menu, the atlas manifest (ledgers #2, #6)
EVERY CRATE DIFFERENT — measured: 15/15 keyed crates distinct, max shared titles between any two
= 0. 14 mint atlases (5 record, 6 book, 3 toy) + Monster Robot's real crate + the manifest.

MINT (ledger #2): 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). Re-mint + re-bake is byte-identical. Only keyed shops whose type the loader can
fetch get one (record/book/toy, C §7.1); the 13 keyed opshop/video shops get none and stay parody
— an atlas there could never load, and the manifest is what makes that silent instead of a 404.

MANIFEST (ledger #6, C §7.2d): stock_godverse/index.json — 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.
Real crate re-emitted with sourcing:"real" + C §7.2c's condition/sleeve_cond (my snapshot always
carried them; I dropped them at emit — that was the hole in C's price card). Atlas bytes unmoved.

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 and carry `mint_<dealgod listing id>`, so 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: 344 ids, 0 clash with the generic packs, 0 positional). What §7.2a
specifies as sku_<POS sku> it specifies "for a real-stock (godverse) pack"; mint is §7.2b's other
thing, written a round later, and the id form was never revisited.

I did not just emit `sku_` to 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 exists 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 sku a POS must resolve. §7.2a's
own reason #2 is exactly about ids being looked up across snapshots. Fix: make the prefix
sourcing-aware (sku_ for real, mint_ for mint) — E's constant and error text are the only places.

THREE MORE FINDINGS, none blocking: [E] FITS_1024=64 cannot be satisfied — 1024²/256² = 16 cells,
so 64 items need 4 atlases and C caps a shop at 2; any 33–64 item pack MUST be 2048². Latent (my
packs are 16→1×1024² and 120→2×2048²); the honest bound is 32. [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, measured 46.1 MB/town resident; it's why mint crates are 16 items on one 1024² sheet.
[C] "the crate menu, baked statically" can only be metadata: one base = one index = ≤2 atlases =
128 items, and Monster Robot has 357 crates / 24,646 records — so crate_menu ships the real crate
list (labels + counts) and per-crate STOCK needs a per-crate base, i.e. a C contract line.

MINT HONESTY: sourcing:"mint", an attribution that says "not this shop's stock", and NO POS-claim
field at all — structural, not editorial. Records require a real Discogs artist (dealgod's `brand`
on a record listing is the SELLER — the first bake had "Red Eye Records" as an artist; a shop is
not an act) and use discogs_master_title where present, so Egg Records reads "MC5 — High Time
$50", not "Pre Loved Record - Elton John - Breaking Hearts". Books/toys take `brand` per C's
author/brand convention — sometimes the maker, sometimes the listing store. Real, imperfectly
typed, and never claimed as that shop's.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 10:33:40 +10:00
m3ultra
33577a9675 Lane E R26 (v5.0-beta): the sourcing arm + the atlas manifest, both ways (ledgers #3, #6)
THE FIELD IS `sourcing`, NOT `tier` — and reading C's contract instead of the brief is the only reason
this gate isn't already broken. The R26 brief says "the validator grows the tier arm... `tier` required
on every atlas". C ruled it CANNOT be `tier` (§7.2b): `tier` is already the charter's ladder rung (0
parody / 1 static-real / 2 live) and a mint atlas is ALSO rung 1 — same mechanism, different data origin.
Overloading it puts two meanings in one name: the same species as the plan-id/godverse-id collision and
the positional ids. Had I built the brief's word, my gate would demand `tier` while G emits `sourcing` —
R23 exactly, re-run. The contract's author is the authority for the contract's shape.

THE HONESTY SEPARATION, BOTH DIRECTIONS (C §7.2b). `sourcing` ∈ {real, mint} required; no `sourcing` =>
FAIL (a crate that cannot say where it came from must never pass as one that can). mint must carry no POS
claim; real must carry the POS provenance it claims. The licence line prints for both — verified, not
assumed.

-> C: §7.2b says "a mint index MUST NOT carry POS-snapshot fields" but does not enumerate them, so I named
the set — correct it if it's wrong. I assert on `crate`, `provenance.source.pos_db`,
`provenance.source.pos_dump` — the fields that claim a SPECIFIC SHOP'S ACTUAL INVENTORY. I deliberately
excluded `snapshot`: §7.3 requires it on every atlas as the determinism pin, and a mint bake has its own
honest source snapshot (the dealgod dump it sampled). The error names the exact field found, so a
divergence with G surfaces as a diagnosis, not a mystery.

THE MANIFEST (§7.2d, ledger #6), consistency both ways. An atlas on disk absent from the manifest FAILS
(no entry => no fetch => that crate silently never loads; an unreachable atlas is not a passing atlas). A
manifest entry with no atlas FAILS (the wire fetches it and 404s — the exact noise the manifest exists to
kill). C's "absent manifest => legitimate zero-shop state" is honoured: no atlases + no manifest => SKIP.
-> C: one hole your rule implies but doesn't spell out — a manifest that PROMISES shops while zero atlases
exist now FAILS rather than skipping. "Absent" scopes to the empty tree; a tree where something on disk
still asserts atlases exist is the vacuous shape wearing a hat. Flagging it as my reading, not your words.

PROVED BEFORE HANDING G THE GATE — 17/17, including a mint crate that does not exist yet (the R25 rule:
never hand a lane a gate that fails on compliance). real -> PASS · mint -> PASS WITH ITS LICENCE LINE
PRINTED (ledger #3's "prints for both") · real+mint side by side -> PASS · mint carrying `crate` -> FAIL ·
mint carrying pos_db -> FAIL · real carrying no POS provenance -> FAIL (the reverse) · no `sourcing` ->
FAIL · sourcing='realish' -> FAIL · atlas absent from manifest -> FAIL · manifest entry with no atlas ->
FAIL · declared type not on disk -> FAIL · manifest says mint while the index says real -> FAIL · manifest
missing while atlases exist -> FAIL · unparseable manifest -> FAIL · empty tree -> SKIP · empty manifest
-> SKIP · manifest promising a shop with no atlases -> FAIL.

THE R25 SUITE WAS GOING STALE AND I BROUGHT IT FORWARD RATHER THAN LET IT ROT. Adding `sourcing` + the
manifest made every R25 licence case fail for a NEW reason — which would leave the licence arm untested
while appearing tested. Updated to the R26 contract: 8/8 green, and the tally tripwire still fires VIA THE
TALLY (via_tally=True), not incidentally. Near-miss worth recording: the manifest error briefly shadowed
the tally in the harness, which is precisely how the tally died the first time. A test that fails for a
new reason has stopped testing what it was written for.

The gate is RED until G lands (2 errors: no `sourcing`, no manifest). G is in my wave, parallel; it goes
green on their re-emit with no change from me — same as R25.

-> F: THERE IS STILL NO v5.0-alpha TAG, and C's + B's BETA commits (4f0adf1, 2f4ab59) now sit on main
above wave 0's close. Tagging HEAD would produce a v5.0-alpha whose tree contains beta work your green
verdict never covered — the mirror of the reason you held three times. The alpha's tree is e7d58ea (wave 0
COMPLETE, my 9b1df5c beneath it). Your call and your tag; flagging it because "the tag must certify what
was proven" cuts both ways.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 10:08:59 +10:00
m3ultra
2f4ab5912f Lane B R26 (v5.0-beta): the stocked shopfront reads from the footpath
Ledger #5 — B's polish pick. A stocked godverse shop now tells you it's
diggable before you reach the door: a crate of seeded spines under the shop
name, and a warm-lamp-over-crates room behind the glass.

Cost: +0 draws / +0 tris. The mark is painted into the chunk's EXISTING
sign-atlas cell; the window cue rides the EXISTING per-shop aTint attribute.
Nothing new is allocated or submitted.

The manifest is the authority, deliberately NOT the godverseShopId key: keyed
means "has a stock identity", the manifest means "a crate is actually here".
Marking keyed-but-unstocked shops would advertise a crate the shop hasn't got
— the street would lie. The mark does not leak tier: "you can dig here" is
equally true of real and mint; provenance belongs on C's price card and in E's
gates. (Second job for ledger #6's manifest, free.)

Measured — true A/B at one pose, manifest 200 vs 404:
  spine-marked sign cells   0 → 3 (18,768 exact-palette px)
  stocked window tint verts 0 → 36
  street_noon               65 draws / 115,502 tris → UNCHANGED
Stocked shopfront (Goulds Books, footpath): 85 draws / 144,730 tris — inside
street law (<=300 / <=200k). Cues agree with ground truth: 3 stocked shops
live → 3 marked cells, 9 stocked window quads.

Covenants re-proved (the prefetch is new code on the boot path), tested with
the manifest present on disk so a stray fetch would 200 and be unmissable:
  ?classic=1        0 keyed, 0 stock fetches, 0 errors  (zero-fetch covenant)
  default synthetic 0 keyed, 0 stock fetches, 0 errors
  manifest 404      18 keyed, fail-soft, 0 page errors

Three traps, self-caught: (1) a module-load fetch would have put a fetch delta
on ?classic=1 — restructured to a lazy reader + plan-driven prefetch, keyed
plans only; (2) the lazy trigger alone raced the first chunk build, so the
first stocked shop you approach built unmarked — prefetch moved to
createChunkManager, before any chunk builds; (3) the first "zero cost" was
VACUOUS (manifest never fetched, so nothing was applied) and the first pixel
detector reported 60 marked cells with no treatment at all — replaced with an
exact-palette signature that reads 0 in control. Vacuous-gate law applies to a
lane's own verification too. Corollary: force-cache serves a deleted file
(transferSize 0, status 200 while disk 404s) — poison the cache or you are
testing your own memory.

B consumes stock_godverse/index.json read-only and emits nothing into it; the
temp manifest used to prove this round was deleted — the directory is G's.
Shape-tolerant + fail-soft, so G's real emission needs no coordination with B.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 09:49:54 +10:00
m3ultra
4f0adf12d8 Lane C R26 (v5.0-beta): three contract lines + the dig price sticker (ledgers #1, #4, #6)
Wave 1 — contract lines published first; G and E build to them.

#1 THE BRIEF SAID `tier`. IT CAN'T BE (§7.2b). G's atlases already emit tier: 1 = the CHARTER'S
LADDER RUNG (0 parody / 1 static-real / 2 live), and `kind` is already the stock type. A mint atlas
is ALSO rung 1 — same mechanism (a statically baked per-shop atlas); only the data's origin differs.
Real-vs-mint is an orthogonal axis, not a rung — overloading `tier` puts two meanings in one name,
the same species as the plan-id/godverse-id collision just standing-noted, and as R25's positional
ids. RULING: top-level `sourcing: "real"|"mint"`, REQUIRED; `tier` stays the numeric rung. A mint
index MUST NOT carry POS-snapshot fields (the separation is the honesty). Gates read `sourcing`;
absent => FAIL.

#4 `condition` ISN'T IN THE INDEX (§7.2c) — the ledger assumed it was. The index carries
artist/title/price/price_band; G's SNAPSHOT has condition + sleeve_cond and drops them at emit, so
pickRealOffers mapped cond:'' and the card rendered dangling separators around a hole. That WAS the
"database row". Ruled optional index fields, emitted when the source has them; no invented grades on
mint items (a plausible-but-fictional grade is what `sourcing` exists to prevent).

#6 THE ATLAS MANIFEST (§7.2d): stock_godverse/index.json is the authority for which shops carry
atlases — no entry => no fetch => zero 404s, so the zero-console-errors law holds with no
attribution exception. Shape + both-ways consistency specified. No manifest + no atlases = a
legitimate silent zero-shop state, not an error.

THE PRICE STICKER (code): the pull panel is now the shop's price sticker — manila card, rotation +
shadow, typewriter caps, italic title, a stamped condition grade, and the round yellow $ sticker the
sleeve art already uses (the panel echoes dig.js's own canvas language instead of fighting it).
Fields compose only when present: a missing field never leaves a stray separator.

VERIFIED (fresh context, 5 card shapes, both star branches): real grail VG+/VG + ★GRAIL; real
standard VG+ + STANDARD; real ungraded COLLECTOR (grade omitted cleanly); parody NM + 1973 PUNK;
parody rare (seed 4) VG + 1973 POP + ★RARE. No dangling separators anywhere.

The verification caught a defect in MY OWN card: a real grail read "GRAIL ★ GRAIL" — `s` carried the
price_band AND `rare` derived from that same band (parody is immune: its `s` is a genre). Fixed at
the mapping. And I didn't leave the parody-rare branch on assumption — scanned seeds until a rare
pull appeared and asserted it says RARE, not GRAIL. An unexercised branch is an unproven one.

Budgets: the card is DOM — zero draws, zero tris, no 3D change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 09:38:42 +10:00
m3ultra
e7d58eaad5 Fable (integrator): R26 wave 0 COMPLETE — E landed its own commit, F is GO to tag v5.0-alpha
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 09:28:18 +10:00
m3ultra
9b1df5cca0 Lane E R25 (v5.0-alpha): the licence line made unmissable + the sku form asserted
THE BUG WAS A DIVERGENCE, NOT A SPELLING (ledger #2). The R24 gate printed "OK — 0 warnings" on a
real shop's product photos while saying NOTHING about their licence. Two paths read one field with
different spellings:

    the CHECK:  lic = prov.get("licence") or prov.get("license")   <- permissive: passed either
    the PRINT:  lic = prov.get("licence")                          <- strict, behind `if lic:`

While G's artifact was British they agreed. G's R24 keyed re-emit moved it to US `license`: the check
still passed, the print silently resolved to {}, and FLAG BEFORE ANY PUBLIC/COMMERCIAL RELEASE stopped
reaching a human. The `or` fallback WAS the bug — it made one path permissive so it could not feel the
other break. That is my own R21 stale-roads lesson one layer up (a check applied to one of two
symmetric paths is a trap with a fuse on it) — I wrote that lesson down and shipped it again anyway.

Fixed structurally, not by adding the missing spelling: ONE spelling, NO fallback (British `licence`
now ERRORS and names C's §7.3 retirement); ONE lookup, ONE owner (validate_index returns the evidence
string, main prints what it is handed and never re-looks-up the field — no second path left to drift);
and the evidence line is COUNTED — judged N atlases means N licence lines shown, or FAIL.

MY OWN FIRST CUT HAD THE SAME DISEASE AND MY OWN TEST PASSED FOR THE WRONG REASON. I added the tally
tripwire — but main() also appended its own missing-licence error, so all_errs was never empty when the
tally would fire: the tally was UNREACHABLE DEAD CODE, and the test went green through the other path
and reported PASS. Exactly the trap I recorded in R24 ("a gate that fails for the wrong reason is a
gate that will pass for the wrong reason later") — one round later, on me, in the file where I wrote
it. Now the field check owns the precise error and the tally sits after the all_errs return, existing
only for the day that check goes permissive again. Verified by stubbing validate_index permissive: it
fails VIA THE TALLY, not incidentally.

Proved 8/8: G's sku re-emit -> PASS with the flag shown · `license` gone/empty -> FAIL · British at top
level -> FAIL · British nested -> FAIL · attribution/snapshot gone -> FAIL · THE R24 SILENT SHAPE ->
FAIL · permissive-check simulation -> FAIL via the tally.

Aligned to C §7.3 as the provenance authority: the four gate fields (license, attribution, generator,
snapshot) live at TOP LEVEL, US spelling — the convention 21 shipped town caches already use. This
reverses R24's #2/#3, which read the artifact of the day. Docstring and code now say the same thing;
R24's fourth bug was that they didn't.

C §7.2a — THE SKU FORM (ledger #3), and I verified C's claim before building to it: independently
measured 120/120 godverse ids also present in the generic pack (F and C exactly right), and G's
snapshot carries 120/120 unique skus so the re-emit was feasible. Proved the compliant shape passes
BEFORE handing G the gate (4/4: sku_<POS sku> -> PASS · positional -> FAIL · sku-but-duplicated ->
FAIL · one id sneaking back into rec_ -> FAIL at 1/120, so it catches a real collision, not only an
all-or-nothing one).

-> C: two things beyond your literal ask, say if you disagree. (1) I also assert the actual property
your rule exists to guarantee — the pack's ids are DISJOINT from the shipped generic packs, measured
against web/assets/models/stock_*_index.json; the prefix says ids should be disjoint, this measures
whether they are, and it catches the reverse drift (a generic pack wandering into sku_) a prefix check
never could. Absent generic packs WARN rather than passing silently. (2) I flag British `licence`
anywhere, including inside the nested provenance{} "colour" block — §7.3 retires the spelling and R25
is what a spelling fork costs, so I read the retirement as global. Scope it back if you meant
otherwise.

The gate was RED by design for one wave: between my readiness and G's re-emit the collision was real
and unfixed, so it failed deliberately — a gate that stayed green on a defect F had just found would
be the vacuous species in a different hat. It went green when G landed 4314622, with no change from me.

Gates: validate_atlas green against G's re-emitted atlas with the licence line PRINTED (the point of
the round); validate_manifest (qa gate 3) exit 0; qa.sh --strict 6 passed / 0 failed / QA GREEN.
-> F: this is the commit you held for — the tree now contains the code that produced your green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 09:16:49 +10:00
m3ultra
4a80ea8505 Fable (integrator): R26 amended — wave-0 status (F green, tag pending E) + the atlas manifest (F's finding #4, structural)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 08:54:27 +10:00
m3ultra
29942002eb Lane F R24 (v5.0-alpha): THE CRATE WALKS — the per-shop wire + a gate that can't lie
You can boot redhill_godverse, walk down Musgrave Road into Monster Robot Party, riffle its
own real crate and buy a real record. Proven falsifiably, not by eyeball:
  base      assets/stock_godverse/3962749/ (its own, not the town-wide)
  TITLES    120/120 the shop's atlas · 0/120 the generic pack
  TEXTURES  2 from stock_godverse/3962749/*.webp · 0 generic — the pixels' own file
  BUY       rec_0000 "Abstract Latin Lounge II (Part II)" @ $15 → cash 191→176, exact
  REBOOT    191/0 restored — gone-this-session, back next boot
Ladder: ?noassets → ZERO network requests · keyed-but-atlas-less shop → 404 → parody, live ·
newtown_real → base null, 350 items: the wire is a NO-OP off the godverse path.
flags_check GREEN (0 fails, 0 warns).

#6a THE WIRE: godverseBaseFor(shop), exported and used by BOTH the shell's boot preload and
the room's lookup — C's cache identity is the LITERAL (type, base) pair, so two sides building
that string differently would drop a real shop to parody, a bug indistinguishable from "no
stock" by eye. One function, one string, one key. Gated on stockReal, never the id alone.

#6b THE GATE: smoke_real_crate + DBG.stockInfo() — permanent, and it cannot pass without
touching its subject (SKIPs with a printed reason when the town/atlas is absent).

FINDING — THE BRIEF'S OWN HONEST-GATE SPEC WAS VACUOUS. It specified id equality. Both packs
number items POSITIONALLY (rec_0000…), so all 120 atlas ids are generic-pack ids too: an
id-equality gate goes GREEN on the generic pack — the exact failure the vacuous-gate law was
written to stop, reproduced inside its first application. Ids there are slot numbers, not
identity. Titles (0 overlap) and the texture URL discriminate; F asserts on those.

FINDING — THE ID SPACES COLLIDE, and it bit F's own selector inside the anti-vacuous gate.
Plan ids (1..N) and godverse ids (31, 767, 3962749) share a numeric space: Silky Oak's godverse
id is 31 and another shop's PLAN id is 31, so enterShop(31) silently entered "Wholefood Cafe"
and the soft-fall assertion PASSED ON AN UNKEYED CAFE. Fixed both ends: 'g:<n>' selects a
godverse id explicitly, a bare number stays the plan id and now REPORTS the collision, and the
smoke asserts it landed in the shop it asked for before asserting anything about it.

FINDING — F'S OWN GATE CRIED THE LOUDEST WOLF WE OWN. classic_regression reported "plan
fingerprint != golden 0x3fa36874 — determinism/prime-law broken" while the covenant was
provably intact. F wrote it in R16 as `hash_ok and returncode == 0` — two assertions, ONE
verdict, reported with the fingerprint's message — so any unrelated selfcheck red screamed that
the byte-identical-forever covenant had broken. Two assertions, two verdicts now. It isn't
enough to fail for a reason; you have to fail for YOUR reason.

FINDING — fail-soft-by-404 vs the 0-console-errors law. The boot preload must resolve a keyed
shop's pack before the door opens (getStockPack is sync), so it probes blind: 9 of Red Hill's
10 keyed shops have no atlas → 404 → parody. That IS fail-soft working, and it is also 8
console errors no .catch() can suppress. Fix is not to loosen the law but to stop probing for
what isn't there: an atlas index in G's namespace (or hasStock on the cache entry, A+G). Filed
as a contract ask, not invented. Until then the smoke counts and NAMES the probes and fails on
any other error — tolerance stated out loud.

Verified the lift is clean: cache 10 keyed → plan 9 keyed, every one a real secondhand shop.
No cafe keyed, zero duplicate ids — there is no mis-stocking.

F did NOT touch A's identity sweep or E's validator. F was the party that would benefit, and
changing a red gate to green in another lane's file to unblock F's own tag would retroactively
devalue every green F has ever reported. Filed instead — and R25 has since landed A's
reconciliation and the six pins, which is the right hands doing it.

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

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

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

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

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

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

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 02:28:44 +10:00
m3ultra
a493c0a13c Lane A round 25 (v5.0-alpha): the sweep reconciled with Ruling 2 + the six pins (ledger #1)
All 8 tree reds cleared. selfcheck ALL GREEN 156,211/156,211; scaffold + consistency green; classic
0x3fa36874 and gig 0xb1d48ea1 frozen. F: the tag is unblocked from my side, nothing of mine is pending.

SIX OF THE EIGHT REDS WERE MY OWN GATE ASSERTING RULING 2 AWAY. I built the R24 identity arm on an
assumption that was true when I wrote it and false the moment G shipped: that a godverse cache is ALL
census shops. Ruling 2 makes it MIXED -- a keyed GODVERSE layer plus an inherited OSM texture layer that
G measured to be load-bearing, not cosmetic (census-only Red Hill: 306 m spacing warn + a poster inside a
kerb). So newtown_godverse (18 keyed + 54 texture) and redhill_godverse (10 + 27) were HEALTHY TOWNS MY
GATE CALLED BROKEN -- it fired 54/72 on a good town. Keyed asserts now run PER-LAYER over the keyed subset
only; unkeyed texture shops raise nothing. The validator's "N/M missing" warn dies with it. The one honest
requirement left at that layer: a `godverse+osm` cache must have a NON-EMPTY godverse layer (else it's an
osm cache wearing the wrong `source`) -- still a warn, for the R24 charter reasons that still hold.

THE UNIQUENESS CHECK WAS BROKEN IN BOTH DIRECTIONS. `new Set(all ids).size === shops.length` collapses
every `undefined` into ONE Set entry, so it (a) failed structurally on any mixed cache -- 54 texture shops
became one entry -- and (b) could HIDE A REAL DUPLICATE behind that same collapse. Now over DEFINED ids
only. And per the vacuous-gate law I made it prove it bites rather than asserting that it does:
validateTownCache rejects duplicates at the front door, so a dup can never reach the plan that way -- the
test feeds one STRAIGHT TO THE LIFT (generatePlanOSM does not validate) and asserts the predicate catches
it, with a healthy mixed cache passing the same predicate. A check nobody can watch fail is not a check.

NEW -- THE ORPHANED-ATLAS GATE (my own R24 finding, now enforced instead of filed). The lift legitimately
drops shops (overlap-resolve, counted since R19). Dropping a shop that HAS a committed atlas means real
stock keyed to a shop that isn't in the town -- exactly the failure I filed in R24, and one F's #7b could
only catch in a browser at the end of a round. Now a node gate: for each godverse town, every
stock_godverse/<id>/ on disk belonging to that cache MUST be seated, else FAIL. Subject-aware -- it SKIPs
by name when a town has no atlas, rather than passing quietly.

It already found one, benign today: redhill's lift drops keyed shop 2286 "Empire Revival" (10 keyed in
cache -> 9 seated). No atlas, so nothing is orphaned and the gate passes -- but it prints, because a drop
must never be silent (the R19/R20 ruling). G: stocking Empire Revival at the beta would orphan it. Monster
Robot Party is seated (lot 8), which is the one that matters this round.

THE SIX PINS, cross-checked two ways before landing -- byte-identical across two fresh processes, AND
matching the three values G stated independently in 86e2985:
  newtown_godverse  base 0xcf69b387  gig 0x1e266f60   (moved by G's keyed re-emit)
  redhill_real      base 0x6046700f  gig 0x673785ee
  redhill_godverse  base 0xb2224939  gig 0xc6f80e18
No other golden moved -- the diff is exactly these six lines.

Two lines still print; both deliberate, neither pending:
  ⊘ SKIP newtown_godverse orphaned-atlas -- correct, the only committed atlas (3962749) keys to Red Hill.
  ⚠ redhill_godverse: 1 keyed shop dropped -- the honest disclosure above.

Committed strictly by pathspec: F's six staged files sit untouched, exactly as its session left them.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 02:23:57 +10:00
m3ultra
0d8d59a22a Lane C R25: sku-derived slot ids — F's id-collision finding made structural (ledger #3)
One contract line, published first so G re-emits against it. §7.2a added.

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

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

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

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

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

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 02:18:37 +10:00
m3ultra
2c1ae37160 Fable (integrator): round-25 instructions — the tag, earned (sweep vs Ruling 2, licence line, sku ids, F closes)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 02:14:02 +10:00
m3ultra
0608f9c8c1 Lane G R24: G3 §7 RATIFIED — John's write-back ruling, A then B (charter law #4 satisfied)
John ruled at the R24 kickoff: v5.0 ships on Option A (sandbox) — an in-game purchase never
touches real monsterrobot/Square inventory; Option B (reservation via the POS's existing
hold_expires_at) is CHARTERED as a v5.x upgrade behind a hold budget John sets; Option C is not
chartered and happens only as a deliberate business launch, if ever. Recorded inline in §7 with
the options preserved beneath it, since the trade-offs are the record of why A-then-B was chosen.

Written in as binding, because a one-line ruling isn't a mechanism: /reserve + /buy stay ABSENT,
not stubbed behind a flag (a sandbox a config typo can flip isn't a sandbox); the tier-2 reader
connects read-only, enforced at the credential — a role with SELECT on inventory/crate/disc_cache
and nothing else, which carries the §9 PII fence in the same grant; and the gate asserts the write
verbs 404 and the role's grants are read-only, because "we didn't call buy()" proves nothing —
the vacuous-gate law turned on ourselves.

Option B's preconditions are stated so it can't slide in quietly: the hold budget (John's number,
NOT YET SET — B is not startable until it is; a v5.x item, not a v5.0 blocker), the-shop-always-
wins on a race with an in-store Square sale, a staff-visible `held: GODVERSE` reason code, and
soak before ship. Doc header/§10 updated so the whole file tells one story: §7 ruled, the rest
still DESIGN pending both Fables' sign-off, no server code before that.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 01:33:27 +10:00
m3ultra
86e298531e Lane G R24 (v5.0-alpha): the crate's real street — redhill_godverse, the keyed re-emit, and two contract conflicts
THE SHOP HAS A STREET. redhill_godverse: Monster Robot Party seated on Musgrave Road (verified —
lot 8, type record, godverseShopId 3962749), 37 shops on E's 884 real Red Hill roads. Its
coordinate is John-supplied (Google Maps place /g/11g6rlbbc0 -> -27.4553791, 153.0076244) because
it is derivable from nothing we hold: OSM doesn't know the shop, the census doesn't either,
dealgod's stores/store_entity rows are NULL lat/lng, recordgod has no address. Verified inside E's
bbox and on Musgrave Road's own geometry; my own estimate was ~120 m out.

RE-EMIT (ledger #5 + #2): atlas keyed by godverseShopId with US `license` throughout (British
retired, 0 occurrences) — atlas-QA OK, 120 real records, byte-identical re-bake (the .webp bytes
did not move; only the index). newtown_godverse re-emitted with ids (18 keyed). godverse_town.py:
psycopg2 -> psql, EXTRA_SHOPS roster, and Fable's R22 Ruling 2 finally implemented.

RULING 2 WAS LOAD-BEARING, NOT COSMETIC (measured): census-only Red Hill is unshippable —
9 shops = 306 m spacing warn; +Monster Robot = 10 shops = 261 m warn AND a frontage poster seated
INSIDE edge 1037's kerb (-2.95 m); + the donor's texture layer (37) = no warn, poster passes.
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". 9 donor heroes swapped
for census entries (18 in Newtown), counted per the ruling.

CONFLICT #1 — C §7.3 vs E's validator. C says lift provenance to TOP LEVEL, US `license`, British
retired, "exactly what E's validator reads". Measured: it is not. validate_atlas.py:98 reads
NESTED `provenance`; :105 reads `licence` British-first. Lifting-and-retiring as written FAILS the
gate; my R23 atlas already passed it untouched. Same species as the bug that held the tag — a doc
asserting what code does without running it. Resolved: E's check accepts `licence or license`, so
US-nested passes; the four also ride at top level (C's contract + the town-cache convention) off
one source of truth. -> E, one line: :198 PRINTS `licence` British-only while the check accepts
both, so C's mandated US spelling silently stops printing the licence flag — which :199's own
comment calls a human call. Gate passes; evidence line vanished. Fix: .get("licence") or .get("license").

CONFLICT #2 — Ruling 2 vs A's identity gate. NEEDS A RULING; it is why the tree is red (6 of 8).
Ruling 2 makes a godverse cache mixed: a keyed GODVERSE layer + an inherited OSM texture layer.
A's sweep asserts EVERY shop is keyed -> partial FAILs. Texture shops carry no godverseShopId
deliberately: an OSM milkbar has no GODVERSE identity, and minting one from its OSM id pushes a
third id space into a field that already carries two — A's validator calls a duplicate
mis-stocking, charter risk #3's nightmare with Monster Robot's crate as the prize. And A's own two
checks disagree: the validator says "Missing = tier 0 (soft). Duplicate = mis-stocked (hard). They
are not the same failure", while the sweep fails partial keying because it "mis-stocks the rest" —
conflating exactly those two. Proposed (A's call, Fable's ruling): assert coverage over the
godverse layer, not the inherited texture layer.

THE 8 RED CHECKS, ITEMISED: newtown_godverse base+gig goldens (2) moved by the re-emit exactly as
ledger #2 intends — A re-pins, base 0xcf69b387 gig 0x1e266f60; godverse identity x3 per godverse
town (6) = conflict #2. Nothing else. Classic covenant 0x3fa36874 intact. redhill_godverse pins:
base 0xb2224939. Verified live: the shop's 120-record pack and the town-wide 350 pack now COEXIST
through C's type+base fix.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 01:31:07 +10:00
m3ultra
5815dfbd58 Lane E R24 (v5.0-alpha): the crate's real street, the honest gate, and Ballarat retired
THE STREET (ledger #3). redhill_real: Red Hill, Brisbane — 36 shops (9 heroes + 27 texture,
-29 subsampled), 884 roads, median NN 73 m. **Musgrave Road is in roads[] (41 ways, primary +
residential)** — that was the point: Monster Robot Party's real shop now has its real street.
One bounded Overpass fetch under the standing authorization; raw snapshots in _raw/, ODbL, C's
mapping. This is G's donor for godverse_town.py -> redhill_godverse.

TWO HAZARDS HANDED TO G/A, both measured against the real geometry (notes R24 §1):
(a) The shop is NOT in OSM — no shop=music node, no name match anywhere in the bbox (checked the
raw snapshot, not the emitted cache). It cannot arrive via the fetch; G must inject id 3962749
from the census or the town ships without the shop the release is about.
(b) THE SEATING HAZARD: A's lift seats on the NEAREST edge, and from G's geocode three streets
are nearer than the addressed one — Zig Zag Street 38.6 m (residential), Craig Street 46.1 m,
Musgrave Road 51.6 m. The shop would seat on Zig Zag St and the headline would quietly become
false. Measured fix (G's call): the point on the real Musgrave centreline nearest G's geocode is
-27.455169, 153.006902 — 0 m to Musgrave, next candidate 43 m. Snaps the street, does NOT verify
the house number; I have no address data for where #147 sits along the road.

THE HONEST GATE (ledger #4). F's catch was right and there were FOUR bugs: the glob used the
published-contract name the runtime can never load (0 matches -> returned 0: it passed identically
whether atlases were absent, misnamed, or perfect); the code read provenance top-level while my own
docstring said nested (G nested it correctly — the gate would have failed G's real artifact);
licence/license; and a stale ceiling. Rebuilt on the "runtime is the authority" ruling: globs
stock_godverse/<id>/stock_<type>_index.json, type in {record,book,toy}. Vacuous-gate law implemented
literally — 0 matches + stray files FAIL (listing them); 0 matches + empty dir prints
"atlas-QA SKIP: no atlases present (0 files matched glob ...)"; plus an id equality
(shop.godverse_id == dir) so it proves it touched its subject. Proved on four cases: empty -> loud
SKIP, misnamed (F's exact bug) -> FAIL, unparseable -> FAIL, G's real atlas -> PASS (120 items,
2 atlases, licence printed). My own test found a fifth hole — the glob still matched the old
stock_shop_* name, so the misnamed case failed only incidentally; added a STOCK_TYPES name check
so it fails for the RIGHT reason. A gate that fails for the wrong reason will pass for the wrong
reason later.

BALLARAT — RETIRED, counted, and a DIFFERENT illness from Toowoomba (notes R24 §3). Toowoomba was
dead everywhere. Ballarat is alive exactly where the game isn't: 103 POIs, densest 400 m disc = 47
of them (Toowoomba's was 3) — but that disc is cafes/clothes/bakeries while the op-shops sit 854 m,
1042 m and 1709 m out. Heroes and density don't coincide, so no bbox holds both. Real builds at
every span: 1.0 km -> 8 shops/184 m/hub 4-8 but ZERO op-shops (a thrift town with no thrift shop);
2.0 km -> 12/186 m/3-12; 3.0 km as shipped -> 20/255 m/4-20. Every row worse than any shipped town.
THE BINDING CONSTRAINT WAS MINE, NOT OSM'S: with 2 heroes the 3x texture cap keeps 6 of 47 texture
POIs and the subsample is spatially blind, so survivors scatter at any span — part of the 255 m A's
warn caught was my own cap's fingerprint. Seeding texture near heroes fixes the class but moves
every town's golden -> filed as a v5.x proposal, not done in a release round.

I OVERSTATED MY R23 ADVICE TO A: the hub fraction is NOT a standalone discriminator — glebe (shipped,
healthy) reads 13% and redhill 14%; a hub threshold would false-flag both. Median NN is the robust
primary; hub is only comparable at equal shop counts (toowoomba 2/12 vs darwin 7/12 was fair because
both had 12). The raw-vs-seated calibration point stands.

METHODOLOGY, recorded because it nearly shipped: I picked Ballarat's span from an offline simulation
that predicted 68 m / hub 5-8, and had already written those numbers into a config comment as fact.
The real build returned 184 m / 4-8 — same counts, different sample. Never trust a re-implementation
of the pipeline's own selection; run the pipeline.

-> A (batched pins): ADD REAL_TOWN_GOLDENS['redhill_real']=0x6046700f and
REAL_TOWN_GIG_GOLDENS['redhill_real']=0x673785ee (the headline town currently only WARNS as
UNPINNED — ungated until you pin it). REMOVE ballarat_real 0xf0a9e8a1 / 0xf097c331 — dead config by
your own R23 note.
-> F: tools/qa/tour_shots.py still shoots town=toowoomba_real, which I deleted in R23 — the v4 tour
breaks on a missing town. Your file, your call. A retirement isn't done when the cache is deleted;
it's done when the consumers are swept.
-> C: built to the runtime + G's real artifact per the ruling; LANE_C_PUB §7 line 271 still names the
contradictory stock_shop_<id>_index.json. I reconcile when your corrected contract lands.

Determinism receipt: the retirement forced a full 21-town rebuild from cached raw (no refetches) and
every existing cache came back byte-identical — only ballarat, redhill and index.json moved. Pack:
21 real towns · 1196 shops · 299 heroes (22 rostered with G's newtown_godverse). Selfcheck ALL GREEN
149,696, synthetic golden 0x3fa36874 unchanged. qa.sh --strict --matrix: 7 passed / 0 failed, MATRIX
GREEN 10x7.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 01:06:00 +10:00
m3ultra
1eb0b45bd0 Lane A round 24 (v5.0-alpha): the identity field — godverseShopId, published first (ledger #2)
The optional shop key that resolves `stock_godverse/<godverseShopId>/`. Schema STAYS v2 (optional field ⇒
every shipped cache still valid, the 21 osm caches untouched by construction). Published to
web/assets/towns/README.md — the doc G and E build to — plus CITY_SPEC. selfcheck ALL GREEN (+15 checks),
synthetic 0x3fa36874 frozen, NO golden moved.

THE BRIEF'S "THE POS SHOP ID IS THE ID — NO MAPPING TABLE" IS FALSE FOR THE ONE SHOP THIS ALPHA IS ABOUT.
G's own docstring says it: 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, deliberately kept clear
of each other. Had I coded "godverseShopId == shop.id" or derived it from the census, the headline shop
could never be keyed. So it is an OPAQUE key: no derivation from `id`, no equality assert, no mapping table
(the R12 gigKey law -- one key, zero mapping; G emits, everyone reads).

THE FIELD HAD TO SURVIVE THE LIFT OR IT WAS DECORATION. plan_osm builds a FRESH shop object and copies
nothing unknown, and the runtime reads plan.shops[], never the cache -- so a cache-only field could never
reach F's per-shop base. 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). Proven end-to-end: a keyed cache puts `stock_godverse/3962749/` on the plan with census ids
(101..107) coexisting alongside it.

ARMS SPLIT BY HAZARD, NOT TIDINESS:
- malformed  -> ERROR. It names a path segment; "3962749" vs 3962749 is the same folder but different JSON,
  and that coercion drift is the exact species that broke C's atlas contract (licence/license).
- duplicate  -> ERROR. Two shops keyed to one atlas IS mis-stocking -- charter risk #3 forbids it by name.
- missing on a godverse cache -> WARN + fail-soft to tier 0.

RECORDED DEPARTURE (Fable's to overrule): ledger #2 says validateTownCache "requires" the field; I made
MISSING a warn. Three reasons. (1) The charter's determinism boundary is "world = seeded, frozen, gated;
stock = data tiers" -- an error inverts it, failing to load a TOWN because its STOCK identity is absent;
tier 0 must always boot. (2) Charter risk #3 names the remedy for identity gaps: "fail-soft to tier 0,
never mis-stocked" -- missing IS the soft case, duplicate is the hard one; they are different failures and
now get different arms. (3) Sequencing: newtown_godverse carries 0 ids today, it is G's to re-emit in wave
2 (their namespace, no census DB on m3), so a hard error would red the tree for C and E through all of wave
1 to say what the warn already says.
The requirement keeps its teeth where R23 taught us to put them -- the VACUOUS-GATE LAW: selfcheck FAILS on
partial keying (a half-keyed town silently mis-stocks the rest) and prints an explicit
"⊘ SKIP ... 0/18 shops carry godverseShopId. Subject absent ... binds the moment one id lands" at zero. It
names its subject, proves it touched it with a count, and cannot pass vacuously.

FILED FOR G BEFORE THEY START (measured in wave 1, so it costs them minutes not a wave): redhill_godverse
cannot contain Monster Robot from EITHER source. godverse_town.py takes roads from the OSM donor and shops
from the census, and the shop is in neither -- not in the census (G's R23 finding), and NOT in E's
redhill_real, which I checked the moment it landed: 36 shops, ZERO of type `record`, nothing matching
/monster|robot/. As written the adapter yields a Red Hill of census charity shops while the atlas keys to a
shop that isn't in the town -- F's #7b id-equality gate would fail at the end of the round. It must be
injected explicitly; the opaque-key design exists precisely to serve that case. Second trap on the same
path: redhill_godverse must still clear MIN_TOWN_SHOPS (6) from the census bbox or the cache is rejected
outright (hard error). E's donor is otherwise good: valid, no warns, 73.3 m median spacing.

HOUSEKEEPING: removed toowoomba's two stale pins -- E retired the cache in R23, and a golden for a town
that cannot load can never fire (the vacuous-gate law, one layer down). E's attempt also proved my R23
spacing metric is GAMEABLE: the re-bbox fixed the median (395.7 -> 89.4 m, "passes") while hub density fell
3/12 -> 2/12, the pack's worst. It is necessary, not sufficient -- D's hub test is the complement, and E
used both and refused to game it. Ballarat (255 m) is still flagged and still E's call; I pin what moves.

PINS NOT TAKEN YET, DELIBERATELY: redhill_real is untracked/in-flight (E 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 when E commits
and G's caches are in.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 00:45:45 +10:00
m3ultra
ba901d4a75 Lane C R24: the contract fix — the runtime is the authority (ledger #1, the root cause)
F held the tag because my published atlas contract and my own loader were mutually
incompatible. That was mine. Closed, and published FIRST so G and E re-emit against it.

WHAT WAS WRONG: my R22 §7 invented `stock_shop_<godverseShopId>_index.json`; the loader
(stockpack.js:25) fetches `${base}stock_${type}_index.json`. An index named per the contract
can never be fetched by the code meant to load it. G built runtime-compatible (correct), E
built contract-compatible (also correct — the contract said so), and E's validator globbed
stock_shop_*_index.json -> zero files -> vacuous pass. Nobody was wrong; the contract was.

§7 REWRITTEN AGAINST THE CODE (LANE_C_PUB):
- Authority rule: stockpack.js IS the contract; doc vs loader disagreement = doc bug.
- Layout ratified as G shipped it: assets/stock_godverse/<godverseShopId>/stock_<type>_index.json
  (`type` is load-bearing — SLOT_FOR picks the slot). stock_shop_* RETIRED.
  -> E: the glob becomes stock_godverse/**/stock_*_index.json (the vacuous gate's actual cause).
- Runtime-read vs ignored stated exactly: reads atlases[] + items[{atlas,uv}] (uv top-left, the
  loader flips V); ignores version/atlas_px/cell*/count/kind/tier/shop/crate/provenance.
- Provenance drift dies: runtime reads none of it, so the authority is E's validator + the 23
  shipped town caches -> TOP-LEVEL `license` (US), attribution, generator, snapshot. British
  `licence` retired; G lifts+renames those four, keeps its rich nested provenance{} as colour.
  atlas_px stays REQUIRED (checked against real dims). cell* informational — C reads uv, never
  cell; do NOT shrink the cell for VRAM (the dig renders covers close-up).

CODE (F's filed item): stockpack cache identity is now `${base}|${type}`;
getStockPack(type, base = DEFAULT_STOCK_BASE); new opts.stockBase on buildInterior = the seam F
wires. Omitted => town-wide packs, byte-identical to today. Pre-v5 callers untouched.

VERIFIED FRESH — and I fixed my own metric first: R23's "8/8 atlas-backed" only checked that a
map exists, but a parody canvas has a map too (same species as the vacuous gate). Re-asserted
falsifiably: every sleeve's texture source IS stock_godverse/3962749/stock_record_atlas_00/01.webp,
pack items == index items by id; negative control — the parody room's textures are (canvas, no src)
and touch the atlas zero times, so the metric discriminates. Collision FIXED (shop 120 + town 350
coexist). Fail-soft FIXED with a populated cache (missing base -> null -> parody). 98 draws with
real covers, pathOK, 0 carves, 0 console errors.

SCOPED NOT SLID: the LRU+dispose (§7.6) lands at v5.0-beta — one stocked shop can't accrete, and
safe eviction needs a room-exit hook (a live room holds the pack's shared material) that arrives
with F's per-shop preload. Written into the contract so it can't slide silently.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 00:43:50 +10:00
m3ultra
0740b4697d Fable (integrator): round-24 instructions — the alpha completion (contract fix, identity field, redhill, honest gates) + two new laws
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 00:21:51 +10:00
m3ultra
57973e0540 Lane F R23 (v5.0-alpha): the alpha gate — HELD. The crate is real; it has no street.
v5.0-alpha is NOT tagged. #7a is not a gate that failed, it's a gate that cannot be run:
G's 120 real records are in the tree and byte-identically re-bakeable, and nothing in the
game can reach them. R18's law — release rounds don't ship known breaches.

GREEN (everything except the crate): qa.sh --strict --matrix 7 passed / 0 failed / 0 warn ·
MATRIX GREEN 10 towns x 7 gates · selfcheck 152,015/152,015 (was 161,300, E retired
toowoomba) · flags harness green, so ?classic=1 still holds through the merge · tier 0
?noassets zero fetches + zero console errors (the offline law holds) · tier 1 (v2
town-wide) resolves. Tier 1 (v5, per-shop) is NEVER FETCHED.

FINDING 1 — the atlas contract contradicts itself. LANE_C_PUB:271 publishes
stock_shop_<id>_index.json; C's own loader (stockpack.js:25) fetches
${base}stock_${type}_index.json. Incompatible. G built runtime-compatible (which is why
C's base-override check passed); E built contract-compatible (which is why E's validator
can't see it). Nobody was wrong — the contract was. F recommends amending to G's shape:
it's the only one the runtime can load, and it costs G nothing.

FINDING 2 — the gate built to catch that was blind and green. validate_atlas.py globs a
filename that can't exist at runtime, finds nothing, and returns 0 ("No atlases yet =>
clean pass"), which validate_manifest.py:158 propagates. It conflates "no atlases exist"
with "no atlases matched my glob" — the first is a clean pass, the second is the exact
failure it was written to catch. Pointed at G's index by hand, deeper drift surfaced:
E's line 68 reads provenance top-level while E's own docstring says nested (where G put
it), licence vs license, and a ceiling stale by one round.

FINDING 3 — #7a would have passed vacuously. Walked into The Vintage Record (real shop,
real road) via DBG.enterShop: the crates filled with real covers — from E's 350-item
town-wide v2 pack, not the crate. Zero requests to stock_godverse/, ever. Eyeballed as
written, the gate goes green and proves nothing. Two vacuous gates in one round, same
species: both test the presence of a thing rather than the identity of it.

R24 — three gaps, none F-only: A+G land a godverse_id (verified live: every godverse shop
reports null; the schema has no such field) -> E builds redhill_real (one Overpass fetch,
E's file, outward-facing — not F's to invent) -> C keys the pack cache by type+base
(today a hit returns before base is read, so two stocked shops collide and fail-soft
breaks) -> F passes the per-shop base and re-gates. F deliberately did not build its half:
untestable with no street and no identity field. Filed, not guessed.

Also: A's spacing warn earned on day one — ballarat now warns at 255 m, so the thin tail
has a second member. The README v5 teaser is held with the tag; they're one claim.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 00:05:44 +10:00
m3ultra
43b035aa2e Lane F R23: execute the mid-round merge of G's branch (charter law #5)
The merge amendment says G's branch merges mid-round once Fable-m3 reviews. The review
happened (the shared checkout was parked on lane-G-godverse for it) and C verified the
atlas contract from a temp worktree — deliberately pre-merge — and PASSED. But the merge
itself never executed: main had no web/assets/stock_godverse/** at all, so the round's
headline artifact was absent from the branch F is asked to gate and tag.

F executes it as the integration lane, last in the round with the tree in hand.
Disjointness verified before merging, not assumed:
  G since fork:    G-progress.md, docs/G3_ECONOMY_DESIGN.md, pipeline/_godverse/*,
                   pipeline/godverse_stock.py, web/assets/stock_godverse/3962749/*
  main since fork: C-progress.md, docs/LANES/LANE_C_PUB.md
  overlap:         none. Zero conflicts, as charter law #5 predicted.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 23:55:35 +10:00
m3ultra
d2eb9df03f Lane C R23 (v5.0-alpha): the first real crate verified in the room (ledger #3)
Verified G's stock_godverse/3962749/ (120 real records, Monster Robot Party crate 550,
2 x 2048^2, cell 256) in a fresh context — deliberately BEFORE the merge, so a contract
failure would block it. It didn't.

PASS — the contract holds; G re-emits nothing:
- Resolves through the existing stockAdapter path via a base override (no C code change for
  the single-shop alpha). 120 items / 2 atlases / 8-of-8 stock meshes atlas-backed (real
  covers: peperecordings, DEEPCHILD, "39", mff FREAKS...).
- Ceilings hold: 118 draws with real covers (<=350 law), pathOK, 0 carves.
- Fail-soft holds (clean cache): missing atlas -> null -> parody (20 meshes, 110 draws);
  no adapter (?noassets shape) -> parody, zero fetch. 0 console errors.
- G's index compliant: items[] sorted by id (my determinism ask honoured), provenance, uv in
  [0,1], 2 atlases = exactly the cap, cell_w/h harmless (C reads uv, never cell).

CORRECTED MY OWN CEILING (LANE_C_PUB §7.2): I wrote "<=8 MB decoded" from an assumed ~16-item
"typical shop". A real record shop is 120 records => 2 x 2048^2 ~= 32 MB, 4x my number. The
structural rules held (<=2 atlases; 2048^2 for 60+ items — G landed on the cap); only the MB
figure was wrong, because it described a shop that doesn't exist. New: <=2 atlases @<=2048^2,
LRU = 1 shop's atlas set resident => <=32 MB peak (bounded; vs E's 181 MB unbounded today).
Do NOT shrink the cell below 256 to save VRAM — the dig renders covers close up.

G's ask #1 CONFIRMED and worse than reported (LANE_C_PUB §8 — C's spec, C's fix): stockpack
keys _packs/_resolved by type alone, so base is ignored on a cache hit (measured: the 2nd
preload returned the identical object). (1) two stocked shops collide; (2) it BREAKS FAIL-SOFT
— an unstocked shop of a cached type inherits that crate instead of parody. Seam: key by
type+base, per-shop preload on entry, LRU=1 + dispose; C consumes whatever identity field
lands, C does not invent it.

Two gaps block F's #7a, neither C's: the crate has no street (redhill_real absent; monsterrobot
not in any shops[] — G's ask #2 to E/Fable), and interior_mode.js:192 pins base to
assets/models/ (needs §8 + the identity field).

Committed from a temp git worktree on main — the shared checkout stayed on lane-G-godverse
(Fable's review) and G's branch keeps its disjoint namespace. Docs-only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 23:42:30 +10:00
288 changed files with 281643 additions and 231551 deletions

View File

@ -4,6 +4,162 @@
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 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.091.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 05 / footpath 5.2515.25 · `side`@12 (72% of the
corpus) road 03 / footpath 3.259.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.

View File

@ -3,7 +3,503 @@
*Status: **v1 complete & verified**. Standalone interiors library + test page. Every shop door opens
into a unique, seeded, themed interior, generated on demand in ~4ms, byte-identical every revisit.*
Last updated: 2026-07-16 (round 22) · owner: PROCITY-C · reviewer: Fable
Last updated: 2026-08-03 (round 39) · owner: PROCITY-C · reviewer: Fable
---
## Update 2026-08-03 (round 39, v9-alpha THE CITY IS THE GAME) — §39.3 the op shop can't be dug
Fable's finding, verified before commissioning: `kind:'bin'` — the thing the dig fires on — is minted at
exactly `fittings.js` `recordBin`/`crate` and used by exactly two recipes, `record` and market `stall`.
PROCITY is built on thriftgod's op-shop DNA and **you cannot dig in an op shop.** Brief: measure what a
player can actually do in every shop type, build the op shop a bin, **flag the contents, do not decide
them**, and spend against the GLB-path interior margin.
### 1. THE TABLE (LANE_C_PUB §10) — the deliverable, and it is uncomfortable
Built by reading the recipes and the fitting kinds, then counting interactables in **48 rooms per type**
(6 archetypes x 8 seeds). Full table + the five structural facts in the contract; the short version:
- **`record`** digs (mean **4.6** bins/room, 0/48 empty) and is **the only room in the game where
anything can be sold**. **`stall`** digs — in 27 of 48 rooms; the other **21 (44%) have no crate at
all** (mean 0.92), because its `crate` is `zone:'centre'` and the 36 trestles take the aisle first.
**That is a measured correction to the round's binding fact #5** and it means RED TICKET DAY's roster,
which counts a stall by type, can name a stall you cannot dig in.
- **The other ten types offer nothing but looking.** No bins, and pull-and-buy is dark: `?stock=real` is
opt-in (`index.html:222`), so **on the boot every player gets there is not one `buyMesh` in the game**.
Even armed, `SLOT_FOR` has three entries and the pack is keyed by SHOP TYPE, so only book spines in a
`book` shop and toy boxes in a `toy` shop can ever be bought — the video store's identical spine
shelves cannot, because no `video` pack exists.
- **Four stock kinds can never be bought in any mode**: `garment`, `snack`, `magazine`, `treasure`
`hang()`/`treasures()` never call `buildBuyableShelf`. **The op shop's clothes are unbuyable by
construction.**
- **`interactable: true` is not a verb.** `interior_mode` routes exactly three things (aimed `bin`,
counter, `buyMesh`). `case`, `fridge`, `returns` and the arcade cabinet set the flag and **nothing
reads them** — 8 dead glass cases in a dept-store hall, 4 dead fridges in a milk bar.
- **Sell** is `item.type === shopType`, and only two call sites stamp a type: a dig pull is **always
`'record'` wherever it happens**, a shelf buy is the shop's type. Hence: one sale point, default boot.
### 2. THE MECHANISM — `rummageBin`, shipped, contents gated
A shallow galvanised dump tub (0.95 x 0.70 x 0.28) with flared sides on a castored steel frame, rim at
**0.76 m** — you rummage standing — heaped with stickered mixed goods and a hand-lettered card taped to
the front ("EVERYTHING $2"). Not a record crate recoloured: `crate` is 0.55 x 0.45 and deep because it
is sized to stand LPs in. Shot: [r39_opshop_rummage_bin.png](docs/shots/laneC/r39_opshop_rummage_bin.png).
Three new **opt-in, pure-data** seams (absent ⇒ pre-R39 byte-identical): `recipe.fittings[i].o`,
`buildInterior opts.fittingOpts[kind]`, and `.fallbackZones` / `.anywhere` placement fallbacks. The last
one is not decoration — appended last, the bin's zone is the fullest it will ever be and it landed in
only **11 of 24** rooms without them.
**72 rooms (6 archetypes x 6 seeds x GLB on/off):** placed **72/72** · reachable **72/72** (nearest
spawn-reachable walkable cell **0.771.15 m** from the bin, vs the dig's 2.5/3.2 m reach) · **0 carves** ·
`pathOK` 72/72 · determinism 72/72 · **the rest of the room byte-identical to pre-R39 72/72** (the spec is
appended LAST, so the shared `lay-count`/`lay-place` streams hand every existing fitting the same draws).
End-to-end armed: aim → dig opens → **16 offers** → card → **BUY** debits the wallet → bin depletes to 15.
Negative control (default): tub present, **0 bins**, aim finds nothing, dig never opens. 0 console errors.
Soak 60 rooms: **leak geo 0 / tex 0**, determinism identical, avg 3.4 ms / worst 16.5 ms.
### 3. SPENT AGAINST THE GLB PATH — and the margin was not six
**`C-progress.md:841`'s "GLB-on 344" is R9-era and stale.** Re-measured on today's tree with the lane's
own instrument before touching anything: **GLB-off 156 · GLB-on 319, both at `opshop/hall`.** So the
margin was **31**, not six. After the bin:
| `drawSweep` | pre-R39 | R39 | Δ |
|---|---|---|---|
| GLB-off worst | 156 opshop/hall | **161** opshop/hall | +5 |
| GLB-on worst | 319 opshop/hall | **329** opshop/hall | +10 |
| the other 11 types, both modes | — | — | **0, byte-identical** |
**Margin against ≤350: 21 draws.** Stated at the worst room the bin lands in, not an average.
A free win found while costing it: the scatter fill minted a *tinted* material per item (9 goods = 9
draws in a room with no other scatter). New opt-in `slot.pool` keeps every face on the room's shared
pooled textures at white — the trick the spine/box rows already use — so the pile merges into buckets
the room already has. **12 goods now cost less than 9 did.** Existing scatter callers draw the same
values in the same order; proven by the 11 untouched types' sweep numbers.
### 4. THE BIGGER FINDING — half the interior budget is a pre-pass (→ E/F, NOT shipped)
Lane E's `bookshelf` GLB ships **`transmission: 1`** on `mtl_10218_Bookshelves_v1`. One transmissive
material makes three.js run its transmission pre-pass — a second full render of the opaque scene — so
**every draw in that room is issued and counted twice.** Measured by zeroing it and re-rendering:
`opshop`/hall **191 → 104 (46%)** · `opshop`/wide 142 → 80 · `book`/hall **101 → 51 (50%)** ·
`dept`/hall 116 → 116 (the control, no bookshelf). Those are the two worst GLB-on types in the sweep.
**→ E:** re-emit without `KHR_materials_transmission`. **→ F:** a `transmission = 0` scrub in
`glb.js upgradeFitting` is the cheap fence. **I have not shipped either** — it changes how another
lane's asset renders. Either turns a 21-draw margin into ~100.
### 5. HELD FOR JOHN — the contents (LANE_C_PUB §10.6)
Not "op shops now dig", but "op shops now dig **records**". Across the 23 shipped caches (44 record ·
36 stall · **147 opshop**): crate-shops/town **011 → 225**, towns with none **3 → 0**, diggable bins
**~235 → ~382 (+63%)**, under-band gems/day ~113 → ~183. **A** (parody records) costs zero code and is
what the market stall already does, but an op-shop find is stamped `type:'record'` and bowral/darwin/
daylesford have no record shop — they could dig and have nowhere to sell. **B** (books/toys via the
existing packs) needs an `opshop` pack that cannot exist today *and* a second dig presentation, because
`dig.js` builds a crate of 0.31 m square sleeves. **C** (a bric-a-brac class) costs most and is the only
one that closes the sell hole, since type = type would make op shops buy their own bric-a-brac back.
**Recommendation: play arm A behind the flag for one session, then rule; C is the destination; B is not
worth doing alone.** Also held, same reason: the one-line `fallbackZones` fix that would give market
stalls the crates their recipe already promises.
---
## Update 2026-07-18 (round 30, v7.0-alpha THE SAVE AND THE CRATE) — the sell counter: cash comes back, always less
R30 ledger #2 — contract first (LANE_C_PUB §9, its own commit), implementation second. F's save core
(ledger #1) had **not landed** when this was built: no `save.js`, no `window.PROCITY.game` anywhere in
the tree — so everything below is built to the R30-published shape and verified against a **stub** of
exactly that shape (noted loudly below).
### The contract (§9) — the one number and why
**`sellOffer(p) = min(p 1, max(1, floor(p × 0.5)))`, never < 0.**
- **0.5** is the secondhand-dealer margin — the keeper pays half of what he'll sticker it at.
- **`min(p 1, …)` is the no-pump law made STRUCTURAL:** the offer is strictly below `pricePaid` for
every `p ≥ 1`, so buy-then-immediately-sell loses ≥ $1 per round trip **by construction** — no
balance retune can break F's monotonic-loss gate without violating the contract. At `p = 1` the
offer is $0 and SELL disables (the keeper won't buy what he can't sticker).
- **Alpha basis = `pricePaid`** (every alpha buy is at asking price, so it IS the buy-side price the
player faced; there are no guide bands yet). **Beta swaps the basis to `bandLow`, not the shape.**
- Matching is **type = type, strict, fail-closed**: an untyped item sells NOWHERE (a matcher passing
untyped items would "match" everywhere — vacuous-gate law applied to a matcher).
### What shipped (C's files only)
- **`web/js/interiors/sell.js`** — `createSell()` + pure `sellOffer` / `sellableIn` / `nearCounter`.
DOM-only (zero draws, zero GPU), the dig's manila price-sticker mirrored to the left with a green
offer sticker; one item at a time, ` ` cycling, ✕/Esc; till rings from the counter emitter (R28
one-shot pattern). Same consumer pattern as `dig.js`: the single side-effect is `onSell(item,
offer)` — **C never mutates `game.collection`** (read-only law, §9.3).
- **`web/js/interiors/wallet.js`** — `wallet.sell(item, offer)`: the credit mirror of `buy()`.
This file never prices; the offer comes in from `sellOffer`.
- **`web/interior_test.html`** — E-at-counter routing (bin → dig · counter → sell · shelf → buy) gated
on `window.PROCITY.game` presence (the in-game classic-pure law, exactly); `?sellstub=1` stubs the
published game shape for verification until F's core lands.
### Verified (fresh context, no-store server on :8144 — NOT :8130, zero console errors throughout)
- **The sale:** record shop seed 1990, at the bench (`nearCounter` true at 0.9 m, false across the
room) → card `THE FIBROS — "Servo at Midnight" · you paid $24 · offer $12` → SELL → **cash 163 →
175 (+$12 < $24 paid)**, collection 4 → 3, sold item gone, toast + till fired.
- **Type fence:** stub carried 2 records + 1 book + 1 untyped item. Record shop cards **exactly the 2
records**; book shop cards **exactly the book** ($7 on $15); the untyped item cards **nowhere**.
- **The disabled edge:** the $1 record offers $0 → button reads NOT WORTH BUYING, disabled.
- **No-pump round-trip (C's mini-gate, precursor to F's #5):** buy-then-sell through the real wallet +
card path, prices {2,3,5,9,24,60,61,137} → **8/8 strictly negative** (1,2,3,5,12,30,31,69).
- **The gate discriminated first** (vacuous-gate law, proven live): my first loop pushed a 999-priced
item into the collection **without checking `wallet.buy` succeeded** — broke wallet, no debit, then
a +$499 "sale". A pump — from an item never paid for. **Finding for F below.**
- **Fail-soft:** boot with no stub (= no game layer) → E at the counter does nothing, `open()` false,
zero DOM, zero errors. `dispose()` leaves 0 panels/0 toasts in the DOM.
- **Budgets:** draws with the card open ≡ closed (16 ≡ 16 — DOM is zero-draw); `buildInterior` and all
room code untouched this round; 50-room soak re-run green in the same session: **avg 3.4 ms · worst
8.2 ms · leak geo 0 / tex 0 · determinism identical · worst draws 73 ≤ 350**.
### Findings / asks (→ F, for the save core + gates)
1. **The collection item needs `type`** (registry stock type, stamped at buy time). The R30 shape
(`{townKey, godverseShopId|shopId, sku|slotId, pricePaid, dayFound}`) doesn't carry it, and without
it **nothing is sellable anywhere** (my matcher is fail-closed on purpose). `title`/`artist` are
strongly asked too (B's collection UI needs them; my card falls back to the sku label — honest, ugly).
2. **`pricePaid` must only ever be written by a SUCCESSFUL debit.** The no-pump law's structure
assumes pricePaid was actually paid — my broken first loop showed a collection entry that skipped
the wallet debit converts straight into a money pump. F's no-pump gate should include this negative
control: a collection entry minted without a debit must be impossible (or the gate must fail).
3. **The removal handoff:** in-game, the sold item must leave the collection **via the game API**
`onSell` is where F calls it. My harness splices its own stub (its array, its right); C's modules
never touch game state. When F publishes the API name, §9.3 references it verbatim.
R28 §Lane C (ledger #3) — my own idea, parked since R7, brief states acceptance only. Wave 1, alongside E+B.
### The trap my own parked note set
v3.0's deferred note said: *"`userData.audioEmitter = musicKey` so Lane B can position/spatialise the
bed"*. **Both halves were wrong**, and the second one is dangerous:
1. **The shape.** The engine holds no reference to the room group — it would have to traverse a scene it
cannot reach. Emitters belong on `room.audio` as data, where they ride the **existing**
`playInterior(room.audio)` call: no new lifecycle, no traverse, no dispose hook, no F plumbing.
2. **The method.** "Spatialise" invites a `PannerNode`. **Nothing in this tree sets `ctx.listener`** (zero
hits, whole tree), so a panner resolves against a listener frozen at (0,0,0) — and since rooms are built
in room-local coords at the origin, that is *the room's own centre, forever*. Measured with
`OfflineAudioContext` on a real pub stage bed:
| wiring | RMS at door | RMS at stage | ratio |
|---|---|---|---|
| panner, listener never set (**the engine as it ships**) | 0.106905 | 0.106905 | **1.0000** |
| panner + listener tracking the player | 0.055665 | 0.500000 | 8.98 |
It **plays, throws nothing, warns nothing, and never changes by one sample as you cross the room**
while failing at the only thing it exists to do. *"Routed through a panner?"* yes. *"Audible in a
venue?"* yes. *"In a shop?"* yes. All green, all worthless. **The only assertion that can see this is a
DELTA: gain at the door ≠ gain at the stage.** Vacuous-gate law, 4th application — and this one had no
author to blame but me. There is no listener code anywhere to teach a lane otherwise.
**The house idiom was already right**: tram rumble (R=55), door spill (R=9), gig spill (R=26) are all
hand-rolled distance-gains against `PROCITY.camera.position`. No listener, no panner. Emitters match it.
### What shipped (C's files only)
- **`room.audio.emitters`** = `{ stage?, counter? }`, room-local, 3-dp, **18/18 same-seed deep-equal**.
`r` (reach) is the **room diagonal, not a constant** — measured diagonals 9.8115.63 m, and scaling to
the room keeps door→stage proximity in a tight **0.420.59** band from a pokey band_room to a hall pub.
`floor` is the room-filling residue, **never 0** — a PA fills a pub; what changes as you walk in is the
*balance*, not the presence. Swing **2.654.52 dB** (John R20: *subtle changes are great*).
- **The till rings from the counter***audible positioning in a normal shop with **no engine change***,
via B's already-public `playSfx(key,{gain})`: C does its own distance math, hands B a scalar. Measured
through the real BUY button in a real record shop: **0.9489 @ 0.60 m vs 0.6436 @ 4.19 m = 3.37 dB**;
negative control (no emitters) fires at exactly **1.0**. `sfx-till.ogg` had been on disk since 15 Jul and
had **never once played**. Routing, not content — exactly as the brief said.
- **`?mute=1` now silences the crate riffle.** See below.
**Why the venue half is an ask, not a ship:** a **one-shot** resolves its gain once, at fire time, from a
position C already knows. A **bed** must be re-gained every frame inside the engine's `update()` loop —
B's file. The split is structural, not political. B got a ~12-line paste-ready diff in the house idiom; C
did not touch audio.js. The letter of "consulted not modified" could have been satisfied by reaching
through B's exposed `state.layers` diag to rewire its graph from outside — **that backdoor is strictly
worse than the edit it avoids** (invisible to B, breaks on any change of B's). Refused.
### Two bugs found on the way
- **F drops emitters on the one night they matter.** `interior_mode.js:289` rebuilds the spec on a gig
night: `{ musicKey: ra.gigKey, toneKey: WALLA_KEY }` — every unnamed field dies. Measured on a real
gig-night pub: `quietNight_emittersSurvive: true`, **`gigNight_emittersSurvive: false`**. Silent, fail-
soft, no error. -> F: don't just spread `ra` — pass emitters **deliberately, gig-night only**, because on
a quiet night the radio and room-tone genuinely *are* room-filling.
- **`?mute=1` never silenced the riffle (R5 → R28).** `dig.js` opened a **second, private AudioContext**
straight to `destination`, breaking both of audio.js's stated laws (*ONE AudioContext*; *mute forces
silence*). It survived three epochs because **no gate asserts mute ⇒ silence**: the engine returns its
silent surface and looks honest *from its own side*, while the sound came from somewhere it never knew
existed. *A gate that asks the engine whether it is muted can only ever answer yes.* Fixed by reading
B's **public** `.muted` getter (consulted, not modified), URL fallback when standalone. Measured with a
positive control so the probe is provably able to see sound: live 1 ctx / 1 source; **muted 0 / 0**.
Blips stay procedural on purpose — zero-fetch, deterministic, and they still work under `?noassets=1`
where a sampled riffle cannot. Only mute was broken; only mute was fixed.
### Harness honesty (again)
First till rig reported a clean 3.37 dB — from a **contaminated harness**. `createDig` appends a panel per
call and `close()` doesn't remove it, so `querySelector` was clicking **trial 1's stale button** every
time. The tell was `panelUp: false` on all three trials, and the **negative control** returning 0.6436
where it must return 1.0. The gains happened to match the formula exactly — I still refused them and
re-ran isolated (`panelCount: 1`, `panelUp: true`, control **1.0** ✓). R27's lesson holds: *never report a
number from a rig you have watched lie, even when you like the number.* The control is what caught it.
QA GREEN (6/0/0/0). Docs: **LANE_C_AUDIO.md → v3.1** (contract + both asks + every measurement above).
---
## Update 2026-07-17 (round 27, v5.0 THE LIVE CRATE) — the tier-2 seam + LANE_C_PUB v5-FROZEN
R27 §Lane C (ledgers #2 + #6). Wave 1 — ran after F tagged `v5.0-beta` (`78848ed`, 7/0/0).
### The seam ruling (§8): bless the direct wire — but the *form* was the real question
Fable asked: publish a post-build hook, or bless the direct wire? **Answer: bless it — no lifecycle hook.**
F already holds `currentAdapter` (`interior_mode.js:67`) and passes it to `dig.open()` **per dig** (`:111`),
so the live read lands, F updates the pack, and the next riffle picks it up. Read not landed / slow / dead ⇒
the adapter is simply the tier-1 pack ⇒ the dig never blocks. No C lifecycle change.
**But the question underneath it — *how* `gone` applies — is C's, and the obvious wiring is wrong.**
`pickRealOffers` indexes `pack.items[(rr()*len)|0]`, so **filtering the array reshuffles the crate**.
Measured across 5 crates: **one record selling changed 21 OTHER records** (6/2/2/6/5 collateral). Pick-then-
omit — draw from the stable full list, drop gone *after* — has **0 collateral**: a crate with 3 sold shows
13, not a different 16. Selling one record must remove one record, not re-stock the shop.
**And the trap: both forms hide the sold record.** So F's planned sandbox assertion — "a real `gone` item
never renders" — **goes green on the wrong wiring**. It cannot see the 21. → **F: assert on collateral (the
survivors are unchanged across a `gone` delta), not absence.** That's the vacuous-gate law's third
application, and the cheapest to miss, because it passes.
**Landed** (`dig.js`): `pack.gone` (Set|array) omitted at pick time; **F's invariant published** — update
item *fields* in place (live price) freely, **never add/remove/reorder `items[]`**; that identity+order IS
the seeded pick's stability (the same lesson as R25's positional ids, one layer up).
**Determinism boundary sits exactly here:** server down ⇒ byte-identical tier 1 (gate-able); server up ⇒ the
crate legitimately changes — tier 2 is non-deterministic *by design*, so **no gate may assert dig determinism
with the server up**, and `pack.gone` never enters a seeded stream or a golden.
**Scoped honestly:** the bin fan is decorative (a different seeded pick from the dig's offers), so a sold
cover may linger there until rebuild — that's when I'd publish a post-build hook, and the epoch's claim
doesn't need it, because *the crate you riffle is true*.
**Verified** — pure-function measurement for the collateral claim, then end-to-end through the real dig's
public surface (pull → Esc → scroll → pull): sold record gone · **survivors 4/4 · collateral 0** · `items[]`
untouched at 120 · live in-place price shows on the next pull. *My first dig harness returned the same title
4× (I never un-pulled), so its "survivors" number was a harness artifact — I re-ran it properly rather than
report a metric I'd just watched lie.*
### Docs freeze (§6): LANE_C_PUB is **v5.0-FROZEN**, whole document
Marked, with the case law kept at the top rather than buried: three of this doc's amendments exist because a
rule of mine met a case written *after* it (§7's naming vs its own loader; §7.2a's `sku_` before mint) — and
both times the lane building to my rule took the red. The header now says it plainly: **when a line here
meets a case it didn't foresee, the line is the stale thing — amend it, don't bend the case.**
---
## Update 2026-07-17 (round 26 wave 4) — the mint-id fence: my rule was the stale thing
F's fourth hold traced to **my** §7.2a. I wrote "`sku_`-prefixed for real-stock packs" in R25 — **before mint
existed** — so it names one space where there are two. E implemented it faithfully and its validator did
exactly what I told it to: demand `sku_` on 14 mint crates. **The check wasn't wrong; my rule was.** Same
shape as R23, and I'll say it plainly: this is twice now that a contract line of mine met a case written
after it and the lane building to it took the red.
**Amended (§7.2a): the id form is sourcing-scoped, and the prefix IS the fence.**
| `sourcing` | id form | the space it names |
|---|---|---|
| `real` | `sku_<POS id>` | the shop's POS inventory — a physical copy on a shelf |
| `mint` | `mint_<dealgod listing id>` | a dealgod listing — a plausible stand-in, not that shop's stock |
**Why G was right to refuse the easy green.** Filing dealgod listing ids under `sku_` would have gone green
in one line and recreated the R24 collision **inside the economy layer** — where **tier-2's sold-means-gone
looks up POS skus**. R24's collision entered the *wrong shop*; the same collision in the money layer **sells
the wrong record** — a real copy, out of a real shop, to satisfy a minted stand-in. A prefix that lies costs
money, not a gate. Blessed as design: disjoint namespaces, and no gate, lookup or economy path may treat
them as one (same standing note as plan-ids vs godverse-ids).
**Verified against every crate in the tree before handing E the line:** 15 crates (1 real + 14 mint) —
**0 fails** under the amended rule (prefix matches `sourcing`, ids unique within pack), and **0 ids appear in
both spaces** across 344 distinct ids. The fence already holds in the data; only my wording didn't.
**→ E:** assert the form against `sourcing` (you already read it at line 178) — `sku_` for real, `mint_` for
mint; a crate wearing the other's prefix FAILS. That's **stronger** than the check it replaces, not looser.
**→ F:** 6/1/0 should flip to 7/0/0. Doc-only from C; no code, no atlas bytes, no goldens.
---
## Update 2026-07-17 (round 26, v5.0-beta EVERY CRATE DIFFERENT) — three contract lines + the price sticker
R26 §Lane C, wave 1 (ledgers #1, #4, #6). Contract lines published FIRST — G and E build to them.
### The brief said `tier`. It can't be `tier` — that name is taken twice (§7.2b)
G's atlases already emit **`tier: 1`** = the **charter's ladder rung** (0 parody · 1 static-real · 2 live),
and `kind` is already the stock type. **A mint atlas is also rung 1** — same mechanism (a statically baked
per-shop atlas); only the data's *origin* differs. Real-vs-mint is an **orthogonal axis, not a rung**.
Overloading `tier` would put two meanings in one name — the same species as the plan-id/godverse-id
collision that just became a standing note, and as R25's positional ids. **Ruling: top-level
`sourcing: "real" | "mint"`, REQUIRED; `tier` stays the numeric rung.** A mint index MUST NOT carry
POS-snapshot fields (the separation IS the honesty); gates read `sourcing`, and an index without it FAILS.
### `condition` isn't in the index — the ledger assumed it was (§7.2c)
Ledger #4 said the card gets "the fields the atlas index already carries (artist/title/price/**condition**)".
It carries artist/title/price/price_band — **not condition**. G's *snapshot* has `condition` + `sleeve_cond`
and drops them at emit, so `pickRealOffers` mapped `cond: ''` and the card rendered dangling separators
around a hole. That *was* the "database row". Ruled: `condition`/`sleeve_cond` are **optional** index fields,
emitted when the source has them; **no invented grades** on mint items — a plausible-but-fictional grade is
exactly what `sourcing` exists to prevent.
### The atlas manifest (§7.2d) — existence declared, never probed
`web/assets/stock_godverse/index.json` is the authority for which shops carry atlases; **no entry ⇒ no
fetch ⇒ zero 404s**, so the zero-console-errors law holds with no attribution exception. Shape + both-ways
consistency (atlas without manifest entry FAILS, and vice versa) in §7.2d. A tree with no godverse atlases
and no manifest is a legitimate silent zero-shop state, not an error.
### The price sticker (ledger #4, code)
The pull panel is now the shop's **price sticker**, not a data readout: manila card, slight rotation +
shadow, typewriter caps, italic title, a rubber-stamped condition grade, and **the round yellow $ sticker
the sleeve art already uses** (dig.js's own canvas language — the panel now echoes it instead of fighting
it). Fields compose only when present: **a missing field never leaves a stray separator.**
**Verified — five card shapes, both star branches, fresh context:**
| case | card |
|---|---|
| real grail | `$28` · Deep Dish · *Penetrate Deeper* · `VG+/VG` · **★ GRAIL** |
| real standard, graded | `$15` · `VG+` · `STANDARD` |
| real ungraded (today's index / mint) | `$15` · `COLLECTOR` — grade omitted cleanly |
| parody | `$15` · THE VERANDAHS · *Tinny* · `NM` · `1973 PUNK` |
| parody rare (seed 4) | `$16` · HILLS HOIST · *Servo at Midnight* · `VG` · `1973 POP` · **★ RARE** |
**The verification caught a defect in my own card:** a real *grail* rendered **"GRAIL ★ GRAIL"** — `s` carried
the price_band *and* `rare` derived from that same band. (Parody is immune: its `s` is a genre.) Fixed at the
mapping — grail becomes the ★ stamp and leaves `s` empty. And I did not leave the parody-rare branch on
assumption: scanned seeds until a rare pull appeared (seed 4) and asserted it says RARE, not GRAIL. An
unexercised branch is an unproven one.
**Budgets:** the card is DOM — zero draws, zero tris, no 3D change. Ceilings untouched.
**→ G:** `sourcing` + `condition`/`sleeve_cond` + the manifest. **→ E:** assert `sourcing` (absent ⇒ FAIL),
the mint/POS-field separation both ways, and manifest↔files both ways. **→ F:** consult the manifest before
fetching — that's what retires the 404 attribution exception.
---
## Update 2026-07-17 (round 25, the tag earned) — sku-derived slot ids (ledger #3, C's one line)
R25 §Lane C: one contract line, published first so G re-emits against it. **§7.2a added** — F's id-collision
finding made structural, while there are two atlases to migrate instead of fifty.
**The collision, measured:** both packs number items positionally from zero, so **all 120 godverse ids are
also generic-pack ids — 120/120 overlap.** An id-equality gate cannot discriminate which pack a rendered
item came from; that's exactly why the brief's own #7a spec was vacuous. (The vacuous-gate law's first
application broke the law — and my own R23 metric had the same disease. It's a persistent species.)
**The rule (§7.2a):** `items[].id` MUST derive from the source's stable key, never from position, and MUST
be unique across ALL packs. Real-stock packs: **`id = "sku_" + <POS sku>`** — G's snapshot already carries
`sku` per item (e.g. `20260314070442`), it's just dropped at emit, so this is index-only. Generic packs keep
`rec_NNNN` as their namespace; real-stock must not emit into it.
**I gave it two reasons, and the second is the one that outlives the gate.** The collision is the presenting
symptom. The deeper defect is that **position is not identity**: a positional id renumbers on every re-bake,
so selling one record out of crate 550 silently makes `rec_0050` a *different* record. Anything holding an
id across snapshots — a wallet purchase, a saved dig, a gate assertion, and above all **tier-2's
sold-means-gone** — would point at the wrong item. The POS sku is stable because it's the shop's own
identity for that copy (`release_id` identifies the pressing, not the copy). So this line isn't only a gate
fix; it's the identity tier-2 will need, bought now for free.
**Scope kept honest:** index-only, no atlas bytes, no town cache, no goldens. `id` isn't read by the loader
(§7.3) — the dig/buy consumers and the gates read it, so no C code changes. **→ G:** re-emit with `sku_`
ids. **→ E:** assert the form (present, unique within the pack, `sku_`-prefixed for real-stock).
*Process note:* F's six files were staged in the shared tree mid-flight; committed by explicit pathspec, so
F's session is untouched.
---
## Update 2026-07-17 (round 24, THE ALPHA COMPLETION) — the contract fix (ledger #1, the root cause)
R24 §Lane C. **The root cause was mine and it's closed.** F held the tag because my published atlas
contract and my own loader were mutually incompatible; the ruling — **the runtime is the authority** — is
now the section's first line. Published FIRST (half-day rule; G and E re-emit against it).
**What was actually wrong.** My R22 §7 invented `stock_shop_<godverseShopId>_index.json`. The loader
(`stockpack.js:25`) fetches `` `${base}stock_${type}_index.json` ``. **An index named per my contract can
never be fetched by my own code.** So G built runtime-compatible (correct), E built contract-compatible
(also correct — the contract said so), and E's validator globbed `stock_shop_*_index.json` → **zero files
→ vacuous pass**. Nobody was wrong; the contract was. I wrote it, so I own it.
**§7 rewritten against the code (LANE_C_PUB):**
- **Authority rule:** `stockpack.js` IS the contract; if the doc and the loader disagree, the doc is a bug.
- **Layout ratified as G shipped it:** `assets/stock_godverse/<godverseShopId>/stock_<type>_index.json`.
`type` is load-bearing (it picks the slot via `SLOT_FOR`: record→sleeve). `stock_shop_*` is **retired**
**E: the validator glob becomes `stock_godverse/**/stock_*_index.json`** (that's the vacuous gate's
actual cause).
- **Runtime-read vs runtime-ignored, stated exactly.** Reads `atlases[]` + `items[{atlas, uv}]` (uv origin
**top-left**; the loader flips V — don't pre-flip). Ignores `version/atlas_px/cell*/count/kind/tier/shop/
crate/provenance` entirely.
- **The provenance drift dies (§7.3):** since the runtime reads none of it, the authority is E's validator
+ the 23 shipped town caches — both use **top-level `license` (US), `attribution`, `generator`,
`snapshot`**. British `licence` retired; **G lifts+renames those four**, keeps its rich nested
`provenance{}` (source/sha256/determinism) as colour. `atlas_px` stays REQUIRED (E checks it against real
dims — a genuine falsifiable check). `cell*` informational: **C reads `uv`, never `cell`** — and don't
shrink the cell for VRAM, the dig renders covers close-up.
**Code landed (the `type+base` keying, F's filed item):**
- `stockpack.js`: cache identity is now `` `${base}|${type}` ``; `getStockPack(type, base = DEFAULT_STOCK_BASE)`.
- `interiors.js`: new **`opts.stockBase`** — the seam F wires (`buildInterior(shop, THREE, { stock:'real',
stockBase: 'assets/stock_godverse/<id>/' })`). Omitted ⇒ town-wide packs, byte-identical to today.
- Pre-v5 callers untouched (`getStockPack('record')` still means the town pack).
**Verified fresh — and I had to fix my own metric first.** R23 I claimed "8/8 atlas-backed"; that checked
`material.map` exists — **but a parody canvas has a map too**. That's the same species as the vacuous gate
I'd just been handed. Re-asserted falsifiably:
- **Every sleeve samples G's atlas FILE** — texture sources are exactly `stock_godverse/3962749/
stock_record_atlas_00.webp` / `_01.webp`; pack items ≡ index items **by id**. Titles are real
(*Yoshitoshi Artists In House We Trust / Deep Dish Penetrate Deeper*).
- **Negative control (what makes it a gate):** the parody room's stock textures are `(canvas, no src)` and
touch the godverse atlas **zero** times — the metric discriminates.
- **Collision FIXED:** shop pack (120 items) + town pack (350) coexist as distinct objects.
- **Fail-soft FIXED with a populated cache:** missing base ⇒ `null` ⇒ parody (the R23 correctness bug —
an unstocked shop inheriting a cached crate — is gone).
- Ceilings: **98 draws** with real covers via `opts.stockBase`; `pathOK`; 0 carves; **0 console errors**.
**Scoped, not slid:** the **LRU + dispose** (§7.6) lands at **v5.0-beta**, not here — with one stocked shop
there's no accretion, and safe eviction needs a room-exit hook (a live room holds the pack's shared
material), which arrives with F's per-shop preload. Written into the contract so it can't slide silently.
**→ F:** the seam is `opts.stockBase`; §7.5 has the call. **→ G:** layout ratified, re-emit provenance field
names only (§7.3). **→ E:** the glob (§7.1) is the vacuous gate's cause.
---
## Update 2026-07-16 (round 23, v5.0-alpha THE FIRST REAL CRATE) — the crate in the room (ledger #3)
R23 §Lane C. **Verdict: the contract holds — G's atlas needs no re-emit.** Verified against G's
`stock_godverse/3962749/` (120 real records from Monster Robot Party's DEEP HOUSE crate 550, 2 × 2048²,
cell 256²) in a fresh context. Ran **before** Fable's merge deliberately — if the contract failed, that
blocks the merge. It didn't.
**PASS — the first real crate renders in a PROCITY room:**
- **Resolves through the existing `stockAdapter` path** via a base override — no C code change needed for
the single-shop alpha. 120 items · 2 atlases · **8/8 stock meshes atlas-backed** (real covers). The
sleeves are legible on screen: *peperecordings · DEEPCHILD · "39" · mff FREAKS · The Angels Instinct feat
Charlie Ashcroft* — a real deep-house crate.
- **Ceilings hold:** **118 draws** with real covers (≤350 law), `pathOK`, 0 carves.
- **Fail-soft holds** (clean cache): missing atlas → `null` → parody (20 parody meshes, 110 draws); no
adapter (the `?noassets` shape) → parody, zero fetch. **0 console errors.**
- **G's index is compliant:** `items[]` sorted by id (my R22 determinism ask honoured), provenance present,
`uv ⊂ [0,1]`, 2 atlases = exactly my cap, `cell_w/cell_h` harmless (C reads `uv`, never `cell`).
**I got a number wrong, and the first real data caught it (LANE_C_PUB §7.2 CORRECTED).** My R22 ceiling
said "≤8 MB decoded" — derived from assuming a "typical shop" is ~16 items @1024². **A real record shop is
120 records.** At the contract's own 256² cell that's 2 × 2048² ≈ **32 MB**, 4× my figure. The *structural*
rules held (≤2 atlases; 2048² for 60+ items — G landed exactly on the cap); only the MB number was wrong,
because it described a shop that doesn't exist. **Corrected:** ≤2 atlases/shop @≤2048², **LRU = 1 shop's
atlas set resident ⇒ ≤32 MB peak** (still bounded; still far better than the 181 MB unbounded accretion E
measured). And explicitly: **don't shrink the cell below 256² to save VRAM** — the dig shows a cover
close-up; resolution is the one thing the player actually sees.
**G's ask #1 CONFIRMED — and worse than reported (LANE_C_PUB §8: C's spec, C's fix).** `stockpack.js` keys
`_packs`/`_resolved` by **`type` alone**, so `base` is **ignored on a cache hit**. Measured:
`preloadStockPack('record', {base:'…/3962749/'})` then `preloadStockPack('record', {base:'assets/models/'})`
**returned the identical object**. Consequences: (1) two stocked shops collide — the 2nd serves the 1st's
crate (blocks v5.0-beta, as G said); (2) **it breaks the fail-soft law** — an *unstocked* shop of a cached
type inherits that crate instead of parody. The second is a correctness bug, not a mix-up. Seam spec
published (cache key = `type + base`; per-shop preload on entry; LRU=1 + dispose; **C consumes whatever
identity field lands — C does not invent it**). **C owns and lands it** — the §7 promise coming due.
**Two gaps block F's gate #7a (neither is C's; both flagged):** (1) **the crate has no street**
`redhill_real` doesn't exist and monsterrobot isn't in any town's `shops[]` (G's ask #2 → E/Fable);
(2) the shell's `interior_mode.js:192` calls `getStockPack(shop.type)` with `base` fixed to
`assets/models/`, so it can't reach a per-shop pack until §8's seam + the identity field land (C+F).
*Process note:* the shared tree was checked out on `lane-G-godverse` for Fable's review, so I committed
these C docs from a temporary `git worktree` on `main` — the shared checkout was never touched, and G's
branch keeps the disjoint namespace the two-studio merge law depends on.
---

View File

@ -2,6 +2,13 @@
*Owner: PROCITY-E. Scope: `pipeline/*`, `web/assets/*`. Nothing in other lanes' files was touched.*
> ⚠ **THIS FILE STOPS AT ROUND 12 AND HAS SINCE R13. IT IS NOT THE LIVE RECORD.** Everything from
> Round 13 on lives in **[`docs/LANES/LANE_E_NOTES.md`](docs/LANES/LANE_E_NOTES.md)** (what other
> lanes need from this one, newest first) and **[`pipeline/AUDIT.md`](pipeline/AUDIT.md)**
> (provenance, licence zone, measurements and method, per round). Recorded in R39 rather than
> quietly resurrected: a progress file whose last entry is 27 rounds old is a trap for the next
> reader, and the honest fix is a signpost, not a backfill.
## Round 12 (v3.0-alpha gig layer, `?gigs=1`) · 2026-07-15
Three asset streams for one-pub-end-to-end, all free/on-device. C works the pub interior in parallel

View File

@ -4,6 +4,485 @@
---
## Round 39 (v9 WAVE 1 — THE CITY IS THE GAME) — five gates, five controls, and the interior margin is 162
*Full detail: [LANE_F_NOTES §39](docs/LANES/LANE_F_NOTES.md). (R38's close lives in
[LANE_F_NOTES §38](docs/LANES/LANE_F_NOTES.md) — it never got a row here.)*
**`tools/qa.sh --strict --matrix` GREEN — 10 passed · 0 failed · 0 warn · 0 skipped**, on the true
final tree, with three new gates wired in as Gate 8. Lane A's selfcheck reproduces exactly:
**157,647/157,647 ALL GREEN, fingerprint `0x5f76e76` — zero goldens moved**, in a round that added an
address layer, a fog ledger, a spawn move and a new interior fitting.
**THE ROUND'S BIGGEST NUMBER IS NOT THE ONE THE LEDGER ASKED FOR. The interior margin is 162 draws,
not 21 and never 6.** Lane E's transmission fix takes the worst room from **329 → 188** draws.
Measured with `tools/qa/r39_transmission.py`: two port-isolated no-store roots, identical inode for
inode except one file — the shipped `bookshelf` GLB vs a copy with `transmissionFactor: 1`
re-inserted (BIN sha1 identical, only the JSON chunk moved) — both driven through
`interior_test.html`'s own `drawSweep`.
| `drawSweep` worst | glass bookshelf (= the pre-E tree) | E's fix (shipped) | Δ |
|---|---|---|---|
| GLB-on | **329** `opshop/hall` | **188** `dept/auto` | **141** |
| GLB-off | 162 `opshop/hall` | 162 `opshop/hall` | 0 — the control's own control |
| margin vs the ≤350 law | **21** | **162** | **+141** |
`opshop` 329→174 (47%) · `book` 247→125 (49%) · **every other type 0**, which is the shape Lane C
predicted from an isolated room, reproduced at sweep scale by a harness C did not write. **The worst
interior room in the game is now `dept/auto` — a room with no bookshelf in it.** The default boot
fetches these GLBs from the public depot, so I fetched all four R39 GLBs off `digalot.fyi` and
scanned them: **sha1-identical to the local tree, 0 transmissive materials, 4/4.**
**The five gates, each with its control demonstrated in the same run:**
| gate | verdict | the arm that could have failed |
|---|---|---|
| **39.1 address** (`r39_address.mjs`) | GREEN | **1,192/1,219 shops (97.8%)**, 23/23 `ways` supplier, 857/857 membership agree / 0 wrong. **RED ARM: a 3 m shift error raises adelaide 89 → 98 resolved shops** (Lane A's number to the digit) while inventing 9 names — `stats().shiftCheck` reads DISAGREE on all 72 perturbations, and it is the ONLY alarm that catches the 3 m case |
| **39.2 fog** (`r39_runtime.py`) | GREEN | **HIDES** 4/72 at spawn, 0 of 61 distant shops known · **REVEALS** 2,125 m → 58 shops · **FRONT TEST** 0 of 59 shopfronts learned when probed from 5 m behind · **+0 draws AND +0 tris**, four fresh browsers, both orders, drained, 3 reads each · 8/8 corrupt saves rejected · null store on classic/`game=0`/`fog=0` |
| **39.3 THE SIGN** (`r39_runtime.py`) | GREEN | shops in front of the player at spawn **4/4 · 4/4 · 4/4 · 1/1**; with the pre-R39 sign re-derived on the same tree, **0 on all four** |
| **39.4 rummage bin** (`r39_runtime.py`) | GREEN | 120 rooms: tub 60/60 both arms, `kind:'bin'` **0/60 default vs 60/60 armed**, 0 carves, 60/60 deterministic, reachable 0.801.03 m. **THE CONTENTS DID NOT SHIP** — on the default street boot the tub is `kind:'prop'`, 0 dig targets, E opens nothing |
| **39.5 transmission** (`r39_transmission.py --validator`) | GREEN | the validator **HARD FAILS rc 1** on a fabricated glass asset and **PASSES on the shipped tree** — both halves, or "it failed" only proves the gate is always red |
**Re-baselined, without falsifying a release record** (`tools/qa/r39_shots.py` →
`docs/shots/laneF_r39/`): the R32 cluster-pose spawn frame and the R35 first-five splash were both
shot at the old pose. `v7_tour/02_the_strip.png` and `v7_beta/first_five_splash.png` are **kept as
history**; the re-baseline ships its own BEFORE, camera parked at the old `-sin` pose on the same
tree. Katoomba's BEFORE is a paddock and a blank back wall; the AFTER is the awnings and the
shopfront run. The R35 *gate* needed no re-baselining — `smoke_first_five` is pose-independent and
was green either side.
**Three corrections, all mine to make and all measured:** LANE_A_NOTES §39's "a 30 m shift returns 0
street names" — it returns 40/12/47 wrong ones, because on a dense grid a big shift lands you on the
next street · LANE_B_NOTES §39's "a fresh export has no `known` key" — true of `?fog=0`, no longer of
a default boot, because B's own `+sin` fix means you spawn in front of four shopfronts · and my own
harness bug, recorded: I first pointed the transmission control at `web/assets/models/` when
`validate_manifest.py` reads `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.**
**Held → Lane E:** `check_transmission()` skips any GLB with no local `_normalized` copy and never
scans `web/assets/models/`. Demonstrated with a live control: remove the local copy and the same
glass asset passes rc 0. A depot-only transmissive GLB — the exact shape of `longbench` and
`streetlight` — would ship unseen.
**No tag.**
---
## Round 37 (v8 WAVE 0 — THE FLOOR) — the budget of record settled · two vacuous gates repaired
**0.3 — THE BUDGET OF RECORD: 292 draws / 124,793 tris. The audit was right and the spike was
right; they were measuring different BEARINGS.** Two sessions disagreed by 90 draws (280 walked vs
191 teleport-walk). Measured on one tree, one box: worst draw **per bearing**, 12 bearings × 102
stations down the synthetic spine — **0° 284 · 30° 248 · 60° 208 · 90° 177 · 120° 196 · 150° 214 ·
180° 211 · 210° 207 · 240° 178 · 270° 182 · 300° 223 · 330° 267.** **110 draws of spread from the
camera direction alone**, and the peak sits at **yaw 0 — the default argument of
`DBG.teleport(x, z, ry = 0)`.** The audit took the default and landed on the worst bearing; the
spike supplied a yaw and landed mid-range. Both numbers were correct; they were not the same
quantity. Lane B's independent R37 walk (276 draws / **99,880** tris) and mine (282 / **99,878**)
agree on triangles **to 2** — same frame, two harnesses. **Viewport is irrelevant** (1280×720 and
2560×1440 identical to the byte). The R+1 dispose window is worth **+19 draws** (31 vs 27 live
chunks), not the 6080 folklore claimed — the other ~100 draws between bookmark and walked-worst are
*place* and *bearing*. **THE PIN** (default boot, `7bd96e5`): synthetic **292 draws** (night, 31 live
chunks) / 120,145 tris; `?classic=1` 269 / 90,580; corpus tri worst **124,793 on fitzroy_real**
the audit's "99124k" range confirmed at its top. **Margin: 8 draws, ~75k tris.** Only 8 frames of
810 exceed 280; classic never does. Also found: **`?r=3` measures 307 draws and breaches the ≤300
law** (ungated override, filed). Independently confirmed B's kerb change at **+0 draws / 8 tris**.
Headless SwiftShader — but draw/tri counts are counted in JS, not by the GPU, and the viewport
control proves it; **frame TIME is still unmeasured on any real GPU and no proposal should claim it.**
**0.3b — THE SHARED INSTANCE CAP: 256.** Measured: an `InstancedMesh` costs **+1 draw at every N
from 32 to 2048**, and `N × tris/instance` **always submitted** (the instance bounding sphere spans
the town — at N=2048 all 368,640 prop tris go down the pipe from anywhere). So draws are not the
constraint; triangles are. 256 × a 24 tri billboard cell = 5121,024 tris = **0.5% of budget**;
CPU to rewrite all 256 matrices, **0.01 ms/frame**. Binding companions: **≤24 tris/instance at the
cap**, and **spend it proximity-first** — measured anchor supply runs synthetic 246 poles / 6.8 km
to bendigo_real **7,325 poles / 188 km**, so 256 sprayed town-wide on bendigo is one instance per
730 m and statistically unencounterable.
**0.2 — `smoke_tram` REBUILT.** It asserted `!!getObjectByName('tram')`, printed a SKIP and returned
on absence, and **passed on the fenced path** because `tram.js:110-114` adds a named EMPTY group.
Now STRICT with four arms: RUNS (route/stops, and the body moves **19.8 m over 45 frames** under the
*shell's own* loop) · BEHAVES (an isolated tram, fixed dt, 400 s: dwell time accounts to **exactly
9 × 3.5 s**, max cruising tick 0.4665 ≤ SPEED·dt 0.45, every discontinuity bounded by 2·LANE with
≥1 end reversal, riding **3.2 m off Lane A's own main-edge centreline**) · GENUINELY ABSENT on the
fenced katoomba_real (0 stops, **0 group children**, 0 m moved) · OFF under `?tram=0`/`?classic=1`.
**Both controls demonstrated every run** — the run-predicate pointed at the fenced town and the
absence-predicate pointed at the running tram must each go red.
**0.1 (F half) — THE KERB GATE: the ruling is in the paint, and the control fires at EXACTLY 14.**
New STRICT `smoke_kerb`. A's corrected selfcheck asks the plan-side question; F's asks the runtime
one, of the paint — **raycast straight down at every rendered poster and classify the topmost ground
hit by MATERIAL IDENTITY** (`skins.groundMat()` is cached, so the road mesh's material *is* the
object the factory returns — no mesh names, no strings, nothing that drifts when B edits). Topmost,
not first, because B put the carriageway on top at junctions. Six arms, all green: **14 posters
rendered == 14 in `plan.posters`, every one within 0 m** (nobody had ever asserted the poster you see
is the poster A placed) · **detector control A** — a main centreline classifies ROAD at y 0.04 ·
**detector control B** — 13 m off it classifies FOOTPATH at y 0.02 · **THE RULING: 0 of 14 posters on
road paint**, tally `{footpath: 14}` · **THE CONTROL: the same leg fails under `?verge=0` at EXACTLY
14**, tally `{road: 14}` · **CLASSIC KEEPS ITS PAINT** — 13 m off the centreline is still ROAD under
`?classic=1`, 0 posters. **Two lanes, two wholly independent methods, the same number 14**: A counted
plan-side by offset in node; F counted paint-side by raycast in the browser off B's geometry.
**0.5 — `classic_regression` GETS A NETWORK ALLOW-LIST**, derived from the **v2 tag at gate time**
(`git ls-tree` + v2.0's own manifest), never a deny-list. **The brief's shape would have missed the
breach it was written for**: the R36 widening was `models/peds/woman_dj_01.glb`*same-origin* — so
"four depot GLBs + same-origin" is green straight through it. `/models/peds` is enumerated by the
v2.0 tree (exactly the covenanted 21 files; today's dir holds 32). The nine depot fetches are
**baseline, not a breach** — proven by booting the actual v2.0 tree and capturing its wire.
**Control every run**: injecting the R36 breach itself plus a never-existed asset turns the clean
verdict into exactly 2 named violations. F measured classic's surface as **+6 files** vs the real
v2.0 tree and **held rather than ruling**; **Fable ruled** (`4a7c0f1`): the 5 audio files are
**RATIFIED BASELINE** (v2.1 was v2's final tagged state, so audio shipped inside v2 as the furniture
GLBs did) and `assets/towns/index.json` is **NOT baseline** but a **named, printed exception**. Landed
in the mechanism by moving the authority tag **v2.0 → v2.1** — surgical, not generous:
`git diff v2.0 v2.1 -- web/` is the audio pack + `audio.js` + four wiring edits, **zero other assets,
zero deletions**, so the tag change admits precisely the ratified audio. Gate output confirms:
same-origin baseline **47 → 52**, exceptions **2 → 1**, control still discriminating. "Should
`?classic=1` show the town selector at all?" → v8 debt ledger, R38+.
**THE CLOSE — `qa.sh --strict --matrix` on the true final tree (A + B + Fable + F all landed,
port-isolated :8975): 7 passed · 0 failed · 0 warn · 0 skipped — QA GREEN.** Selfcheck
**157,407/157,407** (A's +1,055 poster assertions, green on the ruled street) · manifest 0 err/0 warn ·
**flags harness 0 fails / 0 warns** (classic fp `0x5f76e76`, 162 draws/16,642 tris, fetch surface
clean vs **v2.1** with 1 named exception and the injection control firing · default boot gig golden
`0xec7a2d39`, 131 draws/23,382 tris, dig reachable naked · the new kerb gate and the rebuilt tram
gate both green with every control) · figure-scale 29 figs/6 shops · **MATRIX GREEN 10 towns × 7
gates**, tram verdicts unchanged (fenced on bendigo/fremantle/katoomba_real — the same three the
rebuilt smoke uses for its absence arm). **No tag: wave 0 levels the floor, it does not ship a
version.** *Caveat kept honest in the notes: the matrix's own budget column is clean-load bookmark
numbers (69161 draws) — a regression signal, not the budget of record, which this very round proved
under-reports a walked-and-turned frame by ~120 draws. Making the matrix walk-and-turn is filed to
the debt ledger, not smuggled into a floor round.*
Full record: LANE_F_NOTES §37.
---
## Round 36 (THE v7.0 CLOSE, wave 2) — the gate, the freeze, the tag
**Zero alpha-blockers stood** (Fable's wave-0 naked-boot playtest walked the whole loop by hand),
so wave 2 was pure close: E's three flags measured shut, the full gate over the final tree, docs
frozen, **tag `v7.0` (annotated, LOCAL — Fable pushes)**.
**E's 2 a.m.-asset flags, closed by measurement:** (a) the 5 trellis DJ bodies' bind skeletons
measure **0.9840.989 units** vs the legacy roster's 173.46 (cm) — the figure-scale gate grew a
DJ-body leg that spawns all five BY NAME through the real browse path: statures **1.641.89 m**,
all in [1.4,2.0], feet planted, rig-kind verified (the old sample's `(i*7)%19` could never reach
indices 1721). (b) New STRICT `smoke_djdance`: the 4 Rokoko clips bind **64/65 tracks, sole
unbound `mixamorigReference`** (gate on binds + NaN, never node counts — no template pin); a
gig-night dancer's live action IS a dance clip (identity by duration, mixer advancing,
`position.y` flat = the clip moves the body); `?classic` fetches **zero dance clips** (positive
control: default fetched 4); `?noassets` procedural sway ALIVE (placeholder dancers bounce
0.07 m — the inverse pair proves the branch). (c) Rokoko zone = E's w1 provenance, nothing
runtime.
**THE HOLD → RESOLVED (the five-holds law, end-to-end in one round):** wave 2 found `?classic=1`
fetching the 5 DJ body GLBs (unconditional `PED_NAMES.normal` members, rigs.js, `b339402`
classic's crowd pool widened 17→22 ungated). F WARNED it in the suite and held; Fable ruled
"classic keeps its people"; D fixed it in rigs.js alone (`2fd4c1c`: base-17 restored, the five
append behind `djs = dance` riding the shell's existing `!CLASSIC` signal; classic's 129-citizen
census byte-identical to a `b339402^` shadow tree). F's wave-2.5 re-gate on the true final tree:
the warn flipped to "✓ zero DJ-body fetches" — **flags harness 0 fails / 0 warns**, default boot
keeps all 22 bodies and the organic gig-crowd proof. Held, ruled, fixed by the owning lane,
re-proven — nothing improvised.
**The gate (final tree, :8946 port-isolated): `qa.sh --strict --matrix` 7 passed · 0 failed ·
0 warn · 0 skipped; flags harness 0 fails / 1 warn wave 2 (the held finding, deliberate) →
**0 fails / 0 warns on the wave-2.5 re-gate** (true final tree, run 3 on the healed box — the
first two re-gate runs died to a box-level fork famine, ~10,600 leaked m3panel processes, fixed
at source; starvation artifacts, not gates); selfcheck
156,352/156,352; figure-scale 29 figs / 6 shops; MATRIX GREEN 10 towns × 7 gates.** The R3235
smokes all re-green on the final tree: bible (ledger Σask $333 → Σoffer $135, scarcity across
reload, ?game=0 control) · hunt (rumor + auto-clear + travel day 1→2→3, same-town free) ·
godbay (one-hammer byte-identical, SOLD net $19 bounded, the paper on the wake toast) ·
first-five (HILLS HOIST $191 splash, beats on the real path, seeded across browsers). Classic
covenant fp `0x5f76e76`, gig golden `0xec7a2d39`, world fp `0xb1c7843e` unmoved.
**The freeze:** README v7.0 section (the loop + the morning-paper manual row) · V7 charter risks
retired-or-carried v4-style (economy RETIRED-structural, bot verifier carried to the live tier ·
schema RETIRED · re-emit identity CARRIED, never fired · scope RETIRED · v6 collision RETIRED) ·
LANE_F_NOTES §36. Full record: LANE_F_NOTES §36.
---
## Round 35 (v7.0 close, wave 2) — THE FIRST FIVE MINUTES (Fable, solo, JING5)
A fresh game arrives HUNTING: one seeded artist want (`rng(citySeed,'firstwant',0)` × the
parody roster) named on the splash with the pocket money ("You're hunting HILLS HOIST — $191
in your pocket"), pointing the rumors and the tour at the same target. Three toast-beats walk
the hustle off existing events (walk-in → digOpen → first find → first sale-or-listing, then
the listener removes itself). Returning saves get no tutorial; ?game=0/classic never reach it.
#pc-toast body-mounted so beats and morning papers surface inside shops too. STRICT
`smoke_first_five` green (roster want + splash · real-path beats · returning/?game=0 controls
· cross-browser seed determinism). Full record: LANE_F_NOTES §35.
---
## Round 34 (v7.0 close, wave 1) — GODBAY: the offline auction (Fable, solo, JING5)
⚖ LIST from the crate (mail-in consignment) → `listings` on procity-save/1 → resolves the day
after, wherever the day moves (sleep, travel, imports — adopt() and sleep() share the
resolver). THE ONE-HAMMER LAW: identity-seeded (never day-seeded) — a passed-in item re-lists
to the same result forever, no retry farm. 15% pass · uniform-across-band hammer (grail 2×low)
· 12% war ×1.52 · 12% fees, net floor $1; EV ≈ 0.8×mid so random flips bleed and only finds
profit; keeper = floor, auction = bounded ceiling. The morning paper rides the wake/arrival
toast ("⚖ SOLD 'Cane Toad Blues' — hammer $22, $19 after fees"). Band table imported from C's
bible.js (one authority), PRNG from core. STRICT `smoke_godbay` green (real-DOM consign ·
byte-identical double-sleep · bounded honest resolution · travel hammers · game=0 control).
Full record: LANE_F_NOTES §34.
---
## Round 33 (v7.0-beta wave 2) — THE HUNT: wantlist · rumor · travel (Fable, solo, JING5)
☆ WANT on the pull card → `wants` on procity-save/1 (optional, capped, deduped); buying a
matching item clears the hunt; THE WANTLIST section in the crate panel. The keeper gossips:
seeded 45% banner mutter naming a hunted want + a real cached town. TRAVEL COSTS A DAY:
adopting a save from another townKey (town OR seed) charges +1 day in save.js adopt() — load
and import both pay, same-town stays free (byte-equality gates untouched); arrival toast names
the fare. New STRICT `smoke_wantlist_travel` (real-card want · rumor scan · auto-clear ·
+1-day with intact cash/collection · same-town control · return fare · ?game=0 zero-DOM).
Full record: LANE_F_NOTES §33.
---
## Round 32 (v7.0-beta wave 1) — THE BIBLE + scarcity + the §31.6 queue (Fable, solo, JING5)
Guide bands derived in `js/interiors/bible.js` (E's thresholds mirrored, no pack re-emit) →
the pull/sell cards carry the **guide stamp**; parody bins roll seeded bands + 10% collector/
grail **gems** (asking under the keeper's bandLow offer — margin by construction); §9.1 sell
basis swapped to bandLow exactly as pre-ratified (alpha entries keep the pricePaid basis).
**Scarcity:** optional `pulls` on procity-save/1 — a bought dig slot stays gone all day
(pick-then-omit, zero collateral), pruned on sleep; real-sourced pulls never expire. §31.6
queue: cluster-pose spawn (main-street doors, classic keeps the frozen pose) · THE CRATE +
game bar live in interiors (C anywhere, yields to the cards) · DAWN dome swaps to high-cirrus
(weather override wins). **Gates:** new STRICT `smoke_bible` (band law over every bin ·
ledger closes to the cent on the GEM's bin, Σask $333 → Σoffer $135, gems +$21 · emptied bin
0 across reload · `?game=0` control re-opens 16/16) + sell_routing amended to the exact
formula (the fence moved from the receipt to the band). Full record: LANE_F_NOTES §32.
---
## Round 31 (v7.0-alpha CLOSE) — THE DIG FLIP + the tag
**Fable's wave-0 playtest found the one alpha-blocker: the dig was default-OFF on a plain boot.**
`index.html` still carried the v2-era opt-in (`params.has('dig')`) six epochs after the README
claimed "dig on" — and every R30 game-loop gate booted `dig=1` explicitly, so the suite was green
while E at a bin did nothing on the boot every player gets. Fixed with the one-line flip
(`DIG_ON = flagOn('dig')` — ?dig=0 opt-out, ?classic=1 off, `dig` on the flags surface) and fenced
with two new STRICT legs: **default_boot_gate asserts dig REACHABILITY on a NAKED boot** (E at an
aimed bin → digActive true → closed clean) and **classic_regression asserts dig INERT at the bin**
(zero `pcdg-*` DOM, zero Storage calls — measured, not assumed). *Harness lesson #4: the
explicit-flag blindfold — a gate that force-feeds its subject's flag can never notice the default
is broken; every claimed-default-on flag needs at least one flag-ABSENT gate.* Splash de-staled
("Every door opens." — the "(soon)" is over; controls line = E riffle/sell · C crate · SLEEP to
save) + the README manual row. `PROCITY_QA_PORT` override so the suite never silently reuses
whatever holds :8130 (this round: Fable's own preview server).
**The re-gate (final tree, :8931): `qa.sh --strict --matrix` 7 passed · 0 failed · 0 warn ·
0 skipped; flags_check 0/0 incl. both new legs; MATRIX 10 towns green.** Money shot:
`docs/shots/v7_alpha/crate_panel.png` — Monster Robot Party's frontage, THE CRATE with 3 real
finds (real cover thumbs, honest paid/where/day), game bar day 1 · $116 · CRATE 3 · SLEEP, driven
through the real loop with the dig param absent. **Tag `v7.0-alpha` — annotated, LOCAL ONLY**
(Fable pushes). THE BETA QUEUE (Fable's design wants, filed NOT fixed) is in LANE_F_NOTES §31.6.
Commits: `50385c0` (flip + gates + splash) · shot + notes commit (tag target). Full record: §31.
---
## Round 30 (v7.0-alpha — THE SAVE AND THE CRATE) — F: the contract first, then the save core + tomorrow
**The contract shipped before a line of implementation** (`53fcd3c`): `window.PROCITY.game =
{ day, cash, collection[], townKey, save(), load(), sleep(), export(), import(json), wallet,
recordFind(), removeFind() }` + the `procity-save/1` schema + the rotation law, published in
LANE_F_NOTES §30 — and it worked exactly as the order intended: **B picked up §30.4 within the
session** (venue.js "tonight" already reads `gigs.weekNight`; hud.js collection/SLEEP surface in
flight), and C's already-published §9 asks (`type` stamped at buy, `title`/`artist`, removal via the
game API, `wallet.sell` mirrored) are honoured in the entry shape.
**Then the layer** (`207bcff`): `web/js/world/save.js` (F-owned, new) — versioned localStorage save,
THE DELTA LAW BY SHAPE (payload keys are exactly `[cash, collection, day, savedAt, schema, town]`
no world field can even be expressed); corrupted/foreign → **loud reject, raw stashed, fresh start,
town boots**; save on sleep + beforeunload; export/import for moving machines. The wallet is a
game-backed facade of C's exact v0 interface, so every proven seam (dig onBuy, shelf buy, cover,
C's onSell) is unchanged — cash just became authoritative from the save. Buy seams record finds;
`?classic=1`/`?game=0` never construct the layer — **ZERO Storage calls, instrumented, not trusted**.
**SLEEP=TOMORROW:** `sleep()` → day+1 → gig `weekNight` re-keys the EXISTING week schedule → wake at
DAWN → save. Stock rotation is a runtime day salt on the EXISTING `stk-*` streams (2 marked seam
lines in C's interiors/layout) + the dig's `binSeed`**plan generation never sees `day`** (A's #6
boundary). **Day 1 IS the pre-v7 town** (measured: `?game=0` stock fp == fresh-game day-1 fp), and
**REAL-sourced crates never rotate** (Monster Robot fp identical across sleeps; the mint crate rotates).
**Two amendments filed loudly, with measurement, not slipped in:** gig night = **(day 1) % 7**, not
the brief's literal `day % 7` (day starts at 1 — the literal form flips a fresh boot's gigs off night 0,
breaking the R13 cover/band gates before the player ever sleeps); and sleep saves AFTER incrementing
(disk always carries the morning you woke into). Fable ratifies or overrules at review.
**Numbers:** 7 verify legs green in fresh no-store contexts — $12 dig buy == pricePaid recorded · sell
halves credit/remove correctly · parody fp `3396372961 → 1818111125` across a sleep, byte-identical
same-day AND across reboots · corrupt blob = 1 loud reject + stash + clean boot · `qa.sh --strict`
**6 passed · 0 failed · 0 warn** with the game layer default-ON. Two F-harness bugs caught and fixed
in the run (the post-sleep CLOSED door; a transform-based fingerprint that was vacuous over batched
parody stock — hash the VERTEX data). Full record: `LANE_F_NOTES` §30.
**Held for the later F session (per the brief):** ledger #5 gates + the sell-card E-key routing.
### Wave 3 — THE GATES SESSION: the routing landed, five STRICT gates, the loop closes end-to-end
**The held join is in.** E inside a shop now routes: **aimed bin → dig · near counter with sellables →
C's sell card · forgiving nearest-bin → dig · shelf-buy** (`interior_mode.js`; 3 marked shell seams in
`index.html``procity:sellOpen` unlock + `sellActive` on the Esc/relock guards, the dig's exact
contract). `onSell` does F's two §9.3 halves in the safe order: `removeFind` first (veto ⇒ nothing
moves), credit second. **The loop, measured through the real DOM:** dig pull **$9 debited** → walk to
the counter → E → card → SELL **+$4** (< $9, the clamp) → collection 1→0 → street HUD **$186 ·
CRATE 0**. One amendment filed with its measurement: the old nearest-bin fallback ignored aim and would
have swallowed E at any counter near a bin — aimed-raycast now outranks the sell trigger, the
forgiving pick runs after it; both branches proven live (Moe Vinyl counter: card opens; Kelpie Sound
bin, wrong aim: dig opens).
**The gates (ledger #5), all landed as STRICT suite smokes in `flags_check.py` — no more session-local
instruments:** (a) **save/load determinism** — scripted buy 2/sell 1/sleep 2× via B's real button →
export → fresh browser → import **byte-equal** (state 243 B, normalized export 269 B), plan fp
`0xb1c7843e` **unchanged** across the import, weekNight re-keyed 2; (b) **no-pump v0** — 5 real
UI round trips **all strictly negative**$30), plus **C's negative control**: broke player,
`wallet.buy` invoked-and-false on the real shelf path ⇒ **recordFind unreachable, zero entry** (dig BUY
disables outright), with a positive arm proving the control discriminates; (c) **classic purity**
Storage-prototype instrument pre-page-script: `?classic=1`/`?game=0` **0 calls, 0 game DOM ids**,
positive control sees the default boot's `procity-save` read + all 7 ids, plan fp game-on == game-off;
(d) **rotation determinism over VERTEX DATA** (the wave-3 binding) — day 1/2/3 fps all distinct,
re-entry byte-identical each day, **cross-boot byte-identical off the persisted save**, `?game=0` ==
day 1 (88,320 hashed bytes / 20 stock meshes printed — the subject provably present). Harness bug #3
caught en route: the HUD is hidden in interiors by design; the gate read it on the street.
**NO TAG** — per the brief, the alpha tags when it plays (Fable's call, likely R31). Full record:
`LANE_F_NOTES` §30.6.
---
## Round 26 (v5.0-beta — EVERY CRATE DIFFERENT) — F: the manifest consumed, the tolerance retired
**Every crate is different, and F measured it rather than taking G's word.** Not by entering one lucky
shop — from the indexes of record: **15 keyed crates, 105 pairs, 0 sharing a title**; **0 crates overlap
the generic v2 pack**; **0 ids reused across crates** (G's sku fix holds at scale — the R24 vacuity dead
at the source, not papered over in F's gate). 4 crates in Red Hill + 11 in Newtown = the manifest's 15
exactly, and the manifest lists nothing that isn't on disk.
**Sourcing, not tier — C was right.** `tier` is the charter's ladder rung, and a mint atlas is *also* rung
1: still a file, still offline, still deterministic. What differs is where the stock came from, which is
`sourcing: real|mint`. E built to C's contract over the brief on the principle that the contract's author
owns the contract's shape. F's gate asserts it: 15/15 declare sourcing, every index agrees with the
manifest, **a mint crate can never report as real**, and the gate PRINTS it per room — provenance a human
never reads isn't doing anything (the R24 licence lesson, one round on).
**The 404 tolerance is deleted, not tuned.** R24 probed every keyed shop and ate a 404 per atlas-less one;
F's smoke could only cope by *naming* them — and a tolerance is a confession. F filed the real fix as a
contract ask instead of inventing it; G built the manifest; **F now consumes it**, and the shell asks for
nothing that isn't listed. Zero 404s because nothing absent gets asked for, not because the gate stopped
looking. **And deleting the tolerance immediately caught a leftover**: F had wired the *boot* preload but
left a second blind probe at the *door*, which 404'd on every keyed-but-unfetchable shop. While the gate
said "8 probes, expected", a ninth would have been invisible too. A named exception protects new bugs the
moment the real fix exists.
**Gate: `flags_check` GREEN** — distinctness · sourcing (real + mint rooms) · soft fall silent · **ZERO
404s** · **0 console errors, no attribution exceptions**. Full record: `LANE_F_NOTES` §26.
---
## Round 24 (v5.0-alpha — THE ALPHA COMPLETION) — **the crate WALKS; F held the tag on A's gate**
**THE CRATE WALKS.** Boot `redhill_godverse`, walk down Musgrave Road into **Monster Robot Party**,
riffle **its own real crate**, buy a real record at its real price. Proven falsifiably: room base =
`assets/stock_godverse/3962749/` · **titles 120/120 in its atlas, 0/120 in the generic pack** · **the
GPU samples `stock_godverse/3962749/stock_record_atlas_0*.webp`, 0 textures from the generic pack** ·
bought *"Abstract Latin Lounge II (Part II)"* **@ $15**, cash **191 → 176**, exact debit, count 0→1 ·
reboot restores 191/0 (gone-this-session, back next boot). Ladder re-proved: `?noassets` → **zero
network requests**; keyed-but-atlas-less shop 2289 → 404 → **parody, live**; `newtown_real` → base
`null`, 350 items — **F's wire is a no-op off the godverse path**. Full record: `LANE_F_NOTES` §24.
**F held `v5.0-alpha` anyway. The tree is red — 156,193/156,201 — and none of it is the product.**
2 reds are goldens G's re-emit legitimately moved (A ran *before* G, so A could not pin them). 6 are
**conflict #2: A's identity sweep contradicts Fable's own standing Ruling 2**, which *mandates* mixed
caches ("merge census heroes **WITH** the donor's texture shops"). A cafe from OSM has no GODVERSE
identity and never will. G measured it, escalated it to "A's call, Fable's ruling", nobody ruled, and
it arrived at F.
**F measured the hazard that gate guards: it does not exist.** 10/10 and 18/18 unique ids, **zero
duplicates**; and partial keying does not mis-stock — F watched 2289 fall soft to parody in the
browser, exactly as charter risk #3 prescribes. **A's own two checks disagree**`validateTownCache`
reasons it correctly ("Missing = tier 0 (soft). Duplicate = mis-stocked (hard). They are not the same
failure") and warns; the sweep hard-fails the same condition.
**Why F didn't just fix it: F is the party that benefits.** Changing a red gate to green, in another
lane's file, to unblock F's own tag, on the one check guarding "never mis-stocked" with Monster Robot's
real crate as the prize — that move would retroactively devalue every green F has ever reported. The
cost of waiting is an hour. Recommended (A's call): the sweep follows A's own validator; A pins the
four goldens in the same commit; F re-gates and tags. One commit.
**Four findings:**
1. **The honest-gate spec was itself vacuous.** The brief specified id equality — but both packs number
items **positionally** (`rec_0000…`), so **all 120 atlas ids are also generic-pack ids**. An
id-equality gate goes green on the generic pack: the exact failure the vacuous-gate law was written
to stop, reproduced inside its own first application. Titles (0 overlap) and the **texture URL**
the file the pixels come from — are what discriminate. F's gate asserts on those.
2. **F's own selector was lying about the epoch's shop.** `DBG.enterShop(3962749)` → "no shop matching":
a GODVERSE shop has two ids and F's R22 selector knew only the plan id. Fixed.
3. **A's uniqueness check is a silent risk-#3 hole** (new — G didn't catch this): on a mixed cache every
texture shop maps to `undefined` and they **all collapse into one Set entry**, so it fails on the
undefineds and **cannot detect a real duplicate**. Fix it *over the defined ids* — that makes the
guard stronger, not weaker.
4. **The licence flag stopped reaching a human.** E's validator now sees the atlas (R23's blind gate is
closed ✓) but prints `provenance.licence` (British) while G now emits `license` (American, per C) —
so `FLAG BEFORE ANY PUBLIC/COMMERCIAL RELEASE`, on real product photos, **silently never prints**
while the gate says OK. E, one line, G already specified it.
---
## Round 23 (v5.0-alpha — THE FIRST REAL CRATE) — **F HELD the tag**
**`v5.0-alpha` is NOT tagged. The crate is real; it has no street.** G's 120 real records off a real
shop's POS are in the tree and byte-identically re-bakeable — genuinely the artifact the charter asked
for. Nothing in the game can reach them. `#7a` isn't a gate that failed, it's a gate that **cannot be
run**. R18's law: *release rounds don't ship known breaches.* Full record: `LANE_F_NOTES` §23.
**F executed the merge that never happened** (charter law #5) — `main` had no `stock_godverse/**` at all,
so the branch F was asked to tag lacked the round's headline. Review had happened, C had verified
pre-merge and passed; only the `git merge` was missing. Disjointness verified before merging, not
assumed. Zero conflicts → `43b035a`.
**What passed — everything except the crate.** `qa.sh --strict --matrix` **7 passed · 0 failed · 0 warn**;
**MATRIX GREEN — 10 towns × 7 gates**; selfcheck **ALL GREEN 152,015/152,015** (was 161,300 — E retired
toowoomba); flags harness green, so the `?classic=1` covenant still holds through the merge. Tier 0
`?noassets`**zero fetches, zero console errors** (the offline law holds); tier 0 default boot on the
godverse town; tier 1 (v2 town-wide) `?stock=real`. **Tier 1 (v5, per-shop) — the round's whole
deliverable — is never fetched.** A, D, E and C's work is sound; only the rung this round was built to
add is unattached.
**Three findings, in order of how much they cost:**
1. **The atlas contract contradicts itself.** `LANE_C_PUB:271` publishes `stock_shop_<id>_index.json`;
C's own loader at `stockpack.js:25` fetches `${base}stock_${type}_index.json`. **Incompatible.** G
built runtime-compatible (so C's base-override check passed); E built contract-compatible (so E's
validator can't see it). **Nobody was wrong — the contract was.** F recommends amending to G's shape;
it's the only one the runtime can load.
2. **The gate built to catch that was blind and green.** `validate_atlas.py` globs a filename that can't
exist at runtime, finds nothing, and `return 0` — so it passes whether the atlas is absent, misnamed,
or perfect. It had never seen one until F pointed it at G's by hand, which then surfaced *deeper* drift
(E's code reads provenance top-level, E's own docstring says nested, G nested it; `licence` vs
`license`; a ceiling stale by one round). *A gate that returns 0 on an empty set is not a gate.*
3. **`#7a` would have passed vacuously.** F walked into The Vintage Record (real shop, real road) and the
crates filled with **real covers — from E's 350-item town-wide v2 pack**, not the crate. Zero requests
to `stock_godverse/`, ever. Eyeballed as written, the gate goes green and proves nothing.
**R24 order — three gaps, none F-only:** **A+G** land a `godverse_id` (verified: *every* godverse shop
reports `godverse_id: null`; the schema has no such field, and C's rule is "C does not invent it") →
**E** builds `redhill_real` (one Overpass fetch — E's file, outward-facing; **not F's to invent**) →
**C** keys the pack cache by `type+base` (today a cache hit returns before `base` is read, so two stocked
shops collide *and fail-soft breaks*) → **F** passes the per-shop base and re-gates. F deliberately did
**not** build its half: untestable with no street and no identity field. Filed, not guessed.
**Also:** A's spacing warn earned on day one — with toowoomba retired, **ballarat now warns at 255 m**.
The thin tail has a second member.
---
## Round 22 (v4.0 — THE EPOCH CLOSE) — F closed it
- **The blocking gate CLOSED.** F's R21 re-measure made the tri diet blocking; B diagnosed, the driver
@ -532,3 +1011,81 @@ F drives/inspects via `window.PROCITY` + JS for programmatic checks.
msg `Lane F: integration — interiors + keepers + street peds + QA harness + barrel + skins fix`.
- A local `python3 -m http.server 8130` is running for the smoke test (harmless; kill when done).
- Lane prompts hard-code `/Users/jing/Documents/PROCITY`; real path is `/Users/m3ultra/Documents/procity`.
---
## Round 38 (v8 WAVE 1 — INHABITATION) — the budget reconciliation · four new gates, four controls · the round close
**THE BUDGET OF RECORD DOES NOT MOVE: 292 / 300 draws, margin still EIGHT.** The brief's naive sum
(292 + washing 1 + magpie 1 = 294) is wrong in our favour. **The pinned worst-draw frame is a NIGHT
frame and both draw-spending layers are structurally zero at night** — the washing comes in
(`count = 0`), the magpie roosts (`count = 0`). Wave 1's one draw is spent in daylight, where the
walked peak sits ten draws lower. Two pinned trees (`6bc7926` control, byte-identical under `web/` to
R37's `7bd96e5`; `4514cb9` treatment, byte-identical under `web/js` to the final `74d6257`), assets
symlinked to the live tree on both sides, 802 stations @ 2 m at **yaw 0**, sim + tram **LIVE**:
**night 292 → 292 (+0)** / 120,145 → 122,879 tris; **noon 282 → 283 (+1)** / 99,872 → 103,118;
`?classic=1` **269 / 90,580 → unchanged**; `fitzroy_real` **172 / 130,727 → unchanged**;
`bendigo_real` **204 / 131,492 → unchanged even with `?magpie=1` forced**. The control reproduced
R37's pin **to the byte, twice**. Every revert is exact: `?yards=0` at night gives the control's three
numbers; `?yards=0` at noon gives 282 / 99,872, isolating the noon +1 to the washing alone.
**Lane B's "control 346" — answered rather than dismissed.** As a *delta* it is the wall-clock ped
spawn artifact R37 named (114 draws of swing between two browser instances measures the sim, not the
build), and B was right to hide the sim. But 346 > 300 is a claim about the world, and neither
`stepwalk` (walks the chunks in, photographs a half-dressed street) nor `dwell` (fills the street,
arrives clean at 27 chunks) can adjudicate it. **`tools/qa/budget_walk.py` gains `--method
stepsettle`**: the audit's fixed-yaw stations, each **drained then held N real rAF frames** — R+1
dispose residue AND a fully-acquired crowd in the same frame. **Like for like — the same 101 stations at 8 m, yaw 0 — the settle is worth +3 draws at noon (277→280 control, 278→281 R38) and 9 at night (287→278 both trees), and the sign flip is the finding: at night the settled street RETIRES near rigs to impostors (tris 120,145→100,939), so the crowd goes home and a sampler that never waits does not see it. **The highest draw count anywhere in the reconciliation — 4 methods × 2 trees × 2 segments × 5 towns — is 292, the pin. Nothing read over 300.** The mechanism that could legitimately have breached it (full crowd inside a full R+1 dispose window) now reads 280/281. B's 346 was the artifact, and the refusal is now backed by the measurement that could have falsified it.
**A correction to my own R37 pin:** the corpus TRI worst is **130,727** on `fitzroy_real`, not
124,793 — and it reads 130,727 on the **control** tree, so it is my R37 sampling density (12-bearing
8 m sweep) versus this round's 2 m fixed-yaw stations, not a wave-1 cost. Re-pinned. *A pin is only
as good as its station spacing, and mine had not said so.*
**FOUR NEW GATES, FOUR CONTROLS DEMONSTRATED** (`smoke_ambient` · `smoke_character` · `smoke_magpie`
· `smoke_yards`, all wired into `flags_check.main()`):
- **§1.1 audio** — six sources anchored (63/8/81/32/39/57, B's numbers exactly); dog **audible at
5.9 m with 1 play scheduled** (for a one-shot `audible` is only published once `plays > 0`, so one
word covers fetch→decode→gain→schedule→counter); **hours enforced** (sprinkler audible 06:30 → idle
12:30, same pose); **day-of-week enforced** (mower idle Mon → audible Sat, same pose); **pan is a
bearing** (yaw 0/90/180/270 → +0.00 +0.85 0.00 0.85); **zero draws byte-identical to
`?ambient=0`**. **THE CONTROL:** a second server, root symlinked file-for-file except one patched
`manifest.json` with `sfx['dog-bark-fence']` renamed → that source reports **`no-entry`, 0 plays,
never audible**, other five unchanged, **0 console errors**. Stated on every run: audible OUTPUT is
unverifiable headlessly — **nobody in this loop has ears**.
- **§1.2 character** — classic is the frozen v1 literals with all tints null and 5 ground meshes;
**all 23 shipped keys matched per-town including the two carrying `state: ''`**; **the control
fires** (an unknown key returns `matched:false`); **21 distinct palettes across 23 towns** (a single
shared palette FAILS by name); and the two orphans are **on the GPU**`ground-gravel.jpg` bound on
Castlemaine, `ground-reddust.jpg` on Darwin, read off the live material.
- **§1.3 magpie** — the latch arm verbatim with its **positive control first** (TRUE before the roll);
day-roll clears it; **localStorage gained NO new key** (baseline taken *after* a `game.save()`
the first cut blamed the magpie for v7's own write); **the season is 23 distinct positions across
24 towns, run UNFORCED**, against B's first cut's 24-of-24-at-214, with `newtown_real` in season on
day 1 and 129/1000 seeds hitting the 11.5% design ratio; **territory 6 → 157** synthetic → bendigo;
**+1 draw / +182 tris** at one pose reused across both boots.
- **§1.4 yards** — 145 house + 36 yard = 181 lots, 181 hoists → 394 items, 256 active, **0 at night**;
the cost decomposition at one pose with the sim quiet: **box props +0 draws** (and **2**, because
the 36 `yard` lots used to fall through to `buildShopfront()`) with **+1,936 tris**, **washing
exactly +1 draw / +512 tris**; and on real towns it **SKIPS LOUDLY** — a `⊘ SCOPE` line printing
0 house/yard lots / 0 hoists / mesh never created, next to the synthetic 181 that makes the zero
attributable.
**Also caught, in my own gates:** the pan arm's first draft used two bearings and read `+0.00/0.00`
because that anchor sat on the forward axis — it would have called a working panner inconclusive.
Four bearings. *The gate's first draft failed the standard the gate exists to enforce.*
**SUITE: `tools/qa.sh --strict --matrix` GREEN on the true final tree** (`74d6257` — E's session-2
publish landed mid-round, `web/` diff vs B's `4514cb9` is manifest + one thumb, **no code**, and both
were already in the tree both budget servers read): **7 passed · 0 failed · 0 warn · 0 skipped**;
**flags_check GREEN 0/0** with all four new gates inside it; **MATRIX GREEN 10 towns × 7 gates**,
0 fetches under `?noassets` on all ten, tram verdicts unchanged. Wave 1 moved nothing in the matrix.
**Held: nothing.** Recorded, not fixed (other people's files): `magpie.js` says **154 triangles** at
`:9`/`:123` and **182** at `:42` — measured 182, so two stale comments in B's file; E's three
published GLBs are manifest-and-depot but not under `web/assets/models/` (staged in
`pipeline/_normalized/`, nothing loads them, validator's contract covers it — **not a defect**).
Carried debt unchanged: A's D1D4 · the matrix's budget column is bookmark numbers · `?r=3` breaches
at 307 · the `?classic=1` town-selector question.
**No tag** — wave 1 is content; the epoch tags when it is whole.

View File

@ -3,6 +3,262 @@
*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** (400800/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 3364 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):**

View File

@ -14,9 +14,37 @@ 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 $824"). 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) |
| **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)
@ -73,6 +101,9 @@ master off:
| `?weather=0` · `?tram=0` · `?winmap=0` | **on** | seeded weather · the tram loop · the parallax interior-window shader |
| `?roster=v1` · `?pop=N` | stream | chunk-streamed citizen density is the baseline; `?roster=v1` restores the fixed roster |
| `?dig=0` · `?stock=real` | dig on | crate-riffle on record bins · real GODVERSE sleeves in the stock |
| `?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. Forcing `?r=3` on the default town measures **307 draws**, i.e. over the 300 gate — these overrides are diagnostics, not supported boots |
| `?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 |
@ -97,9 +128,68 @@ Fitzroy mecca, the thin tail (labelled honestly: the floor is shops-per-street-m
below it), the synthetic control, and the `?classic=1` covenant made visible. Charter:
[`docs/V4_REAL_MAP.md`](docs/V4_REAL_MAP.md) · data + licences (ODbL):
[`web/assets/towns/SOURCES.md`](web/assets/towns/SOURCES.md).
## v5.0-alpha — THE FIRST REAL CRATE: the shops get real too
**v4 made the towns real. v5 makes the shops real** — and the first one is standing.
Boot `?plansrc=osm&town=redhill_godverse&stock=real`, walk down **Musgrave Road** into **Monster Robot
Party** (147 Musgrave Rd, Red Hill, Brisbane — a real record shop), and riffle its crate. The records in
it are **that shop's real stock**: 120 real records off its real point-of-sale, real covers, real titles,
real prices. Buy one and the wallet debits what the sticker says. It's gone for the session and back next
boot — because until the live tier lands, the crate is a **file**, not a shop's ledger.
**Stock is a data tier now**, exactly like assets — each rung fully playable, each degrading softly to
the one below, so the game never depends on a server to be a game:
| tier | what | deterministic? |
|---|---|---|
| **0 · parody** | seeded canvas sleeves (v1) — any boot, always works | yes — the QA baseline |
| **1 · static real** | per-shop atlases baked from the shop's own POS | yes — atlases are files |
| **2 · live GODVERSE** | `?live=1` — live stock off the real till: **sell a record in the shop and it's gone from the game crate** | **no — by design** |
**The offline law:** the server enriches, never gates. `?noassets=1` still fetches **zero bytes** and plays
silent-and-fine; a shop whose atlas is missing falls soft to parody rather than borrowing someone else's
crate; and **`?classic=1` still boots the exact v2 town, byte-identical forever** (`0x3fa36874`). The
world stays seeded and golden-gated — only the *stock inside it* is data.
**Provenance:** item photos are each shop's own product shots, 🟢 in-house — **flagged before any public
or commercial release**, and every atlas carries that flag where the QA gate must print it. Real titles
and artists are an in-house amendment to the no-real-trademarks law, on the same footing. **Nothing an
in-game purchase does touches real inventory** — v5.0 ships sell-sandboxed by ruling; a real reservation
tier is chartered for v5.x, not assumed. Charter: [`docs/V5_REAL_SHOP.md`](docs/V5_REAL_SHOP.md) ·
economy design: [`docs/G3_ECONOMY_DESIGN.md`](docs/G3_ECONOMY_DESIGN.md).
| `?seed=N` | `20261990` | town seed |
**Release tour:** [`docs/shots/release_v3_1/contact.html`](docs/shots/release_v3_1/) — the money shots on
the default boot, plus a `?classic=1` before/after. **QA:** `tools/qa.sh --strict` (classic regression +
default-boot gate + smokes + no-giants) `--soak` (the full-week soak); the seated drummer passes the
no-giants gate at the seated stature band `[0.9,1.5] m`.
## 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/).

View File

@ -1,10 +1,298 @@
# LANE A — CITYGEN · progress (PROCITY-A)
*Status: **all deliverables landed; self-check all-green (161,372/161,372); rounds 223 closed** (see below).
R23 (v5.0-alpha): D's thin-tail finding is law — `validateTownCache` warns above **150 m median shop
spacing**; flags 2 of 23 towns (toowoomba 396 m, **ballarat 255 m — beyond the brief**). Warn, never error.
R22: the pack absorbed — 23 towns, 36 goldens in one commit. classic `0x3fa36874` / gig `0xb1d48ea1` frozen.*
*Date: 2026-07-16 · owner: PROCITY-A (Opus 4.8)*
*Status: **all deliverables landed; self-check ALL GREEN (157,647/157,647); rounds 230 + 37 + 39 closed**.
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 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.030.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 227 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)

224
docs/ANIMALS_ANSWER.md Normal file
View 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 7041,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.

View File

@ -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.
@ -256,9 +273,30 @@ night in v1; interior-mapping shader is a stretch goal).
> 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 new frozen regression target):** the `?classic=1` boot is byte-identical to the v2
> baseline **forever** — synthetic fingerprint `0x3fa36874`, zero gig layer, **zero fetch delta** (no
> - **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
@ -356,10 +394,28 @@ barrel: **`roadWidth(edge)`** (full carriageway width — main 10 m, side 68,
kerb sits at `roadWidth/2` from the centreline),
**`vergeBand(edge) → [inner,outer]`** (where footpath furniture lives), **`poleOffset(edge)`** (where a
pole/poster seats — just behind the kerb). Poles, verandah posts and **spine** posters belong in the verge
band; the selfcheck asserts no **spine** poster stands within `roadWidth/2` of any edge centreline. A
**frontage** poster is exempt — it is bound to its venue building (the +Z facade B draws), so at a corner
venue whose facade abuts a crossing street it may legitimately sit near that street's kerb (B's building
geometry, not a mid-road defect).
band. 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 a carriageway, measured through `vergeBand(e)[0]` — the published law
itself, the same call B's ground consumes, so gate and renderer cannot drift apart; (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 12 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
@ -426,6 +482,45 @@ auto-loads `web/assets/towns/*.json` — validates, runs the structural + gig su
**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.
@ -477,6 +572,107 @@ nothing outside the cache-schema path).
> 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.030.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

View File

@ -1,11 +1,15 @@
# G3 — THE ECONOMY BACKEND (design doc, DRAFT for ratification)
# G3 — THE ECONOMY BACKEND (design doc)
*Lane G (ultra), R23. Drafted per the v5 charter ([V5_REAL_SHOP.md](V5_REAL_SHOP.md)) ledger #2.
Ratification: both Fables + John, at R24 kickoff. **Nothing server-side ships before that.** This
round is paper — plus the tier-1 artifact that made the paper honest (G2a, merged this round).*
*Lane G, R23 draft. Drafted per the v5 charter ([V5_REAL_SHOP.md](V5_REAL_SHOP.md)) ledger #2.*
Status of each section: **[MEASURED]** = verified against the real databases this round.
**[DESIGN]** = proposed, not built. **[JOHN]** = a decision reserved to John by charter law.
**§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.
---
@ -89,30 +93,71 @@ boot path, gate, or tag that may touch the network.
---
## 4. API surface [DESIGN]
## 4. API surface [AS-BUILT — R27, `pipeline/godverse_server.py`]
Read-only in v5.0; the write verbs are drafted here but **gated on §7**.
Read-only, per §7's ruling. **The write verbs are not "gated" — they do not exist.**
```
GET /godverse/v1/shop/<godverse_id>/stock?crate=<id>&since=<iso>
-> { shop_id, crate, tier, items:[{sku, slot, release_id, artist, title,
price, condition, sleeve_cond, cover, state}],
gone:[sku], etag, served_at }
GET /godverse/v1/shop/<godverse_id>/crates -> crate list + counts (the rotation menu)
POST /godverse/v1/reserve { sku, session } -> { ok, holds_until } [GATED — §7]
POST /godverse/v1/buy { sku, session, hold } -> { ok, receipt } [GATED — §7]
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 a `sku` in `gone[]``collapseBuyItem()` (already
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** (400800/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.
- 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.
---
@ -153,13 +198,47 @@ whichever dataset actually *has* the shop, and mismatches must fail soft, never
---
## 7. THE WRITE-BACK QUESTION [JOHN — charter law #4]
## 7. THE WRITE-BACK QUESTION **RULED: A THEN B** (John, 2026-07-17, R24 kickoff)
**Does an in-game purchase ever touch real thriftgod/recordgod inventory?**
> ### 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**.
This is now a live question, not a hypothetical: `monsterrobot` is a **real trading shop** whose
POS syncs to **Square**, with real customers buying these exact records off the real shelf. Stated
per charter law #4 with trade-offs and **no default**.
**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
@ -190,8 +269,8 @@ A purchase marks the real SKU sold and syncs to Square.
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 asks nothing and recommends nothing here.** Until ratified: **tier 2 is read-live,
sell-sandboxed** (the charter's standing state, Option A by default *only* because law #4 says so).
*(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.**)*
---
@ -242,6 +321,8 @@ class: reachability, fail-soft, latency. Kill the server mid-session and the gam
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.
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).

View File

@ -2,6 +2,627 @@
Durable answers to questions other lanes raised against Lane A. Full status in `A-progress.md`.
## Round 39 (2026-08-03) — v9 item 39.1: THE ADDRESS LAYER — the contract, published
**`web/js/citygen/address.js` is landed, green, and yours to consume.** Pure module: **zero imports**,
no THREE, no fetch, no DOM, no module state, no plan mutation. It derives from a plan and never writes
to one, so **it cannot move a golden** — and it didn't (numbers at the bottom).
### THE CONTRACT — copy this, it is the whole API
```js
import { createAddresses, getTownCache } from './js/citygen/index.js';
const cache = citygen.getTownCache(TOWN); // NEW export — see "how to get the cache" below
const addr = citygen.createAddresses(plan, cache /* or null */);
addr.streetOf(edgeId) // → "Templeton Street" | null — a real street name, or null. NEVER a guess.
addr.localityOf(shopId) // → { shopId, street, district, block, side, label } | null (null = unknown id)
addr.cohort(predicate) // → shopId[] predicate(locality) → bool; ids ascending, deduped
addr.streets() // → [{ name, edges:[edgeId], shops:[shopId] }] sorted by name
addr.stats() // → the measurement (see below) — assert on this, don't trust a claim
```
`localityOf` fields:
| field | type | on a real town | on the synthetic |
|---|---|---|---|
| `street` | `string \| null` | `"Barker Street"` (97.8% of corpus shops) | **always `null`** — there are no named ways |
| `district` | `string` | always `'mainstreet'` (`plan_osm.js:305` adds exactly one) | one of the six `DISTRICT_KINDS` |
| `block` | `int` | `lot.block` | `lot.block` |
| `side` | `'north'\|'south'\|'east'\|'west'` | the cardinal the lot sits on off its `frontEdge` | same |
| `label` | `string \| null` | the street name, or `null` if unresolved | `"the market end"`, `"the arcade"`, `"the north end of the backstreets"` |
### THE ONE RULE: **NEVER BRANCH ON TOWN TYPE**
`label` is the field you print. It is filled by a **different supplier** on each kind of town — real
streets from the cache's named ways, district phrases from `district.kind` + block — and that is the
entire point of the module. Do not test `plan.source`, do not test for a cache, do not special-case
the synthetic. **Print `label`. Group by `block`. Ask for `street` only when you specifically need a
proper name and can honestly render `null`.** The moment one consumer branches, every feature built on
this has to be written twice and the synthetic half rots.
`label` is `null` in exactly one situation: a real-town shop whose frontage resolves to no named way
(**27 of 1,219 corpus shops, 2.2%**). Handle null once, on both town types, and you are done.
### How to get the cache (this was genuinely unreachable before this round)
`web/index.html:129` hands the fetched JSON to `registerTownCache(TOWN, …)` and **drops its own
reference**, so the `roads[]` — and their 17,835 names — were unreachable to everything downstream.
New additive read-only export, no new state, no shell change needed:
```js
citygen.getTownCache(key) // → the registered cache, or undefined
```
`undefined` for an unknown key and for the three checked-in `osm_fixture.js` towns (melbourne /
katoomba / silverton are fixtures, not caches — they carry no `roads[]`). **`createAddresses(plan,
undefined)` is legal** and degrades to district labels rather than throwing — which is exactly the
failure a consumer would never notice, so:
> **Assert `addr.stats().supplier === 'ways'` wherever you require real street names.** Selfcheck does
> this for all 23 caches. A caller that forgets the cache gets `supplier: 'district'` and zero street
> names — visible in `stats()`, never a silent wrong name.
### → LANE B (39.2, the fog): what you can consume today
- **Map street labels.** `addr.streets()` gives you `{ name, edges[], shops[] }` — the `edges` are plan
edge ids you already draw, so you can place a label along an edge run without a second lookup.
**Bring your own dedup**: a real town has **65319 distinct street names** (median 192; adelaide 313,
bendigo 319) over 30,986 edges. Labelling every edge is 322 `fillText` calls a frame, which is the
CPU pass your own note already flags — key the offscreen static cache off the discovered set.
- **HUD tooltip.** `addr.localityOf(shop.id).label` is the template literal. It is a plain string or
null; there is no formatting to do and no town-type test to write.
- **`side` is a real clue term and it discriminates**: the two sides of any street always map to two
opposite cardinals, so `"the north side of Barker Street"` is a stable, speakable cell.
- **Cost**: `createAddresses` is **≤27 ms per real town** (worst bendigo, 2,593 edges × 1,408 named
ways), **0.3 ms on the synthetic**, once, at plan time. Zero draws, zero triangles, zero plan fields.
### → LANE C (39.3): the shop you are digging in now has an address
`localityOf(shop.id)` works for any shop id on the plan, interiors included — it reads `lot.frontEdge`,
which every shop already has. If the op-shop bin's find needs to be written down (`"a $2 crate, Barker
Street"`), that string exists now and it costs nothing. **Do not import `address.js` into an interior
hot path** — build it once alongside the plan and pass the result in.
### The measurements, so nobody has to take my word for it
Fable's binding facts re-derived independently, and **all three land exactly**: **19,132 road ways,
17,835 named (93.2%)**; median plan-edge-sample → nearest named way **0.030.90 m** (worst katoomba);
**1,192 / 1,219 corpus shops (97.8%) resolve to a street name.**
| | edges named | shops with a street | distinct names | | | edges named | shops | names |
|---|---|---|---|---|---|---|---|---|
| adelaide | 89.9% | **80.9%** (89/110) | 313 | | marrickville | 97.5% | 100% (26/26) | 236 |
| bendigo | 98.9% | 100% (35/35) | 319 | | newcastle | 98.7% | 100% (34/34) | 158 |
| bowral | 95.4% | 100% (30/30) | 71 | | newtown_godverse | 93.1% | 100% (67/67) | 306 |
| braddon | 97.1% | 100% (19/19) | 142 | | newtown | 93.1% | 100% (67/67) | 306 |
| brunswick | 98.8% | 100% (92/92) | 273 | | northbridge | 96.7% | 98.3% (57/58) | 166 |
| castlemaine | 98.0% | 100% (22/22) | 141 | | redhill_godverse | 99.1% | 100% (35/35) | 291 |
| darwin | 93.1% | 100% (12/12) | 90 | | redhill | 99.1% | 100% (34/34) | 291 |
| daylesford | 97.7% | 100% (26/26) | 65 | | westend | 92.4% | 100% (41/41) | 159 |
| fitzroy | 92.8% | 99.3% (138/139) | 185 | | fremantle | 97.5% | 100% (79/79) | 134 |
| geelong | 98.4% | 98.4% (61/62) | 226 | | glebe | 97.8% | 100% (51/51) | 225 |
| hobart | 98.9% | 100% (78/78) | 215 | | katoomba | 99.0% | 100% (72/72) | 165 |
| launceston | 96.0% | 90.0% (27/30) | 192 | | **CORPUS** | **96.4%** | **97.8%** (1192/1219) | — |
**The failure modes, named.** All 27 unresolved shops front an edge whose way genuinely has no name in
OSM: **adelaide 21 `arcade`** (adelaide's arcades are the town's retail spine and OSM leaves the
footways unnamed — this is why it is the corpus worst at 80.9% and it is honestly worst, not a bug),
fitzroy 1 `arcade`, geelong 1 `arcade`, launceston 1 `arcade` + 2 `lane`, northbridge 1 `arcade`.
**`null` is the honest answer there** and the module returns it rather than borrowing a neighbour's
name. A shop with `street: null` also gets `label: null`; selfcheck asserts that pairing per town.
### Two things I did NOT do the way the synthesis proposed, both with numbers
**1. The shift is recovered EXACTLY, not voted for.** `plan_osm.js:502-506` centres the town on the
origin, so plan node = `r2(projected way point) + shift`. The synthesis recovers that by centroid-align
+ nearest-waypoint vote. **I implemented that first and it returns the WRONG shift on 5 of the 23
towns** — braddon, fremantle, hobart, northbridge, westend. The cull of shopless islands
(`plan_osm.js:407-414`) drags the plan's centroid off the cache's, the initial alignment is tens of
metres out, and the vote then converges *confidently* on a wrong offset. **A wrong shift is not a
degraded answer — it renames every street in the town.** So: take one plan node, treat every
(node way point) difference as a candidate, and keep only candidates under which further nodes also
land exactly on way points. Two or three nodes collapse ~25,000 candidates to one. **Exact on 23/23,
nothing to tune, and it returns `null` rather than a guess when it cannot prove an answer.**
**2. The resolver samples five points along an edge, not the midpoint.** "Nearest named way to the
edge midpoint" cannot tell a street from the street that *crosses* it — at an intersection both are
~0 m away and it picks one by a hair. A name must now be within tolerance at **all five** samples
(t ∈ 0.15…0.85, endpoints excluded because they *are* the junctions). A crossing street is close at
one sample and far at the rest.
### The tolerance is 8 m — and here is the number that actually sets it
Corpus shops that get a street name: **2 m → 85.2% · 4 m → 94.8% · 6 m → 97.4% · 8 m → 97.8% ·
12 m → 99.1% · 16 m → 99.2%**. But coverage is the wrong quantity to tune on. Checked against an
independent **way-membership** resolver (below), the number of the 884 shop-bearing edges that resolve
to the *same name* is **857 at 6 m — and 857 at 8, 12, 16 and 24 m.**
> **Correctness saturates at 6 m.** Every metre past 6 buys exactly one thing: a name attached to a way
> OSM left unnamed (0 such edges at 6 m, 4 at 8 m, 12 at 12 m, 14 at 24 m). **It can never buy a
> correction.** Raising this knob to chase coverage is always borrowing a neighbour's name and should
> be argued as that, never as accuracy.
8 m keeps the four borrowings, all arcade footways inside a named mall (bowral's *Corbett Plaza* ×2,
fremantle's *High Street* ×2), where the borrowed name is what a person would actually say.
`createAddresses(plan, cache, { tolerance: 6 })` is the strictly-conservative setting at identical
correctness.
### A WRONG STREET NAME IS WORSE THAN NO STREET NAME — so it is gated by an independent resolver
`address.js` asks "which named way is this edge ON?" by **distance**. The selfcheck's control asks the
same question by **point membership**: a plan edge is built from two points of ONE simplified way
(`plan_osm.js:388-394`) and plan nodes are those points snapped on a 3 m lattice, so the true way must
*contain* both endpoints' snap keys. Two genuinely different questions over the same data — the R27
cross-convention pattern, not a restatement.
> **Over the 884 shop-bearing edges — the only edges `localityOf` reads — 857 agree and ZERO disagree.**
> Corpus-wide, 4 of 30,986 named edges disagree, all four are 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 of the four carries a shop.** That count is PINNED at 4, not asserted `> 0` — a
> control allowed to drift toward zero is how a gate goes vacuous, which is the R37 lesson.
**And the resolver degrades to null, not to a wrong name.** Measured: feed it a shift wrong by 30 m and
castlemaine/katoomba/adelaide return **0** street names rather than a town full of wrong ones — the
five-sample rule simply finds nothing within tolerance. Feed it a shift wrong by 3 m and adelaide's
coverage *rises* from 89 to 98 shops while being wrong, which is the whole reason the shift arm is
exact-or-nothing and **coverage is never used as a proxy for correctness.**
### The synthetic supplier, and its honest limit
12 label cells over 493 shops, **median 41, max 147** ("the south end of the main street"). That is
coarse, and it is the same structural fact §8 of the charter already records — the synthetic is a
magnificent strip and not a town you can learn. **`block` is the finer cell: 32 shop-bearing blocks,
median 14, max 41.** Consumers that need precision should `cohort(l => l.block === n)`; consumers that
need prose should print `label`. Both work on both town types, which is the point.
### GOLDENS: NOTHING MOVED. ZERO RE-PINS.
`address.js` never writes to a plan, and selfcheck asserts that byte-for-byte per town
(`JSON.stringify(plan)` before and after the call). Every pinned hash in `selfcheck.js` is untouched:
synthetic `0x5f76e76`, gig `0xec7a2d39`, the 3 osm fixtures, all 23 `REAL_TOWN_GOLDENS`, all 23
`REAL_TOWN_GIG_GOLDENS`. **Selfcheck 157,407 → 157,647 (+240 checks), ALL GREEN, fingerprint unchanged.**
### The one-line upgrade: TAKEN, and it is genuinely that clean
`plan_osm.js` now writes `norm.shift = { shx, shz }` — onto the **normalization log**, which reaches
the caller only through the existing `opts.report` sink at the next line. **Never onto the plan
object.** `JSON.stringify(plan)` is byte-identical by construction; the 46 pinned goldens above are the
proof, not the argument.
**But it is not a dependency, and it should not become one.** `createAddresses(plan, cache)` recovers
the shift itself, exactly, on 23/23. The report line earns its keep as a **cross-check**: the selfcheck
compares `plan_osm`'s published shift against `address.js`'s independently recovered one, two
derivations that share no code, and fires on a **0.01 m** disagreement. Without that line, the recovery
would only ever be checkable against itself — which is precisely the vacuous-gate species R37 was
about. `opts.shift` is accepted as a fast path and `stats().shiftCheck` reports `'agree'` / `'DISAGREE'`.
## Round 37 (2026-08-03) — v8 WAVE 0 §0.1: THE KERB, and the gate that was green over a wrong street
### → Lane B (URGENT, you are consuming these RIGHT NOW): the corridor law, stated plainly
**The exports are correct and you should consume them as-is. I changed no semantics this round.**
`roadWidth` / `vergeBand` / `poleOffset` in `web/js/core/registry.js:217-240`, re-exported from the
barrel `citygen/index.js:18`. But **four things about them will bite you**, and three of them are things
the round instructions state in a way that is true only for the synthetic town:
**1. `roadWidth` is a TRAP OF A NAME, and it is why this bug existed.** It returns a **full width**, and
`edge.width` is *also* a full width — but `edge.width` is the **corridor** (road + both footpaths) and
`roadWidth(e)` is the **carriageway alone**. Two full widths, similar names, nine metres apart on a main.
Your `ground.js` painted the road quad `e.width` wide for 34 rounds because of exactly this. The kerb is
at **`roadWidth(e)/2`**, i.e. `vergeBand(e)[0]`. Prefer `vergeBand(e)[0]` over `roadWidth(e)/2` in new
code — same number by construction, but it reads as what it is and it survives a change to the split.
**2. `lane`'s verge band is NOT `[2,2]`.** The round instructions say it is; that is the **synthetic**
lane (width 4). Measured over 31,039 edges across synthetic + all 26 town caches: **58 lane edges, 2 at
`[2,2]` (synthetic, width 4) and 56 at `[4,4]` (real, width 8).** The invariant is not a literal — it is
**`outer === inner` ⇒ the edge has no footpath at all** (a lane is all carriageway by law). **Branch on
`outer <= inner`, never on the number 2**, or you emit a zero-width strip on the synthetic town and a
wrong-width one on every real town.
**3. `arcade` is `roadWidth 0` ⇒ band `[0, width/2]` — the WHOLE corridor is footpath.** 1,102 arcade
edges in the corpus (1 synthetic at width 5, 1,101 real at width 6). Skip the road quad *and* both kerb
strips: at `roadWidth 0` your two kerbs are coincident on the centreline and will z-fight.
**4. The inward extension is never negative — measured, not assumed.** `roadWidth(e) > e.width` would
make your inward footpath strip negative-width. **0 occurrences in 31,039 edges.** It is nonetheless
*reachable*: `side` returns a flat 6 for any width < 18, so a `side` edge narrower than 6 m would invert.
No such edge exists in any shipped cache. Guard it anyway (`Math.max(0, …)`) — it costs a call.
**The whole contract, exhaustively — the entire corpus reduces to nine (kind, width) pairs:**
| kind | `edge.width` | `roadWidth` | `vergeBand` | footpath each side | `poleOffset` | count | where |
|---|---|---|---|---|---|---|---|
| `arcade` | 5 | 0 | `[0, 2.5]` | 2.5 | 1 | 1 | synthetic |
| `arcade` | 6 | 0 | `[0, 3]` | 3 | 1 | 1,101 | real |
| `lane` | 4 | 4 | `[2, 2]` | **0** | 2 | 2 | synthetic |
| `lane` | 8 | 8 | `[4, 4]` | **0** | 4 | 56 | real |
| `main` | **24** | 10 | `[5, 12]` | **7** | 6 | 7,446 | **real** |
| `main` | 28 | 10 | `[5, 14]` | 9 | 6 | 6 | synthetic |
| `side` | 12 | 6 | `[3, 6]` | 3 | 4 | **22,415** | both |
| `side` | 14 | 6 | `[3, 7]` | 4 | 4 | 8 | synthetic |
| `side` | 20 | 8 | `[4, 10]` | 6 | 5 | 4 | synthetic |
**Two corrections to the charter's numbers, both measured.** The charter and the audit both describe
"the 9 m band" and "18 m of every main street stops being painted as road". **That is the synthetic town
only** — its main is 28 m wide. **On every real town a main is 24 m**, so the band is **7 m** and 14 m of
each main stops being road. And the dominant edge in the whole corpus is not a main at all: **`side` at
width 12 is 22,415 of 31,039 edges (72%)**, with a **3 m** footpath. Size your tri estimate off that, not
off the 9 m headline. (The +0 draws claim is yours to prove; I have no visibility into your merge.)
**On `?verge=0`:** keep it reverting to *exactly* the pre-ruling geometry — road quad at the full
`e.width`, kerb at `e.width/2 + 0.12`, footpaths outside the corridor. My selfcheck now pins the count of
posters standing on that geometry at **exactly 14** on the default boot, and Lane F's control leg asserts
the same failure at runtime. If `?verge=0` drifts into something that is not the pre-ruling street, three
gates in two lanes stop meaning what they say.
### → Fable / Lane F: the vacuous gate was mine, and it is now falsifiable — with the proof
**The audit's claim against my lane is CONFIRMED, exactly, and I can add a number it did not have.**
Default boot (synthetic, golden seed 20261990, gigs default-ON since R16): **14 posters. All 14 stand
inside `e.width/2` — the bitumen B paints. Zero stand inside the ruled kerb `roadWidth/2`.** The audit's
"all 14 gig posters are standing in the road" is precise, not rhetorical.
*One honest refinement it did not make:* the 14 are not one population. **12 are spine posters seated at
`poleOffset` = 6 m, i.e. 8 m inside painted bitumen — genuinely mid-road.** The other **2 are frontage
posters clipping the corridor edge by 0.06 m** (13.94 m of 14, and 6.94 m of 7) — they sit on their
venue's facade plane, and `FACADE_PROUD = 0.06` pushes them a fingernail past it. Both classes are inside
the paint and the fix lands both, but "14 posters standing in the middle of the road" would be an
over-claim: **12 are, 2 are 6 cm over the kerb line.**
**Why the gate was green.** My placer seats spine posters via `poleOffset(e)`, which is *derived from*
`roadWidth`. My selfcheck asserted clearance against `roadWidth(e)/2`. **The gate and its subject were
the same function agreeing with itself** — the R27 cross-convention lesson recurring inside the very file
that documents it (`selfcheck.js:33-40` already says a self-consistent check over a self-contradicting
spec passes under either convention). It could not see B's geometry because it never read a number that
came from B.
**The correction (`selfcheck.js`, districtInvariants), three arms — arm 3 is the control:**
1. **No poster on a carriageway** — now measured through `vergeBand(e)[0]`, the published law itself, the
same call B's ground consumes. Floors unchanged (frontage ≥ `POSTER_CLEAR`0.02, spine ≥ 0; a `lane`
pole seats exactly *on* its kerb, so 0 must stay legal).
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 standing in a paddock; this asserts it stands on paint B
actually lays. Frontage posters exempt **by measurement, not convenience**: on 18 of 108 gig plans a
frontage poster legitimately stands past every corridor edge (worst **3.98 m**, launceston_real),
because `plan_osm.js:459` sets a real-town lot back at `max(KERB, roadWidth/2 + FOOTPATH)`. Spine
violations over the same corpus: **0/108**.
3. **THE CONTROL** — count the posters that would stand on bitumen under the **pre-ruling** ground (B's
road quad at full `e.width`; the `?verge=0` geometry, replicated in `preRulingRoadPosters()` and
pinned to the **flag**, not to a line number that will drift as B edits). Assert it is **> 0** on every
gig plan, and **exactly 14** on the default boot. Arms 12 prove nothing unless the two geometries are
distinguishable; this is what makes them non-vacuous, and pinning 14 rather than `> 0` alone is
deliberate — *a control allowed to drift toward zero is how a gate goes vacuous in the first place.*
Measured range across 510 plans: **min 3, max 212, never 0.**
**THE FALSIFIABILITY PROOF — the new gate fails on the old street, the old gate cannot.** Simulated
`?verge=0` honestly: switched `vergeBand` to the pre-ruling reading (`[width/2, width/2]` — whole
corridor is carriageway) in a scratch copy of the tree, and **pinned `poleOffset` to the true band so the
placer is byte-identical** — `?verge=0` is a *render* flag, the paint changes and A's plan does not.
Fingerprint `0x5f76e76` and every gig golden stayed green in both runs, so the only variable is what the
gate reads. Result:
| gate | under the ruled geometry | under `?verge=0` geometry |
|---|---|---|
| **corrected (R37)** | ALL GREEN 157,407/157,407 | **✗ FAIL — 1020 failures** (2 arms × 510 plans) |
| **pre-R37 (HEAD)** | ALL GREEN 156,352/156,352 | **✓ ALL GREEN 156,352/156,352** |
The old gate is not merely wrong about the street — it is **structurally blind to it**. No change to
where the road is painted can make it fail. That is the vacuous-gate species in one table.
**GOLDEN VERDICT: NOTHING MOVED. Zero re-pins.** Fingerprint `0x5f76e76`, `GIG_GOLDEN 0xec7a2d39`, all
25 base town goldens and all 24 `REAL_TOWN_GIG_GOLDENS` green and untouched. Correct, and predicted: the
only generator-adjacent file I touched is the *selfcheck*, which validates output and never produces it.
Check count **156,352 → 157,407** (+1,055 new assertions), runtime 10.3 s.
### → Fable: v8 DEBT LEDGER — filed, not fixed (four entries, three of them the audit's)
**D1 — `chunkIndex`: two implementations, THREE divergences, and a header comment that is false.**
A's `citygen/plan.js:414` `chunkIndex(plan, chunkSize = CHUNK)` vs B's `world/planutil.js:37`
`chunkIndex(plan)`. The audit named one divergence; there are three:
(a) **bucketing** — A puts a lot in every chunk its rotated-AABB touches, B puts it in the chunk
containing its centre only; (b) **container** — A returns a plain object keyed `"cx,cz"`, B returns a
`Map`; (c) **element type** — A's buckets hold **ids** (`lots:[lot.id]`), B's hold **objects**
(`lots:[lot]`). `planutil.js:4` says *"If Lane A later ships an official chunkIndex(plan) we swap the
import; the shapes match."* — **the shapes do not match, on any of the three axes**, and that sentence is
the trap: it invites exactly the one-line swap that would break the streamer three ways at once.
Consumers are split today: **the runtime streamer uses B's** (`chunks.js:7,16`), **the debug map uses
A's** (`map.html:89,315`), and `sim.js:48-55` re-derives the key math a *third* time, locally, on purpose
(so the standalone sim page needs no Lane B module) — that third copy is defensible and documented.
*Canonical:* **B's for the runtime** (centre-bucketing is what a streamer wants — an AABB-spanning lot
would build in up to four chunks). A's AABB form is right for *coverage* questions, which is what my own
selfcheck asks of it (`selfcheck.js:130-155`). **Cost:** rename, not reconcile — give them distinct names
(`chunkIndexByCentre` / `chunkIndexByCoverage`), delete the false comment, and state which question each
answers. Half a round. **Owner: B** (the file and the runtime consumer are B's); A supplies the rename.
**D2 — `isOpen`: three real implementations plus an ad-hoc fourth site, and A's canonical one has ZERO
importers.** Canonical: `citygen/plan.js:28`, half-open `open ≤ hour < close`, **no hours ⇒ open**,
re-exported at `citygen/index.js:10`. **Nobody imports it** — grep-verified across `world/`, `citizens/`,
`interiors/`. The live one is `web/index.html:454`, published to `window.PROCITY.isOpen` (`:603`) and
consumed by `world/audio.js:270` and **14 call sites across `tools/`** (`flags_check.py`, `shots.py`,
`v2_tour.py`, `qa/interior_scale_check.py`). **It has a real semantic divergence:**
`shop && shop.hours && …` returns **falsy for a shop with no hours**, where A's law says *no hours ⇒
always open*. Third: `citizens/sim.js:363` `_openAt(hours)` — A's semantics, but its own clock
(`(this.timeOfDay % 1) * 24`) instead of `currentHour()`. Fourth site, ad-hoc rather than a rewrite:
`world/dbg.js:241,279,286` inline hour predicates (`hours[1] >= 22`, `hours[0] <= 12 && hours[1] > 13`)
for screenshot bookmarks. **No shipped shop lacks `hours`** (my selfcheck asserts every shop has sane
hours), so the divergence is **latent, not live** — it fires the day a venue, cart, stall or civic lot
carries no hours, which v8's whole slate is about adding. **Cost:** the shell imports A's `isOpen` and
passes `currentHour()`; sim keeps its clock but calls the same predicate; ~5 lines, 1 line of risk.
**Owner: F** (the shell is F's; A's export is already correct and needs no change).
**D3 — two shop registries, and the venue signs are the visible bite.** A's `core/registry.js:32`
`SHOP_TYPES` = 12 types with `sign:{bg,fg,accent,style}`; B's `world/fixture_plan.js:28` `SHOP_REGISTRY`
= **9 types**, missing `pub` / `band_room` / `rsl` entirely, with only `signBg`/`signFg`. Its own header
(`:26-27`) says *"Lane A owns web/js/core/registry.js; it hasn't landed yet, so Lane B carries the slice
it needs"* — **registry.js landed in R13; that comment has been stale for 24 rounds.** The bite is one
line: `buildings.js:344` `const reg = SHOP_REGISTRY[shop.type] || SHOP_REGISTRY.opshop;` — so every gig
venue renders **op-shop** signage (`#2c2820` on `#f0e2c0`) instead of the registry's gilt / stencil / club
palettes, on 24 venues per town across 27 towns, since R13. Confirmed as the audit reported it.
*Also drifted, but harmless:* the facade pools differ on four types (`toy` 3→2, `book` 3→2, `pawn` 4→3,
`stall` 2→1) — **dead config on the live path**, because `buildings.js:405` takes the skin from
`shop.facadeSkin` (A's plan field); B's pool is only reachable on the `fixture_plan` fallback when citygen
is absent (`index.html:138`). **Cost:** `buildings.js` imports A's `SHOP_TYPES` and reads
`.sign.bg`/`.sign.fg`; `SHOP_REGISTRY` shrinks to the fallback-only table it actually is, with an honest
header. Sign atlas colours change on venue lots ⇒ **a screenshot golden may move; not a plan golden.**
Half a round. **Owner: B**, with F re-pinning any shot.
**D4 — NEW, found this round, and it is the same disease one layer down: the poster gate's inner bound
is NEAREST-EDGE, and an `arcade` edge camouflages a carriageway.** Both my selfcheck's arm 1 and my
placer's `gigs.js:304 onCarriageway()` resolve the *nearest* edge and test against that one edge's kerb.
The nearest edge is not always the binding one. Measured over 108 gig plans: the nearest-edge rule reports
**0 violations**; an **all-edges** rule finds **8 plans** (all real towns) where a spine poster stands
inside a *non-nearest* edge's carriageway — **worst 1.78 m inside a main on northbridge_real seed 42**,
where the nearest edge is an `arcade` at 3.22 m whose `roadWidth` is **0**, so it "clears" by 1.45 m and
hides a main whose kerb is 5 m away. `arcade` edges are actively camouflaging: kerb 0 means they win the
nearest-edge test and always pass it. **Zero occurrences on synthetic, including the default boot** — the
kerb ruling is unaffected. **Cost, measured decision-grade, not estimated:** I built the all-edges variant
and diffed the whole corpus — **synthetic 0/4 plans move** (so `0x5f76e76`, `GIG_GOLDEN` and the classic
covenant are untouched), **11 of 26 town gig goldens re-pin**, and **poster counts do not change at all**
(the R20 cap re-subsamples a smaller candidate pool to the same size). So: one placer edit, one gate edit,
11 hash re-pins, no budget change, no visual count change. **Deliberately NOT done this round**: it moves
plan output for a reason that is *not* the kerb ruling, in a shared tree, while F is gating the matrix —
exactly the scope creep a floor round forbids, and it would muddy the "did the kerb ruling move a golden?"
signal, which must stay a clean **no**. **Owner: A. Recommend R38, one small item, self-contained.**
## Round 30 (2026-07-18) — THE ROTATION BOUNDARY VERIFY (ledger #6): **HOLDS**
### → Fable / F: `day` rides RUNTIME streams only — plan generation cannot see it. Measured, not read.
**Verdict: the seeded-rotation boundary HOLDS.** F's `day` salt (207bcff) touches exactly three runtime
seams and nothing upstream: (1) `interiors.js:178` `ctx.stockSalt` from `opts.stockDay`, consumed ONLY in
`layout.js:417` as a suffix on the `stk-*` stock-pick stream labels (room/fittings/audio streams untouched
`stockSalt` has exactly those two references in the tree); (2) the dig `binSeed` key `@d` tag in
`interior_mode.js` (same null-gating: no game / day 1 / real-sourced ⇒ absent); (3) `gig_state.setWeekNight`,
which only **selects** a night from `plan.gigs` — the whole 7-night week is generated at plan time
(`gigs.js:227` `for night < NIGHTS`) from citySeed streams; day picks, never feeds.
**How measured (three falsifiable legs):**
1. **Grep-zero over the proven glob:** `citygen/**` = exactly 8 files, no subdirs, 2,546 lines (census
printed, ls-verified). `grep -riE 'day|stockDay|stockSalt'` → zero identifier hits (only lexical noise:
fixture name "Heydays", shop name "Sunday Stall", "today" in comments, `daylesford_real` golden keys).
Non-vacuous: the same pattern hits `stockDay` 4× in `world/interior_mode.js`.
2. **Structural absence at the entry:** `generatePlanFor(seed, source, opts)` consumes `opts.{town, gigs,
customBands, cache, report}` only (grepped every `opts.` in citygen); both shell call sites
(`index.html:127`, `map.html:315`) pass `{town, gigs, customBands}` — no day exists to pass.
3. **Day-inertness, byte-measured:** hostilely injected `{day, stockDay}` into `generatePlanFor` anyway —
plan JSON hash identical for day-absent / day 1 / day 37 across 3 seeds × both sources (6/6 legs,
e.g. synthetic 20261990 `0xec7a2d39` all three ways). Even a rogue caller can't leak day into a plan.
**Goldens frozen:** selfcheck **ALL GREEN 156,352/156,352**, fingerprint `0x5f76e76` (the covenant),
gig golden `0xec7a2d39`, all town pins (melbourne/katoomba/silverton + real caches) green, no re-pins.
Pre-existing informational ⚠ (redhill "Empire Revival" lift-drop) unchanged, not mine, not new.
## Round 27 wave 5 (2026-07-17) — THE FACADE FIX: it was every town, not just the real ones
### → F: **your files hold the old covenant hashes — they are now wrong, and only you can fix them**
This is the one thing blocking your re-gate. `scaffold_check` is **RED** until you re-pin, and I can't
touch your files. Exact values (verified byte-identical across two fresh processes):
| your file | constant | old | **new** |
|---|---|---|---|
| `tools/flags_check.py:30` | `GOLDEN_HASH` | `0x3fa36874` | **`0x5f76e76`** |
| `tools/flags_check.py:31` | `GIG_GOLDEN` | `0xb1d48ea1` | **`0xec7a2d39`** |
| `tools/qa/scaffold_check.mjs:231` | `TOWN_GOLDENS.melbourne` | `0x34cfdec0` | **`0x9c7e76b3`** |
| `tools/qa/scaffold_check.mjs:231` | `TOWN_GOLDENS.katoomba` | `0x0f652510` | **`0xf3aafec8`** |
| `tools/qa/scaffold_check.mjs:225` | the comment quoting all three | — | same three |
| `tools/qa/tour_v5.py:42`, `tour_shots.py:10` | captions quoting `0x3fa36874` | — | **`0x5f76e76`** |
Also: `flags_check.py:148` greps stdout for the literal `'3fa36874'` — that will silently stop matching.
### The finding: the ruling's premise was wrong — **synthetic was broken too**
The wave-5 ruling says *"synthetic follows B's canon so every golden has been green over a wrong product
since R18"*. Half right, and the wrong half is load-bearing. **Synthetic does not follow B's canon** — it
is broken identically, and so is the marched path, and so are the three checked-in fixtures. It is not
"every real town since R18": it is **every town, every path, since v1**. Measured in B's canon before
touching anything, then confirmed with my own eyes in the browser:
| path | before | after |
|---|---|---|
| synthetic (`plan.js`) — **the classic covenant** | **0 toward / 681 away** | 681 / 0 |
| marched-osm + the 3 fixtures | 0 / 80, 0 / 95, 0 / 19, 0 / 12 | all toward |
| real-roads (B's finding) | 0 / 72 katoomba | **72 / 72** — matches B exactly |
**The screenshot is the proof:** standing on the synthetic main street at `?classic=1`, lot 0 — three blank
grey boxes. Walk *behind* them, into the block, and there is NUMBAT PLAYTHINGS: sign, door, windows, awning,
facing the dirt. Same camera after the fix: CROWN TOYS & GAMES · MARBLES & KITES · NUMBAT PLAYTHINGS, a
strip of shopfronts facing the road. **637 of 681 synthetic lots had their facade pointing at no street at
all.**
**Why it survived**: all four sites are the same one-sign error — `atan2(outward)` where the intent was
`atan2(outward)` — and *every one of them says so in its own comment* (`plan.js:122` "facade faces back
down the outward normal, to the street"). The code never did what its comment said, and CITY_SPEC:71 blessed
the result. Nobody was wrong; the spec was.
### The covenant moved — John ruled it
Fixing synthetic necessarily moves `0x3fa36874`, frozen "forever" since R16. That was not mine to decide.
**John's ruling: amend the covenant, fix all paths** — the covenant is anti-drift machinery, not a defect
preservative. **51 goldens re-pinned in one commit** (classic `0x3fa36874 → 0x5f76e76`, gig
`0xb1d48ea1 → 0xec7a2d39`, 3 fixtures, 23 towns × base+gig). The amendment is recorded in CITY_SPEC as a
one-time, ruling-gated precedent — from this hash forward the law reads as it always did.
### A second bug the fix exposed (fixed here, counted)
`buildRealRoads` seated blocks at `KERB = 4 m` **from the centreline** — but a `main` carriageway is 10 m
wide, so its kerb is at 5 m. **Every main-street building has stood 1 m inside the road since R18.** The
footprint never moved; only which face is the front — so it was invisible until the shopfront turned around
and its poster landed in the traffic lane (1.07 m). Blocks now seat behind the kerb per A's own corridor
law (`roadWidth/2 + FOOTPATH`). Pack drops **6.4% → 6.6%**, counted, not hidden.
### The cross-convention gate — built to F's lesson, not to a spec line
F proved that measuring lots against CITY_SPEC:71's own convention is **self-agreement**: 72/72 "facing" over
a town of blank walls. So the new gate does not restate a spec line. It **replicates Lane B's own placement
maths** from `buildings.js` (`toWorld(lot, 0, y, d/2 + 0.02)`) and asks a physical question neither spec line
can fudge: *does the quad the renderer actually draws land nearer the street than the lot centre?* It fires
if A's `ry` sign drifts **or** if B's `zf` sign drifts. It would have caught this on day one of v1.
## Round 27 (2026-07-17) — v5.0: the docs freeze — CITY_SPEC v5 + the risk list judged (ledger #6)
### → F + Fable: **A's docs half is done and the tree is green; nothing of mine gates your tag**
selfcheck **ALL GREEN 156,212/156,212**, classic `0x3fa36874` / gig `0xb1d48ea1` frozen, **no golden moved**
(none should — tier 2 touches no plan, no cache, no atlas bytes). CITY_SPEC has its **v5 section** (the tier
ladder · `sourcing` vs `tier` · the three id-space fences · the manifest · Ruling 2 · A's one seam), and the
charter risk list is judged line by line in `V5_REAL_SHOP.md`.
### The risk list: I did NOT pre-retire your gate
Written in wave 2, so **risk #1 (latency/stutter at tier 2) is CARRIED, not retired** — your reader and
kill-the-server gate don't exist yet, and **nothing retires that line except the gate going green**. Retiring
it from a doc while the code is unwritten would be the exact species this epoch spent four holds catching: a
rule meeting a case that doesn't exist yet. #2 retired (measured), #3 retired (designed out), #4 **split**
write-back retired by John's ruling-as-mechanism, the `gone` read carried to your #4. Two new carried lines
(#6 orphaned atlas, #7 "real" is one shop wide).
### The gate that armed itself — worth seeing, since it's the law working unattended
My R25 orphaned-atlas gate **SKIPped** on `newtown_godverse` all through R25 ("no committed atlas keys to
this town" — subject absent, so it said so instead of passing). The moment G's 14 mint atlases landed, it
**started binding on its own** and passed: selfcheck went 156,211 → **156,212**, and that +1 is the gate
arming itself. Nobody edited it. That's what the vacuous-gate law buys — a gate that waits, in the open, for
its subject.
**Live near-miss on the books → G:** `redhill_godverse`'s lift drops keyed shop **2286 "Empire Revival"**;
you stocked its numeric neighbour **2287**. Nothing is orphaned and the gate is green — but **stocking 2286
would orphan it today**, and the hazard grows with every shop stocked. Carried as risk #6.
### One correction to the round doc's framing, for the record
The brief lists "the manifest" and "sourcing" as if `tier` were superseded. The artifacts say otherwise, and
**C's ruling is the precise one**: every atlas index carries **both**`tier: 1` (the ladder rung; a mint
atlas is *still* a static file, so still rung 1) **and** `sourcing: "real"|"mint"` (where the contents came
from). They are different axes. The spec documents both, because collapsing them is exactly how a mint crate
would come to read as real.
## Round 25 (2026-07-17) — v5.0-alpha: the sweep reconciled with Ruling 2, six goldens pinned (ledger #1)
### → F: **the tree is green — all 8 reds are gone, nothing of mine is pending** (the handshake)
selfcheck **ALL GREEN 156,211/156,211**; scaffold + consistency green; classic `0x3fa36874` and gig
`0xb1d48ea1` frozen. The six goldens are pinned: `newtown_godverse` base `0xcf69b387` / gig `0x1e266f60`,
`redhill_real` base `0x6046700f` / gig `0x673785ee`, `redhill_godverse` base `0xb2224939` / gig
`0xc6f80e18`. Cross-checked two ways before pinning — **byte-identical across two fresh processes**, and
the three values G stated independently in `86e2985` match mine exactly. **Your third attempt is unblocked
from my side.**
Two lines still print and **both are deliberate, neither is pending**:
- `⊘ SKIP real/newtown_godverse: orphaned-atlas check` — correct: the only committed atlas (3962749) keys
to Red Hill, so newtown has no subject. The vacuous-gate law says say so rather than pass quietly.
- `⚠ real/redhill_godverse: 1 keyed shop(s) dropped by the lift — 2286 "Empire Revival"` — an honest
disclosure, not a defect (see below).
### My R24 gate asserted Ruling 2 away — that's the whole of my 6 reds, and it was my error
I built the identity gate on an assumption that was true when I wrote it and false the moment G shipped:
that a godverse cache is *all* census shops. Ruling 2 makes it **mixed** — a keyed GODVERSE layer plus an
inherited texture layer — so `newtown_godverse` (18 keyed + 54 texture) and `redhill_godverse` (10 + 27)
are **healthy towns my gate called broken**. Every keyed assert now runs **per-layer, over the keyed subset
only**; unkeyed texture shops raise nothing. The validator's "N/M missing" warn is gone with it — the only
honest requirement left at that layer is that a `godverse+osm` cache have a **non-empty** godverse layer at
all (else it's an osm cache wearing the wrong `source`), which stays a warn for the R24 charter reasons.
### The uniqueness check was broken in both directions — rebuilt so it can actually fail
The R24 version was `new Set(all ids).size === shops.length`. Every `undefined` collapses into **one** Set
entry, so it (a) failed structurally on any mixed cache — 54 texture shops became one entry — and (b) could
**hide a real duplicate behind that same collapse**. It now runs over **defined ids only**. And per the
vacuous-gate law I made it prove it bites: `validateTownCache` rejects duplicates at the front door, so a
dup can never reach the plan that way — the test feeds one **straight to the lift** (`generatePlanOSM` does
not validate) and asserts the predicate catches it, plus a healthy mixed cache passes the same predicate.
A check nobody can watch fail is not a check.
### New — the orphaned-atlas gate (my R24 finding, now enforced instead of just filed)
The lift legitimately drops shops (overlap-resolve, counted since R19). Dropping a shop that **has a
committed atlas** means real stock keyed to a shop that isn't in the town — exactly the failure I filed in
R24, and one F's #7b would only catch at the *end* of a round, in a browser. It's now a node gate: for each
godverse town, every `stock_godverse/<id>/` on disk whose id belongs to that cache **must be seated**, else
FAIL. Subject-aware — SKIPs by name when a town has no atlas.
**It already found something real, benign today:** redhill's lift drops keyed shop **2286 "Empire Revival"**
(10 keyed in cache → 9 seated). It has no atlas, so nothing is orphaned and the gate passes. **→ G: if you
stock Empire Revival at the beta, it will orphan** — the shop isn't on the plan. Monster Robot Party is
safely seated (lot 8), which is the one that matters this round.
## Round 24 (2026-07-17) — v5.0-alpha: the identity field (ledger #2)
### → G: **`godverseShopId` is live — emit it, and mind the id-space trap** (the handshake for your #5)
The field is published and the lift carries it: put `godverseShopId` on shop entries in a
`source: "godverse+osm"` cache and it lands on `plan.shops[]`, which is what F's per-shop `base` reads.
Contract in `web/assets/towns/README.md`; `validateTownCache` enforces it; selfcheck gates it.
**The round doc says "the POS shop id IS the id — no mapping table". That's true for census shops and
false for the shop this whole alpha is about** — and your own docstring is what told me:
| shop | id source | equals `shop.id`? |
|---|---|---|
| census shops (`newtown_godverse`) | thriftgod `shop.id` (max 2992) | yes, incidentally |
| **Monster Robot Party** | **dealgod store 3962749** — not in thriftgod's census | **no** |
So I built it as an **opaque key**, not a census id: no equality assert, no derivation from `id`, no mapping
table. Both spaces ride it, and your disjointness (2992 vs 3962749) is what makes that safe. **Uniqueness is
an error**, because two shops keyed to one atlas is mis-stocking (charter risk #3).
**⚠ The thing that will bite you in wave 2 — `redhill_godverse` cannot contain Monster Robot from either
source. Measured, not inferred.** `godverse_town.py` takes **roads from the OSM donor** and **shops from
thriftgod's census**. The shop is in neither:
- **Not in the census** — your own R23 finding (that's *why* its id is a dealgod store id).
- **Not in E's `redhill_real` donor** — I checked the cache the moment it landed: **36 shops, zero of type
`record`, nothing matching /monster|robot/**. OSM's Red Hill doesn't know the shop exists.
So the adapter as written produces a Red Hill of census charity shops, and the atlas at
`stock_godverse/3962749/` keys to a shop that **isn't in the town** — F's #7b id-equality assertion would
fail, correctly, at the end of the round. **Inject it explicitly**: name, `type: "record"`, its real lat/lon
(147 Musgrave Rd), `godverseShopId: 3962749`. The schema is ready for it — that's exactly the case the
opaque-key design exists to serve.
**Second, cheaper trap on the same path:** `redhill_godverse` still has to clear **`MIN_TOWN_SHOPS` (6)**
from the census bbox alone. Newtown yielded 18; Red Hill is a Brisbane suburb and thriftgod's census is
thin there. If the bbox returns < 6, the cache is **rejected outright** (hard error, not a warn) and the
town won't exist. I can't measure that from here — no census DB on m3 — but you'll see it instantly. If it
bites, the count includes any shop you inject.
**E's donor is otherwise good for you:** valid, no warnings, **73.3 m median spacing** — half the warn floor,
a proper high street to hang the shop on.
### → F: the seam you need for #6a
`plan.shops[].godverseShopId` is the per-shop `base` key — read it off the **plan**, not the cache. It's
absent (not `undefined`) on every non-godverse shop, so `'godverseShopId' in shop` is a clean tier test:
present ⇒ tier 1 candidate, absent ⇒ tier 0 parody. That absence is also why **no golden moved this round**.
### → Fable: one recorded departure from the ledger, and why
Ledger #2 says `validateTownCache` **requires** the field on godverse shops. I implemented **missing → warn**
(malformed and duplicate → hard errors). Three reasons, and it's yours to overrule:
1. **The charter's determinism boundary***world = seeded, frozen, gated; stock = data tiers*. An error
inverts it: the **town** would fail to load because its **stock** identity is absent. Tier 0 must boot.
2. **Charter risk #3 names the remedy** — identity gaps "fail-soft to tier 0, never mis-stocked". Missing
*is* the soft case; duplicate is the hard one. They're different failures and now get different arms.
3. **Sequencing**`newtown_godverse` carries 0 ids today, it's G's to re-emit in wave 2, and I can't do
it here (their namespace; no census DB on m3). A hard error would red the tree for C and E through all
of wave 1 to say what the warn already says.
The requirement still has teeth, in the place R23 taught us to put them: selfcheck **fails on partial
keying** and prints an explicit **SKIP with the count** at zero — it cannot pass vacuously.
### Also: E's toowoomba retirement showed my spacing metric is gameable (worth knowing)
E tried the re-bbox before retiring and found it **fixed my metric while making the town worse** — median NN
395.7 → 89.4 m ("passes") but hub density 3/12 → 2/12, the pack's worst. My warn is **necessary, not
sufficient**: it catches scatter, and it can be satisfied by tightening the box around a scatter. D's hub
test is the complement, and E used both. Retired pins removed from both golden maps (a golden for a deleted
town can never fire — the vacuous-gate law one layer down). **Ballarat (255 m) is still flagged and still
E's call this round; if it moves, I pin it.**
## Round 23 (2026-07-16) — v5.0-alpha: the spacing warn (D's thin-tail finding as law)
### → E: **the warn is live — and it flags TWO towns, not one** (the handshake for your #4)

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +1,11 @@
# LANE C — interior audio contract (v3.0-FROZEN) → for Lane F + Lane B
# LANE C — interior audio contract (v3.1) → for Lane F + Lane B
> **v3.0-FROZEN** · 2026-07-16 · durable contract for Lane F + Lane B. Describes the shipped state; changes
> require a version bump.
> **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 }`, seeded per shop. Lane F's interior_mode just
plays it; Lane B's audio.js resolves the keys → files.*
*`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>`
@ -17,6 +18,7 @@ plays it; Lane B's audio.js resolves the keys → files.*
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
@ -39,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.8115.63 m**, and scaling
`r` to the room holds the door→stage proximity in a consistent **0.420.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.654.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):
@ -50,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.
## Deferred idea (v3.1+, not in the frozen contract)
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. A tidy v3.1 item: the
`userData.audioEmitter` hook + the two props. v3.0 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.

View File

@ -1,4 +1,11 @@
# LANE C — interiors contract (venues + real-town class mapping) → Lanes A/D/E/F
# 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).
@ -7,6 +14,29 @@
> (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(p1, 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 ~4650% 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
@ -223,55 +253,491 @@ mapping for the caches is this table.)
---
## 7. G2a — per-shop stock atlases: C's interior ceilings (v4.0, ledger #3) → Lane G + Lane E
## 7. Real-stock packs — THE RUNTIME IS THE AUTHORITY (v5.0-alpha, R24 ledger #1) → Lanes E/F/G
*Answering Fable's three priors + E's half (LANE_E_NOTES). **Verdict: all three priors hold — atlas-per-shop
is the right shape, and it's strictly BETTER for draws than today's type-packs.** The binding constraint is
**VRAM, not draws**, and E's dispose ask is granted: it's my seam and I own the fix.*
> **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).**
**1. Draw ceiling — ONE atlas per shop (hard cap 2).**
Real stock batches to **one draw per atlas** (shared material per atlas + per-item UV baked into geometry →
`batch.js` merges). A room shows exactly one shop's stock, so a per-shop atlas = **1 atlas = 1 draw** — better
than a type-pack, which can hit several atlases per room. **Rule: 1 atlas/shop; hard cap 2** (each extra = +1
draw, and the R9 buyable-shelf path wants one atlas per shelf to stay one-mesh/one-draw). A 60+ item shop
spills to the shared type-pack rather than fragmenting the room (E's (b) — agreed). *Headroom: `?stock=real`
worst rooms today are record 41 / book 58 / toy 57 draws against the ≤350 law; my R22 23-town soak worst is
159. Draws are not the constraint.*
### 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).
**2. Texture/VRAM ceiling — the real limit. E's dispose ask: GRANTED, and it's mine to land.**
E's audit is correct and the cause is a deliberate C design note (`stockpack.js:11`): atlas textures are
"shared/cached in the pack (**NOT per-room**), like depot GLBs". That is **right for 3 bounded type-packs**
and **wrong for unbounded per-shop atlases** (23 towns × N shops). The ceiling:
- **Resident: ≤2 shop atlases** (the room you're in + 1 for re-entry), **≤8 MB decoded**.
- **Atlas size: ≤1024² for a typical shop** (16 items @256² cell); **2048² only for a 60+ item shop**, and
prefer spilling to the type-pack. At 1024² RGBA ≈ 4 MB → ≤2 resident ≈ **8 MB** (vs E's measured **181 MB**
resident today — ~20× better, and bounded instead of accreting).
- **C lands the LRU cap + dispose in `stockpack.js`** (evicted shop's atlas texture + material disposed).
Type-packs keep today's shared cache (bounded, reused across rooms of a type) — unchanged.
- **Scheduling:** today's 181 MB accretion is **pre-existing**, and `?stock=real` is **opt-in**
(`index.html:196` — `params.get('stock')==='real' && !NOASSETS`), so it cannot touch the default boot and is
**not a v4.0-tag blocker**. I land the cap as **C's first v4.x item — before G2a ships**. G: build to the
ceiling; the loader will honour it.
### 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`**.
**3. Index schema — E's (c) confirmed VERBATIM, keyed per shop.**
`stock_shop_<godverseShopId>_index.json` = `{version, atlas_px, cell, atlases[], items:[{id,title,artist,price,
price_band,atlas,uv:[u0,v0,u1,v1]}]}`. `stockpack.js` consumes it unchanged (it already groups items by atlas
and shares a material per atlas). **UV origin stays top-left (image-natural) — C flips V for WebGL** (R7 law,
unchanged). Book cell 176×256 portrait is fine: **C reads `uv`, never `cell`**.
### 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.**
**4. Fail-soft + `?noassets` — unchanged law, confirmed.**
Index/atlas missing → `getStockPack` → null → **parody canvas + one warn** (R7, proven since). `?noassets`
fetches zero. A godverse town under `?noassets` stays silent-and-fine. `?stock=real` opt-in ⇒ G2a cannot
regress the default or classic boots.
> **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.)*
**5. Determinism — one ask back to G.**
The seeded pick per bin is C's and stays: same `shop.seed` → same crate contents, byte-identical revisits.
That indexes into `items[]`, so **G must emit `items[]` in a deterministic order** (sort by `id`) — otherwise
a re-emit silently reshuffles every crate. Same discipline as E's byte-identical cache re-runs.
**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.)*
**6. Buy-anywhere (R9) — compatible, no change.**
`buildBuyableShelf` merges per-item-addressable covers from ONE atlas → one draw (`noBatch`). A per-shop atlas
satisfies that naturally (better than the type-pack's seeded `atlasPool` pick). Parody = not buyable stays.
**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. Parody law — E's (d), unchanged and not C's to relax.** Metadata deterministically parody-transformed;
cover art real; prices real-or-seeded.
### 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** — 28 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
36 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.771.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 / 39 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` (12) and `book` (58) 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 | 011 | **225** |
| 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.

View File

@ -6,6 +6,160 @@ _rotOnly/head-bone normalize/upgradeStreetPeople). Measurements on the M3 Ultra
---
## ROUND 36 (wave 2.5) — CLASSIC KEEPS ITS PEOPLE: the DJ bodies gated out of `?classic`
**The ruling (Fable, on F's §36.4 held warn):** `b339402` (July 20) appended the 5 trellis'd DJ
bodies (`woman_dj_01/02/03`, `dj_techno_01`, `dj_phrtt_01`) to `PED_NAMES.normal` UNCONDITIONALLY —
so `?classic=1` fetched them and its seeded crowd pool widened 17→22, six epochs after the covenant
froze. Ruled option (b): the five gate OUT of classic's pool, mirroring the sit/look/dance `!CLASSIC`
clip gates. Classic is the v2 town INCLUDING ITS PEOPLE; the DJs walk every other boot.
**Mechanism (D's call — one file, `rigs.js`, per the wave-2.5 pathspec law):**
- `PED_NAMES.normal` is restored to the covenanted **base 17** (exact pre-July-20 list, order
preserved). The five move to a new `PED_NAMES.djs` extension list.
- `loadPedFleet` gains **`djs = dance`** — a new opt whose DEFAULT tracks the existing `dance` flag.
The shell already passes `dance: !CLASSIC` (index.html:194), so the classic-ness signal arrives
with ZERO shell edits: classic ⇒ `dance:false``djs:false` ⇒ no DJ fetch, 17-pool; default ⇒
`djs:true` ⇒ all 22. Separable for callers wanting clips without bodies (or vice versa).
- The roster is built `djs ? [...normal, ...djs] : normal` — the five APPEND, so no base slot is
renumbered in either mode: the default-boot 22-list is byte-identical to the pre-ruling list, and
classic's 17-list is byte-identical to pre-b339402. `pickRig` maps `r01 * 997 % pool.length`, so
pool LENGTH is crowd identity — 17 restores the exact pre-July-20 mapping, not merely the width.
- Known side effect (accepted, noted): `citizens_test.html` calls `loadPedFleet` with no opts ⇒
`dance:false` ⇒ its dev-harness roster shows 17+2, not 22+2. It is not a covenanted surface; a
future D round can pass `{djs:true}` there if the harness wants the five back.
**Measured (fresh contexts each, port-isolated no-store servers :8971 current / :8972 pre-b339402
shadow tree, seed 20261990):**
| check | `?classic=1` (gated) | default boot (gated) |
|---|---|---|
| DJ-GLB fetches | **0 of 5** (21 ped-GLB fetches total = 17+2 bodies + walk+idle) | **5 of 5** |
| pool | **17 normal** / 2 comical / 19 all | **22 normal** / 24 all |
| crowd census | 129 active, 129 named, **19 distinct — all base-17 comical, zero DJ** | 153 named street peds incl. **37 DJ-bodied** (techno 13 / w01 8 / phrtt 7 / w03 6 / w02 3) |
| gig crowd (F's organic proof) | n/a (gig layer absent) | **preserved** — pinned seed's venue crew (12) includes `dj_phrtt_01` + `woman_dj_02` in the crowd |
| console errors | **0** | **0** |
**The pre-July-20 identity check (the R29 lesson: measure, don't assume):** booted `?classic=1` on a
shadow tree whose `rigs.js` is `b339402^` (everything else current), same seed, same server law.
Roster arrays identical; the full 129-citizen census — every `(id, pedRoll, pedIndex, pedName)`
tuple — is **byte-identical** to the gated tree's classic boot. Classic's crowd is not just 17-wide;
it is the SAME crowd, ped for ped, as before July 20. (This held by construction — the five were
appended, so gating them off restores both list content and length — but it is now held by
measurement too.)
## ROUND 29 — v6 SPIKE 1: `look.glb` bound and wired — peds glance (wave 0)
*PROCITY-D, 2026-07-17. The spike finally reaches the game: E's `look.glb` (mesh-free, 65 joints, 6.5 s
'Look') is bound through the fleet gate and street peds now glance around during window-shop stops. `lean`
is not here and never was — E measured the stash (R28). Goldens untouched (clips sit outside every plan
hash); the synthetic anchor is R27's amended `0x5f76e76`, unchanged by me.*
### What shipped
- **`rigs.js``loadPedFleet(base, { sit, look })`** → `fleet.lookClip`, the exact twin of the R16 sit gate.
The shell passes `look: !CLASSIC`, so **`?classic` fetches nothing** — a raw fetch would have breached the
zero-fetch-delta covenant (E flagged this; the gate is the answer).
- **`makeActor({ …, lookClip })`** → a `lookA` action **played only on `setLooking(true)`**, so walkers,
placeholders and `?classic` never advance it. `setLooking` **crossfades** (0.3 s) rather than snapping the
way sit does — idle → idle-variant reads naturally, and there's no discrete replant for a fade to fight.
- **`sim.js`** — a dedicated `glance` rng stream (`rng(citySeed,'glance',id)`), rolled **always** (deterministic)
but only flipped when a clip exists ⇒ classic inert at the source. **Mutually exclusive with the bench-sit**:
the sit rolls first at 35 %, the glance takes 40 % of what's left. `RigPool` threads `lookClip`; release
snaps the glance off so no rig is pooled mid-look.
### The finding — I predicted this needed no re-plant, and the measurement said otherwise
I wrote in my own first draft that a *standing* clip needs no foot re-plant ("no hip descent to recover —
that trap was sit's alone"). **Wrong, and the numbers caught it:**
| lowest bone over the full loop | idle clip | **look.glb, no plant** |
|---|---|---|
| min → max | 0.04 → 0.00 (span **4 cm**) | 0.009 → **+0.205** (span **21 cm**) |
The ped **hovers up to 20.5 cm** (`mixamorigLeftToe_End` clean off the ground), drifting through the loop. It
is the sit trap wearing a different coat: `_rotOnly` drops the Hips POSITION track (rig-height independence),
so the clip's authored weight-shift **dip cannot move the hips — it comes out as the feet rising instead**.
Sit's one-time replant cannot fix it because **the float varies frame to frame**.
**Fix: `plantFeet()` — the per-frame twin of sit's replant.** Reset to the bind plant → measure the posed
feet → drop `inner` by float / scale. **Absolute (reset→measure→correct), never incremental, so it cannot
drift.** It reproduces the authored motion *exactly*: the hips dip relative to the ground instead of the feet
leaving it. Scale-aware like sit's. Foot bones cached once; cost paid **only** by glancing peds, and only on
frames their mixer actually ran (both mixer sites — the drummer's post-mix lean taught me that coupling rule).
### Proofs
| | before | after |
|---|---|---|
| foot float over the loop | **0.205 m** | **0.000 m** (pinned) |
| head yaw sweep (the glance itself) | — | **span 1.735** (0.85 → +0.89) |
| `?classic`: look fetch / `Look` action / glance flips | — | **none / none / 0** — inert at the source |
| identity signature | `-1,-1#0:6:0:1.777:1.171:1:-1` | **unchanged — no `glance` term** |
| sit ∧ glance simultaneously | — | **0** (mutually exclusive) |
| NaN | 0 | **0** |
| goldens | `0x5f76e76` | **`0x5f76e76`** untouched |
A glancing ped is **not** tagged `procitySeated`/`procityPosed` — it is a standing pose at standing stature,
so F's no-giants gate needs no exemption arm for it.
### Handshakes
- **D → F:** **the pose gate has its subject** — un-SKIP it. A fresh `?localdepot=1` boot shows a ped glancing
on the footpath, head sweeping, feet planted (screenshot taken). Classic/`?noassets` untouched.
- **Spike 2 readiness:** `plantFeet()` is the general machinery the brief wanted ("data, not new code") — the
riffle and the wall-lean will land as **clip + roll**, not new pose code. The lean's wall-contact placement
is the only genuinely new thing it will need.
- **Stale line spotted (A's file, A rests):** `selfcheck.js:301` still says *"the synthetic golden above stays
0x3fa36874"* while line 267 pins the R27-amended `0x5f76e76`. A comment, not a rule — but it is the same
species as the epoch's five holds (a line outliving the case that wrote it), so: flagged, not touched.
---
## ROUND 28 — v5.1 THE SWEEP: instrument LOD — **KILLED with the measurement** (ledger #4)
*PROCITY-D, 2026-07-17. The oldest tri item on the books (v3.1-era). The brief attached the wind-sway rule:
**"if the measured win is <20k tris in practice, KILL the item with the measurement and it leaves the
backlog forever."** It does not survive. **E: do not build the `_lo` variants — that work is now unnecessary.***
### The measurement
Instrument tri counts, parsed straight from the shipped depot GLBs (`web/assets/models/`):
| instrument | tris |
|---|---|
| electric_guitar | 14,000 |
| bass_guitar | 14,000 |
| drum_kit | 14,000 |
| guitar_amp | 14,000 |
| mic_stand | 14,000 |
| **band + amp, all on stage** | **70,000** |
On paper that clears the bar: a 34k `_lo` each would "save" ~52k. **But the rule says *in practice*, and the
brief named the reason — "interiors are close-quarters."** So I measured the rooms an LOD would have to fire in
(live, all three venue archetypes, `interiorMode` driven by hand since rAF is throttled):
| venue | room | **max diagonal** | room tris (no band) | draws |
|---|---|---|---|---|
| pub — The Exchange Hotel | 9.7 × 9.7 m | 13.7 m | 12,522 | 38 |
| band_room — The Vinyl Shed | 8.9 × 9.6 m | 13.0 m | 11,699 | 35 |
| rsl — Wangaratta RSL (biggest) | 12 × 9.4 m | **15.2 m** | 15,115 | 38 |
### The verdict: practical win = **0 tris**. KILLED.
**A player can never be more than 15.2 m from the band** — that is corner-to-corner in the *largest* venue, and
the stage sits at one end, so the real stage-to-player max is shorter still. An LOD is a *distance* trade: it
pays when the subject is small on screen. Inside a ≤15.2 m room the band is never small — it is the subject of
the gig, and of F's tour money shot. Any sane swap threshold (1525 m) **never fires**; an aggressive 8 m one
would fire only in half a room, at a distance where a 34 k `_lo` is *visibly* worse with no perceptual cover.
The win is not "small" — it is **structurally zero**, because the distance the LOD needs does not exist in this
game. **Per the wind-sway rule: killed, off the backlog forever.**
No budget motivates it either: a venue interior is ~1215 k tris, ~85 k with the band — comfortably inside the
200 k stress ceiling E's R22 tri diet restored, at 3538 draws against a ≤350 ceiling.
### Side-observation for E (NOT a proposal — no breach motivates it)
All five instruments are **exactly 14,000 tris**, which is a decimator target, not a measurement of the objects:
a **mic stand does not need 14 k**. If instrument tris ever *do* become the driver of a real breach, the lever is
a **flat re-decimation by object complexity** (mic stand → ~1 k), which beats any LOD because it needs no
distance — not a `_lo` swap. Filing the observation so the next tri hunt starts from it; **not** asking for work.
---
## ROUND 23 — v5.0-alpha: the GIG_RANGE chunk-neighbour fix (ledger #6, the carried v4.x item)
*PROCITY-D, 2026-07-16. My own filed finding, finally fixed — post-release, as Fable scheduled. Hot path,

View File

@ -1,5 +1,838 @@
# LANE E — cross-lane notes
## Round 39 — the magpie settled, the arcade measured, and three GLBs that were secretly glass ⟨v9 39.4⟩
Full provenance and method: `pipeline/AUDIT.md` § Round 39. Sheets:
`docs/shots/laneE/r39_magpie_ab.png` (the ruling picture) · `r39_transmission_fix.png` ·
`r39_arcade_skins.png` · `r39_arcade_kit.png`.
### → LANE C + LANE F + FABLE: THE TRANSMISSION PRE-PASS — **FIXED, PUBLISHED, GATED.** And it was **three** assets, not one.
Lane C measured that zeroing the bookshelf's transmission halves interior draws. The cause, with
vendored line numbers: **three.js runs a transmission PRE-PASS.** `three.module.js:16209`
`if ( transmissiveObjects.length > 0 ) renderTransmissionPass( opaqueObjects, … )`; `:16433` renders
the **whole opaque list** into a render target, so **every opaque draw in that scene is issued
twice**. The trigger is `material.transmission > 0` and nothing else (`:6890`), set straight from the
glTF by `GLTFLoader.js:1202-1204`. And at 1.0 it is not "a bit glassy": `:494`'s
`transmission_fragment` does `totalDiffuse = mix( totalDiffuse, transmitted.rgb, material.transmission )`
— **the diffuse term is replaced entirely by the refracted backdrop.**
**I scanned all 103 GLBs in the repo. THREE carried `transmissionFactor: 1`:**
| file | material | state |
|---|---|---|
| `procity_fit_bookshelf_01.glb` | `mtl_10218_Bookshelves_v1` | **shipped and hurting** — the one C measured |
| `procity_street_longbench_01.glb` | `lambert3SG` | **NOT WIRED** — the 5.86 m arcade/market bench the v9 charter asks for **by name**, and which my own R38 notes call a "true one-liner" for Lane B |
| `procity_street_streetlight_01.glb` | `Streetlight_HighResSG3` | **NOT WIRED** — one per ~18 m of street, also on my R38 recommend list |
> **→ LANE B: the two unwired ones are the worse finding, and they were pointed at you.** The street
> budget is **292/300 with eight draws of margin**. A transmission pre-pass on the street does not
> cost eight draws, it costs **another ~292**. Both are now fixed on the depot; **if you had a local
> copy cached, re-fetch.** No other GLB in the repo carries `KHR_materials_transmission` or
> `KHR_materials_volume`.
**Fixed with `pipeline/strip_transmission.py`** — JSON chunk rewritten, BIN chunk copied through, and
the tool prints sha1(BIN) before and after: **`BIN IDENTICAL` on all three.** Tris/dims/materials/
images/max-tex unchanged. `KHR_materials_ior` deliberately left (ior does not trigger the pre-pass).
Published under the R22 standing authorization and **fetched back off the public depot: 6/6 HTTP 200,
bytes exact, sha1 identical**, and the depot copies re-scanned clean. `_published.json` 56 → 59.
**→ LANE F, the gate you can now write:** `validate_manifest.py` **hard-fails** on
`transmissionFactor > 0` for every manifest GLB (message carries the three.js line numbers and the
fix command), and `glb_stat.py` grew a **`transmission` column** plus a loud footer. `qa.sh` already
runs the validator, so the control is live. C's measurement is the falsifiable arm: re-run the
interior draw sweep and `opshop`/hall should sit at ~104, not 191.
**→ LANE C: neither fix you held back is needed now.** The asset is corrected at source, so `glb.js`
needs no `transmission = 0` scrub and no appearance compromise. You were right to hold — it was my
asset and my call, and it was a defect, not a style: **a bookshelf, a park bench and a streetlight
are not glass.** This was an exporter default that rode in with the source meshes.
### → FABLE: THE MAGPIE — **RECOMMENDATION: TAKE THE GLB.** The picture is `r39_magpie_ab.png`.
Both birds shot through the **same rig** (`render_views.py --eevee --noemit`), same sky and sun,
same lens, each framed to its own extent so neither gets a size advantage, **emission stripped from
both** — and B's bird is **B's own code**, not a port: `pipeline/dump_bird.mjs` imports
`web/js/world/magpie.js` unmodified (bare `three` resolved to the repo's own vendored build through
a node hook) and dumps `birdGeometry()` vertex for vertex.
**Three findings, in the order they decide it.**
1. **The player is looking at the PERCH, not the swoop.** Simulated on magpie.js's own clock
(`--sim`: DEFEND_R 34 m, drawn out to 74.8 m, COOLDOWN 5.5 s, SWOOP_T 1.25 s, RETURN_T 1.9 s;
60 s walk at WALK 4.6 m/s passing 4 m off the perch): the bird is drawn in **54.1 % of frames**,
and **of those it is perched 80.5 % · swooping 7.7 % · returning 11.8 %**. The swoop is the
memory. The perch is the picture — and **the perch is the pose E's asset already is** (a standing
bird with legs), while B's perch is the flight geometry squashed 0.42× in X.
2. **At the sizes it is seen, E reads as a magpie and B reads as a dark aeroplane.** 64 / 48 / 32 px,
three views, still and under two linear motion-blur levels. E is pied and unmistakable at 64 and
48 and still black-and-white at 32, and survives a 25 % smear. B is a grey dash at every size in
every view except top-down.
3. **And the mechanism is geometric, which is why no re-tint could have saved it.** All four of B's
white marks are invisible from where the player stands, and the arithmetic is short:
- **THE WING BARS ARE 3 mm ABOVE THE WING PLANES** — bar at `y 0.048`, wing at `y 0.045`
(`:145-148`), **both horizontal** after `rotateX(-π/2)`, both `DoubleSide`, and the bar's
footprint (x 0.1740.316, z 0.0050.055) is **entirely inside the wing's** (x 00.34,
z 0.0850.065). So from **underneath** — and the player is always underneath: perch 4.32 m
against EYE 1.62 m — **the black wing occludes its own white bar.** `magpie.js`'s header says
*"that black-and-white flash is the entire read"*. The flash is painted on the roof of the wing.
- **the nape box is inside the head.** `0.052×0.014×0.055` at y 0.06, z 0.10: at z = 0.10 the
head sphere's surface is at **y 0.0728** and the box tops out at **0.067**. Only its rear
~12 mm (z > 0.085, where it clears both head and body) is ever proud.
- **and from the flank, which is 80 % of the time, there is nothing to see anyway** — the nape and
rump marks are horizontal slabs **14 mm and 20 mm thick** on a **166 mm** tall bird. At 64 px
for a 0.67 m bird, one pixel is ~10 mm: **1.3 px and 2 px of white.**
**Cost of the swap, stated plainly:** **+712 triangles at one instance.** Draws are a **wash**
both are one town-wide `InstancedMesh` at count 0 or 1. The GLB adds +1 fetch, +1 material, +1 512²
texture, and **it cannot replace the procedural bird**: `?noassets` and `?classic` still need it. So
this is `furniture.js`'s **use-if-ready second path** at `birdGeometry()` / `birdMaterial()`, not a
substitution. It is also **no longer a blind probe** — R26's ruling was about probing for a file
nobody had shipped; `magpie` has been in `manifest.json` and on the public depot since R38 §7a.
**The one thing that could flip it, said out loud:** B's bird flaps (the `wing` wind weight) and E's
GLB is rigid, and a spread-wing silhouette is more bird-shaped in flight than a folded one. That is
worth 7.7 % of the frames. If Fable weighs the swoop above the perch, the answer changes — and in
that case the honest outcome is **both**, which costs two paths and a second draw, and I do not
recommend it.
### → LANE B: three asks, and one of them costs zero triangles
**ASK 1 — `magpie.js` has two stale tri comments.** `:9` and `:123` both say **154 triangles**;
`:42`'s table says 182 and **182 is what I measured** — by running your own `birdGeometry()` (216
verts, index length 546) and again by `glb_stat.py` on the export. The arithmetic agrees: sphere(8,6)
80 + sphere(6,5) 48 + cone(5) 10 + 3 boxes 36 + 4 planes 8 = **182**. *Your file, your edit.*
**ASK 2 — the white bar is on the wrong side of the wing, and moving it costs +0 tris.** See finding
3 above. `magpie.js:147-148` puts each bar at `y 0.048` on a wing whose plane is at `y 0.045`. The
player is **always below** the bird (perch height 4.32 m, EYE 1.62 m). Moving the bar to **y ≈ 0.042**
makes it visible from below and hidden from above — the correct trade at zero cost. A bar on **both**
faces is +8 tris total (4 bars × 2). Same class of fix for the nape (raise y 0.06 → ~0.082, or slide
it back to z ≈ 0.06 where a nape actually is) and the rump (raise y 0.055 → ~0.075). **This matters
whatever Fable rules on the GLB, because `?classic` and `?noassets` will show the procedural bird
forever.**
**ASK 3 — `skins.js`'s `GROUND_FALLBACK` has no entry for either arcade surface.** `arcade-floor`
and `arcade-roof` match none of the nine regexes, so both fall to the generic `#6b6b64` grey under
`?noassets` or a 404 — and the house law is that the town looks right with zero assets. Suggested,
in your vocabulary and prepended like the R38 entries: `[/arcade-floor|terrazzo/, '#c3b6a2']` and
`[/arcade-roof|ceiling/, '#ded7c6']`.
### → LANE B: THE ARCADE, MEASURED — the numbers before the assets
Reproduce any of this with **`node pipeline/arcade_measure.mjs`** (reads Lane A's own `generatePlan`;
nothing below is estimated). Five seeds.
| | measured |
|---|---|
| lane | **ONE edge, `kind:'arcade'`, 42.00 m × 5 m**, 2 blocks (one per side) |
| shops | **1617** (17 at seed 20261990 and 20260101, 16 at 7/123456/999) |
| frontage | 3.004.94 m, **mean 3.874.05 m** · depth 5.107.99 m · 411 of them two-storey |
| walkable | lot faces at ±2.50 m ⇒ **5.00 m** between shopfronts |
| roof | `AWNING_DEPTH 2.2` from each face ⇒ the two slabs reach ±0.30 m ⇒ **already roofed except a 0.60 m slot** down the centreline, at **y 2.95 m** |
| floor | `ground.js`'s arcade branch emits **one** footpath quad, 42.00 × 12.00 m = **504 m²** — the biggest single surface in the district, wearing `footSkin` |
> **⚠ THE AWNING POSTS ARE IN THE MIDDLE OF THE ARCADE.** `buildings.js:643` plants them at
> `d/2 + AWNING_DEPTH 0.2` = **2.00 m out from each lot face**, i.e. **±0.50 m from the
> centreline** — **two rows of posts 1.00 m apart down a 5.00 m lane.** They carry no collider
> (`colliders.push(lotCollider(lot))` is the only one), so the charter's 4.99 m walkable stands and
> nothing traps the player; it just *reads* as a colonnade planted in its own doorway. `AWNING_DEPTH
> 2.2` was sized against `ground.js`'s 3.5 m `FOOT` band, not a 2.5 m half-lane. **A real arcade's
> roof spans; it has no posts.** The cheapest fix is a district-keyed depth (or no posts at all for
> `district.kind === 'arcade'`), and it is +0 draws either way — two `boxMats` entries fewer per lot.
**The synthetic-only rule, as a code rule.** `plan_osm.js:305` is one unconditional
`addDistrict('mainstreet')`, so **no cache town has an arcade DISTRICT**. Real towns *do* have
**1,102 arcade-KIND edges = 39 km of ordinary suburban footpath**. Key every piece of arcade
dressing on `district.kind`, never on `edge.kind` (the v9 cut list, restated with the number).
### → LANE B: the kit, sized by `tris × the instance count YOUR placement rule implies`
Ranked by **value per draw**, because the margin is eight and this is one district on one town.
| # | asset | form | instances | cost |
|---|---|---|---|---|
| 1 | **`facade-shuttered`** | facade skin, 768×595 | 12 shops | **+0 draws, +0 tris**, 1 of the **14 free** facade-atlas slots |
| 2 | **`ground-arcade-floor`** | ground skin, 512² | 1 class | **+1 draw town-wide**, **+0 tris** (the 504 m² quad *moves* out of `footGeos`) |
| 3 | **`ground-arcade-roof`** | ground skin, 512² | 1 quad | **+1 draw in the 12 chunks that hold arcade lots**, **+2 tris** |
| 4 | **hanging blade signage** | ***no asset — see below*** | 17 | **+0 draws, +0 assets, +4 tris/shop = 68** |
| 5 | **`aframe_board`** | GLB **366 tris**, 0.68 × 0.64 m, h 0.890 | ≤16/town ⇒ ≤5,856 tris | +1 draw as ONE town-wide `InstancedMesh` |
| 6 | **`blade_sign`** | GLB **299 tris**, 0.66 × 0.10 m, h 0.549 | 17 ⇒ 5,083 tris | +1 draw — **the fallback for #4, not the answer** |
| 7 | **`keycutter_sign`** | GLB **498 tris**, 0.29 × 0.06 m, h 0.599 | 1 | +1 draw for one object — **lowest value in the kit** |
All three are published and **sha1-verified off the public depot**, in `manifest.json` under
`furniture` (13 → 16), and the local-depot mirror is re-staged for `?localdepot=1`. House GLB law
clean: metres · +Y up · base origin · 1 mtl / 1 img / 512² WebP · no Draco.
> **`blade_sign` ships with a flagged defect.** Its FRONT face is the blank white the house law
> requires; its BACK face carries **TRELLIS-hallucinated artwork** invented where the single concept
> photo had no data. A blade sign in a 5 m arcade is read from both sides. It is shipped flagged
> rather than quietly (the R38 magpie-albedo precedent), and it is one more reason **#4's +0-draw
> quad is the recommendation and this GLB is the fallback.** Fix path if wanted: `tint_atlas.py`
> already rasterizes UV→position maps, so flat-whiting every texel with `z < 0` is the same pass
> that repainted the magpie.
**#4 is a refusal with a better answer, and it is the most useful thing in this table.** A blade
sign does not need a mesh or a texture. `buildings.js` already draws every shop's sign into a
per-chunk 512×168 canvas atlas cell and pushes ONE quad into the merged `signQuads` mesh. **Push a
SECOND quad, perpendicular to the facade, with the same `signUV.get(shop.id)` rect** and you get a
projecting sign carrying the shop's **real name** for **+0 draws** (it rides the mesh that already
exists) and **+2 tris single-sided / +4 back-to-back** — **68 triangles for all 17 shops.** The
bracket is two `boxMats` boxes, which is also +0 draws. A GLB blade sign at 17 instances cannot beat
that; it can only look different. Generated anyway (AUDIT §Round 39 §6) so the option exists, but
**wire the quad first**.
> The insertion point is the block you already have — `buildings.js:620-631`, immediately after
> `const uv = signUV.get(shop.id)`. Today's quad is `sh 0.5 · sy = fh 0.35 · sw = w×0.92 ·
> zs = zf + 0.05`, i.e. spanning **y 2.402.90** on a 3.0 m facade. **Do not reuse that y for a
> blade**: the awning slab is `AWNING_Y 2.95` × 0.16 thick ⇒ its underside is **2.87**, so today's
> sign band already grazes it. A blade wants **centre y ≈ 2.35, height ≈ 0.45 ⇒ 2.132.58** — clear
> under the slab and well over EYE 1.62. Project it **~0.7 m** from the facade (`z` from `zf+0.05`
> outward), fixed `x` at a lot edge: 0.7 m into a 2.50 m half-lane leaves **1.8 m clear** each side,
> which is more clearance than the awning posts currently leave (§ the post finding above).
**#2 and #3's `use` values name slots that do not exist yet — deliberately.** R38 recorded `gravel`
(`use:'verge'`) and `reddust` (`use:'outback'`) as selected by nothing *by accident*. These two are
selected by nothing *on purpose and with the price printed*: the arcade cannot ride `footpath`,
because that is one town-wide merged mesh and re-using it would lay terrazzo over every footpath in
the town. Measured for #3: **arcade lots span exactly 2 chunks on all five seeds** (`0,0` and `0,1`;
`CHUNK` 64 m), so a per-chunk arcade class is +1 draw in 2 chunks and +0 in the other 23.
> **⚠ AND THE CLASSIC ARGUMENT IS THE OPPOSITE ONE THIS TIME — do not reuse R38's.** R38's ground
> palette is safe under `?classic=1` **by absence**: `character.js:83-87` returns the frozen v1
> literals for a null town key, and classic has no town key, so there is *no* `if (classic)` in that
> path. **The arcade is the reverse case: the arcade district exists ONLY on the synthetic, and the
> synthetic IS the classic boot.** Every piece of this kit therefore lands squarely on the covenanted
> town and needs an **explicit flag test**`ground.js:41-47`'s `VERGE` pattern, where `?classic=1`
> forces it off — not the by-absence pattern. Getting this backwards would move the golden render on
> a dressing change.
### → EVERY LATER ASSET ROUND: the sizing law, now standing in AUDIT
Folded into `pipeline/AUDIT.md` as a **standing instruction**, not a round note: *get the instance
count from the consuming lane BEFORE generating anything that tiles, edges, or repeats per-lot.*
R39 is the first round that ran it as a script (`pipeline/arcade_measure.mjs`) rather than as a
conversation, and it changed the deliverable — item #4 above stopped being an asset.
**And a second clause the arcade added to it: for a SKIN the budget is not bytes, it is the SLOT.**
`skins.js:facadeAtlasUV` has **36 slots (6×6)** and past 36 it `console.warn`s and **reuses one**
so a 37th facade skin does not fail, it silently repaints an existing shop. Measured: the synthetic
uses **22 distinct facade skins** (21 at seed 123456), so there are **14 free**. That is the real
budget line for a facade skin, and it is the number to ask for before generating one.
## Round 38 — v8 wave 1: the asset wave ⟨v8 WAVE 1⟩
Full provenance, measurements and method: `pipeline/AUDIT.md` § Round 38. Contact sheets:
`docs/shots/laneE/r38_props_shipped.png` · `r38_thin_structure_failure.png` ·
`r38_magpie_albedo_probe.png` · `inhabitation_spectrograms.png`.
### → LANE B: what you can consume RIGHT NOW (no publish needed)
**1.1 — the six inhabitation sounds are LIVE in the manifest and on disk.** They land in the normal
`audio.ambience` / `audio.sfx` groups (so nothing existing changes) and each carries **`life: true`**
— the same flag shape R12 used for `gig: true`. Filter on it and you have exactly your six layers:
| manifest key | group | loop | gain | `anchor` hint | dur |
|---|---|---|---|---|---|
| `dog-bark-fence` | sfx | — | 0.55 | `yard` | 3.2 s |
| `mower-distant` | ambience | ✅ | 0.30 | `town` | 12.0 s |
| `radio-through-window` | ambience | ✅ | 0.25 | `house` | 8.6 s |
| `sprinkler` | ambience | ✅ | 0.35 | `yard` | 8.0 s |
| `roller-door` | sfx | — | 0.50 | `shop` | 2.8 s |
| `magpie-carol` | sfx | — | 0.45 | `tree` | 2.6 s |
- **`anchor` is a HINT, not a contract** — you own placement, re-anchor freely.
- **All three beds loop seamlessly** — measured wrap-jump ≤ 0.18× the 95th-percentile internal step
(radio 0.00×, mower 0.08×). The mower's LFOs are exact harmonics of its 12 s loop *by construction*.
- **Two things I could NOT ship in the audio, and you need to know both.** (a) Per the round's binding
correction, `playSfx` is mono — so **direction is unavailable**. I spent "distance" in the
**spectrum** instead: `mower-distant` is 99% below 1 kHz because that is what a couple of hundred
metres of air actually does. (b) **Do not ship distance as a level**`gen_audio.encode()` runs
loudnorm over every `ambience` asset and will undo a quiet render. Use the manifest `gain`.
- **Budget: +0.86 MB** (pack 10 → 10.9 MB against the 25 MB cap). **Zero draws, zero tris.**
**1.2 — four new grounds are LIVE in the manifest** (`skins.ground`), 512² JPEG, `+0 draws / +0 tris`
through `ground.js`'s merged per-class mesh:
`grass-dry` (use `yard`) · `grass-temperate` (`yard`) · `coastal-sand` (`yard`) · `bitumen-reddust` (`road`).
> **⚠ The reason `gravel` and `reddust` are selected by nothing is probably not the selector.**
> `ground.js:177` hardcodes exactly **four** slots — `roadSkin` / `footSkin` / `grassSkin` (the base
> plane) / `plazaSkin`. `gravel`'s use is **`verge`** and `reddust`'s is **`outback`** — **neither
> names a slot that exists.** My four deliberately use live slot vocabulary so your new selector can
> eat them without a vocabulary change. If you want gravel/reddust in the palette, they need a `use`
> that maps to a real slot (I left them alone rather than change semantics under you).
>
> **Also, pre-existing, recorded not fixed:** the eight flux-era grounds are **512×279**, not square,
> so they stretch under a square UV tile. The two `djsim-*` grounds are 512×512 and are the correct
> precedent — mine follow them.
### → LANE B: the mesh verdict, including the one you should be glad about
**`hills_hoist` and `tv_aerial` FAILED. Use primitives — and that is the better outcome anyway.**
Not a near miss: TRELLIS reconstructs the hoist's pole, footing and arms fine, but **the wire returns
as thousands of disconnected fragments**, so the mesh both *looks* frayed and *will not decimate*
(floors at 250 k). And a voxel remesh coarse enough to hit 600 tris **deletes the pole** — the 496-tri
hoist is a floating horizontal smear. There is no voxel size that keeps a 30 mm pole and a 2 mm wire
at 600 tris. Both were **deleted from `_normalized/`** so no later manifest build can catalogue them.
**Your `boxMats` primitives cost +0 draws; the GLBs would have cost a draw and looked worse.**
**Three shipped — PUBLISHED and byte-verified off the public depot (session 2)** — measured,
GLB-law clean, 1 mtl / 1 img / 512² WebP / no Draco:
| id | tris | footprint (m) | height (m) | sha1 (local == depot) | B's verdict |
|---|---|---|---|---|---|
| `magpie` | **894** | 0.15 × 0.40 | 0.358 | `b5f02b26…ee10` (tinted) | superseded by B's 182-tri procedural bird — **upgrade path** |
| `paling_fence` | 1 121 | 0.28 × 3.59 | 1.788 | `a580a883…8050` | **REJECTED on instance budget** — see the sizing law below |
| `water_tank` | **500** | 1.73 × 1.64 | 2.186 | `9b08fb97…f360` | **ACCEPTED** — replaces a 24-tri pair at low count |
### → FABLE / LANE B (session 2): THE MAGPIE NOW READS AS A MAGPIE
**The atlas tint is DONE, published and verified in the game's own renderer.** Render:
`docs/shots/laneE/r38_magpie_tint.png` (before/after × 5 angles, a 64/48/32 px distance strip, and
the published file loaded off `digalot.fyi/3god` through the vendored `GLTFLoader` + three.js).
**Method — `pipeline/tint_atlas.py`, and the reason it is not a paint job.** The baked atlas is a
smart-UV unwrap: ~100 scattered islands, so you cannot tell by looking which island is a nape. So
the tint is defined in the MESH's frame and projected through the UVs: rasterize every UV triangle
→ texel→(position, normal) maps → evaluate soft anatomical windows on the POSITIONS (nape / wing
bar / rump + tail base, with the crown, face, bib and tail tip left black) → paint, keeping the
bake's own luminance as shading → dilate 5 texels into the bake's UV padding so no black fringe
survives bilinear/mip at an island edge. **Geometry is untouched byte for byte** — only the image
bufferView is rewritten: still 894 tris / 1 mtl / 1 img / 512² WebP / no Draco / 0.155 × 0.358 ×
0.404 m, confirmed by `glb_stat.py` *and* by loading the published file in the vendored loader
(1 mesh, 894 tris, `MeshStandardMaterial`, 512×512 texture decoded).
Because the region masks are 3-D, the soft edge is consistent across every island at once — a 2-D
blur could never do this (it would bleed island-to-island through the black background).
**VERDICT: it reads.** Painted albedo median luma **0.84** (p90 0.95) against the bake's 0.13.
At **64 px and 48 px** — a 0.36 m bird at 310 m on a 1080-high screen — the pied pattern is
unmistakable from flank and rear-quarter; at 32 px it is still clearly a black-and-white bird.
**The one honest caveat:** from *directly underneath and in front* (the `swoop` view) it stays
dark — which is what a real magpie does, because its bib and belly ARE black; the white is on the
nape, shoulders and rump, i.e. what you see as it comes over you and as it leaves.
**Thumbnail:** re-rendered from the **rear quarter** of the same `bake_lowpoly` camera rig, because
the roster's standard front-quarter looks straight at a magpie's black face and records none of the
plumage — the one thing this asset's thumb has to show.
**Status after B's R38 (commit `4514cb9`): this is an UPGRADE PATH, not a dependency.** B shipped a
182-tri procedural bird carrying the marks as vertex colours and fetching nothing — 4.9× lighter
and B's argument is right. The tinted GLB is on the depot and in the manifest if the swap at
`birdGeometry()` / `birdMaterial()` is ever wanted; **nothing probes for it, and nothing should**
(the blind probe R26 killed). It costs +1 draw and +894 tris at one instance if taken.
### → FABLE: the depot step is CLOSED
Published under **John's R22 standing authorization for Lane E assets** (the same authority that
shipped `sit.glb` in R28 and the nine trellis/Rokoko assets in R36) via the passwordless tailnet
ingress: `_published.json` **53 → 56**, drift check `✓ all 56 recorded assets live`. Then all three
**fetched back off the PUBLIC depot** (`https://digalot.fyi/3god/a/<name>.glb`) — **HTTP 200, byte
counts exact, sha1 identical to local, 3/3** (the R36 discipline; sha1s in the table above).
`_r38_results.PENDING_PUBLISH.json``_r38_results.json`, `build_manifest.py` re-run (GLBs 33 → 36,
they land in `furniture`), **`validate_manifest.py` → 0 errors, 0 warnings.**
### → FABLE: two review figures corrected, one confirmed
- **CONFIRMED — the `trellis2_mlx` swap.** Median **128.0 s** across the 5-prop batch (including the
new 56 s RMBG cutout stage) vs `trellis_mac`'s 245.4 s p50 = **1.92×**. 5/5 first attempt, **0
retries used** against the 45% budget. **But the operator's tail is long even when nothing fails**:
one job took **489 s**, 3.8× the median, same params, same box, no error. Plan on the median, staff
for the tail.
- **CORRECTED — `gen_skins.py --local` is ~9.7 s here, not 5.7 s.** Not a regression: the review's
figure is the 1024×576 facade geometry and my grounds are **1024² square tiles, 1.78× the pixels**.
Per-pixel the two agree exactly. Square is deliberate (grounds tile).
- **CORRECTED — the reuse list's "one line each, zero licence work" holds for only 5 of the 9.**
`furniture.js`'s `extractGLB` merges all meshes and **keeps only the FIRST material**, so a
multi-material GLB silently renders entirely in material #1: `park_bench` (2 mtl), `coffee_table`
(2), `box_crate` (3) all have a cosmetic caveat, and **`bench_wood` has ZERO materials** so it falls
to the grey `0x888888` default. `streetlight` (4 999 tris, placed every ~18 m ≈ 250 k tris at ~50
instances) genuinely must be decimated before wiring, as the review said. The true one-liners are
`novelty_record`, `bench_modern`, `longbench` and `counter`→`bar_counter`.
- **CONFIRMED — the stale blocker.** `furniture.js:30`'s comment holding `novelty_record` on its
primitive says **"26.5k tris"**; Lane E baked it to **8 000** in R5. One stale comment is the only
thing keeping a finished landmark prop out of the game. ✅ **Lane B corrected it in `4514cb9`.**
- **CORRECTED — `normalize.py`'s collapse decimator cannot reach small budgets on TRELLIS output.**
Measured on all five R38 props: 470 442→**13 849** against a 900 target · 478 236→**250 068** (600)
· 476 974→**18 791** (400) · 485 565→**89 306** (400) · 475 047→**70 893** (500). Collapse cannot
merge across disconnected shells, so **the floor IS the shell count** — this is not a tuning miss,
and nobody had asked it for ≤900 before. **`bake_lowpoly.py` (voxel-remesh → decimate → Cycles
bake) clears it every time**: 894 / 500 / 299 / 496. Route any ≤2 k target through the bake, never
through collapse.
### → EVERY LATER ASSET ROUND: the sizing law R38 paid for (Lane B's `paling_fence` measurement)
**An asset's triangle budget is not "≤5 k like a prop" — it is `tris × the instance count the
placement rule implies`.** `paling_fence` at **1 121 tris** is a perfectly good panel and a
**REJECTED** asset: B measured **5 runs × 181 lots = 905 instances = 1.01 MILLION triangles**
against ~75 k spare, ~47× over, and a GLB cannot ride the `boxMats` InstancedMesh that makes the
other yard props free. It ships as a primitive. **`water_tank` at 500 tris was ACCEPTED on the same
measurement** — one per yard at low count, replacing a 24-tri pair.
**So the budget question at generation time is not "is this prop small?" but "how many of these does
the placement rule put on screen, and can they ride an existing InstancedMesh?"** A fence panel
repeated on every lot boundary is in a different budget class from a one-per-town water tank, and
the number to beat is set by the *rule*, not by the asset. Ask Lane B for the instance count
**before** generating anything that tiles, edges, or repeats per-lot. (The published fence stays on
the depot and in the manifest — harmless, provenance-recorded, and available if a low-count use
ever appears.)
## Round 36 wave 1 — the 2 a.m. commits, regularized ⟨v7.0⟩
**Scope: sweep `046668e` + `b339402` (July 20, landed outside the round system) into the pipeline —
provenance, licence table, structural checks, depot.** Everything below is measured off the committed
bytes, not the commit messages.
**1. THE COUNT: the brief said 7 assets; there are NINE.** `046668e` = 4 dance clips.
**`b339402` = FIVE trellis DJ bodies, not 3** (`dj_phrtt_01 · dj_techno_01 · woman_dj_01/02/03` — the
commit's own diffstat says five, and all five are in `PED_NAMES`). All 9 swept; the table rows say 9.
**2. THE ZONE CORRECTION — the dance clips are NOT Mixamo. They are ROKOKO.** The brief guessed the
Adobe Mixamo walk/idle/sit/look family; 90sDJsim's own clip baker (`tools/bake_clips.py`, the
`fbx_to_clip.py` port) names the true source: **Rokoko's free 263-asset motion-capture library**,
`Rokoko Studio (Mocap)/Dancing/{GentleSwayingDancing,NPC_DancingParty,DancingMedium,DancingwithDrink}_mixamo.fbx`
— the pack is on this box (`~/Documents/Rokoko_Free_Mocap_FBX_263`), the `_mixamo` suffix is **Rokoko's
own Mixamo-skeleton export** (which is why they bind like Mixamo clips without being Mixamo clips).
Licence verified at the source (rokoko.com, the pack's own download page): *"use them in any animation,
VFX, game, 3D art or other 3D project you want, **including for commercial use**"* — royalty-free, no
attribution demanded, motion only and mesh-free as shipped. **🟢 web-ok, but a NEW zone** — the licence
table gains a Rokoko row rather than quietly widening the Mixamo one. A licence spine that files a
Rokoko clip under Adobe's licence is the R25 spelling-fork one layer up: right answer, wrong authority.
**3. Clip structure — the sit.glb checklist, run; the shape DEVIATES from sit/look and I'm recording
it, not normalizing it.** All 4: **mesh-free (0 meshes)**, 1 animation, **198 tracks**, dangling skin
**with inverseBindMatrices present**, **0 NaN / 0 Inf** across every sampler accessor (156k268k floats
scanned per clip), durations 30.58 / 30.50 / 44.04 / 52.42 s (party/medium/drink/sway). But the Rokoko
export shape is **68 nodes, 52-joint skin** — not the Mixamo clips' 66 nodes / 195 tracks / 65-joint
skin. The bind census against a shipped ped skeleton (`man_casual_01`, 65 canonical bones): **65/66
distinct canonicalised track targets bind; the sole unbound is `mixamorigReference`** (Rokoko's root
node — `_canon` can't map it, three.js drops the orphan track silently; matches 90sDJsim's in-game
64/65-tracks measurement). Not a defect: both repos verified the dancers animating in-scene.
**4. Bodies — GLB law + ped law, PASS; one number F's gate must see.** Tris **4000 / 4000 / 3999 /
3999 / 3998** (≤5k ped law, the commit's "4k" claim is honest) · 1 material / 1 image / **tex 1024²
webp** (`EXT_texture_webp` — the whole existing roster carries the same extension, so no seam) · **no
Draco** · 65-joint mixamorig skin, IBM clean (0 NaN/1040). **⚠ → F (figure-scale gate, your wave 2):
the new bodies' bind-pose skeletons measure ~0.950.99 units tall; the pre-existing roster measures
~173180** (the cm-scale convention). In-game this is absorbed by design — `rigs.js:163` scales every
rig to target height (`inner.scale.setScalar(height/span)`), and both repos eyeballed them animating —
but they joined the roster ungated and the R36 brief already routes them through your figure-scale
gate: this is the number it should expect. FLAG, not a fix; the files are untouched.
**5. Provenance chain, proved not narrated.** All 9 procity GLBs are **sha1-identical to the 90sDJsim
originals** (`f8f0208` clips / `1906294` bodies, both July 20). Bodies' upstream: John's own DJ
characters (`grldj5/grldj2/djgrl3/techno_01/phrtt2`) — flux_local sheet → **TRELLIS.2 on-device mesh**
(the instrument-pipeline zone, CC0-equiv) → **Adobe Mixamo auto-rig** (full 65-bone) → MESHGOD
`scripts/fbx_to_ped.py` FBX→skinned-GLB + 4k decimate. Honest caveat: the source FBXs live in the
rigroom library (`~/meshgod-backups`, on **ultra** — not this box), so the mesh-generation step is
recorded from 90sDJsim's commit + both repos' `trellis'd DJ bodies` roster comments + MESHGOD's
rigroom autorig flow, while everything downstream of the FBX is verified from bytes here.
**6. Depot — published per standing practice, verified by fetching the bytes back (R28 discipline).**
Staged to `_normalized/` (the sit/look pattern), pushed via the passwordless tailnet ingress under
**John's R22 standing authorization for Lane E assets**: `_published.json` **44 → 53**, drift check
`✓ all 53 recorded assets live`. Then all 9 fetched back from the **public** depot
(`https://digalot.fyi/3god/a/<name>.glb`) — **HTTP 200, byte counts exact, sha1 identical to local,
9/9.** Gates re-run after the sweep: `validate_manifest` **OK — 0 errors, 0 warnings** (ped assets load
by convention, not the manifest — unchanged, same as walk/idle/sit/look) · `publish.py --verify` green.
**→ F (wave 2), the full flag list from this sweep:** (a) the figure-scale number in §4; (b) the
clips' 68-node/52-joint Rokoko shape in §3 — if your gate pins the sit.glb structural template,
these four are legitimately different; gate on binds (65/66) and NaN (0), not node count; (c) nothing
else — no defective file found, nothing held back.
## Round 28 — Spike 1: half of it shipped, half of it doesn't exist ⟨v5.1⟩
**1. `look.glb` — DONE, published, verified.** `Looking_Around.fbx` (675 KB, from the cached tailnet stash,
not re-downloaded — the skill's law) → `fbx_to_clip.py`**`web/models/peds/look.glb`**: mesh-free, **65
`mixamorig` joints, 195 tracks, inverseBindMatrices present, 136 KB** — structurally identical to the
`sit.glb` precedent on every field I can measure (`meshes/nodes/skins/joints/bind`), so D's `_canon` binds
it exactly like walk/idle/sit. Depot: published to `digalot.fyi/3god` under John's **R22 standing
authorization for Lane E assets** (`_published.json` 43 → 44). **→ D: it's ready.**
**2. 🚩 `lean` DOES NOT EXIST — the spike's premise is half wrong, and only John can unblock it.** The
brief and the kickoff both say "two of the 34 already-cached clips (lean, look-around)" and "Spike 1 needs
nothing from anyone". **`look-around` is cached. `lean` is not — anywhere.** Measured, not assumed:
- The stash's 34 clips contain no lean (`Agreeing · Bored · Breathing_Idle · Counting · … · Looking_Around
· … · Yawn`).
- `find ~/Documents -iname "*lean*"` on ultra: **nothing** — not in `mixamo-fetch`, not in `character_kit`,
not in `3D=models`, no FBX by that name at any depth.
- The fetch tool can't help unattended: `node fetch.cjs search "lean"`
`FATAL nothing on debug port 9222 — quit Brave and relaunch with --remote-debugging-port=9222`. It needs
**John's logged-in Brave session** (his browser, his Mixamo account) — **I won't relaunch it.**
**→ John, one small unblocker (adds to your two):** relaunch Brave with `--remote-debugging-port=9222`,
open a Mixamo tab, and either run `node fetch.cjs search "lean"` yourself or tell me and I'll fetch +
convert it in minutes (the path is proven twice now). **I did NOT substitute a lookalike.** `Bored` is the
nearest stash clip and it's a standing weight-shift idle, not a wall-lean — D's half explicitly needs
shopfront adjacency ("`lean` against a wall wants the same care" as the scale-aware traps), so a standing
idle would quietly change what Spike 1 proves. Half a spike, honestly, beats a whole one that lies.
**3. Instrument LOD — D killed it, and D's measurement is better than mine.** I'd built the five `_lo`
variants in scratch (14k → 3500/3500/4000/**6000**/3000) before D's commit landed saying *"E: STOP — do not
build the _lo variants."* **Nothing shipped**; scratch discarded. D measured the rooms an LOD would have to
fire in — **max diagonal 15.2 m in the largest venue, so the player can never be far enough: practical win
0 tris.** That's decisive in a way my argument wasn't. My own reading agreed on the verdict by a different
route, and it's worth recording as corroboration: **C's §4 already says "the interior gate is *draws*; tris
are the *street* budget (200k)"** — and an LOD swap doesn't change draws *at all* (same object, same
material, fewer tris). So the item's entire win landed on a metric no interior gate reads. Two independent
measurements, one verdict. **The oldest tri item is dead, correctly.**
*(One salvage worth keeping if it ever returns: at the brief's ~3k the **guitar_amp shreds** — white gaps
punched through its silhouette, confirmed against the shipped 14k thumb as a decimation artifact and not a
source defect, the R22 `food_cart` discipline. It needs **6k** to read clean. The other four were clean at
34k.)*
**4. 🚩 → A: your own file's comment now contradicts its code.** Investigating why the fingerprint read
`0x5f76e76` instead of the `0x3fa36874` I'd seen every round, I confirmed it's **legitimate** — your R27 w5
facade fix re-pinned 51 goldens under the amended covenant. But `selfcheck.js:267` pins
`hash: 0x5f76e76` while **`selfcheck.js:301` still says "the synthetic golden above stays 0x3fa36874"**.
Code and comment disagree by 34 lines. That's the exact species as R24's validator bug (my docstring said
nested, my code read top-level) — the next reader trusts whichever they read first. Your file, your call;
flagging per the round's "flag loudly if anything disagrees".
**5. Gates.** selfcheck **ALL GREEN 156,352/156,352** · `qa.sh --strict` **6 passed / 0 failed**. `look.glb`
moves **no golden** — proved by moving it aside and re-running: fingerprint identical either way, because an
unreferenced clip is never fetched. The `?classic=1` covenant is untouched: like `sit.glb`, a raw fetch
would breach the zero-fetch-delta rule, so **D must gate `look` behind an opt** the way `rigs.js` gates
`sit` (`loadPedFleet(base, { sit })` → the same shape for `look`). Depot-live verified by fetching the
bytes back: `https://digalot.fyi/3god/a/look.glb`**HTTP 200, 139,640 bytes, sha1 identical to local**.
## Round 27 — provenance v5-final, and the roster gap the docs freeze caught ⟨v5.0⟩
**1. THE BUG THE FREEZE FOUND: `redhill_godverse` was on disk but NOT in the roster — for three rounds.**
The v5 provenance table needed real numbers, so I measured instead of carrying the v4 row forward. The
count didn't match: **23 caches on disk, 22 rostered.** The missing one was **`redhill_godverse` — the
town the entire v5 epoch walks to.**
**Mine, squarely.** `index.json` is Lane E's file (`write_index()`). G's `redhill_godverse` landed in R24
(`86e2985`) *after* my last index run (`5815dfb`), and nobody re-rostered it. **An addition isn't done when
the cache is written — it's done when the roster is regenerated.** That's the exact mirror of my own R24
lesson (*"a retirement isn't done when the cache is deleted; it's done when the consumers are swept"*) —
I wrote the lesson for deletions and then missed its reflection for additions.
**Why four green rounds never caught it — worth reading, because it's a gate-shape lesson.** Nothing was
broken *for the gates*: `selfcheck` enumerates `web/assets/towns/*.json` **directly** (so it saw the town
and A even pinned its goldens), and F's crate smoke boots **by key** (`?town=redhill_godverse`). The
roster's *only* consumer is **B's HUD selector** (`hud.js`, my R20 ledger #2 design). So the single symptom
was that **a player could not choose the headline town from the dropdown** — invisible to every automated
check by construction. A gate that reads around the artifact under test cannot test it: the same species as
the vacuous-gate law, one layer out. **Fixed:** re-ran `write_index()` → **23 rostered, 23 on disk, 0
missing**; only `index.json` moved (every cache byte-identical again); selfcheck **ALL GREEN 156,212**,
golden `0x3fa36874` untouched; `qa.sh --strict --matrix` **7/0/0**.
**→ F: this matters for #5, the tour.** The money shot walks Musgrave Road into Monster Robot Party. If any
tour frame reaches that town **through B's selector** rather than by key, it could not have found it before
this commit — by key it always worked. Worth one glance at your framing path.
**→ B:** the dropdown now carries both godverse towns. Your `${t.state ? ', ' + t.state : ''}` handles their
empty `state` gracefully — no change needed, and the defensive write is why this cost nothing cosmetically.
**→ G:** your godverse caches carry no `state`, so they roster as `state:''`. Harmless today (B degrades
cleanly). If you want "Red Hill, QLD" in the picker, emit `state` in the cache — my `write_index()` already
prefers the cache's own field, so it needs nothing from me.
**2. Provenance v5-final (ledger #6).** The ⟨v5.0-FINAL⟩ rows are measured, not inherited: the town pack
row was still claiming **v4's "22 towns / 1192 shops"** — stale since my R24 ballarat retirement and redhill
build (now **21 real / 1196 / 299 heroes**, plus both godverse towns = 23 rostered). Added the two v5 art
rows: the **real-stock atlas** (1 crate, 120 items, Monster Robot's own POS) and the **mint atlases** (14
crates, 224 items, seeded from real dealgod listings). **These are the table's first 🟡 rows** — everything
before v5 was 🟢 web-ok; real product photography is the first asset class this project ships that a human
must clear before public release.
**3. The licence line's story, written down as a mechanism (ledger #6).** The 🟡 rows are guarded by exactly
one printed line, so the freeze records why it took two rounds to make honest: R23's glob matched 0 files
and returned 0; R25's **permissive `or` fallback let the print go silent while the check still passed**
the flag stopped reaching a human on real product photos while the gate said OK. Now: one spelling, one
lookup with one owner, and the line is **counted** (judged N ⇒ N lines, or FAIL). **Both zones print**
mint carries the same in-house-green flag, because minting changes whose stock a crate *claims* to be,
never whose photograph it *is*. Frozen today at **15 crates, 15 flags, every one in front of a human.**
## Round 26 wave 4 — the mint-id fence: my check was faithful, the rule was stale ⟨v5.0-beta⟩
**The red was mine and the fix is C's ruling.** My R25 slot-id check demanded `sku_` on every real-stock
pack — faithfully implementing C's R25 line, which was **written before mint existed**. It then failed all
14 of G's mint crates, whose ids honour its every intent: non-positional, seeded, stable, zero reuse. C's
ruling names it exactly: *"the rule was the stale thing, not E's check."* Fourth hold of the epoch, and
the fourth to trace to **a rule meeting a case written before the case existed.**
**The fix is a fence, and it is STRONGER than the rule it replaces — that was the thing to prove.** The
easy green was to drop the prefix check. Instead the form is now sourcing-scoped per C §7.2a (R26 w4):
`sourcing:"real"``sku_` (a POS copy on a shelf) · `sourcing:"mint"``mint_` (a dealgod listing
standing in for one). **A crate wearing the other's prefix FAILS.**
**Why this is a money bug and not a naming bug — G's judgment, now law.** G refused to file dealgod listing
ids under `sku_` to go green the easy way. They were right: **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 a real record
out of a real shop to satisfy a minted stand-in.** The gate's error says that, in those words — the cost,
not the rule.
**Proved 7/7 — that the fence bites, not merely that the red stopped.** G's real crate (`sku_…`) → PASS ·
G's mint crate (`mint_…`) → PASS · **mint filed under `sku_` (the exact shortcut G refused) → FAIL**, naming
the fence and the real cost · real wearing `mint_` (the reverse) → FAIL · **positional ids still FAIL in
BOTH sourcings** (the old rule's whole intent, retained) · duplicate ids → FAIL. Loosening would have
passed the first two and lost the last four; this passes the first two and keeps all four.
**Both older suites brought forward rather than left to rot.** The R26 suite's mint fixture wore `sku_`
ids, so the fence correctly rejected it — a fixture that fails for a *new* reason has stopped testing what
it was written for. Moved to the `mint_` space: **17/17**. R25 licence suite: **8/8**, tally still firing
**via the tally**. This is the third round running where the near-miss was a test going quietly stale
around a changing contract; it is the failure mode I now look for first.
**Gates:** `atlas-QA OK — 15 per-shop atlas(es) valid, 15 licence line(s) shown, 0 warning(s)` (1 real +
14 mint, every one showing its flag to a human) · `validate_manifest` exit 0 · **`qa.sh --strict --matrix`
7 passed / 0 failed / 0 warn — QA GREEN.** F's 6/1/0 is now 7/0/0. **→ F: the beta is yours to tag.**
## Round 26 — the sourcing arm + the atlas manifest ⟨v5.0-beta⟩
**1. The field is `sourcing`, NOT `tier` — and reading C's contract instead of the brief is the only
reason this gate isn't already broken (ledger #3).** The R26 brief says *"the validator grows the tier
arm… `tier` required on every atlas"*. **C ruled it cannot be `tier`** (§7.2b): `tier` is already the
charter's ladder rung (0 parody / 1 static-real / 2 live) and **a mint atlas is also rung 1** — same
mechanism, different data origin. Overloading it would put two meanings in one name: the same species as
the plan-id/godverse-id collision and the positional ids. Had I built the brief's word, my gate would have
demanded `tier` while G emitted `sourcing`**R23 exactly, re-run**. The contract's author is the
authority for the contract's shape. **Built to C §7.2b.**
**2. The honesty separation, asserted both directions.** `sourcing ∈ {real, mint}` required (vacuous-gate
law: no `sourcing` ⇒ FAIL — a crate that can't say where it came from must never pass as one that can).
Then: **mint must carry no POS claim, real must carry the POS provenance it claims.**
**→ C: §7.2b says "a mint index MUST NOT carry POS-snapshot fields" but does not enumerate them, so I
named the set — please correct it if it's wrong.** I assert on `crate`, `provenance.source.pos_db`,
`provenance.source.pos_dump` — the fields that claim *a specific shop's actual inventory*. I deliberately
**excluded `snapshot`**: §7.3 requires it on every atlas as the determinism pin, and a mint bake has its
own honest source snapshot (the dealgod dump it sampled). If a mint bake legitimately needs any field in
my set, say so and the set changes. The error names the exact field found, so a divergence shows up as a
diagnosis rather than a mystery.
**3. The manifest (ledger #6), consistency both ways + one hole C's rule implies but doesn't spell out.**
`stock_godverse/index.json` per §7.2d. An atlas on disk absent from the manifest FAILS (no entry ⇒ no
fetch ⇒ that crate silently never loads — an unreachable atlas is not a passing atlas); a manifest entry
with no atlas FAILS (the wire fetches it and 404s — the exact noise the manifest exists to kill). C's
"absent manifest ⇒ legitimate zero-shop state" is honoured: **no atlases + no manifest ⇒ SKIP, not an
error.** But **a manifest that PROMISES shops while zero atlases exist now FAILS** rather than skipping —
C's rule scopes "absent" to the empty tree, and a tree where something on disk still asserts atlases exist
is the vacuous shape wearing a hat. Flagging it since it's my reading, not C's words.
**4. Proved before handing G the gate — 17/17, including a mint crate that does not exist yet.** The R25
rule (never hand a lane a gate that fails on compliance) applied: I hand-built G's likely R26 emit and a
seeded mint index and ran them. Real → PASS · **mint → PASS with its licence line printed** (ledger #3's
"prints for both" — verified, not assumed) · real+mint side by side → PASS · mint carrying `crate` → FAIL ·
mint carrying `pos_db` → FAIL · **real carrying no POS provenance → FAIL** (the reverse direction) · no
`sourcing` → FAIL · `sourcing:'realish'` → FAIL · atlas absent from manifest → FAIL · manifest entry with
no atlas → FAIL · declared type not on disk → FAIL · **manifest says mint, index says real → FAIL** ·
manifest missing while atlases exist → FAIL · unparseable manifest → FAIL · empty tree → SKIP · empty
manifest → SKIP · manifest promising a shop with no atlases → FAIL.
**5. The R25 suite was going stale and I brought it forward rather than let it rot.** Adding `sourcing` +
the manifest meant every R25 licence case started failing for a NEW reason — which would have left the
licence arm *untested while appearing tested*. Updated to the R26 contract; **8/8 still green, and the
tally tripwire still fires VIA THE TALLY** (`via_tally=True`), not incidentally. Worth noting the near-miss:
the manifest error briefly shadowed the tally in the harness, which is precisely how the tally died the
first time — a test that fails for a new reason has stopped testing what it was written for.
**6. The gate is RED until G lands, by design (2 errors: no `sourcing`, no manifest).** Same as R25: G is
in my wave, parallel. It goes green when G's re-emit + manifest land, with no change from me.
**→ F: one thing before the alpha tag.** There is still **no `v5.0-alpha` tag**, and C's + B's **beta**
commits (`4f0adf1`, `2f4ab59`) are now on `main` ahead of wave 0's close. Tagging `HEAD` would produce a
`v5.0-alpha` whose tree contains beta work your green verdict never covered — the mirror of the reason you
held three times. The alpha's tree is **`e7d58ea`** (wave 0 COMPLETE, my `9b1df5c` beneath it). Your call
and your tag; flagging it because "the tag must certify what was proven" cuts both ways.
## Round 25 — the licence line, made unmissable + the sku form ⟨v5.0-alpha⟩
**1. The bug was not a spelling — it was a DIVERGENCE, and the fallback was the culprit (ledger #2).**
The R24 gate printed `OK — 0 warnings` on a real shop's product photos while saying **nothing** about
their licence. Two paths read one field with different spellings:
```
the CHECK: lic = prov.get("licence") or prov.get("license") ← permissive: passed either spelling
the PRINT: lic = prov.get("licence") ← strict: British only, behind `if lic:`
```
While G's artifact was British they agreed. **G's R24 keyed re-emit moved it to US `license`** — the
check still passed, the print silently resolved to `{}`, and `FLAG BEFORE ANY PUBLIC/COMMERCIAL RELEASE`
stopped reaching a human. **The `or` fallback WAS the bug**: it made one path permissive so it could not
feel the other path break. This is **my R21 stale-roads lesson one layer up** — *a check applied to one of
two symmetric paths is a trap with a fuse on it* — and I wrote that lesson down and still shipped it
again. Fixed structurally, not by adding the missing spelling:
- **One spelling, no fallback.** British `licence` now ERRORS and names C's §7.3 retirement.
- **One lookup, one owner.** `validate_index` returns the evidence string; `main` prints what it is
handed and never re-looks-up the field. There is no second path left to drift.
- **The evidence line is COUNTED.** Judged N atlases ⇒ N licence lines shown, or FAIL.
**2. My own first fix had the same disease, and my own test passed for the WRONG REASON.** I added the
tally tripwire — then `main()` *also* appended its own missing-licence error, so `all_errs` was never
empty when the tally would fire: **the tally was unreachable dead code.** The test went green through the
other path and reported PASS. That is exactly the trap I recorded in R24 (*"a gate that fails for the
wrong reason is a gate that will pass for the wrong reason later"*) — one round later, on me, in the very
file where I wrote it. The fix: the field check owns the precise error; the tally sits *after* the
`all_errs` return and exists **only** for the day that check goes permissive again. Verified by stubbing
`validate_index` permissive — it now fails **via the tally** (`via_tally=True`), not incidentally.
**Proved, 8/8:** G's re-emit → PASS with the flag shown · `license` gone / empty → FAIL · British at top
level → FAIL · British nested → FAIL · `attribution` / `snapshot` gone → FAIL · **the R24 silent shape →
FAIL** · permissive-check simulation → FAIL via the tally.
**3. C §7.2a — the sku form asserted, and I verified C's claim myself before building to it (ledger #3).**
Independently measured: **120/120 godverse ids also exist in the generic pack** — F and C are exactly
right, and an id-equality gate cannot discriminate while that holds. G's snapshot carries **120/120 unique
skus**, so the re-emit was feasible, and I proved the compliant shape passes **before** handing G the gate
(4/4: `sku_<POS sku>` → PASS · positional → FAIL · sku-but-duplicated → FAIL · one id sneaking back into
`rec_` → FAIL at 1/120, so it can catch a *real* collision, not just an all-or-nothing one).
**→ C: two things I did beyond your literal ask — say if you disagree.**
- Your ask was "id present, unique within the pack, `sku_`-prefixed". I also assert the **actual property
your rule exists to guarantee**: the pack's ids are **disjoint from the shipped generic packs**, read
from `web/assets/models/stock_*_index.json`. The prefix says ids *should* be disjoint; this measures
whether they *are*, and it catches the reverse drift (a generic pack wandering into `sku_`) that a
prefix check never could. If the generic packs are absent it **warns** rather than passing silently.
- I flag British `licence` **anywhere**, including inside the nested `provenance{}` "colour" block. Your
§7.3 retires the spelling and R25 is what a spelling fork costs, so I read the retirement as global. If
you meant the block to stay G's business, say so and I'll scope it to the four gate fields.
**4. The gate was RED by design for one wave, and that was the point.** Between my commit-readiness and
G's re-emit the collision was real and unfixed, so `validate_atlas` failed — deliberately. A gate that
stayed green on a defect F had just found would be the vacuous species wearing a different hat. It went
green the moment G landed `4314622`, with no change from me.
## Round 24 — the alpha completion: redhill, the honest validator, ballarat ⟨v5.0-alpha⟩
**1. `redhill_real` — the crate's real street (ledger #3). BUILT.** One bounded Overpass fetch under the
standing authorization. **Red Hill, Brisbane** · centre `-27.4553, 153.0064` · span 2.4 km · **36 shops
(9 heroes + 27 texture, 29 subsampled) · 884 roads** · median NN 73 m, hub 5/36. **Musgrave Road is in
`roads[]` — 41 ways, kinds `[primary, residential]`.** That was the round's point: Monster Robot Party's
real shop now has its real street. Raw snapshots in `_raw/`, ODbL, C's mapping, same discipline as the
pack. **→ Lane G: this is your donor** for `godverse_town.py redhill_real redhill_godverse`.
**→ G AND A: TWO HAZARDS ON THIS DONOR. Please read before you seat the shop.**
> **(a) The shop is NOT in OSM.** There is no `shop=music` node and no "Monster Robot"/"monsterrobot"
> anywhere in the Red Hill bbox — I checked the raw snapshot, not just the emitted cache. It **cannot**
> arrive via my fetch; **G must inject it from the census** (id `3962749`) into `shops[]`. If G assumes
> the donor already carries it, the town ships without the one shop the release is about.
>
> **(b) THE SEATING HAZARD — from G's geocode, the shop seats on the WRONG STREET.** A's lift seats each
> shop on its **nearest edge**. Measured against the real `roads[]` geometry, from G's geocode
> `-27.4553, 153.0064`:
>
> | distance | street | kind |
> |---|---|---|
> | **38.6 m** | **Zig Zag Street** | residential ← **A would seat it HERE** |
> | 46.1 m | Craig Street | residential |
> | 51.6 m | **Musgrave Road** | primary ← the real address (147 Musgrave Rd) |
>
> Three streets are nearer than the addressed one, so Monster Robot Party lands on Zig Zag Street and the
> round's headline quietly becomes false. A street-address geocode sitting 52 m off its own centreline is
> itself the tell — the pin is roughly the block, not the shopfront.
>
> **The measured fix (E's recommendation, G's call):** the point on the real Musgrave Road centreline
> nearest G's geocode is **`-27.455169, 153.006902`**. Seating there: **0 m to Musgrave Road, next
> candidate 43 m away** — unambiguous, with margin. It moves the shop 51.6 m; the residual error *along*
> the street is G's geocode error and is below the game's resolution. **Caveat, stated plainly:** this
> snaps to the right street, it does not verify the house number — I have no address data to confirm
> where #147 sits along Musgrave Rd.
>
> **The robust fix (A's call, likely v5.x):** a godverse shop knows its addressed street; A's lift could
> prefer a name-matching edge over the merely-nearest one. That fixes the whole class, not this one shop.
> The snap above is enough to ship the alpha honestly.
**2. `validate_atlas` — made honest (ledger #4). F's catch was right; there were FOUR bugs, not one.**
The R23 gate could pass without ever touching its subject — the vacuous-gate law's exact species. Fixed:
| # | bug | why it mattered |
|---|---|---|
| 1 | globbed the **published-contract** name (`stock_shop_<id>_index.json`) — a name the runtime can *never* load | matched **0 files** and returned 0. It passed identically whether atlases were absent, misnamed, or perfect |
| 2 | the **code read provenance top-level** while **my own docstring said nested** | G nested it (correctly). The gate would have failed G's real, correct atlas |
| 3 | `license` vs **`licence`** | same — a spelling fork silently failing a valid artifact |
| 4 | ceiling stale by one round | drifted from C's §7 |
Per the R24 **"the runtime is the authority"** ruling the gate now globs the path the loader fetches:
`stock_godverse/<godverseShopId>/stock_<type>_index.json`, `type ∈ {record, book, toy}`. **The vacuous-gate
law is implemented literally:** 0 matches **+ stray files** ⇒ **FAIL**, listing the unmatched paths; 0
matches **+ a truly empty dir** ⇒ `atlas-QA SKIP: no atlases present (0 files matched glob …)`, exit 0 —
it skips **loudly, with the glob printed**, so a silent pass can't hide. It also asserts identity
(`shop.godverse_id` == the directory name) — a falsifiable id equality, per the law's "prove it touched
its subject".
**Proved, not asserted** — four cases: empty ⇒ SKIP (loud, exit 0) · **misnamed (F's exact bug) ⇒ FAIL**
· unparseable ⇒ FAIL · **G's real atlas ⇒ PASS**: `3962749/stock_record_index.json — Monster Robot Party
— items=120 atlases=2 px=2048 OK`, licence printed, 0 warnings. **My own test found a fifth hole:** the
glob still matched the old `stock_shop_*` name, so the misnamed case failed only *incidentally* — I added
a `STOCK_TYPES` name check, and it now fails **for the right reason**. A gate that fails for the wrong
reason is a gate that will pass for the wrong reason later.
**→ Lane C:** built to the runtime + G's real artifact per the ruling. `LANE_C_PUB §7` **line 271** still
names the contradictory `stock_shop_<godverseShopId>_index.json` — when your corrected contract lands I'll
reconcile; if it names anything other than the above, the gate and the loader disagree again.
**3. Ballarat — RETIRED (my call, counted). A different illness from Toowoomba.** Full rationale in
`SOURCES.md` and the commented config entry. The short version, because the distinction matters:
**Toowoomba was dead everywhere. Ballarat is alive exactly where the game isn't.** It has 103 POIs and a
genuinely dense core (densest 400 m disc = **47** POIs; Toowoomba's was **3**) — but that disc is
cafés/clothes/bakeries, while the op-shops sit 854 m / 1042 m / 1709 m out. Heroes and density don't
coincide, so no bbox holds both:
| span | shops | heroes | median NN | hub ≤160 m | op-shops |
|---|---|---|---|---|---|
| 1.0 km (the live core) | 8 | 2 | 184 m | 4/8 (50%) | **0** ← a thrift town with no thrift shop |
| 2.0 km | 12 | 3 | 186 m | 3/12 (25%) | 1 |
| 3.0 km (as shipped) | 20 | 5 | 255 m | 4/20 (20%) | 2 |
Every row is worse than any shipped town (darwin 68 m, glebe 60 m, redhill 73 m, katoomba 22 m).
**→ A, D: the binding constraint was MINE, not OSM's — and it's a real finding.** With 2 heroes the 3×
texture cap keeps 6 of 47 texture POIs, and **the subsample is spatially blind** (deterministic hash rank
over osmId). So in any hero-poor/texture-rich town the survivors scatter *no matter how tight the box*
**part of the 255 m A's warn caught was my own cap's fingerprint, not Ballarat's.** Seeding texture near
heroes would fix the class, but it moves **every** town's golden → I'm filing it as a **v5.x proposal**,
not doing it in a release round.
**→ A: I OVERSTATED MY R23 ADVICE — please don't encode it as a threshold.** I told you the hub fraction
is the discriminator. It isn't, on its own: **glebe — shipped and healthy — reads 7/52 (13%)**, and
redhill reads 5/36 (14%). A hub threshold would false-flag both. What actually held up:
- **median NN is the robust primary** (dead: toowoomba 396 m, ballarat 255 m; alive: 2277 m).
- **hub is only comparable at equal shop counts** — toowoomba 2/12 vs darwin 7/12 was a fair comparison
*because both had 12 shops*; hub% otherwise scales with town size and morphology.
- The R23 calibration point still stands and still matters: **your `validateTownCache` sees raw cache
lat/lon; D's numbers are seated (post-lift)** — darwin is 27 m seated but **68.3 m raw**, so a threshold
read off D's table will false-warn darwin.
**4. A methodology note I'm recording because it nearly bit me.** I simulated the re-bbox offline to pick
Ballarat's span; the sim predicted **68 m / hub 5-8** and I had already written those numbers into a
config comment as fact. The real build returned **184 m / 4-8***same* counts (2 heroes + 6 texture),
*different sample*: my re-implementation of the hash subsample picked a dense six where the pipeline picks
a spread six. **Never trust a re-implementation of the pipeline's own selection — run the pipeline.** The
sim's numbers would have shipped as a claim about a town nobody would have re-measured.
**5. Determinism receipt.** The ballarat retirement forced a full 21-town rebuild from the cached raw
snapshots (no refetches). **Every existing cache came back byte-identical** — only ballarat (deleted),
redhill (new) and `index.json` moved. Pack now **21 real towns · 1196 shops · 299 heroes**, 22 rostered
with Lane G's `newtown_godverse`. Selfcheck **ALL GREEN, 149,696 checks**, synthetic golden `0x3fa36874`
unchanged (the count moves with the roster: ballarat's 1144-edge graph out, redhill's 884 in).
**6. → A: THE GOLDEN PINS ARE YOURS AND MY ROUND MOVED THEM (the batched pin, ledger law).** Your own
selfcheck printed the exact values — no round trip needed:
```
REAL_TOWN_GOLDENS['redhill_real'] = 0x6046700f // ADD (new town, currently UNPINNED)
REAL_TOWN_GIG_GOLDENS['redhill_real'] = 0x673785ee // ADD
REAL_TOWN_GOLDENS['ballarat_real'] = 0xf0a9e8a1 // REMOVE (cache deleted — dead config)
REAL_TOWN_GIG_GOLDENS['ballarat_real'] = 0xf097c331 // REMOVE
```
The ballarat pins are now exactly what **your R23 note** called the toowoomba pin: *"a golden for a town
that cannot load is dead config: it can never fire, so it can never fail — same species as the vacuous-gate
law, one layer down."* Your words, my retirement, same fix. And note the mirror on the other side:
`redhill_real` currently **warns** (`⚠ base UNPINNED`) rather than failing, so **the round's headline town
is ungated until you pin it** — an unpinned town is the same vacuous species pointing the other way. Your
warn-with-the-hash design is what made this a two-line handoff instead of a bug hunt; worth keeping.
**7. → F: an R23 orphan I created and owe you.** `tools/qa/tour_shots.py` (your v4.0 release tour) still
shoots `plansrc=osm&town=toowoomba_real` for its `toowoomba_thintail` frame — I deleted that cache in R23
and never swept the consumers, so **re-running the v4 tour now breaks on a missing town.** It's your file
and it's a shipped historical artifact (the frame *was* the honest thin-tail proof), so I'm flagging
rather than editing: either re-point the frame at a surviving thin town or retire it with a note. Same
class, lower stakes: `web/js/citygen/gigs.js:134` names ballarat/toowoomba in a comment as thin-town
examples — stale prose only, no behaviour. **The general lesson for me: a retirement isn't done when the
cache is deleted; it's done when the consumers are swept.**
## Round 23 — v5.0-alpha: the atlas validator + the toowoomba call ⟨v5.0-alpha⟩
**1. `validate_atlas` — the G2a format gate (ledger #4).** `pipeline/validate_atlas.py`, wired into
@ -412,12 +1245,54 @@ loader wiring (`spawnRig`/fleet). Bench-sit loiter (Fable's stretch goal) reuses
| Audio pack | 28 | 100% procedural numpy synthesis (`gen_audio.py`) | oscillator/noise render → ffmpeg OGG+M4A | 🟢 CC0-equiv, parody-safe |
| Stock packs | 3 (350/311/273) | GODVERSE `dealgod` Postgres — **real** cover art | `build_stock_pack.py`: parody-transform metadata + atlas | 🟢 real-art / parody-metadata |
| Ped clip **⟨v3.1⟩** | `sit.glb` (E R16; + pre-existing `walk`/`idle` from D/90sDJsim) | Adobe **Mixamo** `Sitting_Idle.fbx` | `fbx_to_clip.py` (Blender, mesh-strip) | 🟢 Mixamo royalty-free (motion only, mesh stripped) |
| Town pack **⟨v4.0-FINAL⟩** | **22 real AU towns, all 8 states/territories** — 1192 shops + real street geometry (`roads[]`) | **OpenStreetMap** via Overpass API — bounded per-town bbox; raw shops+roads snapshots committed in `_raw/` | `build_towns.py`: fetch → dedupe/suburb-fill/parody → C's §6 class map → 3× texture cap (drops counted) → v2 cache + roads | 🟢 **ODbL 1.0** (© OSM contributors; `web/assets/towns/SOURCES.md` + `license`/`attribution` on every cache) |
| Godverse town *(Lane G's, listed for completeness)* | `newtown_godverse` — 18 shops, rostered in E's `index.json` | GODVERSE/thriftgod shop census + OSM road geometry | `pipeline/godverse_town.py` (**Lane G owns this provenance**) | 🟢 ODbL 1.0 + GODVERSE census (G's own `license`/`attribution` fields) |
| Ped clip **⟨v5.1⟩** | `look.glb` (E R28 — v6 Spike 1) — mesh-free, 65 `mixamorig` joints, 195 tracks, 136 KB; structurally identical to `sit`/`walk`/`idle` | Adobe **Mixamo** `Looking_Around.fbx` (675 KB) — from the **cached tailnet stash** (`johnking@ultra:~/Documents/mixamo-fetch/out/`), not re-downloaded | `fbx_to_clip.py` (Blender, mesh-strip) — the R16 path, unchanged | 🟢 Mixamo royalty-free (motion only, mesh stripped) — same zone as walk/idle/sit |
| Ped clip **⟨v5.1⟩ NOT BUILT** | `lean.glb`**the source does not exist** (see R28 §1) | — | — | — |
| Ped dance clips **⟨v7.0⟩** | `dance_party` · `dance_medium` · `dance_drink` · `dance_sway` (4 — joined `046668e` outside the round system, swept R36 w1) — mesh-free, 68 nodes / 198 tracks / 52-joint dangling skin + IBM each, 0 NaN, 30.552.4 s | **Rokoko free motion-capture library** (263-asset pack, `~/Documents/Rokoko_Free_Mocap_FBX_263`, `Rokoko Studio (Mocap)/Dancing/*_mixamo.fbx` — Rokoko's own Mixamo-skeleton export). **NOT Mixamo** — R36 measured zone correction | 90sDJsim `tools/bake_clips.py` (the `fbx_to_clip.py` port, ACTIVE_ACTIONS fix) → sha1-identical copy into procity (`f8f0208` → `046668e`) | 🟢 **Rokoko royalty-free incl. commercial** ("use in any … 3D project … including for commercial use" — rokoko.com pack page); motion only, mesh-free |
| Ped bodies **⟨v7.0⟩** | `dj_phrtt_01` · `dj_techno_01` · `woman_dj_01/02/03` (**5, not the brief's 3** — joined `b339402` outside the round system, swept R36 w1) — 4000/4000/3999/3999/3998 tris, 1024² webp (`EXT_texture_webp`, roster-standard), no Draco, 65-joint mixamorig skin, IBM clean | John's own DJ characters (`grldj5/grldj2/djgrl3/techno_01/phrtt2`): `flux_local` sheet → **TRELLIS.2 on-device mesh** → Adobe **Mixamo auto-rig** (full 65-bone). Source FBXs in the rigroom library on ultra | MESHGOD `scripts/fbx_to_ped.py` (FBX→skinned GLB, 4k collapse-decimate, mixamorig + skin preserved) → sha1-identical copy (`1906294` → `b339402`) | 🟢 **CC0-equiv on-device mesh (the instrument-pipeline zone) + Mixamo auto-rig output (royalty-free)** · ⚠ figure-scale gate pending (R36 w2, F) |
| Town pack **⟨v5.0-FINAL⟩** | **21 real AU towns, all 8 states/territories****1196 shops** (299 heroes) + real street geometry (`roads[]`) | **OpenStreetMap** via Overpass API — bounded per-town bbox; raw shops+roads snapshots committed in `_raw/` | `build_towns.py`: fetch → dedupe/suburb-fill/parody → C's §6 class map → 3× texture cap (drops counted) → v2 cache + roads | 🟢 **ODbL 1.0** (© OSM contributors; `web/assets/towns/SOURCES.md` + `license`/`attribution` on every cache) |
| Godverse towns *(Lane G's, listed for completeness)* **⟨v5.0-FINAL⟩** | `newtown_godverse` (72 shops) · **`redhill_godverse` (37)** — the town whose record shop is THE shop. Both rostered in E's `index.json` (**23 total**) | GODVERSE/thriftgod shop census + OSM road geometry; `redhill_real` is E's donor | `pipeline/godverse_town.py` (**Lane G owns this provenance**) | 🟢 ODbL 1.0 + GODVERSE census (G's own `license`/`attribution` fields) |
| Real-stock atlas **⟨v5.0-FINAL⟩** | **1 crate, 120 items**, 2×2048² webp — Monster Robot Party (`3962749`) | **the real POS** (`recordgod`) — crate 550 "DEEP HOUSE"; covers are **the shop's own product shots**, cached by dealgod | `pipeline/godverse_stock.py` (**Lane G owns**) — `snapshot`(DB-side, sha256-pinned) → `pack`(pure) | 🟡 **in-house-green — FLAG BEFORE ANY PUBLIC/COMMERCIAL RELEASE** · `sourcing:"real"` · ids `sku_<POS id>` |
| Mint atlases **⟨v5.0-FINAL⟩** | **14 crates, 224 items**, 14×1024² webp — keyed hero shops with **no POS** (5 record · 6 book · 3 toy across both godverse towns; the 6th record crate is the real one above) | **real dealgod listings** (the sellers' own product shots) — a *seeded sample*, `random.Random(shop_id)`, deterministic | `godverse_stock.py` mint baker (**Lane G owns**) — same bake path, sampled source | 🟡 **in-house-green — same flag** · `sourcing:"mint"` · ids `mint_<listing id>` · **never claimed as that shop's stock** |
**GLB fleet = 33 manifest (23 fit + 10 furniture) + 5 `_hi` = 38 published.** Only external spend ever:
~$0.64 (fal-Hunyuan, R5). Everything else is $0 / on-device (electricity only).
### ⟨v5.0-FINAL⟩ The licence line's story — why one printed line is a provenance mechanism
**The only 🟡 in this table is the v5 stock art, and the flag is the whole control.** Charter law #3 says
clearing photos for public release is **a human call** — so the gate does not pattern-match the licence
and rule on it. It **prints it**, every run, for every atlas, and a human decides. That makes one printed
line the mechanism the amber rows depend on, which is why it earned two rounds of work:
- **R23** — the gate globbed a name the runtime can never load: **0 files matched, returned 0**. It passed
identically whether atlases were absent, misnamed, or perfect. (The vacuous-gate law came from this.)
- **R25 — the line went silent and the gate still said OK.** Not a typo: the **check** read
`licence or license` (permissive) while the **print** read `licence` only. G's re-emit moved to US
`license`; the check passed, the print resolved to `{}`, and `FLAG BEFORE ANY PUBLIC/COMMERCIAL RELEASE`
**stopped reaching a human on a real shop's product photos**. *The fallback WAS the bug* — it let one
path stay permissive so it could not feel the other break.
- **The fix is structural, not a spelling.** One spelling, no fallback (British `licence` now ERRORS,
naming C's §7.3 retirement) · **one lookup, one owner** (`validate_index` returns the evidence string;
`main` prints what it is handed and never re-looks-up the field — no second path left to drift) · and
**the line is COUNTED**: judged N atlases ⇒ N licence lines shown, or FAIL. A gate that can say OK while
hiding the flag cannot exist by construction.
**Today, frozen:** `atlas-QA OK — 15 per-shop atlas(es) valid, 15 licence line(s) shown, 0 warning(s)`.
Fifteen crates, fifteen flags, every one in front of a human. **Both zones print** — the mint rows carry the
same in-house-green flag because a seeded sample of real dealgod listings is still **the sellers' own
product shots**. Minting changes whose stock it *claims* to be, never whose photograph it *is*: that is why
`sourcing` is honesty and the licence is unchanged by it.
### ⟨v5.0-FINAL⟩ The id-space fences — provenance carried in the id itself
Two disjoint namespaces, blessed as design (C §7.2a, R26 w4), asserted by `validate_atlas`:
**`sku_<POS id>`** names a physical copy on Monster Robot's shelf · **`mint_<listing id>`** names a dealgod
listing standing in for one. A crate wearing the other's prefix **FAILS**. This is not naming hygiene:
tier-2's sold-means-gone looks up POS skus, so a `mint_` id filed under `sku_` doesn't fail a gate — it
**sells a real record out of a real shop to satisfy a minted stand-in**. G refused that shortcut when it
would have gone green the easy way, and that judgment is the fence. *(Same standing note as
plan-ids vs godverse-ids: one name, one space — never compare across.)*
### Audio inventory — 28 assets (`web/assets/audio/`, committed; contract = `manifest.audio`)
All procedural, seeded, $0, parody-safe; OGG/Opus + M4A/AAC; beds+music loudnorm 16 LUFS, SFX peak-norm.

File diff suppressed because it is too large Load Diff

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

View 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 AF 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.

View 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 12 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.

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

View 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 (~34k) 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 23
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.

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

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

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

View 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 $2559"), 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 13:** 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 $824" 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.

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

View 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.52.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.

View 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).

View File

@ -0,0 +1,65 @@
# PROCITY — Round 36 instructions (from Fable, integrator)
Date: 2026-07-23 · **R32R35 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 R3235 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 12 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]

View 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 (99124k 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 D1D4 (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.

View 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.74.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:** 34 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 AugOct, 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 D1D4 · 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 D1D4 · the matrix's budget
column is bookmark numbers · `?r=3` breaches at 307 · should `?classic=1` show the town
selector at all.

View 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.030.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.139.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 D1D4 · 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.

View 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 D1D4, 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.

122
docs/RETRO_V5.md Normal file
View 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
View 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(p1, …)` 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.*

View 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 — 99k124k 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.53 min, $0 | 🟢 CC0-equiv | None. ~2025 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 45% 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 6080 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 34 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 AugOct, 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.74.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.
35. **`magpie-clack`, `dog-bark-fence`, `mower-distant`** — three `gen_audio.py` PACK entries. Minutes, $0, deterministic, parody-safe by construction.
67. **`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 34 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 45% 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
View 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 36 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) | 74369 m | 842 s |
| 2 shops | 7 | 6390 m | 144 s |
| 1 shop | 11 | — | — |
| **0 — no sale day** | 3 (bowral, darwin, daylesford) | — | — |
Synthetic towns (`plansrc` default, ~480 shops, 103151 crate-shops): roster of 3, tour **827 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 38 crates × 16 sleeves = **48128 records**, of which (modelled off the shipped band table at `bible.js:26-30`) **513 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 1230 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 38 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 1018 (`registry.js:40`), stalls 814. 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 $2559` line stays exactly where it is: **that is the discount theatre.** `~~$22~~ $17` inside `guide $824` and `~~$50~~ $31` inside `guide $2559` 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 × (1d)))`, and no band ⇒ no ticket (fail-closed). One `Math.max` buys both fences at once: the keeper's counter offer is `min(lo1, ⌊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.52.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 $2559` 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** | 5075% 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 16.
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 012 per real town to something like 325 — 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
View 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 **1520 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.

View File

@ -81,3 +81,52 @@ plus this epoch's own:
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
View 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) + 13 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 23 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
View 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(bandLow1, ⌊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
(R3031), beta opened the 23 (R3233), v7.0 closed with the auction + onboarding (R3435).
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.

View 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, 7041,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 99124k 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.

View 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 23 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 13 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 110 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%** | 9093% | 8389% | 7778% | 6974% | 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, 212 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 1018 (`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:3012: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 1185% inside 368 m.
- **You've walked Templeton before.** It's on your fogged map, labelled. You go. 4090 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, 105466 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. 368704 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, 23 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 **518% 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 14**, p90 218, **1989% uniquely identified**. Without the street term: median 349.
- Adding side-of-street: median 14, **2789% unique**, worst tails cut (katoomba 18→15, fitzroy 10→7, brunswick 12→9).
- **51100% 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 13 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 51100% (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.030.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.

Binary file not shown.

After

Width:  |  Height:  |  Size: 362 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 535 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 241 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 327 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 505 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 690 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 746 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 844 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 912 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 886 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 347 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 623 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 880 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 498 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 564 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 436 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 600 KiB

View File

@ -0,0 +1,29 @@
# Lane F — R39: the spawn-pose RE-BASELINE (v9 wave 1)
Lane B's `+sin` fix moved the non-classic spawn (`index.html:500`, R32's cluster-pose). **Two of this
lane's picture baselines were taken at the old pose and are therefore photographs of the defect:**
| baseline | what it shows | status |
|---|---|---|
| `docs/shots/v7_tour/02_the_strip.png` | "the cluster-pose spawn: midday on the main street, shopfronts down the block" | **HISTORICAL** — kept. It is an artefact of the tagged v7.0 epoch; re-shooting it on a v9 tree would falsify a release record. |
| `docs/shots/v7_beta/first_five_splash.png` | the R35 first-five-minutes splash, shot at the spawn | **HISTORICAL** — kept, same reason. |
The re-baseline lives here, and it ships its own BEFORE — the **same tree**, camera parked at the
old `-sin` pose — so the pair is a comparison rather than a claim. Regenerate with
`tools/.venv/bin/python tools/qa/r39_shots.py` (seed 20261990, `?roster=v1&pop=0`, segment 2).
| frame | pose | what is in it |
|---|---|---|
| `the_strip-BEFORE-minus-sin.png` | synthetic (25.5, 17.9) yaw π | the shipped-since-R32 spawn: a footpath facing a plaza, not one shopfront in frame |
| `the_strip-AFTER-plus-sin.png` | synthetic (6.1, 19.8) yaw π | the fixed spawn: the strip, four shopfronts inside 25 m and all four in front of you |
| `the_strip_katoomba-BEFORE-minus-sin.png` | katoomba_real (219.0, 471.3) yaw 0 | **standing in a paddock facing the blank back wall of a building** — the R31 playtest's own complaint, the one R32 was written to fix and, because of the sign, never did |
| `the_strip_katoomba-AFTER-plus-sin.png` | katoomba_real (200.0, 474.4) yaw 0 | under the awnings on Katoomba Street with the shopfronts and their signage running away down the block |
| `first_five-AFTER-plus-sin.png` | synthetic, fresh game | the R35 frame re-shot: *"You're hunting HILLS HOIST — $191 in your pocket."* over a street that now has shops in it |
**The automated R35 gate did not need re-baselining, only the picture did.** `smoke_first_five`
(`tools/flags_check.py`) reads the hunt line and drives the beats through `DBG.enterShop`, so it is
pose-independent; it was green before and after the fix. What the fix changed is the frame the gate's
subject is *seen* in. The falsifiable arm that now guards this pose is
`tools/qa/r39_runtime.py --only sign`: **at the spawn, the count of shops within 25 m that are in
front of the player must be > 0** — measured 4/4 · 4/4 · 4/4 · 1/1 on synthetic / katoomba / bowral /
fitzroy after the fix, and **0 on all four** with the old sign re-derived on the same tree.

Binary file not shown.

After

Width:  |  Height:  |  Size: 770 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 771 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 799 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 629 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 680 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 784 KiB

View File

@ -0,0 +1,8 @@
<!doctype html><meta charset=utf-8><title>PROCITY v5.0 — THE LIVE CRATE</title>
<style>body{background:#111;color:#ddd;font:15px/1.6 -apple-system,sans-serif;max-width:1100px;margin:40px auto;padding:0 20px}img{width:100%;border-radius:6px;margin:10px 0}h1{font-weight:600}p{color:#aaa}hr{border:0;border-top:1px solid #333;margin:36px 0}</style>
<h1>PROCITY v5.0 — THE LIVE CRATE</h1>
<p>One real record shop, on its real street, holding its real stock — and a server that can die mid-session without the game noticing.</p>
<hr><h3>musgrave_road</h3><img src='musgrave_road.png'><p>the frontage — Monster Robot Party on Musgrave Road, Red Hill, Brisbane. It is on this road in the game because it is on this road · Red Hill · retail heart: 2 shops/60m · 91 draws · 62,576 tris</p>
<hr><h3>the_real_crate</h3><img src='the_real_crate.png'><p>THE REAL CRATE — Monster Robot Party (147 Musgrave Rd): 120 records off its own till, real covers, real titles, real prices. Sell one in the shop and it leaves this crate · Monster Robot Party · 120 records from assets/stock_godverse/3962749/ · 40 draws · 40,239 tris</p>
<hr><h3>a_mint_crate</h3><img src='a_mint_crate.png'><p>a MINT crate — plausible, NOT real: a seeded sample standing in for a shop whose till we don't hold. Same tier, same offline guarantee, different provenance — and the atlas says so in its own index · Presents of Mind · 16 records from assets/stock_godverse/767/ · 51 draws · 30,897 tris</p>
<hr><h3>classic_covenant</h3><img src='classic_covenant.png'><p>?classic=1 — the exact v2 town, byte-identical forever (0x5f76e76). Five epochs, one covenant · Boolarra Heads · retail heart: 35 shops/60m · 162 draws · 39,259 tris</p>

Binary file not shown.

After

Width:  |  Height:  |  Size: 778 KiB

View File

@ -0,0 +1,22 @@
[
{
"name": "musgrave_road",
"file": "musgrave_road.png",
"caption": "the frontage \u2014 Monster Robot Party on Musgrave Road, Red Hill, Brisbane. It is on this road in the game because it is on this road \u00b7 Red Hill \u00b7 retail heart: 2 shops/60m \u00b7 91 draws \u00b7 62,576 tris"
},
{
"name": "the_real_crate",
"file": "the_real_crate.png",
"caption": "THE REAL CRATE \u2014 Monster Robot Party (147 Musgrave Rd): 120 records off its own till, real covers, real titles, real prices. Sell one in the shop and it leaves this crate \u00b7 Monster Robot Party \u00b7 120 records from assets/stock_godverse/3962749/ \u00b7 40 draws \u00b7 40,239 tris"
},
{
"name": "a_mint_crate",
"file": "a_mint_crate.png",
"caption": "a MINT crate \u2014 plausible, NOT real: a seeded sample standing in for a shop whose till we don't hold. Same tier, same offline guarantee, different provenance \u2014 and the atlas says so in its own index \u00b7 Presents of Mind \u00b7 16 records from assets/stock_godverse/767/ \u00b7 51 draws \u00b7 30,897 tris"
},
{
"name": "classic_covenant",
"file": "classic_covenant.png",
"caption": "?classic=1 \u2014 the exact v2 town, byte-identical forever (0x5f76e76). Five epochs, one covenant \u00b7 Boolarra Heads \u00b7 retail heart: 35 shops/60m \u00b7 162 draws \u00b7 39,259 tris"
}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 894 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 789 KiB

Some files were not shown because too many files have changed in this diff Show More