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>
This commit is contained in:
m3ultra 2026-07-16 18:27:29 +10:00
parent 06fe64875a
commit 955ec3497b
5 changed files with 75 additions and 9 deletions

View File

@ -1,11 +1,30 @@
# LANE A — CITYGEN · progress (PROCITY-A)
*Status: **all deliverables landed; self-check all-green (51670/51670); rounds 219 closed** (see below).
v4.0-alpha alpha-close: fragmentation resolved (105→1 component on katoomba), E's spine-poster finding
fixed, all 5 real towns pinned on real-roads goldens (katoomba `0xb7ffbca2`). classic/gig/marched-fixtures
frozen.*
*Status: **all deliverables landed; self-check all-green (51675/51675); rounds 219 closed, R20 poster-cap
landed** (see below). v4.0-beta: spine-poster cap scaled by shop count (katoomba 438→31, closes F's
venue_night edge); real-town gig goldens pinned. classic/gig/marched-fixtures frozen. Density absorb pends
E's widened caches.*
*Date: 2026-07-16 · owner: PROCITY-A (Opus 4.8)*
## Round 20 (2026-07-16) — v4.0-beta THE FIVE TOWNS: the poster cap (density absorb pends E)
Two Lane-A deliverables; the first (independent) landed this commit, the second waits on E's widened caches.
1. **Poster cap (ledger #1) — LANDED.** My R19 finding (real katoomba: 438 spine posters, driving F's one
budget edge, `venue_night` 200,965 tris) is fixed. `gigs.js buildPosters` caps the spine pole run at
**`POSTER_PER_SHOP × shops`** (1.5/shop, floor 12) — a seeded subsample of the run. **Scaled by shop
count, not edge count** (the whole point: a sparse real town has few shops but a big graph). Result:
katoomba **438 → 31** posters (~2× the synthetic feel), fremantle 32, castlemaine 14. **No-op on the
synthetic town** (493 shops ⇒ cap ~740 ≫ its ~15 posters), so the synthetic gig golden `0xb1d48ea1` is
frozen. Posters are gig-layer, so the real-town BASE goldens are unchanged — I added
`REAL_TOWN_GIG_GOLDENS` (the full district+poster plan per town: katoomba `0xbd332e83`, …) to
regression-guard the capped output byte-exact. selfcheck ALL GREEN **51675/51675**; scaffold + consistency
GREEN; cross-process deterministic. → F: re-measure `venue_night`, the +965 edge should close.
2. **Density absorb (ledger #2) — PENDING E.** E re-fetches general retail (C's class→archetype table) and
re-emits the five caches at 36× shop count. When they land I load-test seating/overflow/venue placement
and re-pin the real goldens once. The poster cap already reads the live shop count → auto-scales to the
widened towns.
## Round 19 (2026-07-16) — v4.0-alpha CLOSE: fragmentation ruling + findings hardened + goldens pinned
A was the round's spine (the A→E→A finalization deferred from R18). Every filed finding resolved; the tag is

View File

@ -311,7 +311,11 @@ camera are all `+Z`; position `lot + (sin ry, cos ry)·(d/2+ε)`), turned to rea
R15 it was on the `Z` back and invisible from the street). So the poster never sits over a back-street's
bitumen, **`pickVenues` prefers a band_room/rsl lot whose facade clears every carriageway by ≥ `POSTER_CLEAR`**
(ROUND16 ledger #6 — exported from the barrel; the selfcheck asserts the same floor). Plus a seeded run along
the spine — 2 per main edge (or, when a town has no main spine, every edge), at `t ∈ [0.2,0.8]` of the block, on **either verge**. Different venues' posters share the same pole
the spine — 2 per main edge (or, when a town has no main spine, every edge), at `t ∈ [0.2,0.8]` of the block, on **either verge**;
a candidate that would overhang a **real** crossing carriageway is skipped (ROUND19, E's finding), and the
run is **capped at `POSTER_PER_SHOP × shops`** (ROUND20) — a sparse real town has few shops but a big edge
graph, so the raw "2 per edge" run reads busy (katoomba 438 → 31). The cap never binds on the shop-dense
synthetic town, so its gig golden is frozen. Different venues' posters share the same pole
runs — that IS the district reading. `ry` follows the house convention (a poster's printed face =
`(sin ry, cos ry)`, so `atan2` of the *away-from-viewer* normal); B places E's poster skin at `(x, z)`
rotated `ry`; F wires "poster → that venue's tonight-gig".

View File

@ -2,6 +2,28 @@
Durable answers to questions other lanes raised against Lane A. Full status in `A-progress.md`.
## Round 20 (2026-07-16) — v4.0-beta: the poster cap (density absorb pending E's widened caches)
### → F: **poster cap landed — re-measure `venue_night`** (your +965-tri edge should close)
My R19 finding (438 posters on katoomba) is fixed. `gigs.js` now **caps the spine pole run at
`POSTER_PER_SHOP × shops` (1.5/shop, floor 12)** — a seeded subsample. Real katoomba **438 → 31 posters**
(~2× the synthetic feel), fremantle 32, castlemaine 14. Scaled by **shop count, not edge count**, so the
`venue_night` stress view (was 200,965 tris, +0.5% over 200k) should drop well under. Re-measure and close it.
### Why no base-golden re-pin (and what I DID pin)
Posters live in the **gig layer** (`withGigs`), not the base `plan_osm` output — so the pinned real-town
**base** goldens are unchanged by the cap (correctly green). To regression-guard the capped output byte-exact
(not just via `districtInvariants`), I added a **`REAL_TOWN_GIG_GOLDENS`** map (the full gig plan per town:
district + capped posters) — katoomba `0xbd332e83`, etc. Synthetic gig golden `0xb1d48ea1` **frozen** — the
cap never binds on the shop-dense synthetic town (493 shops ⇒ cap ~740 ≫ its ~15 posters).
### Density absorb — PENDING E's widened caches (ledger #2)
E re-fetches general retail (per C's class→archetype table) and re-emits the five caches at 36× shop count.
When they land (E→A "caches re-emitted"), I load-test seating capacity + overflow accounting (`norm.dropped`
stays honest) + venue placement, then re-pin the real goldens **once** (base + gig). The R18 overflow fence
is exactly the load-bearing part now — I'll widen edge capacity / split runs if a dense edge overwhelms. My
poster cap already reads the LIVE shop count, so it auto-scales to the widened towns (no change needed).
## Round 19 (2026-07-16) — the alpha close: fragmentation resolved + findings hardened + goldens pinned
### → B / C / D / F: **`katoomba_real` boots on real roads, golden pinned, findings resolved** (the handshake)

View File

@ -23,6 +23,10 @@ const r4 = v => Math.round(v * 10000) / 10000;
// selfcheck asserts the same floor — import POSTER_CLEAR there, don't re-guess it.
export const POSTER_CLEAR = 0.5;
const FACADE_PROUD = 0.06;
// ROUND20 poster cap: the spine pole run is "2 per main edge", which on a sparse REAL town (few shops, big
// edge graph) reads busy (katoomba: 438). Cap it SCALED BY SHOP COUNT — a seeded subsample of the run. On
// the synthetic town the shop count is in the hundreds, so the cap never binds and the gig golden is frozen.
const POSTER_PER_SHOP = 1.5, POSTER_SPINE_FLOOR = 12;
// nearest point on segment AB to P (same helper the selfcheck uses; kept local so gigs.js stays pure).
const nearestSeg = (px, pz, ax, az, bx, bz) => {
const dx = bx - ax, dz = bz - az, L2 = dx * dx + dz * dz || 1;
@ -275,7 +279,7 @@ function buildPosters(plan, citySeed, venues, gigs) {
};
const pr = rng(citySeed, 'posters', 0);
const PER_EDGE = 2;
let k = 0;
const spineCands = [];
for (const e of runs) {
const a = nodeById.get(e.a), b = nodeById.get(e.b);
if (!a || !b) continue;
@ -291,9 +295,15 @@ function buildPosters(plan, citySeed, venues, gigs) {
const nx = side > 0 ? -uz : uz, nz = side > 0 ? ux : -ux;
const x = a.x + ux * len * t + nx * off, z = a.z + uz * len * t + nz * off;
if (onCarriageway(r2(x), r2(z))) continue; // check the r2-rounded position `add` stores — overhangs a crossing → skip (E's R18 finding)
add(x, z, Math.atan2(nx, nz), bill[k++ % bill.length].gigId);
spineCands.push({ x, z, ry: Math.atan2(nx, nz) });
}
}
// ROUND20 poster cap: thin the pole run to ~POSTER_PER_SHOP × shops (a seeded subsample), so a sparse
// real town reads like a main street, not a poster wall. cap ≫ candidates on synthetic ⇒ no-op ⇒ frozen.
const cap = Math.max(POSTER_SPINE_FLOOR, Math.round(POSTER_PER_SHOP * plan.shops.length));
const chosen = spineCands.length > cap ? shuffle(rng(citySeed, 'postercap', 0), spineCands.slice()).slice(0, cap) : spineCands;
let k = 0;
for (const c of chosen) add(c.x, c.z, c.ry, bill[k++ % bill.length].gigId);
return posters;
}

View File

@ -457,6 +457,13 @@ const REAL_TOWN_GOLDENS = {
katoomba_real: 0xb7ffbca2, newtown_real: 0x6f984c81, fremantle_real: 0xfb197000,
bendigo_real: 0x21179b3b, castlemaine_real: 0x7c47f639,
};
// ROUND20: the GIG-layer golden per real town (district + capped posters) — the base golden above is
// gigs-off so it never captured the poster cap. Pinned here so the ROUND20 poster cap (and the density
// absorb to come) are regression-guarded byte-exact, not just by districtInvariants.
const REAL_TOWN_GIG_GOLDENS = {
katoomba_real: 0xbd332e83, newtown_real: 0xd36f09ca, fremantle_real: 0xca42f569,
bendigo_real: 0x93ae8765, castlemaine_real: 0x9a4e904c,
};
const townFiles = (existsSync(TOWNS_DIR) ? readdirSync(TOWNS_DIR) : []).filter(f => f.endsWith('.json'));
if (!townFiles.length) console.log(" (none yet — the contract + hardening are live, ready for E's build_towns.py)");
for (const f of townFiles) {
@ -470,8 +477,12 @@ for (const f of townFiles) {
for (const s of GIG_SEEDS) districtInvariants(generatePlanFor(s, 'osm', { gigs: true, town: key, cache }), `real/${key} ${s}`);
const hash = xmur3(JSON.stringify(generatePlanOSM(20261990, key, { cache })))() >>> 0;
const want = REAL_TOWN_GOLDENS[key];
if (want === undefined) console.log(` ⚠ real/${key}: UNPINNED — add REAL_TOWN_GOLDENS['${key}'] = 0x${hash.toString(16).padStart(8, '0')}`);
else ok(hash === (want >>> 0), `real/${key}: golden 0x${hash.toString(16)} matches pinned 0x${(want >>> 0).toString(16)}`);
if (want === undefined) console.log(` ⚠ real/${key}: base UNPINNED — add REAL_TOWN_GOLDENS['${key}'] = 0x${hash.toString(16).padStart(8, '0')}`);
else ok(hash === (want >>> 0), `real/${key}: base golden 0x${hash.toString(16)} matches pinned 0x${(want >>> 0).toString(16)}`);
const ghash = xmur3(JSON.stringify(generatePlanFor(20261990, 'osm', { gigs: true, town: key, cache })))() >>> 0;
const gwant = REAL_TOWN_GIG_GOLDENS[key];
if (gwant === undefined) console.log(` ⚠ real/${key}: gig UNPINNED — add REAL_TOWN_GIG_GOLDENS['${key}'] = 0x${ghash.toString(16).padStart(8, '0')}`);
else ok(ghash === (gwant >>> 0), `real/${key}: gig golden 0x${ghash.toString(16)} matches pinned 0x${(gwant >>> 0).toString(16)}`);
}
// ── 4. every facade skin referenced by the registry exists on disk ──────────────────