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>
This commit is contained in:
m3ultra 2026-07-16 17:37:01 +10:00
parent f17babd8d7
commit 6fc4b40632
6 changed files with 164 additions and 26 deletions

View File

@ -1,11 +1,37 @@
# LANE A — CITYGEN · progress (PROCITY-A)
*Status: **all deliverables landed; self-check all-green (16810/16810); rounds 218 closed** (see below).
v4.0-alpha REAL ROADS: cache schema v2 (roads[]) published first + the real-graph lift (OSM ways →
CityPlan street graph) implemented, proven on a synthetic irregular graph. All osm/classic/gig goldens
frozen; katoomba_real's real-roads golden pins when E's roads land (A→E→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.*
*Date: 2026-07-16 · owner: PROCITY-A (Opus 4.8)*
## Round 19 (2026-07-16) — v4.0-alpha CLOSE: fragmentation ruling + findings hardened + goldens pinned
A was the round's spine (the A→E→A finalization deferred from R18). Every filed finding resolved; the tag is
F's to land.
1. **Fragmentation (D's finding — Fable ruled, A owns details).** The naïve real graph fragmented
(katoomba **105 components**, 58% street-metres on islands). Fixed in `plan_osm.buildRealRoads`:
(a) **junction-protected DouglasPeucker** — a point shared by ≥2 ways is a junction; simplify only
between junctions, never through one (the collinear tee-off drop). Alone → 94100% main-net.
(b) **cull/bridge** — main component + near-fragments joined (`JOIN_TOL` 12 m; shop-island bridge ≤200 m);
far **shopless** islands culled. **Result: 1 connected component on all five towns** (katoomba 100%
main-net, 2 islands culled). Every dropped shop **counted** (`norm.dropped`) — katoomba 19/20 (1 crowd
drop, the R18 overflow prediction measured), the other four kept all.
2. **E's poster-clearance finding fixed** (`gigs.js`): a spine poster overhanging a real crossing carriageway
is skipped — measured exactly as the selfcheck kerb floor, a **no-op on synthetic** (gig golden frozen).
The 23 real-town clearance failures → 0.
3. **Goldens pinned** (the finalization): all 5 real towns on their real-roads output
(katoomba `0xb7ffbca2`, newtown `0x6f984c81`, fremantle `0xfb197000`, bendigo `0x21179b3b`,
castlemaine `0x7c47f639`). synthetic/marched-fixtures/classic/default **frozen** — the alpha changed
nothing outside the cache-schema path. selfcheck ALL GREEN **51670/51670**; scaffold + consistency GREEN.
4. **New finding filed (beta, non-blocking):** spine-poster density scales with edge count (katoomba 438
posters vs ~14 synthetic; draws stay low via instancing) — a beta posters-per-town cap.
Handshake fired: `katoomba_real` boots real roads, golden pinned, findings resolved → B/C/D/F. D re-measures
(1 component / 100% / 0 stranded); F applies B's `BIG_CITY` one-liner + gates + tags `v4.0-alpha`.
## Round 18 (2026-07-16) — v4.0-alpha REAL ROADS: cache schema v2 + the real-graph lift (the epoch headline)
Lane A is the round's long pole (serial spine A→E→A). Two commits:

View File

@ -387,8 +387,20 @@ OSM `highway=*` class (`validateTownCache` accepts schema `…/1` and `…/2`; a
4. **seat** each shop on its **nearest real edge** at its real order + real side, marched with regularised
spacing (real shops overlap — pixel-exact positions can't satisfy the no-overlap invariant), a
frontage-strip block per edge, then a deterministic **overlap-resolve** pass (crowded intersections drop
the later-id lot). **Same CityPlan schema out** — B/C/D/F consume it unchanged by contract; the golden
re-pins per the amendment law (`katoomba_real`'s when E's roads land; synthetic + fixtures untouched).
the later-id lot). **Same CityPlan schema out** — B/C/D/F consume it unchanged by contract.
**The fragmentation ruling (ROUND19, Fable — A owns the details).** Real geometry fragments the naïve graph
(katoomba: **105 components**, 58% of street-metres on islands, D's finding). Two mechanisms, both handled:
(a) **junction-protected simplification** — a point shared by ≥2 ways is a junction; DouglasPeucker runs
only *between* junctions, never through one (the collinear junction-drop that tee-off side streets hit). That
alone reconnects to 94100% main-net. (b) **cull/bridge** — the town is the **main component + joined
near-fragments** (endpoints ≤ `JOIN_TOL` 12 m, or a shop-bearing island ≤ 200 m, get a bridge edge); a **far
shopless island is culled** (bbox-clipped noise + dead streaming budget). A **stranded shop is always
connected or dropped with a count** (`norm.dropped`). Result on all five towns: **one connected component**.
A spine poster that would overhang a real crossing carriageway is **skipped** (E's finding; no-op on the
synthetic town, so its gig golden is frozen). Golden re-pins per the amendment law (all five real towns pin
on their real-roads output; synthetic + marched fixtures + classic/default untouched — the alpha changed
nothing outside the cache-schema path).
## Shop types v1 (registry lives in `web/js/core/registry.js`, Lane A writes it, all lanes read)

View File

@ -2,6 +2,39 @@
Durable answers to questions other lanes raised against Lane A. Full status in `A-progress.md`.
## 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)
The A→E→A finalization landed. `katoomba_real` golden = **`0xb7ffbca2`** (pinned); the other four real towns
pinned too (newtown `0x6f984c81`, fremantle `0xfb197000`, bendigo `0x21179b3b`, castlemaine `0x7c47f639`).
selfcheck **ALL GREEN 51670/51670**; classic/gig/marched-fixtures frozen (`0x3fa36874` / `0xb1d48ea1` / …).
### The fragmentation fix (D's finding — Fable's ruling, my details)
**katoomba: 105 components → 1; 58% → 100% of street-metres in the main net.** Two mechanisms:
1. **Junction-protected DouglasPeucker** (the root cause). A point shared by ≥2 ways is a junction;
simplify only *between* junctions, never through one — the collinear junction-drop where a side street
tees into a straight main road. This alone took every town to 94100% main-net.
2. **Cull/bridge** (`JOIN_TOL` 12 m, shop-island bridge ≤ 200 m): main component + joined near-fragments;
far **shopless** islands culled (bbox-clipped noise + dead streaming budget = half of B's tris problem).
Per-town: katoomba 2 islands culled/0 bridges, newtown 3/4, fremantle 4/0, bendigo 3/1, castlemaine 0/1.
**Every dropped shop is counted** in `norm.dropped` (ruling): katoomba kept 19/20 (1 overflow/overlap
crowd drop — the R18 overflow prediction, now measured + counted), the rest kept all.
### → D: your re-measure inputs (post-fix)
Component count → **1** (all towns). Street-metres in main net → **100%**. Stranded shops → **0** (all
connected or counted-dropped). Near-crowd on fragments should collapse to ~0 (one component). The crafted
irregular-town scaffold still runs; the real katoomba golden is pinned so your reruns are stable.
### → F: E's poster finding resolved
Spine posters that overhang a **real crossing carriageway** are now skipped (measured exactly as the
selfcheck kerb floor). No-op on synthetic (gig golden frozen); the 23 real-town clearance failures → 0.
### New finding (v4.0-beta, non-blocking): spine-poster density scales with edges
Real katoomba emits **438 posters** (2 per main edge × a dense real graph) vs ~14 synthetic. Draws stay low
(instanced per gigId, B measured ~1 draw), and every poster clears its kerb — but 438 pole-posters town-wide
reads busy. **Beta candidate:** cap posters-per-town (seeded subsample of the spine run). Not fixed this
round — invariants hold, it's density polish.
## Round 18 (2026-07-16) — v4.0-alpha REAL ROADS: cache schema v2 (roads[]) + the real-graph lift
### → E: **schema v2 is live (committed first) — fetch roads to it**

View File

@ -256,6 +256,23 @@ function buildPosters(plan, citySeed, venues, gigs) {
const bill = [...tonight.values()];
if (!runs.length || !bill.length) return posters;
// ROUND19 (E's finding): on a REAL street graph a spine edge is crossed by side streets, so a pole
// placed on the spine verge can overhang a crossing carriageway. Skip any candidate that lands on a
// carriageway — measured EXACTLY as the selfcheck's floor (nearest edge, `roadWidth/2`). This is a
// no-op on the synthetic town (its spine posters already clear every carriageway, so nothing skips and
// the gig golden is frozen); it only drops the overhanging ones on real graphs. `pr()` is consumed
// before the check, so the rng stream — hence the synthetic output — is unchanged.
const onCarriageway = (x, z) => {
let best = Infinity, bestE = null;
for (const e2 of plan.streets.edges) {
const a2 = nodeById.get(e2.a), b2 = nodeById.get(e2.b); if (!a2 || !b2) continue;
const ex = b2.x - a2.x, ez = b2.z - a2.z, L2 = ex * ex + ez * ez || 1;
let tt = ((x - a2.x) * ex + (z - a2.z) * ez) / L2; tt = Math.max(0, Math.min(1, tt));
const d = Math.hypot(x - (a2.x + ex * tt), z - (a2.z + ez * tt));
if (d < best) { best = d; bestE = e2; }
}
return bestE && best < roadWidth(bestE) / 2 - 1e-6;
};
const pr = rng(citySeed, 'posters', 0);
const PER_EDGE = 2;
let k = 0;
@ -273,6 +290,7 @@ function buildPosters(plan, citySeed, venues, gigs) {
// faces back across its own footpath, so ry = atan2(nx, nz).
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);
}
}

View File

@ -239,23 +239,69 @@ export function generatePlanOSM(citySeed, town = DEFAULT_TOWN, opts = {}) {
// 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)
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 };
const ways = [];
const skey = p => `${Math.round(p.x / SNAP)},${Math.round(p.z / SNAP)}`;
const rawWays = [];
for (const rd of roads) {
if (!rd || !Array.isArray(rd.pts) || rd.pts.length < 2) continue;
let pl = rd.pts.filter(p => Array.isArray(p) && isNum(p[0]) && isNum(p[1])).map(([lat, lon]) => ({ x: projX(lon), z: projZ(lat) }));
pl = simplifyDP(pl, EPS);
if (pl.length >= 2) ways.push({ kind: roadEdgeKind(rd.kind), pl });
const pl = rd.pts.filter(p => Array.isArray(p) && isNum(p[0]) && isNum(p[1])).map(([lat, lon]) => ({ x: projX(lon), z: projZ(lat) }));
if (pl.length >= 2) rawWays.push({ kind: roadEdgeKind(rd.kind), pl });
}
const nodeAt = new Map();
const snap = p => { const k = `${Math.round(p.x / SNAP)},${Math.round(p.z / SNAP)}`; let n = nodeAt.get(k); if (!n) { n = addNode(p.x, p.z); nodeAt.set(k, n); } return n; };
const seen = new Set(), realEdges = [];
// ROUND19: junctions = points shared by ≥2 ways. PROTECT them from DouglasPeucker — simplify only
// BETWEEN junctions, never through one. The collinear junction-drop (a straight way losing the point
// where a side street tees in) is what fragmented the graph into 105 components (D + B, R18).
const keyWays = new Map();
rawWays.forEach((w, wi) => { for (const p of w.pl) { const k = skey(p); (keyWays.get(k) || keyWays.set(k, new Set()).get(k)).add(wi); } });
const isJct = k => (keyWays.get(k)?.size || 0) >= 2;
const ways = [];
for (const w of rawWays) {
const brk = [0]; for (let i = 1; i < w.pl.length - 1; i++) if (isJct(skey(w.pl[i]))) brk.push(i); brk.push(w.pl.length - 1);
let out = [];
for (let b = 0; b + 1 < brk.length; b++) { const s = simplifyDP(w.pl.slice(brk[b], brk[b + 1] + 1), EPS); out = out.concat(b === 0 ? s : s.slice(1)); }
if (out.length >= 2) ways.push({ kind: w.kind, pl: out });
}
// ── build a CANDIDATE graph (local ids), resolve fragmentation, THEN commit the survivors ──────────
// ROUND19 ruling (Fable): the town is the main component + joined near-fragments; a far SHOPLESS island
// is bbox-clipped noise + dead streaming budget, so it's CULLED. A shop-bearing island is bridged so no
// shop strands; a shop we still can't reach drops with a count. (Committing after resolution is what
// lets us cull — addEdge can't un-add.)
const cnodes = [], cnodeAt = new Map();
const cnode = p => { const k = skey(p); let id = cnodeAt.get(k); if (id === undefined) { id = cnodes.length; cnodes.push({ x: p.x, z: p.z }); cnodeAt.set(k, id); } return id; };
const cedges = [], eseen = new Set();
for (const w of ways) for (let i = 0; i + 1 < w.pl.length; i++) {
const na = snap(w.pl[i]), nb = snap(w.pl[i + 1]);
if (na.id === nb.id || Math.hypot(nb.x - na.x, nb.z - na.z) < 3) continue;
const ek = `${Math.min(na.id, nb.id)}-${Math.max(na.id, nb.id)}`; if (seen.has(ek)) continue; seen.add(ek);
realEdges.push({ id: addEdge(na.id, nb.id, WKIND[w.kind] || AV_W, w.kind), a: na, b: nb, kind: w.kind });
const a = cnode(w.pl[i]), b = cnode(w.pl[i + 1]);
if (a === b) continue;
const ek = `${Math.min(a, b)}-${Math.max(a, b)}`; if (eseen.has(ek)) continue; eseen.add(ek);
if (Math.hypot(cnodes[a].x - cnodes[b].x, cnodes[a].z - cnodes[b].z) < 3) continue;
cedges.push({ a, b, kind: w.kind });
}
if (!cedges.length) { buildMarched(); norm.mode = 'roads→marched (no usable ways)'; return; }
const par = cnodes.map((_, i) => i); const find = x => { while (par[x] !== x) x = par[x] = par[par[x]]; return x; };
const clen = (a, b) => Math.hypot(cnodes[a].x - cnodes[b].x, cnodes[a].z - cnodes[b].z);
for (const e of cedges) par[find(e.a)] = find(e.b);
const compLen = new Map(); for (const e of cedges) { const r = find(e.a); compLen.set(r, (compLen.get(r) || 0) + clen(e.a, e.b)); }
let mainRoot = -1, mainLen = -1; for (const [r, L] of compLen) if (L > mainLen) { mainLen = L; mainRoot = r; }
const nearestC = (px, pz) => { let be = -1, bd = Infinity; for (let i = 0; i < cedges.length; i++) { const e = cedges[i], A = cnodes[e.a], B = cnodes[e.b], dx = B.x - A.x, dz = B.z - A.z, L2 = dx * dx + dz * dz || 1; let t = ((px - A.x) * dx + (pz - A.z) * dz) / L2; t = Math.max(0, Math.min(1, t)); const d = Math.hypot(px - (A.x + dx * t), pz - (A.z + dz * t)); if (d < bd) { bd = d; be = i; } } return be; };
const shopRoot = new Set();
for (const s of fx.shops) { const ei = nearestC(projX(s.lon), projZ(s.lat)); if (ei >= 0) shopRoot.add(find(cedges[ei].a)); }
const rootNodes = new Map(); cnodes.forEach((_, i) => { const r = find(i); (rootNodes.get(r) || rootNodes.set(r, []).get(r)).push(i); });
const inMain = i => find(i) === find(mainRoot);
const connectors = []; let culled = 0;
for (const r of [...compLen.keys()].filter(r => r !== mainRoot).sort((a, b) => (compLen.get(b) || 0) - (compLen.get(a) || 0))) {
if (inMain(r)) continue; // already merged by an earlier bridge
const grp = rootNodes.get(r) || [], shop = shopRoot.has(find(grp[0]));
let ga = -1, gb = -1, bd = Infinity; // nearest (fragment node, main-net node) pair
for (const i of grp) for (let j = 0; j < cnodes.length; j++) { if (!inMain(j)) continue; const d = clen(i, j); if (d < bd) { bd = d; ga = i; gb = j; } }
if (bd <= JOIN_TOL || (shop && bd <= BRIDGE_MAX)) { connectors.push([ga, gb]); par[find(ga)] = find(gb); }
else culled++; // shopless (or unreachable-shop) far island → cull
}
const nodeMap = new Map();
const putNode = ci => { let n = nodeMap.get(ci); if (!n) { n = addNode(cnodes[ci].x, cnodes[ci].z); nodeMap.set(ci, n); } return n; };
const realEdges = [];
for (const e of cedges) { if (!inMain(e.a)) continue; const na = putNode(e.a), nb = putNode(e.b); realEdges.push({ id: addEdge(na.id, nb.id, WKIND[e.kind] || AV_W, e.kind), a: na, b: nb, kind: e.kind }); }
for (const [a, b] of connectors) { const na = putNode(a), nb = putNode(b); realEdges.push({ id: addEdge(na.id, nb.id, WKIND.lane, 'lane'), a: na, b: nb, kind: 'lane' }); }
norm.components = compLen.size; norm.culledIslands = culled; norm.bridges = connectors.length;
if (!realEdges.length) { buildMarched(); norm.mode = 'roads→marched (no usable ways)'; return; }
const nearest = (px, pz) => {
let best = null, bd = Infinity;
@ -263,13 +309,14 @@ export function generatePlanOSM(citySeed, town = DEFAULT_TOWN, opts = {}) {
const dx = e.b.x - e.a.x, dz = e.b.z - e.a.z, L2 = dx * dx + dz * dz || 1;
let t = ((px - e.a.x) * dx + (pz - e.a.z) * dz) / L2; t = Math.max(0, Math.min(1, t));
const d = Math.hypot(px - (e.a.x + dx * t), pz - (e.a.z + dz * t));
if (d < bd) { bd = d; best = { e, t, side: (dx * (pz - e.a.z) - dz * (px - e.a.x)) >= 0 ? 1 : -1 }; }
if (d < bd) { bd = d; best = { e, t, d, side: (dx * (pz - e.a.z) - dz * (px - e.a.x)) >= 0 ? 1 : -1 }; }
}
return best;
};
const perEdge = new Map();
for (const s of fx.shops) {
const a = nearest(projX(s.lon), projZ(s.lat)); if (!a) continue;
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)
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 });
}
@ -288,9 +335,9 @@ export function generatePlanOSM(citySeed, town = DEFAULT_TOWN, opts = {}) {
[e.a.x + nx * off, e.a.z + nz * off], [e.b.x + nx * off, e.b.z + nz * off],
[e.b.x + nx * (off + dep), e.b.z + nz * (off + dep)], [e.a.x + nx * (off + dep), e.a.z + nz * (off + dep)]]);
let along = NODE_CLEAR;
for (const { s } of list) {
const f = footOf(SHOP_TYPES[s.type] ? s.type : 'opshop');
if (along + f > len - NODE_CLEAR) break; // edge full — overflow shops drop out (real order kept)
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)
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;
}

View File

@ -449,11 +449,13 @@ 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');
// The trailing towns are pinned on their stable v1 (marched) output — regression guards. katoomba_real
// stays UNPINNED this round: it is the v4.0-alpha town, and its golden re-pins to the real-ROADS output
// the moment E lands its roads[] (amendment law — the A→E→A finalization). The other four get roads in beta.
// ROUND19: all five real towns are now on REAL ROADS (E's schema-v2 roads[] landed) 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 = {
bendigo_real: 0x5e3e76a6, castlemaine_real: 0xf1a628a1, fremantle_real: 0xee9fd09a, newtown_real: 0x95978b45,
katoomba_real: 0xb7ffbca2, newtown_real: 0x6f984c81, fremantle_real: 0xfb197000,
bendigo_real: 0x21179b3b, castlemaine_real: 0x7c47f639,
};
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)");