Commit Graph

99 Commits

Author SHA1 Message Date
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
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
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
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
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
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
47f05ecaa8 Lane D R23 (v5.0-alpha): the GIG_RANGE chunk-neighbour fix — scoped to the gig path (ledger #6)
My carried v4.x finding, fixed post-release as scheduled. Runtime-only: no plan, no
golden, no flag, no fetch. selfcheck ALL GREEN 161300/161300, 0x3fa36874 frozen.

THE FINDING GREW WHILE IT WAITED: filed at 8.7% (measured at katoomba's isolated pub);
A's R21 cluster bias made it WORSE — venues now sit inside the retail cluster where peds
converge from every side, so 42.6% of in-gig-range peds (5094/11971) were chunk-blind.

THE OVERRIDE (measurement wins): the brief asked for "extend to chunk + 8 neighbours"
AND "byte-identical flags-off". I built the literal version and measured that those
cannot both hold: on the synthetic town (what ?classic=1 boots, gig layer absent) a full
3x3 scan sourced 127 of 855 finds (14.9%) from neighbour chunks — finds v2 never made.
The frozen v2 crowd would shop ~15% more. Covenant breach, in a clause of the same brief.

So the fix is scoped to the GIG path — which is what I actually filed:
- ordinary shops: own chunk only (v2 semantics, frozen — noticed from its own block)
- gig venues: the full 34m radius, honest across chunk edges ("follow the sound" is the
  whole reason GIG_RANGE 34 > PATRON_RANGE 18)
- no gig on -> _gigVenues empty -> neighbour loop skipped entirely -> ?classic and every
  flags-off boot take the IDENTICAL code path and cost to v2. Byte-identical BY
  CONSTRUCTION, not by measurement luck.
Both ranges < CHUNK(64) so 3x3 is exact, not approximate; fixed dz/dx order keeps ties
deterministic as the old single-list scan was.

BEFORE -> AFTER: cross-chunk in-range peds seeing the gig 0% (structural: v2 scanned only
the ped's own chunk) -> 100% (katoomba 4/4, fitzroy 11/11). Same-chunk unchanged 100%.
Classic neighbour-sourced finds: 0 (127 under the rejected full scan). Determinism
identical. 0 NaN on katoomba/fitzroy/synthetic. Sim 0.056 -> 0.057 ms/frame (+2%, i.e.
0.006% of a 16.7ms budget); lookup 0.58us unchanged with no gig, 1.3-2.7us with a gig on
(fires per PATRON_STRIDE, not per frame). Fitzroy clean at 376 peds.

CARRIED, documented: PATRON_RANGE keeps v2's own chunk-blindness — fixing it costs the
classic covenant, and the covenant wins. A frozen v2 quirk now, not an oversight.
Known minor: a scheduled-but-closed gig still runs the loop for nothing (0.58->1.3us).

D->F: no blocker for v5.0-alpha; classic untouched by construction.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 23:06:07 +10:00
m3ultra
292b07aa91 Lane A round 23 (v5.0-alpha): the spacing warn — D's thin-tail finding as law (ledger #5)
validateTownCache now warns when medianShopSpacing(cache) > MAX_MEDIAN_SPACING_M (150 m). Metric +
threshold exported from the barrel so no lane re-derives them. WARN, NEVER ERROR: a flagged cache still
boots a valid district (toowoomba shipped green through the entire v4.0 pack) — E curates, A flags.
selfcheck ALL GREEN 161,372/161,372 (+72); synthetic 0x3fa36874 frozen; NO golden moved (only E's
toowoomba re-bbox may move one this round, per the round's law).

THE THRESHOLD IS MEASURED, NOT PICKED. The brief offered darwin 27 m / toowoomba 388 m as "plenty of
daylight for a line" — but that's 2 points. Measuring all 23 towns shows a near-continuum, and the line
is FORCED into (119.3, 254.9]: braddon at 119.3 m is ALIVE by D's own hub test (7 shops within 160 m,
identical to darwin's 7), ballarat at 254.9 m is not. 150 m sits in the pack's widest gap (x2.14), with
1.26x margin below and 1.70x above. Flags 2 of 23.

TWO TOWNS FLAG, NOT ONE. The brief names only toowoomba (D spot-checked the two 12-shop towns). The
measurement adds BALLARAT at 255 m — nobody has looked at it, and it's independently one of the three
towns my R22 graded cluster fallback fired on (no venue candidate at the >=3-shops/160 m floor). Two
signals agree. Filed for E as a curation call, counted either way.

THE BRIEF'S ANCHORS WERE IN THE WRONG SPACE, AND IT MATTERS. D's 27 m is PLAN space (post-lift metres);
validateTownCache is CACHE space (raw lat/lon — the only thing that exists at ingest, and the layer where
a bad bbox must be caught). Darwin is 68.3 m in cache space, not 27. Interpolating a threshold between
D's figures would have silently mixed spaces. I verified the hypothesis before trusting it: computing the
metric plan-side reproduces D's numbers exactly (darwin 27.2 vs 27, toowoomba 386.8 vs 388, hubs 7 and 3).

Cache space is correct HERE and provably safe: the lift marches shops at a regularised cadence, so it
COMPRESSES dense towns (darwin plan/cache 0.40) but not sparse ones (toowoomba 0.98, ballarat 1.01) --
the error lives at the dense end, the warn only fires at the sparse end. Verified all 23 towns in both
spaces: exactly ONE disagrees, newtown_godverse (94.4 cache passes / 158.3 plan would warn). Filed for
G/Fable, NOT acted on: no cache-space line catches it without false-positiving braddon, and godverse is a
census subset (18 thriftgod shops over the footprint that carries 72 OSM shops) so its sparsity is
coverage, not a bad bbox — re-bboxing, the warn's only remedy, cannot fix it. Worth knowing since R23
stocks the real crate in that town and the beta asks "fitzroy_godverse if the census is dense there".

Also fixed en route: the R17 silent-fallback trap bit again in my own scratch harness — an unregistered
town key resolves to the melbourne fixture rather than erroring, so five towns silently measured
identical. Registered the caches; noting it because it has now cost two rounds.

Coverage: sparse cache warns + still boots (structural + district suites on a flagged town), compact
cache does NOT warn (no false positive), metric edge cases (< 2 shops -> null, coincident -> 0, no throw
on empty), and an exact-value check (a 100 m pair measures 100 m, projection sane).
Cost: ~1 ms/town at registration (25.6 ms for all 23 incl. fitzroy's 160 shops) vs a <100 ms plan budget.

E -> use the warn as your acceptance test for #4: re-bbox, re-run, warn goes silent = the town has a high
street. If your fix moves toowoomba's or ballarat's cache, ping me — goldens are mine to re-pin, and only
what you move.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 23:04:43 +10:00
m3ultra
8ac3b18b36 Lane F R22 (v4.0 — THE EPOCH CLOSE): the re-measure, the selector, the release tour, the tag
The blocking gate is CLOSED, and the epoch closes green.

- #1b THE BLOCKING RE-MEASURE: F's R21 re-measure made the tri diet blocking; B diagnosed layer-by-layer,
  the driver traced A -> E (A's own commit: "B's numbers say the fix is E's"), E fixed it, F re-measured:
  katoomba venue_night 221,935 (+11%) -> 108,365 (-51%); bendigo 201k -> 124,682; fremantle -> 88,397.
  Worst 124,682 — the 200k stress edge is closed. The measure-first loop worked end to end.
- #3 DBG.enterShop(selector) (Lane G's ask): id · registry type · exact name · name substring, resolved
  most-specific-first so an id-shaped string can't be shadowed. One-liner QA on any of 23 towns.
- #4 The release tour -> docs/shots/release_v4/: the Fremantle hero · the Fitzroy mecca · the Toowoomba thin
  tail · the synthetic control · the ?classic=1 covenant. Every real-town frame poses through F's R21
  cluster primitive; B's R22 bookmarks anchor on the same retail heart, so tour and bookmarks agree by
  construction. HUD hidden throughout (F's R21 ruling corollary).
  The primitive earned itself twice: the first cut posed via street_noon and the MECCA CAME OUT AS BARE
  BITUMEN — the same trap as R21. Re-posed through the cluster, Fitzroy reads.
- HONEST FINDING (filed v4.x): the brief asked for the thin tail as "proof the floor holds". It does not,
  and the frame says so — Toowoomba reads as an empty road. The floor is a RATIO, not a count: 0.008
  shops/edge vs the heroes' 0.06-0.08. Shipped labelled honestly rather than swapped for a flattering town.
- #6 Docs: README v4 section (the towns ARE the product), LANE_F_NOTES §22, F-progress.

qa.sh --strict --matrix GREEN 7/7; MATRIX GREEN — 10 towns × 7 gates (the budget gate green everywhere for
the first time); selfcheck 161,300/161,300 (A's 36 goldens + the 23-town pack).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 22:32:32 +10:00
m3ultra
af8a6db020 Lane B round 22 (v4.0): #3 per-town bookmarks — anchor on the retail heart (Lane G's filed bug) + F's primitive
ROOT CAUSE: every "nearest" resolver in dbg.js sorted by distance from the WORLD ORIGIN. That's the town
centre only on a synthetic plan -- a real OSM town is projected about its cache's bbox centre while its shops
cluster hundreds of metres away, so bookmarks teleported into arbitrary far-flung geometry, exactly as G
reported. Measured origin-pick distance from the retail heart: katoomba 408m, newtown 626m, fremantle 371m.

FIX: resolve bookmarks about the town's DENSEST SHOP CLUSTER (grid-bin shop lots at 120m; heaviest bin's
centroid = the retail heart) instead of the origin -- byDist, lotsByDist, streetViewPose, stopPose, and
crossroadsPose's tie-break.

GATED on the REAL-ROADS signal (edges > 200) -- the same primitive as the R20 tram fence -- so synthetic and
the marched fixtures keep their exact origin-centred behaviour and F's release-tour control shot cannot move.
The gate is load-bearing, not cosmetic: synthetic's anchor is only 117m out but it DOES re-pick (Anglibarn Op
Shop -> Dusty Pages), so an unconditional switch would have silently moved the control. Same reason the new
crossroadsPose tie-break is gated (a degree tie would otherwise resolve differently on synthetic).

Verified across the town set: synthetic(22 edges)/melbourne(15)/katoomba-fixture(15)/silverton(1) -> origin,
UNCHANGED; newtown_godverse(1626) -> anchor(98,-629); katoomba_real(993) -> anchor(-227,-625). G's own town
is fixed.

Result on katoomba: shopfront bookmarks went from ~408m adrift to <=42m of the retail heart (arcade 7m,
milkbar_dusk/patronage_door/shopfront_detail 24m, market_square 26m, dig_real 42m); strip views ~195m on the
high street; venue_night/queue_night ~345m (A's venue placement, not the bookmark). All 19 resolve, 0 errors;
synthetic 19/19 unchanged. warehouse_fringe sits 3,074m out -- correct by design (it's the far:true fringe
shot; on a 5km town the fringe genuinely is 3km away).

-> F (ledger #4, the shared posing primitive): published on the harness as DBG.townAnchor {x,z,n,anchored,ref}
   (the retail heart + whether it's in use) and DBG.clusterPose(kinds) (stand on the road at the densest
   cluster, look along it). Your cluster-pose finding as a dbg.js-consumable -- the tour and the bookmarks now
   resolve through the SAME primitive, so a tour shot and its bookmark can't drift apart.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 22:01:41 +10:00
m3ultra
3face97046 Lane A round 22: absorb the town pack — 23 towns, 36 goldens pinned in one commit (v4.0, ledger #2)
E's 17 new towns + Lane G's newtown_godverse fed through the lift. ONE batched golden commit as
briefed: REAL_TOWN_GOLDENS + REAL_TOWN_GIG_GOLDENS go 5 -> 23 towns each, zero unpinned.

THE HEADLINE: everything built this epoch held at 23-town scale, FIRST TRY. 1,210 shops -> 1,133 seated
(77 dropped, 6.4%, all counted by cause); 1 CONNECTED COMPONENT and 3 VENUES on every one of the 23 towns.
fitzroy — 160 shops, 2x anything absorbed before — landed 139 seated, 1 component, 3 venues, 212 posters,
all invariants green. The junction-protected DP, fragmentation ruling, cluster bias, poster cap and
capacity fence needed NO new hardening: the pack needed absorbing, not fixing.

- Graded cluster fallback (the one refinement). Three towns have no candidate at the floor at all
  (ballarat/toowoomba/newtown_godverse top out at 2 shops within 160m); R21's bias switched off entirely
  there, stranding venues at 0. It now falls back to the DENSEST AVAILABLE — ballarat 0,2,0 -> 2,2,2,
  godverse 0,1,1 -> 2,2,2, toowoomba 1,0,0 -> 1,2,1. Fires only where the floor is unreachable, so NO
  already-pinned town moved (verified). Acceptance at pack scale: 20 of 23 towns have every venue >=3;
  the three above sit at their town's genuine ceiling — the carved-out case, stated explicitly.
- Drop outliers, counted not hidden: daylesford 19% (32 shops on a 171-road graph, the thinnest in the
  pack, all overflow), fitzroy 13%. RGAP is already 0.5m and NODE_CLEAR 3m; the only lever left is
  spilling overflow onto a DIFFERENT street, which buys a few percent by putting a shop on a road it
  isn't on. Same ruling as R20: drop and count beats teleport.
- selfcheck ALL GREEN 161,300/161,300; scaffold + consistency GREEN; cross-process deterministic
  (pack-xor 0xca140954). classic 0x3fa36874 / gig 0xb1d48ea1 / marched fixtures FROZEN.

Handshake -> C + D + F: the pack is in, 23 towns pinned, start your verifies. Tri diet (ledger #1): if
B's layer-by-layer names plan geometry as the venue_night driver, I own the fix.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 21:55:05 +10:00
m3ultra
110c82833b Lane A round 21: venue cluster-adjacency bias — D's relocation finding (ledger #1)
D found katoomba's pub 1,000m from the retail cluster — a gig with no passing crowd. Measuring all
five towns first showed it was SYSTEMIC: 11 of 15 venues sat with <3 shops within 160m (newtown's were
0/0/1 — every venue stranded). After the bias: 0 of 15. Katoomba's pub 1 → 9. Castlemaine cleared without
needing the floor exception the brief anticipated.

- pickVenues builds a CLUSTER-ADJACENT field first (>= CLUSTER_MIN 3 shops within CLUSTER_R 160m), then the
  kind flavors (pub corner / band_room fringe / rsl off-spine) bias WITHIN that field rather than overriding
  it (per the brief: biases, not absolutes) — a fringe band_room in the cluster beats one a kilometre from
  anyone. A town with no cluster at all falls back to the pool.
- SYNTHETIC FROZEN BY CONSTRUCTION, not by luck. The brief assumed a blanket filter would be a no-op because
  'the synthetic town is one dense cluster anyway'. Measurement says otherwise: 4 synthetic candidates PER
  SEED sit under the threshold (min cluster 0-1), so a blanket filter WOULD have moved the frozen golden.
  Gated on plan.source === 'osm' (undefined on synthetic) — impossible rather than unlikely. The marched
  fixtures are 'osm' but genuinely dense (min cluster 3/11/13) ⇒ verified no-op.
- Only the GIG goldens moved (venue selection is gig-layer; base town goldens untouched): katoomba
  0xbf586b77, newtown 0xacdb3eee, fremantle 0xcf3426fc, bendigo 0x646b2c23, castlemaine 0xf9713a69.
  classic 0x3fa36874 / gig 0xb1d48ea1 / marched fixtures FROZEN. selfcheck ALL GREEN 66419/66419;
  scaffold + consistency GREEN; cross-process deterministic.
- FYI: E's v4.0 pack (glebe/marrickville/newcastle) already boots clean through my loader (validates + full
  suites pass), listed UNPINNED — I pin the pack in R22; it does not gate the beta tag.

Handshake → C + D: cluster bias landed, goldens re-pinned, start your verifies (before/after table in
LANE_A_NOTES §R21).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 20:37:45 +10:00
m3ultra
4b0144989b Lane B round 21 (v4.0-beta): town selector reads E's towns index (ledger #2, the missed R20 #5)
Closes the gap Fable caught (hud.js:11 still hardcoded REAL_TOWNS). The real-town list now comes from E's
web/assets/towns/index.json (procity-towns-index/1), so it grows with the town pack instead of being edited
in hud.js. Verified fresh Chromium. Nothing else touched -- tram/ground work is done and verified.

Fail-soft by construction: createHUD is synchronous, so the picker is built immediately from the fallback
literal and UPGRADED IN PLACE when the index resolves -- if the index never arrives, a working list is
already on screen.
- index present -> real names/states/shop counts: "Katoomba, NSW · 80 shops", "Fremantle, WA · 80 shops",
  "Newtown, NSW · 72 shops", "Bendigo, VIC · 36 shops", "Castlemaine, VIC · 24 shops" (roads:false -> "· marched").
- ?noassets -> the fetch is SKIPPED entirely (asset law; the shell doesn't load town caches there either).
  Measured: 0 index fetches, fallback list, 0 errors.
- anything wrong with the index -> the fallback simply stands. Exercised the exact chain: 404, network error,
  malformed JSON, towns:[], towns-not-an-array, junk entries (no key) -> none throws, none rebuilds from junk;
  only a good index rebuilds.

REAL_TOWNS_FALLBACK is retained ONLY as that fallback (comment says so), no longer the source of truth.
Verified live: booting katoomba_real shows "Katoomba, NSW · 80 shops" pre-selected; fits (193px of 230px max);
0 page errors; navigation unchanged (onchange is set once on the <select>, so rebuilding options never clears it).

-> F: unchanged from R19 -- NO shell seam (selector is entirely hud.js, already built by createHUD) and it
   lives inside #pc-hud so setVisible(false) hides it for the beta money shot. The gating call is still yours.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 20:35:47 +10:00
m3ultra
5d29863b78 Lane A round 20: absorb E's density widening — capacity, pub facade bias, goldens pinned (ledger #2)
E's five caches landed at 3-6x shops (292 total: katoomba 20→80, fremantle 21→80, newtown 18→72,
bendigo 9→36, castlemaine 6→24). All five absorb cleanly and still place 3 venues; the R20 poster cap
auto-scaled off the live shop count exactly as designed (katoomba 111 posters at 80 shops).

- CAPACITY WIDENED (the R18 overflow fence, load-tested). At 4x the dominant drop cause was OVERFLOW —
  an edge-side holding more shops than fit (katoomba 10 of 11 drops). Tightened the run: NODE_CLEAR 6→3
  and a ROADS-ONLY RGAP 2→0.5 (the shared marched GAP is untouched ⇒ marched fixtures stay frozen).
  Drops 7.9% → 5.5% (16/292). 0.5m also reads truer — a real AU main street is continuous terrace
  shopfronts, not detached boxes. Stopped there deliberately: the next lever (spilling overflow onto a
  DIFFERENT street) buys ~1% by putting a shop on a road it isn't on — dropping a crowded shop is the
  honest trade. Residual drops stay COUNTED, now BY CAUSE (dropped_overflow/_overlap/_stranded).
- VENUE FACADE BIAS NOW COVERS pub. R16 exempted pub ('spine-fronted, +Z always open') — true on the
  synthetic/marched towns, but on a REAL graph at density the pub landed where its frontage poster
  overhung a cross street (newtown, -0.91m). The POSTER_CLEAR filter now covers every venue kind; still a
  NO-OP on synthetic (every pub candidate already clears) ⇒ gig golden 0xb1d48ea1 frozen.
- Goldens re-pinned ONCE (base + gig, all five towns). classic 0x3fa36874 / gig 0xb1d48ea1 / marched
  fixtures FROZEN. selfcheck ALL GREEN 51866/51866; scaffold + consistency GREEN; cross-process
  deterministic.
- Fixed: my real-cache loader globbed *.json and tried to validate E's new index.json as a town cache —
  it now skips it (that's B's selector index, not a cache).

Handshake → all: densities absorbed, goldens pinned. → F: re-measure venue_night + gate the five towns.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 20:05:11 +10:00
m3ultra
94e56ed5fa Lane B round 20 (v4.0-beta): the tram ruling — route by shop adjacency + per-town fence (ledger #3/#4)
Verified fresh Chromium across 9 town configs (synthetic, 3 fixtures, 5 real).

TRAM RULING (ledger #3): spinePolyline now walks from each main-subgraph dead-end, greedily taking the
unused main edge fronting the most shops (lot.frontEdge), keeping the best-scoring path (ties -> longer).
SYNTHETIC + FIXTURES BYTE-IDENTICAL by construction AND measurement (a synthetic main node has <=2 mains so
there's never a choice; its two dead-ends tie exactly and strict > keeps the pre-R20 start). Old vs new
logic on the same plan = identical polylines (Boolarra: 7 pts, 6 edges, 810m, 172 shops, 2 stops).

The alpha tram fronted 0 shops on 4 of 5 real towns (it was a highway bus). New: katoomba 0->3, newtown
0->8, fremantle 0->2, bendigo 0->1, castlemaine 1->6. Where the best main chain still fronts <5 shops the
tram is FENCED per-town (Fable's fallback). RUNS: synthetic, all fixtures, newtown (8 shops/1924m/16 stops),
castlemaine (6/2184m/10 stops). FENCED: katoomba (3), fremantle (2), bendigo (1) -- highway, not high street.
The fence keys off the REAL-ROADS signal (edges>200), NEVER shop count alone: the marched fixtures front 0
shops on their mains (bare spine, avenues carry shops) so a naive fence would have killed their v2 tram.
routeInfo{fenced,reason,shopsFronted,routeMetres} exposed for F's smoke.

BUG FOUND+FIXED: the stop projection accepted every shelter in the town with no distance gate -- harmless on
synthetic (2 shelters on the spine), nonsense on real graphs: Newtown made 149 phantom stops (~9 min of dwell
at points the tram never passes). Added a 30m gate (a shelter sits halfRoad+1.7 <=~16m off its centreline, so
genuine stops survive): newtown 149->16, castlemaine 37->10; synthetic 2 / fixtures 3 unchanged.

GROUND POLISH (ledger #4): measured, NO fix needed. Real Katoomba has 325 deg-3+ junctions, 28 under 30 deg,
worst 4.3 deg (near-parallel). At grazing eye level the worst reads clean -- no z-fight, no gaps. Layers are
already y-separated (road 0.0/foot 0.02/kerb 0.06) and road-on-road overlap is invisible by design (OVERLAP=1.5
overruns nodes on purpose; one skin, one y, one merged draw on uniform asphalt). Cosmetic only: footpath/kerb
double up on the sliver between <10deg near-parallel roads -- v4.0 candidate, not a defect.

Selector polish (ledger #5): BLOCKED -- E's towns/index.json not landed (build_towns.py WIP); REAL_TOWNS
hardcode stands, one-line swap when E lands.

-> A (finding, measured): only a slice of shops front a MAIN edge on real towns -- katoomba 7/69, fremantle
   4/79, bendigo 7/35, newtown 32/64, castlemaine 11/22. The real high street classifies as 'side', which is
   WHY three towns need the fence. If the main/side classifier promoted shop-dense ways to main, the fence
   would lift. Note onMain also gates gigs.js pickVenues pub placement.
-> F: tram verdict above; selector gating (HUD vs ?dbg) is your call, one line either way.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 19:35:56 +10:00
m3ultra
955ec3497b Lane A round 20: spine-poster cap scaled by shop count (v4.0-beta, ledger #1)
Closes my own R19 finding + F's one open budget edge. A sparse REAL town has few shops but a big edge
graph, so the 'two posters per main edge' pole run reads busy (katoomba 438) and drove venue_night to
200,965 tris (+0.5% over the 200k soft ceiling).

- gigs.js buildPosters caps the spine pole run at POSTER_PER_SHOP × shops (1.5/shop, floor 12) — a seeded
  subsample. SCALED BY SHOP COUNT, not edge count (the whole point). katoomba 438→31 posters (~2x the
  synthetic feel), fremantle 32, castlemaine 14. NO-OP on the shop-dense synthetic town (493 shops ⇒ cap
  ~740 ≫ its ~15 posters), so the synthetic gig golden 0xb1d48ea1 stays FROZEN.
- Posters are gig-layer (withGigs), so the real-town BASE goldens are unchanged (correctly green). Added
  REAL_TOWN_GIG_GOLDENS (the full district+capped-poster plan per town: katoomba 0xbd332e83, …) to
  regression-guard the capped output byte-exact, not just via districtInvariants. classic/gig/marched
  fixtures frozen; selfcheck ALL GREEN 51675/51675; scaffold + consistency GREEN; cross-process deterministic.

→ F: re-measure venue_night, the +965 edge should close. Density absorb (ledger #2) pends E's widened
caches — the poster cap already reads the live shop count, so it auto-scales to the widened towns.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 18:27:29 +10:00
m3ultra
73694c97aa Lane B round 19 (v4.0-alpha): in-game town selector (ledger #4, beta pull-forward, non-blocking)
Shipped the town picker in hud.js (which already owns its DOM overlay -> NO index.html seam). A compact
<select> in the HUD (top-left, pointer-events:auto), 9 options: synthetic + 3 OSM fixtures (osmTownKeys
minus _real) + 5 real-road caches (Katoomba/Newtown/Fremantle/Bendigo/Castlemaine, hardcoded REAL_TOWNS --
no manifest yet, beta derives it). Selecting navigates: synthetic clears plansrc/town; a town sets
?plansrc=osm&town=<key>, preserving all other params. Current pre-selected; unknown-current still shown.
Verified end-to-end: picked katoomba_real through the dropdown -> booted real Katoomba (966 edges/323
intersections/3 venues), 0 errors. Lives inside #pc-hud so hud.setVisible(false) hides it for money shots.

-> F (B->F seam): NONE -- self-contained in hud.js (already constructed by createHUD). Gate it behind ?dbg
   with one setVisible call if you prefer; left always-in-HUD as a modest debug-grade feature.

Corner-strips spot-check (R18 failure-item #3): ground fills real intersections OK -- degree-4 junction
(58 exist) shows road strips overlap-filling the junction, lane markings + zebra crossing render, no gross
gap. Acceptable for alpha; finer acute-angle pass = beta.

Budget note -> F (measured vs A's UNCOMMITTED WIP -- caveat, not a defect): F's BIG_CITY one-liner is live
(chunks 50->26). But venue block still ~237k tris at night against the current tree, which carries A's
uncommitted R19 finalization (plan_osm modified; graph grew 799->966 edges, junction-joining in but the
far-island CULL not yet). Fable's ruling: the cull is "half of B's tris problem" -> the <=200k gate needs
F's fix AND A's committed cull. Re-measure the busiest real block after A pins (the deferred tris-post-cull
re-measure genuinely waits on A's commit; the dev server serves other lanes' uncommitted edits).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 17:38:59 +10:00
m3ultra
6fc4b40632 Lane A round 19: the v4.0-alpha close — fragmentation ruling + findings hardened + goldens pinned
A's finalization (ledger #1), the round's spine. Every filed R18/R19 finding resolved; the tag is F's.

- Fragmentation (D's finding; Fable ruled, A owns details). The naive real graph fragmented (katoomba 105
  components, 58% street-metres on islands). plan_osm.buildRealRoads now: (a) JUNCTION-PROTECTED
  Douglas-Peucker — a point shared by >=2 ways is a junction; simplify only BETWEEN junctions, never
  through one (the collinear tee-off drop, the root cause) → 94-100% main-net alone; (b) cull/bridge —
  main component + near-fragments joined (JOIN_TOL 12m; shop-island bridge <=200m), far SHOPLESS islands
  culled. Result: 1 connected component on all 5 towns (katoomba 100% main-net, 2 islands culled). Every
  dropped shop COUNTED in norm.dropped (ruling): katoomba 19/20 (1 crowd drop — R18 overflow prediction
  measured), the other four kept all.
- E's poster-clearance finding fixed (gigs.js): a spine poster overhanging a real crossing carriageway is
  skipped, measured exactly as the selfcheck kerb floor — a NO-OP on synthetic (gig golden frozen, pr()
  consumed before the check). 23 real-town clearance failures → 0.
- Goldens pinned (A->E->A finalization): all 5 real towns on their real-roads output (katoomba 0xb7ffbca2,
  newtown 0x6f984c81, fremantle 0xfb197000, bendigo 0x21179b3b, castlemaine 0x7c47f639). synthetic
  0x3fa36874 / gig 0xb1d48ea1 / marched fixtures / classic / default FROZEN — the alpha changed nothing
  outside the cache-schema path. selfcheck ALL GREEN 51670/51670; scaffold + consistency GREEN. Cross-
  process deterministic.
- New finding filed (beta, non-blocking): spine-poster density scales with edges (katoomba 438 vs ~14
  synthetic; draws low via instancing) → a beta posters-per-town cap.

Handshake fired → B/C/D/F: katoomba_real boots real roads, golden pinned, findings resolved.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 17:37:01 +10:00
m3ultra
a78dc76f84 Lane A round 18: the real-graph lift — OSM ways → CityPlan street graph (v4.0-alpha REAL ROADS)
The epoch headline (ledger #2). A v2 cache's roads[] now builds the CityPlan street graph FROM real
OSM geometry instead of marching shops onto synthetic avenues. SAME CityPlan schema out — B/C/D/F consume
it unchanged by contract. roads absent ⇒ the v1 marched fallback, so classic/gig/osm goldens are FROZEN.

plan_osm.buildRealRoads: project ways+shops to the local metre frame → simplify each way (Douglas-Peucker
eps~6m, the fidelity knob) → snap coincident points (<=3m) to shared nodes = real intersections →
straight-segment edges → classify main by OSM highway class + shop density → seat each shop on its NEAREST
real edge (real order, real side), marched with regularised spacing + a deterministic overlap-resolve pass.

Fidelity (charter risk #4): real edge/order/side + regularised spacing — NOT pixel positions (real shops
overlap; the no-overlap invariant is load-bearing). Alpha failure list in LANE_A_NOTES §R18: overflow drop
on short edges; intersections = shared-coordinates only (a D sim-graph risk); per-edge frontage-strip
blocks not planar faces (a B ground-render item); crowded-intersection overlap-resolve.

Proven: a committed synthetic irregular graph (main + two cross streets + a real intersection + an acute
angle + a dead-end lane + a curve) passes the FULL structuralSuite + gig district + byte-identical
determinism; mutation-tested non-vacuous (flip the facade facing → 'faces its frontEdge' fails). Robust on
messy inputs (single road, coincident dups, disconnected ways, all-roads-too-short → marched). All osm
goldens frozen; E's 4 trailing caches pinned on v1; katoomba_real UNPINNED (its real-roads golden pins when
E lands roads — the A->E->A finalization). selfcheck ALL GREEN 16810/16810; scaffold + consistency GREEN.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 16:10:14 +10:00
m3ultra
4dc80c2146 Lane A round 18: town-cache schema v2 (roads[]) — published FIRST (v4.0-alpha REAL ROADS)
The half-day handshake so E's build_towns.py can fetch road geometry (ledger #1). Optional roads[]
on the town cache carries real OSM way geometry; plan_osm's graph lift (consuming it) follows in the
next commit. v1 caches (roads absent) stay valid — the marched fallback — so nothing shipped regresses.

- validateTownCache accepts schema procity-town-cache/1 AND /2; when roads[] is present each road must be
  { kind:string, pts:[[lat,lon],…] } with >=2 finite points (else rejected); <2 pts dropped, unmapped
  highway kind → 'side' (warnings). ROAD_KIND maps OSM highway=* → CityPlan edge kind (main/side/lane/arcade).
- Full v2 contract in web/assets/towns/README.md (the doc E builds to): shape, road validity, the kind map,
  the fidelity knob (charter risk #4), and the bbox-bound-the-roads rule.
- E's 5 existing v1 caches re-validate clean; selfcheck ALL GREEN.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 15:54:19 +10:00
m3ultra
e4a49b5bc8 Lane F R17 (v3.2): real towns boot in the engine + the town-matrix scout gate (ledger #7)
F wired the last mile of the real-map scout and gated the whole matrix.

- index.js barrel re-exports registerTownCache/validateTownCache; the shell fetches + registers
  assets/towns/<key>.json before generatePlanFor when the town isn't a checked-in fixture — so
  ?plansrc=osm&town=bendigo_real boots a REAL AU town in the engine (the gap: E's caches were on disk +
  in the node selfcheck, but the shell never loaded them → silent Melbourne fallback). Fail-soft
  (404 / invalid / ?noassets -> fixture fallback); opt-in, never on the default or ?classic path.
- tools/qa/town_matrix.py (opt-in qa.sh --matrix): boot / determinism / budget / district / ?noassets
  across 10 towns (2 synthetic hero seeds + 3 osm fixtures + E's 5 real AU caches). ALL 5x10 GREEN —
  Castlemaine at the 6-shop floor still lands a 3-venue district. The existing CityPlan contract eats real
  Australian geography: the evidence John charters v4 = THE REAL MAP on.
- docs: LANE_F_NOTES §17 (the town x gate table), README real-town row, F-progress R17.

qa.sh --strict --soak GREEN 7/7. Verified: ?plansrc=osm&town=bendigo_real -> Bendigo, 9 shops, 3 venues, 0 errors.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 15:23:15 +10:00
m3ultra
64ded4ea01 Lane D R17 (v3.2): bench-sit loiter (ledger #2) — the carried R16 stretch
Street peds occasionally sit during a window-shop loiter (verandah/footpath
edge). makeActor gains an opt-in setSitting with a SCALE-AWARE re-plant (the
R16 spawnRig re-plant was scale-naive; the sim scales these rigs per-citizen),
played only on sit so walkers/placeholders/?classic are byte-identical. Seeded
via a dedicated benchsit rng stream (no shift to turn/loit/patron; identity
signature unchanged). Classic inert at the source (roll gated on sitClip).

In-shell (default, seed 20261990): head bone 1.466->1.070, bone span
1.667->1.299 (in seated gate [0.9,1.5]), feet re-plant to y=0 and hold through
sit-idle, stand-up restores inner.y 0.929 byte-for-byte, procitySeated tagged,
innerRotY:pi throughout. 9 distinct peds sat naturally per soak. Classic:
fleetSitClip:false, everSat:0. Plan golden 0x3fa36874 unchanged. Draws 35/tris 6k.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 14:53:22 +10:00
m3ultra
6671cf08f4 Lane D R17 (v3.2): the drummer's pelvic-lean (ledger #1)
The R16 seated drummer sat bolt-upright (the sit clip can't carry a pelvic tilt
— Hips.quaternion is unusable on these rigs). Fix: a small constant forward torso
lean applied POST-MIX. spawnRig({seated}) caches the mid-spine bone (seatBone);
seatedLean(spine) rotates it forward ~0.34 rad each frame after the mixer resets
it (constant offset, not accumulation); band.js calls it for m.seated members.
The drummer leans into the kit — head 0.28m forward of the hips, reads as a real
drummer from the side.

Byte-identical (my signature): standing rigs get no seatBone (found only in the
seated block) and seatedLean runs for seated figs only. Verified with the lean
live — walkers 188/188 face travel, keeper standing rig (stature 1.718,
hasSeatBone:false, innerRotY pi). No existing consumer moves.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 14:32:58 +10:00
m3ultra
6fffd19043 Lane A round 17: v3.2 the town-cache contract (published first) + plan_osm hardened for real data
The real-map scout (ledger #6), A's half. Scope fence held: NO new plan features, NO golden moved
(classic 0x3fa36874, gig 0xb1d48ea1, all three osm fixtures frozen). The scout proves the EXISTING
?plansrc=osm contract eats real geography.

- The town-cache contract (published FIRST — E builds build_towns.py to it). plan_osm.js gains
  validateTownCache() (authoritative {ok,errors,warnings}), registerTownCache(), a generatePlanOSM(..,
  {cache}) seam, and TOWN_CACHE_SCHEMA/MIN_TOWN_SHOPS(6)/MAX_TOWN_SPAN_M(5km). Shape: {schema,key,town,
  source,license,attribution,center{lat,lon},shops[{id,name,type,lat,lon,suburb?}]}. Home declared:
  web/assets/towns/<key>.json (+ README.md full spec). Documented in CITY_SPEC v3.2 + LANE_A_NOTES R17.
- plan_osm hardened: blank name -> registry label; the validator gates finite center/coords + a >=6-shop
  floor and warns on unknown-type(->opshop)/dup-id/span>5km. Proven vs thriftgod's real 2,828-shop
  city_source.json: boots a valid plan, 0 bad coords, 0 'undefined' names.
- selfcheck: a committed synthetic-adversarial contract suite (valid-messy caches boot + pass structural/
  gig; invalid reject; wide-spread warns; registry seam) + an auto-loader for web/assets/towns/*.json
  (validate -> suites -> pin per-town golden), empty and green until E lands caches. ALL GREEN 15277/15277.
- v4 charter risk list (LANE_A_NOTES R17): the MEGA-STRIP (unbounded bbox -> 11.6 km strip; town selection
  is E's real lever, warned >5km); sparse compact towns (2 km inner-Canberra held 6 shops); osm facades
  clear (rows 54 m apart) so R16's bias is a no-op on real towns.

E->A handshake: E drops a cache in web/assets/towns/, the selfcheck prints its golden, I pin it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 14:32:14 +10:00
m3ultra
ee16545941 Lane B round 17 (v3.2): wind sway SHIPS -- weather-driven gum-tree sway (ledger #5, parked since R7)
Verdict: shipped, not killed. The R7 park reason ("touches shared materials") is a non-blocker under
R16's measurement-over-brief standard: the gum-tree billboard material is B-owned (furniture.js), so
this is entirely Lane B -- no C/E material seam. Verified fresh Chromium seed 20261990.

- New web/js/world/wind.js: shared uniforms (uWindTime/uWindAmp) + applyWind(material) (onBeforeCompile
  vertex patch: top-weighted horizontal sway, per-instance world-position phase, USE_INSTANCING-guarded)
  + updateWind(dt,intensity). furniture.js applies it to the tree material; weather.js drives it from
  update(dt) (base breeze 0.05 clear -> 0.05+intensity*0.30 ~0.35 rain gusts).
- Trees only. Awnings excluded by MEASUREMENT: rigid posted-verandah box slabs don't flutter (fabric
  canopy ripple = separate v3.3, shares the rigid awning InstancedMesh today).
- Byte-identical when weather off (the covenant): updateWind runs only inside weather.update, and the
  weather system isn't constructed under ?weather=0 / ?classic=1 -> uWindAmp stays 0 -> sin(...)*0.0==0.0
  -> identical vertices. Proven: amp=0 two times = 0/180000 px differ; ?weather=0 boot = 0 tree-motion px;
  ?classic=1 = amp 0, trees render, 0 errors. No plan/fetch/rng change -> F's classic gate untouched.
- Sway works: rain amp 0.305 moves ~14100/180000 region px over 0.8s (exaggerated 2.5 -> 101k); trees
  render clean, no glitch. ~0 draws (ALU ops in the existing tree InstancedMesh vertex program), 0 errors.

-> Lane F (B->F handshake): SWAY SHIPPED. In old frames under the default boot; ?classic=1/?weather=0
   sway-free + byte-identical. Procedural (no asset -> E has nothing to do).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 14:30:09 +10:00
m3ultra
a4946b7ca2 Lane F R16 (v3.1 THE FLIP): defaults on + ?classic covenant + seated-stature gate (ledger #1, #3)
The town shows its best by default. gigs / weather / winmap / tram flip ON (each keeps a ?flag=0 opt-out;
?classic=1 forces the exact v2 boot). The prime-law covenant MOVED from "flags off" to "?classic=1" —
byte-identical forever (synthetic fingerprint 0x3fa36874). The default boot is now the full experience,
deterministic to the gig golden 0xb1d48ea1 (A's R16 corner-poster re-pin).

- index.html: CLASSIC + flagOn(); gigs/weather/tram default-on; loadPedFleet({sit:!CLASSIC}) for the seated
  drummer (?classic keeps the zero-fetch-delta covenant); a window.PROCITY.flags intent surface for the gates
- buildings.js: winmap default-on + classic-aware (the one cross-lane flip line the flag placement requires)
- flags_check.py: flags_off_regression -> classic_regression (?classic=1 byte-identical); a new default_boot_gate
  (all four flags on, gig layer present, gig golden, budget); every gig smoke re-pointed at the DEFAULT boot
- seated-stature gate (ledger #3, published for D): interior_scale_check.py + flags_check.py accept
  fig.userData.procitySeated at stature [0.9,1.5] m (the drummer); standing figs stay [1.4,2.0]
- qa.sh flags-harness gate name updated (classic regression + default-boot gate)

Verified: ?classic byte-identical (0x3fa36874, gig layer absent); default boot = the full town (14 gigs, 15
posters, tram, gig golden green, 112 draws @ the venue block); the seated drummer passes at 1.32 m stature;
district (3 venues / 3 genres), continuity, and cover all green on the default boot.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 13:20:43 +10:00
m3ultra
ab7a8d0edf Lane D R16 (v3.1): the drummer sits + backline unification (ledger #2, #5)
Ledger #2 — drummer sits. E's sit.glb is wired into the shared rig stack:
- FINDING: Fable's prescribed _rotWithHips (keep Hips.quaternion) MANGLES the
  pose in-shell (head folds to hip height / laid flat) — the fleet rigs' bind
  Hips orientation is incompatible with the Mixamo clip's, exactly what _rotOnly
  documents. So the sit clip rides _rotOnly (drops Hips.quaternion) like every
  clip; _rotWithHips removed. The seated read comes from the leg bends + a new
  spawnRig({seated}) foot-replant (opt-in; standing spawns skip it).
- Drummer uses fleet.sitClip when present (retires SEAT_DROP), tags
  fig.userData.procitySeated=true (F's gate). Stature ~1.1-1.3m in [0.9,1.5];
  reads seated from the crowd POV (kit hides the legs). Graceful SEAT_DROP
  fallback when sitClip absent (?noassets / before F wires {sit}).
- BYTE-IDENTICAL proof (load-bearing): with sitClip null AND loaded, keepers are
  standing rigs (stature 1.718, innerRotY pi, replanted:false) and walkers 188/188
  face travel. Parallel/opt-in path; _rotOnly + makeActor untouched.
- F seam: loadPedFleet(base, {sit}) — pass sit:<gig active> (false under
  ?classic → no sit.glb fetch, classic covenant intact). Default off, graceful.

Ledger #5 — backline unification. _addBackline plants E's guitar_amp at C's
stage.backline[0] (== D's R15 pose, 0.75m bass clearance) instead of a hardcoded
pose. C dropped its primitive ampStacks, so D owns the backline: placeholder-
persists (primitive amp → GLB swap) with the primitive as the ?noassets fallback.
Verified: GLB swaps, primitive under noassets, no crash, 0 fetch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 12:49:44 +10:00
m3ultra
2b13dd4ad7 Lane A round 16: v3.1 corner-poster clearance bias (#6) + no-spine osm fixture (#7)
Ledger #6 fix + the round's one sanctioned gig-golden re-pin, and ledger #7. Classic/synthetic
golden 0x3fa36874 frozen (classic boot byte-identical across all seeds); base plan untouched.

- Corner poster: the R15 finding (frontage poster near a crossing kerb) is NOT fixable by the brief's
  'nudge along the facade' — proved the crossing edge is PARALLEL and spans the facade (0/187 slides
  help); band_room/rsl land on through-lots on narrow warehouse blocks whose +Z wall (B's door side)
  overlaps the block's other street by up to ~3 m. Surfaced to Fable; ruling: bias pickVenues. It now
  prefers a band_room/rsl candidate whose +Z facade clears every carriageway by >= POSTER_CLEAR (0.5 m),
  broadening off-spine if the fringe is uniformly narrow. Result: 0/851 frontage posters below floor
  across 400 seeds (worst 0.51 m); pub untouched (hero shot unmoved).
- Gig golden re-pinned 0x4f4a549d -> 0xb1d48ea1 (posters moved). selfcheck's R15 blanket frontage
  exemption becomes a real floor: frontage >= POSTER_CLEAR, spine >= 0 (POSTER_CLEAR exported from the
  barrel). Mutation-tested non-vacuous.
- No-spine osm fixture 'silverton' (single-row town, 0 main edges) proves pickVenues' no-spine branch +
  buildPosters' no-main fallback, closing the R14 sweep gap. Base golden pinned 0xd4b351c9; sweep asserts
  0 main edges + graceful 2-4-venue placement. selfcheck ALL GREEN 14769/14769; scaffold + consistency GREEN.
- CITY_SPEC v3 amended (golden, poster clearance-bias prose, no-spine note, recorded amendment). Left the
  prime-flag-law blockquote for F (the v3.1 classic-law + flags-table is F's amendment). For F:
  default-boot golden = 0xb1d48ea1; ?classic=1 target = 0x3fa36874.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 12:35:30 +10:00
m3ultra
54a030b38b Lane B round 16 (v3.1 THE FLIP): town-wide streetlamp pools + default-boot budget re-baseline
Ledger #4. Verified fresh Chromium seed 20261990. No new rng, goldens untouched.

- Town-wide pools (venue.js): extended the R13 instanced-pool pass from venue frontages to EVERY
  streetlight + bus shelter. New townPoolSpecs(plan) replicates furniture.js's lamp cadence off the
  plan street graph (lamp every 18m from s=8, alternating sides, off=halfRoad+0.7) and reuses the
  exported busShelterStops(plan) -- read-only, furniture.js untouched (imported one function). All
  pools ride the ONE existing InstancedMesh -> still ~1 draw: 381 instances / 1 draw / ~760 tris.
  Night-driven; fog:true now so distant pools fade into night fog (pools aren't chunk-streamed) instead
  of floating as dots. Gig-gated -> ?classic=1 is pool-free = v2 parity by construction (no always-on
  furniture change, the v3.0 blocker). Deterministic cadence, no seeded draws (release law).

- Default-boot re-baseline: measured the default content (gigs + weather=rain + winmap + tram all
  live), settled. Busiest venue block (pub, NIGHT, rain) ~157 scene / ~170 total draws, 66k tris;
  venue_night 122, queue_night 121, district_posters/street_noon 185 (worst tour shot), market_square
  fresh 97/52k. ALL <=300 draws / <=200k tris. Flip adds ~3 draws (rain 1 + tram 2; winmap 0), pools
  add ~0 (instanced). Daytime dense-citizen ceiling (~275, Lane D, live-walk) unchanged (pools night-only).

- Bookmark spot-check: all 19 resolve, un-letterboxed, 0 errors under the default boot; rain+tram+winmap
  compose cleanly in old frames (street_noon rainy 177/114k, nothing blocked). No pose fixes.

Ran before F's flip landed (out of the stated order): pools are gig-gated so they work under ?gigs now;
re-baseline uses explicit all-flags URLs = post-flip default content. F re-confirms at close.

-> Lane F (B->F handshake): default-boot busiest block ~170 total draws / 66k tris; worst street view
   185 / 131k; densest daytime 97 / 52k. All under budget. Town-wide pools ~1 draw. Pool sprite is
   procedural (E has nothing to do).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 12:21:19 +10:00
m3ultra
42b11e717d Lane C R16 (v3.1): stage.backline[] amp unification, drop primitive ampStacks (ledger #5, C half)
C exposes room.stage.backline[] — 2 seeded up-stage amp slots {x,z,ry,y} (pure data) — and
drops its primitive ampStacks from placeStage. Lane D plants E's guitar_amp GLB at backline[0]
(D's primitive fallback under ?noassets): one unified backline, GLB when present.

- backline[0] = (stage.x + stage.w*0.36, deckY, stage.z - stage.d*0.22) == D's R15-verified amp
  pose exactly, so plumbing D's plant to backline[0] moves nothing on screen. backline[1] = spare
  stage-left mirror. ry=pi (audience-facing), y=deckY.
- Verified fresh (seed 20261990): slots up-stage, on-deck, clear of all 4 bandPoses (nearest-bass
  0.87 pub / 0.70 band_room / 0.92 rsl) and all watchPoints (>=1.95 m); determinism byte-identical;
  0 leftover ampStack groups; drawSweep venues 122/120/131 (was 126/124/135, -~4 draws each) <=350.
- Determinism/golden-safe: dropping the amp loop only drops draws on the 'stage' sub-stream (nothing
  downstream reads it); placement snapshot excludes ampStacks; interiors aren't hashed; ?classic has
  no venues. Disjoint from F's flip. LANE_C_PUB §3 amended (v3.1 marker).

C->D handshake: backline shape live. Verify with D once the GLB is wired at backline[0].

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 12:16:38 +10:00
m3ultra
687d23f654 Lane F R15 (v3.0): identity continuity — wire D's tonight-roster into the crowd (ledger #3)
The ped you followed into a venue now turns up in its crowd. D published the seam in R14; F wires the three touches:
- interior_mode.js: crew.spawn({ roster: rosterOf(shop.id) }) — the front crowd slots become tonight's roster
- index.html: rosterOf=(id)=>citizens.tonightRoster(id) into interior_mode; onAdmit=(e)=>citizens.recordVenueEntry(id,e)
  on the queue (each admit relays into the roster); the clear is automatic on the existing setGig(id,false)
- flags_check.py: smoke_continuity — asserts the seam (crowd includes roster-cap, pedIndex in tonightRoster),
  empty-roster injects nobody (shell side of D's byte-identical law), + ledger #5 settled-tris honesty

Verified: 2 queue admits carry into the crowd; empty roster 0-injected; settled interior 71k->182k tris
(C's finding — interior gate is draws <=54, no violation). 0 console errors.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 11:19:19 +10:00
m3ultra
146b5f3d2d Lane F R15 (v3.0): retire the R12 single-venue alias (ledger #2)
The compat alias delegating to the primary venue is gone — every read is now keyed by venueShopId.
B swept the cross-lane readers in R14; this migrates F's own tools + the spec, one atomic commit.

- gig_state.js: delete the alpha-shape alias block + the now-unused primaryId/P; update() just ticks all latches
- flags_check.py + gig_shot.py: .venueShopId->venueShopIds[0], .state->stateOf(id), .cover->coverOf(id), .bandName->bandNameOf(id)
- CITY_SPEC \S v3: delete the two alias sentences, add the omitted nightOf(id) to the accessor list

selfcheck 14264/14264 green (base 0x3fa36874, gig 0x4f4a549d); flags_off + smoke_gigs green, 0 warn.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 11:14:19 +10:00
m3ultra
66aec0f83c Lane D R15 (v3.0): guitar_amp up-stage per C's §3 ruling (ledger #4)
The R13 backline plant sat down-stage (stage.z + 0.15), 0.29 m from the bass rig
— it interpenetrated the bassist (C's R14 audit caught it). Move it up-stage-right
to C's blessed pose (stage.x + w*0.36, deckY, stage.z - d*0.22): 0.75 m clear of
the bass, behind the front line, flanking C's primitive ampStacks. Pose-only.

Verified: draw/tri delta 0 (same GLB moved — pub 50 draws / ~183k tris, <=350);
?noassets unaffected (amp is fleet-gated dressing → 0 props, band placeholders,
no crash). No other code — continuity + the ratified sit-clip deferral are closed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 10:25:08 +10:00
m3ultra
a0b578477c Lane B round 15: verify hero after A's poster flip + night poster readability (?gigs=1)
Verified A's ledger-#1 flip (35da83c): the pub's own-frontage poster moved from 15.9m behind the
building to 0.09m from the door on the +Z street face, facing the reader -- no queue_night pose
change needed (R14 prediction held). Gig golden 0x1f636349->0x4f4a549d consumed cleanly.

Fix (John-sanctioned exception to the R14 freeze): posters were unlit MeshStandardMaterial and
rendered pure black at NIGHT (measured frontage poster centre [183,71,59] day -> [4,0,1] night), so
queue_night + district_posters showed no readable bill even after A's flip. venue.js now gives posters
emissiveMap = the poster art, emissiveIntensity ramped in update() by night (0 day -> 0.55 night, same
lerp as glow/pools). Self-lights the bill after dark; daylight byte-identical (verified midday spine
poster pixel-unchanged). No new rng, no plan change, goldens frozen. ?noassets fallback self-lights too.

Verified fresh Chromium seed 20261990: queue_night (seg5, live queue) frames marquee + window glow +
streetlamp pools + a readable red poster by the door + queue punters (90 draws/29k tris);
district_posters night now shows a poster (was black, 180/138k); daylight unregressed.

-> Lane F (B->F handshake): HERO VERIFIED. Shoot queue_night at seg 5 (NIGHT) -- poster now reads there
   and night is needed for pools+glow; drive the queue live (rAF throttled in automation). Poster
   night-emissive rides the shell's per-frame venuePresentation.update(gigState.byVenue).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 10:11:53 +10:00
m3ultra
35da83c347 Lane A round 15: frontage poster +Z street-face flip (ledger #1) + gig golden re-pin
R14 review found every venue's own-frontage poster on the building's −Z BACK since R13 (~16 m
behind the street face, B measured 15.9 m) — invisible from the street, why queue_night framed no
poster. Ledger #1, A-first (B's hero re-shoot + F's tour hang off this).

- gigs.js buildPosters item 1: flip to the +Z street facade (B's canon: buildings.js facade zf=+d/2
  + venue.js door/queue/camera are +Z) AND ry=lot.ry+π so the printed face points +Z at the queue-side
  camera (else DoubleSide shows a mirrored band name). Proven: pub+rsl frontage posters now sit 0.06 m
  from B's door, printed-face·(+Z street)=1.000. Spine posters untouched.
- Gig golden re-pinned 0x1f636349 → 0x4f4a549d (the round's ONE sanctioned move; posters moved).
  Synthetic golden 0x3fa36874 frozen. §v3 FROZEN marker records the amendment (amendment law).
- selfcheck: the +Z facade is the block-interior side in plan coords, so corner venues (band_room/rsl
  fronting one street with another behind — seed 1 lot#481, 1.58 m from a crossing centreline) seat the
  frontage poster near that street. That's B's building geometry (the door is there too), not a mid-road
  defect — so the no-bitumen assert now EXEMPTS building-bound frontage posters, keeps it for spine
  posters. 400-seed sweep green again: 14264/14264.
- CITY_SPEC §v3 amended (poster prose → +Z facade + ry+π; golden line; frontage/spine carriageway split;
  recorded amendment in the FROZEN marker). LANE_A_NOTES §R15 hands B/F the new golden + a corner-venue
  eyeball note. Convention settled: visible venue face is +Z; selfcheck's −Z 'faces frontEdge' is the
  sim/GLB convention (π-off the visual front, the R13 RY_FLIP ambiguity).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 09:48:17 +10:00
m3ultra
9a65f51a88 Lane D R14 (v3.0): identity continuity — the ped you followed is in the crowd
The last charter promise. A per-venue tonight roster (sim.js _venueRoster) of
who entered a gig venue tonight — surge occupants self-record in _enter, queue
admits relay via citizens.recordVenueEntry — is consumed by GigCrew.spawn: the
crowd's front slots BECOME the roster identities (same pedIndex, same height),
seeded strangers fill the rest.

- No golden move: both goldens are plan-gen hashes; each crowd slot is an
  independently-keyed rng stream, so a roster override touches only that slot and
  an EMPTY roster is byte-identical to R13 (verified). Roster heights 1.55-1.95m
  stay inside the no-giants [1.4,2.0] gate. Pure bookkeeping: 0 rng, 0 GPU.
- Roster persists across interior exit/re-enter, bounded, auto-clears on the
  setGig(id,false) transition (per-night). Deduped by stable key.
- queue.js: admitOne() returns {pedIndex,height,key} + fires an onAdmit callback
  (auto-drain AND explicit) so F relays every walked-in punter to the roster;
  _spawnAt stashes the identity. band.js: _make returns pedIndex; members carry
  it for F's continuity smoke; crew.spawn is now null-gig-safe.
- Verified: surge 8/8 + queue 4/4 real identities land in the crowd; empty-roster
  == R13; 7-night x 3-venue soak leak geo0/tex0, roster bounded, no identity
  churn; interior 64 draws <=350 (0 new rigs); ?noassets placeholders, 0 fetch.
- F seam in LANE_D_NOTES: crew.spawn({roster}) + queue onAdmit relay + the
  tonightRoster(venueShopId) accessor.
- Debt #2 (sit clip): investigated, deferred to v3.1 — the clip converts+binds
  (tailnet Sitting_Idle.fbx -> 394KB sit.glb) but a real seated pose is ~1.2m
  stature (fails F's no-giants floor) and needs a _rotWithHips loader + replant
  (shared rig-stack) + an F smoke exemption; not a freeze-week change. SEAT_DROP
  hack kept (reads seated from the money-shot front).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 09:04:06 +10:00
m3ultra
eee60e05a5 Lane B (Streetscape) round 14: v3.0 release — alpha-alias retirement + tour audit + queue_night hero (?gigs=1)
Release round: no new systems, no rng, goldens frozen (0x3fa36874 / gig 0x1f636349 both unmoved).
Files: audio.js, venue.js, dbg.js (+ LANE_B_NOTES). Verified fresh Chromium, seed 20261990.

- Alpha-alias readers dropped (debt #1, B first): audio.js venueGigState() loses the alias hop
  (g.venueShopId===id -> g.state); byVenue + clock fallback only. venue.js stateGetter() loses the
  bare-string arm (F passes gigState.byVenue since R13, confirmed index.html:379); seed = update(null).
  Verified live: byVenue {116:on,404:on,490:quiet} at NIGHT drives the marquee with no string arm;
  audio resolves gig-<genreKey> per venue with no alias hop. F is UNBLOCKED to delete the getters.
- Bookmark/tour audit under ?gigs=1: all 19 bookmarks resolve, frame un-letterboxed, 0 page errors,
  <=300 draws. District venue-conversions hijacked none (night_neon->Video Regal, dig_real->Retro
  Groove). No pose fixes needed. (Orphan-chunk teleport inflates tris across sequential shots -- fresh
  counts <=153k; tram_stop's real ~293k is close-range Lane D GLB ped rigs, framing fine, not a venue block.)
- queue_night bookmark added (dbg.js): John's release hero -- close 3/4 on the pub door at seg 5
  ('on'), biased to the queue side; new poseForVenueQueue() recomputes venue.js frontage geometry.
  Frames marquee + window glow + streetlamp pools + the queue (spawned live by the shell loop; F's
  gig_shot.py drives it live -- verified by manually spawning D's VenueQueue: 2 punters at the door).
- Poster-repetition call (debt #4): FINE, no ask to E -- 2 gigs tonight => 2 distinct designs
  (screenprint/Screaming Utes x7, xerox/The Dead Ringers x7); variety scales with venues playing.
- Town-wide streetlamp pools: v3.1 candidate per Fable ruling (venue-scoped for v3.0; no code).

-> Lane F: cross-lane alias sweep is CLEAN (no A/C/D/E reads it); only your own 20 tool lines in
   flags_check.py + gig_shot.py remain -- migrate to *Of(id)/venueShopIds[0] when you delete the
   gig_state.js:141-151 getters + the CITY_SPEC alias line (with A).
-> Lane A (now gates John's hero-shot poster): buildPosters item 1 still seats each venue's own
   frontage poster on local -Z (back wall, 15.9m from the +Z street facade). Flip to +sin,+cos and it
   seats by the door -- in the existing queue_night frame, no pose change. Per "don't touch world
   code," B did not work around it. Re-flagged from R13.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 08:43:22 +10:00
m3ultra
62e38c5df8 Lane C R14 (v3.0 release): venue audit + docs freeze + guitar_amp ruling
Light release round — C interior code unchanged since R13 (venue geometry stable by
construction); this is the audit, the amp ruling, and the v3.0-FROZEN docs.

- Re-verified vs the current tree (D's continuity + RY-fix + real instrument GLBs landed):
  contract byte-identical (caps 8/8/12, deckY/riserY, gigKeys, determinism), drawSweep <=350
  (worst 161), gig-night draws pub 60 / band_room 52 / rsl 71 <=350. Release-law confirmed:
  D's rng namespace is disjoint from C's ctx.stream; no golden can move.
- guitar_amp ruling (LANE_C_PUB §3): NOT blessed as-is. D's pose (stage.z+0.15, down-stage)
  interpenetrates the bass rig by 0.29 m; move it up-stage-right ~ (stage.w*0.36, deckY,
  stage.z - stage.d*0.22), clear of the bass + C's primitive ampStacks. Full amp unification
  = v3.1. Cosmetic + ?gigs opt-in -> not a tag blocker. C->D handshake.
- Docs freeze: LANE_C_PUB.md + LANE_C_AUDIO.md marked v3.0-FROZEN; RY §0 reframed past-tense
  (the +Z fleet was fixed at source in D's buildFigure, R13 - no per-consumer flips); budgets
  updated to R14; in-flight handoffs removed.
- Comment-only code (zero runtime/golden/rng change): fixed stale R12 inline return-shape
  comments (watchPoints 6..8 -> 8/8/12; stage bandPoses[3] -> [4] + riserY) in interiors.js +
  layout.js; documented the standalone gigKey hand-build. theme.js untouched.

Note for E/F: settled gig-night interior is ~182k tris (E instruments 14k each x5); F's smoke
reports ~34k measuring before the async GLBs load. Interior draw gate passes; v3.1 LOD candidate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 08:40:42 +10:00
m3ultra
a3911a4590 Lane A round 14: v3.0 release — 400-seed district invariant sweep + CITY_SPEC §v3 frozen
Release round: harden + freeze, no new systems. No golden moved (synthetic 0x3fa36874, gig
0x1f636349 both re-verified frozen); selfcheck.js is the only code touched, so no seeded generation
stream gained a draw (release-round law).

- selfcheck.js: factored the district checks out of gigSuite into districtInvariants(plan,label)
  (behaviour-preserving — hero-seed gigSuite runs byte-identical), then added a 400-seed synthetic
  sweep + the large edge seeds asserting the full district contract each (2-4 venues, >=1 pub, never
  the openLate shop, no band twice/night town-wide, per-venue 4-6 nights, every poster off the
  carriageway). Asserts the seeded count spans {2,3,4} across 400 seeds (146/121/133 — range is
  exercised, not pinned). osm graceful placement: katoomba (19 shops, no warehouse district) still
  lands 2-4 valid venues incl. a pub for every hero seed. Poster check kept at roadWidth/2 (a literal
  poleOffset compare would false-fail corner posters); added -1e-6 epsilon on the strict boundary.
  ALL GREEN 14264/14264 in ~0.7s.
- CITY_SPEC §v3: final wording pass, marked FROZEN at v3.0 (round 14) with a v2-style marker + an
  explicit carve-out for F's pending alias-line deletion. Fixed the roadWidth prose (full carriageway
  width, not 'half-corridor'). Genres final (pubrock/grunge/covers).

Coordination: the R12 single-venue alias line stays in §v3 for F to delete atomically with the
gig_state.js getters (debt #1) — deleting now would be doc-ahead-of-code (alias still live) and race F.
Flagged to F that the runtime block omits nightOf(id); to B that venue.js:228 is a 2nd bare-string
site. Verified: mutation-tested the sweep (non-vacuous — catches 5-venue/7-night/on-road violations)
+ adversarial pass (goldens frozen, freeze matches code, deterministic).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 08:36:31 +10:00
m3ultra
a358c5d9ef Lane F R13 (v3.0-beta, the district): per-venue gig state + walla/applause + queue wiring; qa --strict 6/6
Wires A–E + D's district together and fires it. Seed 20261990 → "Boolarra Heads": 3 venues, 2 playing
tonight (The Exchange Hotel pub gig-pubrock + an RSL gig-covers, one dark), Screaming Utes $10 on the door.

- gig_state.js → PER-VENUE latches off the shared procity:segment spine (one per venueShopId); all three
  hard-won R12 laws kept. New runtime surface window.PROCITY.gigs.byVenue + onOf/openOf/gigOf/coverOf/
  bandNameOf/paidOf(id) + markPaidOf/venueShopIds; the R12 single-venue alias kept for one round.
- interior_mode.js: crowd-walla rides the interior tone layer under the live band bed while `on`; an
  applause sting fires once on the on->done edge. DELETE the GIG_BED bridge — debt #1, and it was a LIVE
  BUG: E renamed pubrock-live->gig-pubrock, so the bridge (gig-pubrock->pubrock-live) pointed at a dead
  key => the band would have mimed in silence. room.audio.gigKey (already the manifest key) feeds directly.
- index.html: per-venue cover charge (a night at two venues is two covers), concurrent per-venue setGig
  surges, and the outdoor VenueQueue lifecycle (D shipped the class + left the street-side seam to F:
  spawn at doors off B's queueZoneFor, drive each frame, dispose by done), exposed as queueCountOf(id).
- interior_scale_check.py: human-size assert now measures STATURE (crown-foot, platform-independent);
  world-crown ceiling-clip assert kept as its own line (debt #6).
- flags_check.py smoke_gigs -> THE DISTRICT: band->4, per-venue loop (every gigKey resolves across 3
  genres, crowd <= its own watchPoints, no-giants by stature), street budget at the busiest venue block,
  the queue assert; plus the R12 determinism/cover/noassets/flags-off arms.
- CITY_SPEC: pin the window.PROCITY.gigs runtime surface. gig_shot.py -> r13 interior + venue_night shots.

qa.sh --strict GREEN 6/6, 0 warn. Interior 63 draws/35k tris; street @ venue block 121 draws/51k tris;
band+crowd stature 1.61-1.81 m under a 4 m ceiling; flags-off fingerprint 0x3fa36874 unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 00:22:03 +10:00
m3ultra
fefbb4b806 Lane D R13 (v3.0-beta): 4-piece + instruments, outdoor queue, multi-venue surge, RY fix
- RY audit (debt #4): the ped GLB fleet's visual front is +Z but the whole
  system assumed -Z (sim heading math, C's pose ry, placeholder, impostor).
  Fixed at the source (rigs.js buildFigure normalises every rig to -Z front)
  and dropped band.js RY_FLIP. Corrects keepers/browsers (faced the back wall)
  and streamed walkers (moon-walked): 223/223 gig + 188/188 flags-off now face
  travel. Visual only, no golden move. keepers.js/placeholder.js/impostor.js
  unchanged (fixed for free). Canonical convention -> LANE_D_NOTES for C.
- Instruments (debt #2): E's manifest GLBs swap over the primitives async
  (placeholder-persists, fleet-gated -> 0 fetch under ?noassets), guitar/bass
  held, mic + kit planted, guitar_amp dressing.
- Drummer (debt #3): 4-piece, seated at C's drums pose (sink + kit occlusion;
  reads from the front/crowd POV - a sit clip is the polish).
- Outdoor queue (new queue.js): seeded 2-6 line at the frontage, drains on
  admitOne() + auto-drain, consumes B's queueZone or a frontage-hugging door
  fallback. F wiring seam documented in LANE_D_NOTES.
- Multi-venue surge (sim.js): setGig -> Set of concurrent venues.
- Interior gig 56 draws <=350; ?noassets+?gigs placeholders, silent-and-fine.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 00:00:05 +10:00
m3ultra
46127c7e95 Lane B (Streetscape) round 13: v3.0-beta the district — N-venue frontage + night pass + posters + queue zone (?gigs=1)
Generalises the R12 one-pub slice to all 2-4 venues and lands the night-readability polish. All
gig-gated (flags-off byte-identical, verified in a fresh browser context). Files: venue.js, audio.js, dbg.js.

- venue.js -> N venues: per-venue lit marquee/frontage on B's +Z facade side + window-light spill;
  streetlamp pools on venue blocks (one instanced warm-pool mesh, night-driven -> the Friday-night
  walk reads); town-wide posters instanced per gig (14 posters = 2 draws / 2 textures, band-name
  overprint, flat printed-poster fallback). Town-wide pools deferred (would need always-on
  furniture.js -> breaks flags-off parity).
- Queue-zone marker per venue: venue.queueZone = {x,z,ry,len} for Lane D (head at the door, extends
  along the frontage; queuer faces -ry). queueZones{} + queueZoneFor(id).
- audio.js muffled spill per-genre (debt #1): import gigKeyFor; the nearest venue at doors/on within
  26 m spills its gig-<genreKey> through the 470 Hz lowpass, so pub/band_room/RSL crossfade genre.
  F: delete the GIG_BED bridge.
- update(gigStates) polymorphic: per-venue map {[venueShopId]:state} (F wires PROCITY.gigs.byVenue)
  or alpha-compat bare string (primary pub). Contract documented for F in LANE_B_NOTES.
- ?noassets fetch fix (latent R12 bug): gate the poster JPEG fetch behind !noassets -> 0 gig-layer
  network over the 22-JPEG facade/ground baseline.
- dbg.js: venue_night + district_posters bookmarks (seg 5) so tools/qa/gig_shot.py scripts the
  street money shot; poseForVenue resolves from the live plan.

Budget (fresh Chromium, gigs-on NIGHT, busiest pub block, all frontages lit): worst 154 scene /
~167 total draws, 64k tris -- <=300 / <=200k. 0 console errors (gigs / gigs+noassets / flags-off).
Verified 3 venues seed 20261990: pub->gig-pubrock, RSL->gig-covers, band_room->gig-grunge (all in
E's manifest).

Finding for Lane A: buildPosters item 1 seats each venue-frontage poster on the lot -Z (back wall,
measured 15.9 m from the +Z street facade) -- flip to +sin,+cos. Spine/pole posters render correctly
and face the footpath ("SCREAMING UTES" readable). Non-blocking (marquee + pools carry the shot).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 23:11:10 +10:00
m3ultra
82b711f962 Lane C R13 (v3.0-beta, the district): band_room + rsl archetypes, 4-piece band, RY audit (?gigs=1)
- theme.js: band_room (small/scuzzy tin shed) + rsl (carpet-and-flock club) venue recipes,
  reusing the pub machinery via a new per-kind venueSpec (deck size/height + crowd cap).
- layout.js: parametrized placeStage/placeWatchPoints; 4th `drums` pose seated on the riser
  (debt #3 — the band is now a 4-piece across all venues), stage.riserY + per-pose y.
- interiors.js: robust gig-<genreKey> resolution (gig.genreKey -> shop.genreKey -> kind
  default) matching E's renamed manifest beds (debt #1); API doc for the 4-piece + riserY.
- LANE_C_PUB.md: 3-kind venue contract + pinned ry convention and the empirically-verified
  keeper/browser back-to-front finding + source-normalise fix for Lane D (debt #4).

Verified fresh (seed 20261990 + 90-build soak): watch caps 8/8/12, gigKeys
gig-pubrock/gig-grunge/gig-covers, determinism 0, path/carve 0, gig-night worst RSL 237 draws
asset-free (16 rigs) / 77 GLB <=350, leak geo0/tex0. Shot: docs/shots/laneC/rsl_gig_r13.jpg.
Lane F: bump flags_check band==3 -> ==4 (tools/flags_check.py:674,794) — RED by design.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 23:05:03 +10:00
m3ultra
6fba79a1cb Lane A round 13: v3.0-beta the district (?gigs=1) — 2–4 venues, week schedule, town-wide posters, verge fix
Publishes the multi-venue plan.gigs interface C/D/B/F hang off (half-day handshake, A first).

- pickVenues: seeded 2–4 shops converted in place → pub (spine end) / band_room (warehouse
  fringe) / rsl (off-spine); 4th = 2nd pub at far end. Registry gains weight-0 band_room+rsl
  kinds + venueKind/genreKey/placement. Never the openLate landmark; osm-safe fallbacks.
- gigKey contract (debt #1): gigKeyFor(genreKey) => 'gig-'+genreKey, the single exported
  genre→bed mapping. No table anywhere; F deletes the GIG_BED bridge.
- plan.gigs goes district: 7 nights × every venue, each plays a seeded 4–6 (dark reads true);
  cover skewed by kind; no band plays two venues on one night; custom names spread night-major.
- plan.posters goes town-wide: tonight's playing venues each get frontage + seeded spine run.
- Verge fix (debt #5): new corridor law roadWidth/vergeBand/poleOffset(edge) in registry.js
  (road-vs-verge split of edge.width, published as functions ⇒ base goldens frozen). Posters
  seat on the verge, never bitumen; selfcheck asserts no poster on a carriageway.
- registry pub.interior 'band_room'→'pub' (unread field; avoids collision with band_room type).

Gig golden re-pinned 0xa6ae5a5e→0x1f636349 (multi-venue changed output; in-commit per amendment
law). selfcheck ALL GREEN 3273/3273; scaffold_check + consistency GREEN; flags-off byte-identical.
CITY_SPEC §v3.0-beta + LANE_A_NOTES §Round 13 updated in this commit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 22:35:05 +10:00
jing
2498fdc892 Lane F R12: wire the v3.0-alpha gig layer (?gigs=1) — state machine, cover charge, band+crowd, gig audio
F last: five landed lanes → one Friday night. Seed 20261990 → The Thornbury Hotel,
tonight SCREAMING UTES, $10 on the door. qa.sh --strict GREEN 6/6.

- gig_state.js (new, F-owned): quiet → doors (DUSK) → on (NIGHT) → done (DAWN) → quiet.
  Rides lighting's procity:segment event (a poll only sees the segments it samples, and
  missed the night roll entirely); `state` is a getter because B's audio reads it from its
  own rAF while the player is inside a shop.
- ?gigs=1 seam: custom_bands.json fetched in the bootstrap (no fetch flag-off or ?noassets)
  → generatePlanFor(seed, src, {gigs:true, customBands}). 7 of John's 10 names fill the week.
- Cover charge on the R8 buy seam, zero new UI: paid debits once with a toast, re-entry the
  same night is free, broke → polite knockback, free nights walk straight in.
- interior_mode: opts.gig → C's gigKey; D's GigCrew (band 3 + crowd 8, 3 dancing) at C's
  stage/watch points; both disposed with the room (leak-free over 4 cycles).
- Cross-lane fix at the glue: C emits gigKey 'gig-pubrock', E shipped the bed as
  'pubrock-live' — unresolved the pub plays to a silent room and nothing errors. Bridged
  here; C/E/Fable owe CITY_SPEC a contract line. Details + 2 more findings in LANE_F_NOTES §12.
- smoke_gigs (16 gates): schedule determinism, band==3, crowd ≤ watchPoints, dance mix, bed
  resolves, ≤350 draws (47 rig / 171 placeholder), no-giants by STATURE (the band stands on a
  0.32m deck, so world-crown fails a correct band), cover ruling, ?noassets+?gigs.
  Flags-off regression gains a v3 arm: no state machine, no plan.gigs, no pub, no venue geo.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 21:09:56 +10:00
m3ultra
f0a0a99207 Lane B (Streetscape): round-12 v3.0-alpha — venue frontage + posters + muffled-gig spill (?gigs=1)
- web/js/world/venue.js (new): createVenuePresentation — renders E's poster skins at plan.posters with band-name overprint (nameZone, seeded template, flat fallback if JPEG missing); lit pub marquee + bulb row over the door, update(gigState) ramps the glow. Constructed by the shell/F under ?gigs (like weather/tram).
- web/js/world/audio.js: muffled-gig spill — near the venue (<=26m) while a gig is at doors/on, pubrock-live plays through a 470Hz lowpass at low distance-gain. Reads window.PROCITY.gigs (F) with a plan.gigs+clock fallback.
- Prime flag law: inert/absent without the gig layer; goldens frozen (no citygen edits, selfcheck 3074/3074); byte-identical flags-off boot.
- Verified live on a gig plan: THE WOMBATTS posters over band-photo art, The Thornbury Hotel lit at night, spill gain ~0.14. F wiring documented in LANE_B_NOTES; streetlamp-pool stretch deferred.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 20:15:40 +10:00
m3ultra
888335d633 Lane D round 12: gig band + crowd + patronage surge (?gigs=1)
v3.0-alpha — Friday night at the pub. New web/js/citizens/band.js (GigCrew):
- Band trio on C's stage.bandPoses (deckY), distinct fleet peds (seeded per gig), each with a
  primitive instrument on the chest (guitar/mic/bass; E's GLBs drop in via opts.instrumentFor).
- Crowd (<=8) at C's watchPoints on the gate-protected spawnRig path: dance:false = weight-shift
  idle, dance:true = bounce/sway/step with seeded phase (no metronome).
- Deterministic from citySeed; placeholder crew under ?noassets.

sim.js: setGig(venueShopId, on) gig-night surge — patronage pulls peds from further (GIG_RANGE)
and ducks in harder (GIG_SURGE) at the venue; occupants drain at close per A's closing-time ruling.
Guarded by _gigVenue==null so gig-off is byte-identical (prime flag law; goldens unmoved).

Verified standalone (A withGigs + C buildInterior + D): band 3, crowd 8, dancing 3, all 11 figures
human-sized (1.60-1.83m under a 4.0m ceiling; R10 no-giants holds), band faces audience + crowd
faces stage, deterministic, leak-free (8 cycles, 0 delta). Crew ~40 draws. Surge: venue occupancy
1 -> peak 5, identity stable. Shot: docs/shots/laneD/r12_gig_night.jpg.

Flag for C/F: band.js flips C's watchPoints/bandPoses ry by pi (RY_FLIP) — the fleet mesh's visual
front is local +Z, not the -Z the comments assume. Remove RY_FLIP if C re-fronts the gig poses.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 19:49:07 +10:00
m3ultra
05e020e3b8 Lane C: round-12 pub venue — archetype + watchPoints + audio.gigKey (?gigs=1)
v3.0-alpha band room. New pub recipe (venue:true): stage + PA + amps at the back wall (new stage/paSpeaker/ampStack fittings, GLB slots stage/pa_speaker/amp for E), bar counter (keeper), sticky carpet, blocked back door = green room.

buildInterior returns for venues: room.watchPoints[] (6-8 seeded audience poses {x,z,ry,dance} facing the stage, dance ~1/3, counter.stand ry convention); room.stage {x,z,w,d,deckY,frontZ,bandPoses[3]} for Lane D's band; room.audio.gigKey ('gig-'+genreKey) set when Lane F passes opts.gig.

Flags-off byte-identical: venue code runs only for recipe.venue on its own seed sub-streams; non-pub rooms have watchPoints:[], stage:null, unchanged placement/audio.

Verified: 40-seed pub sweep (0 throws/carves/path-fails, 8 watchPoints, dance 0.38) + 60-room mixed soak (0 det/path fails, leak geo0/tex0, worst 19ms). Non-pub unchanged. Interface for D/F: docs/LANES/LANE_C_PUB.md. scaffold + manifest green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 18:59:48 +10:00