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>
This commit is contained in:
m3ultra 2026-07-16 20:05:11 +10:00
parent 94e56ed5fa
commit 5d29863b78
5 changed files with 67 additions and 31 deletions

View File

@ -1,9 +1,9 @@
# LANE A — CITYGEN · progress (PROCITY-A)
*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.*
*Status: **all deliverables landed; self-check all-green (51866/51866); rounds 220 closed** (see below).
v4.0-beta: spine-poster cap scaled by shop count (katoomba 438→31, closes F's venue_night edge) + E's
density widening absorbed (5 towns at 36× shops, all place 3 venues, drops 5.5% counted by cause); all 10
real-town goldens (base + gig) pinned. classic/gig/marched-fixtures frozen.*
*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)
@ -20,10 +20,20 @@ Two Lane-A deliverables; the first (independent) landed this commit, the second
`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.
2. **Density absorb (ledger #2) — LANDED.** E's five caches at **36× shops** (292 total: katoomba 20→80,
fremantle 21→80, newtown 18→72, bendigo 9→36, castlemaine 6→24) absorbed; **all five place 3 venues**;
the poster cap auto-scaled off the live shop count as designed. **Capacity widened** — at 4× the dominant
drop cause was overflow (edge-side full), so `NODE_CLEAR` 6→3 + a **roads-only `RGAP`** 2→0.5 (shared
marched `GAP` untouched ⇒ fixtures frozen): drops **7.9% → 5.5%** (16/292), residual **counted by cause**
(`dropped_overflow`/`_overlap`/`_stranded`). Stopped there deliberately — the next lever (spilling
overflow onto a different street) buys ~1% by putting a shop on a road it isn't on. **Venue facade bias
extended to `pub`** (R16 exempted it; on a real graph at density the pub's frontage poster overhung a
cross street — newtown 0.91 m); still a no-op on synthetic. **Goldens re-pinned once** (base + gig, all
five). **Fixed:** the real-cache loader now skips E's new `index.json` (a selector index, not a cache).
selfcheck ALL GREEN **51866/51866**; scaffold + consistency GREEN; cross-process deterministic.
Handshake fired → all: **densities absorbed, goldens pinned.** → F: re-measure `venue_night` (poster cap)
and gate the five-town matrix.
## Round 19 (2026-07-16) — v4.0-alpha CLOSE: fragmentation ruling + findings hardened + goldens pinned

View File

@ -17,12 +17,26 @@ Posters live in the **gig layer** (`withGigs`), not the base `plan_osm` output
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).
### → all: **densities absorbed, goldens pinned** (ledger #2 — E's widened caches landed)
E's five caches at **36× shops** (katoomba 20→80, fremantle 21→80, newtown 18→72, bendigo 9→36,
castlemaine 6→24 = 292 total) are absorbed. **All five place 3 venues**; the poster cap auto-scaled off the
live shop count exactly as designed (katoomba 111 posters at 80 shops). selfcheck **ALL GREEN 51866/51866**.
- **Capacity widened** (the R18 overflow fence, load-tested). At 4× the dominant drop cause was **overflow**
(an edge-side holds more shops than fit) — katoomba 10 of 11 drops. Tightened the run: `NODE_CLEAR` 6→3
and a **roads-only `RGAP` 2→0.5** (the shared marched `GAP` is untouched ⇒ marched fixtures stay frozen).
Drops **7.9% → 5.5%** (16/292). 0.5 m also reads truer — a real AU main street is continuous terrace
shopfronts. Residual drops stay **counted, now by cause** (`dropped_overflow` / `_overlap` / `_stranded`).
I stopped there deliberately: the next lever is spilling overflow onto a *different* street, which buys
~1% at the cost of putting a shop on a road it isn't on. Dropping a crowded shop is the honest trade.
- **Venue facade bias now covers `pub`.** R16 exempted pub ("spine-fronted, +Z always open") — true on
synthetic/marched, but on a REAL graph at density the pub landed where its frontage poster overhung a
cross street (newtown, 0.91 m). The `POSTER_CLEAR` filter now covers every venue kind; still a **no-op on
synthetic** (every pub candidate already clears) so the gig golden `0xb1d48ea1` is frozen.
- **Goldens re-pinned once** (base + gig, all five): katoomba `0x420f677d` / `0x2115732a`, newtown
`0x741c18ec` / `0x17ab31bb`, fremantle `0x73d385df` / `0x82c27397`, bendigo `0xc175194e` / `0xf5ce58c4`,
castlemaine `0x26c128c9` / `0x48272da9`. classic/gig/marched fixtures **frozen**.
- **Fixed:** my real-cache loader globbed `*.json` and tried to validate E's new `index.json` as a town
cache. It now skips it (it's B's selector index, not a cache).
## Round 19 (2026-07-16) — the alpha close: fragmentation resolved + findings hardened + goldens pinned

View File

@ -125,12 +125,13 @@ function pickVenues(plan, citySeed) {
if (far.length) cand = far;
}
}
// ROUND16 ledger #6: keep the off-spine kinds off a back-street's bitumen. Prefer candidates whose
// +Z facade clears every carriageway by ≥ POSTER_CLEAR; if the warehouse fringe is uniformly narrow,
// broaden to ANY off-spine clear lot before falling back. (pub is spine-fronted — its +Z is open —
// so it keeps its placement untouched; the hero shot doesn't move.) Always a clear lot exists in the
// synthetic town; the fallback only ever fires on a degenerate/osm plan.
if (kind === 'band_room' || kind === 'rsl') {
// ROUND16 ledger #6 (widened ROUND20): keep a venue's frontage poster off a crossing street's bitumen.
// Prefer candidates whose +Z facade clears every carriageway by ≥ POSTER_CLEAR; if the primary set has
// none, broaden to any off-spine clear lot before falling back. R16 exempted `pub` because on the
// synthetic/marched towns its +Z is always open — but on a REAL road graph at density the pub can land
// where its facade overhangs a cross street (newtown, R20). The filter now covers every venue kind: on
// the synthetic town every pub/rsl candidate already clears, so it stays a no-op there (golden frozen).
{
let clear = cand.filter(s => facadeClear(s) >= POSTER_CLEAR);
if (!clear.length) clear = pool.filter(s => !onMain(s) && facadeClear(s) >= POSTER_CLEAR);
if (clear.length) cand = clear;

View File

@ -238,9 +238,18 @@ export function generatePlanOSM(citySeed, town = DEFAULT_TOWN, opts = {}) {
// stringent structuralSuite still holds). Fidelity is the alpha knob (charter risk #4): real edge,
// real order, real side, regularised spacing — not pixel positions (real shops overlap).
function buildRealRoads() {
const EPS = 6, SNAP = 3, KERB = 4, NODE_CLEAR = 6; // fidelity + geometry knobs (metres)
// ROUND20 capacity (the density widening load-tested the R18 overflow fence): at 36× shops the
// dominant drop cause was OVERFLOW — more shops assign to an edge-side than fit. Widened by tightening
// the run: NODE_CLEAR 6→3 and a roads-only RGAP 2→0.5 (the shared marched `GAP` is untouched, so the
// marched fixtures stay frozen). 0.5 m also reads truer — a real AU main street is continuous terrace
// shopfronts, not detached boxes. Drops 7.9% → 5.5% across the five; the residual stays COUNTED, and
// dropping a crowded shop beats teleporting it onto a street it isn't on.
const EPS = 6, SNAP = 3, KERB = 4, NODE_CLEAR = 3, RGAP = 0.5; // fidelity + geometry knobs (metres)
const JOIN_TOL = 12, BRIDGE_MAX = 200, SEAT_MAX = 200; // ROUND19 fragmentation ruling (metres)
const WKIND = { main: SPINE_W, side: AV_W, lane: 8, arcade: 6 };
// every dropped shop is counted (Fable's ROUND19 ruling) — and counted BY CAUSE, so the density
// accounting stays honest as real towns widen (ROUND20).
const drop = (why, n = 1) => { norm.dropped = (norm.dropped || 0) + n; norm['dropped_' + why] = (norm['dropped_' + why] || 0) + n; };
const skey = p => `${Math.round(p.x / SNAP)},${Math.round(p.z / SNAP)}`;
const rawWays = [];
for (const rd of roads) {
@ -316,7 +325,7 @@ export function generatePlanOSM(citySeed, town = DEFAULT_TOWN, opts = {}) {
const perEdge = new Map();
for (const s of fx.shops) {
const a = nearest(projX(s.lon), projZ(s.lat));
if (!a || a.d > SEAT_MAX) { norm.dropped = (norm.dropped || 0) + 1; continue; } // stranded past bridging → drop + COUNT (ruling)
if (!a || a.d > SEAT_MAX) { drop('stranded'); continue; } // stranded past bridging → drop + COUNT (ruling)
const key = a.e.id * 2 + (a.side > 0 ? 1 : 0);
(perEdge.get(key) || perEdge.set(key, { e: a.e, side: a.side, list: [] }).get(key)).list.push({ s, t: a.t });
}
@ -337,16 +346,16 @@ export function generatePlanOSM(citySeed, town = DEFAULT_TOWN, opts = {}) {
let along = NODE_CLEAR;
for (let k = 0; k < list.length; k++) {
const s = list[k].s, f = footOf(SHOP_TYPES[s.type] ? s.type : 'opshop');
if (along + f > len - NODE_CLEAR) { norm.dropped = (norm.dropped || 0) + (list.length - k); break; } // edge full — overflow drops COUNTED (ruling)
if (along + f > len - NODE_CLEAR) { drop('overflow', list.length - k); break; } // edge-side full — overflow COUNTED
cands.push({ block, x: e.a.x + ux * (along + f / 2) + nx * (off + dep / 2), z: e.a.z + uz * (along + f / 2) + nz * (off + dep / 2), w: f, d: dep, ry, frontEdge: e.id, s });
along += f + GAP;
along += f + RGAP;
}
}
cands.sort((p, q) => p.s.id - q.s.id); // real streets crowd at intersections → deterministic overlap-resolve
const placed = [];
for (const c of cands) {
const corners = lotCorners(c);
if (placed.some(p => obbOverlap(p.corners, corners))) { norm.dropped = (norm.dropped || 0) + 1; continue; }
if (placed.some(p => obbOverlap(p.corners, corners))) { drop('overlap'); continue; }
placed.push({ corners, c });
}
for (const { c } of placed) addShop(addLot(c.block, c.x, c.z, c.w, c.d, c.ry, c.frontEdge, 'shop'), c.s);

View File

@ -449,22 +449,24 @@ section('v4 real-roads graph lift (schema v2)');
// pinned. Guarded so the gate is green before E's first cache — A pins goldens as caches arrive.
section('real town caches (web/assets/towns/*.json)');
const TOWNS_DIR = join(HERE, '..', '..', 'assets', 'towns');
// ROUND19: all five real towns are now on REAL ROADS (E's schema-v2 roads[] landed) and pinned on the
// ROUND20: re-pinned once for E's DENSITY WIDENING (5 caches at 3-6x shops) + A's poster cap +
// capacity widen. All five real towns are on REAL ROADS and pinned on the
// real-roads graph output — the A→E→A finalization, katoomba's golden included. Re-pinned this round after
// the fragmentation ruling (junction-protected DP + cull/bridge) + E's spine-poster fix. Amendment law:
// the alpha changed nothing outside the cache-schema path; synthetic/fixtures/classic/default stay frozen.
const REAL_TOWN_GOLDENS = {
katoomba_real: 0xb7ffbca2, newtown_real: 0x6f984c81, fremantle_real: 0xfb197000,
bendigo_real: 0x21179b3b, castlemaine_real: 0x7c47f639,
katoomba_real: 0x420f677d, newtown_real: 0x741c18ec, fremantle_real: 0x73d385df,
bendigo_real: 0xc175194e, castlemaine_real: 0x26c128c9,
};
// 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,
katoomba_real: 0x2115732a, newtown_real: 0x17ab31bb, fremantle_real: 0x82c27397,
bendigo_real: 0xf5ce58c4, castlemaine_real: 0x48272da9,
};
const townFiles = (existsSync(TOWNS_DIR) ? readdirSync(TOWNS_DIR) : []).filter(f => f.endsWith('.json'));
// `index.json` is E's towns INDEX (key/town/state/shops/roads for B's selector), not a town cache — skip it.
const townFiles = (existsSync(TOWNS_DIR) ? readdirSync(TOWNS_DIR) : []).filter(f => f.endsWith('.json') && f !== 'index.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) {
const key = f.replace(/\.json$/, '');