The contract, published for B and C in LANE_A_NOTES §39 (first commit, per the half-day rule).
createAddresses(plan, cache|null) → streetOf(edgeId) · localityOf(shopId) · cohort(pred)
· streets() · stats()
New pure module web/js/citygen/address.js: ZERO imports, no THREE, no fetch, no DOM, no module
state, no plan mutation. Two suppliers, ONE consumer contract — a real town fills `label` from the
cache's named ways, the synthetic fills it from district.kind + block. Consumers must never branch
on town type; that constraint is the design.
MEASURED (Fable's binding facts re-derived independently, all three land exactly):
· 19,132 road ways, 17,835 named (93.2%)
· median plan-edge-sample → nearest named way 0.03–0.90 m (worst katoomba)
· 1,192 / 1,219 corpus shops resolve to a street name (97.8%); 29,865/30,986 edges named (96.4%)
· all 27 unresolved shops front a way OSM genuinely leaves unnamed (adelaide 21 arcade) — null is
the honest answer and the module returns it rather than borrowing a neighbour's name
TWO DEPARTURES FROM THE SYNTHESIS, both with numbers:
· the shift is recovered EXACTLY (node↔waypoint lattice intersection), not voted for. The proposed
centroid-align + nearest-waypoint vote returns the WRONG shift on 5 of 23 towns (braddon,
fremantle, hobart, northbridge, westend) — island culling drags the plan centroid off the
cache's. A wrong shift renames every street in the town. Exact on 23/23, nothing to tune.
· the resolver samples FIVE points along an edge, not the midpoint — a midpoint cannot tell a
street from the street that crosses it.
THE ONE-LINE UPGRADE: TAKEN. plan_osm.js writes norm.shift = {shx,shz} into the normalization log,
reaching the caller only via the existing opts.report sink — never onto the plan. It is a
cross-check, not a dependency: selfcheck compares plan_osm's published shift against address.js's
independently recovered one and fires on a 0.01 m disagreement.
GATES (+240 checks), each proven to fire on a broken world:
· 0 wrong street names on 884 shop-bearing edges, against an INDEPENDENT way-membership resolver
(857 agree, 0 disagree). Corpus-wide 4/30,986 disagree — all 2D-stacked ways, none carrying a
shop — PINNED at 4, not asserted > 0.
· CONTROL: with roads[].name stripped (exactly the pre-change state) the layer resolves 0 streets.
· CONTROL: no cache ⇒ supplier 'district', visible in stats(), never a silent wrong name.
· createAddresses does not mutate the plan — asserted byte-for-byte, per town.
· tolerance 8 m, and the finding that sets it: correctness SATURATES AT 6 m (857 agreeing frontage
names at 6, 8, 12, 16 and 24 m). Every metre past 6 buys only a name borrowed from an unnamed
way; it can never buy a correction.
Additive: getTownCache(key) — index.html registers the cache and drops its reference, so roads[]
was unreachable downstream. Read-only, no new state, no shell change.
GOLDENS: NOTHING MOVED, ZERO RE-PINS. selfcheck 157,407 → 157,647 ALL GREEN, fingerprint 0x5f76e76.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
41 lines
2.9 KiB
JavaScript
41 lines
2.9 KiB
JavaScript
// PROCITY citygen — canonical Lane A public entry point (barrel).
|
||
//
|
||
// Consumers import the whole CityGen API from here (not from plan.js directly), e.g. Lane B's shell:
|
||
// const citygen = await import('./js/citygen/index.js'); citygen.generatePlan(seed) → CityPlan
|
||
// Keep this the stable import surface for the lane; internals may move between files behind it.
|
||
//
|
||
// (Originated as a Lane F integration shim because the shell imports ./js/citygen/index.js while the
|
||
// generator ships as plan.js; adopted here as the real Lane A entry point, per the shim's own note.)
|
||
|
||
export { generatePlan, chunkIndex, chunkKey, CHUNK, lotCorners, obbOverlap, isOpen } from './plan.js';
|
||
export { shopName, townName, bandName } from './names.js';
|
||
export { generatePlanOSM, osmTownKeys, registerTownCache, getTownCache, validateTownCache, MIN_TOWN_SHOPS,
|
||
medianShopSpacing, MAX_MEDIAN_SPACING_M } from './plan_osm.js';
|
||
export { withGigs, gigKeyFor, POSTER_CLEAR } from './gigs.js';
|
||
// v9 THE ADDRESS LAYER (ROUND39 item 39.1). Pure, no THREE, no fetch, no plan mutation — it derives
|
||
// from a plan and never writes to one, so it cannot move a golden. ONE consumer contract across both
|
||
// town types: print `localityOf(shopId).label`, never branch on `plan.source`. Full contract in the
|
||
// header of address.js and in LANE_A_NOTES §39.
|
||
export { createAddresses, STREET_TOLERANCE_M } from './address.js';
|
||
// The street-corridor law + venue vocabulary, re-exported so consumers get the whole Lane A contract
|
||
// from one import (ROUND13). `roadWidth`/`vergeBand`/`poleOffset` are the road-vs-verge split of
|
||
// `edge.width` — see the note in registry.js; `gigKeyFor` is the one and only genre→audio-key mapping.
|
||
export { SHOP_TYPES, VENUE_KINDS, genreForVenueKind, roadWidth, vergeBand, poleOffset } from '../core/registry.js';
|
||
|
||
// planSource selector (round 6; multi-town round 8). Default 'synthetic' → the byte-identical
|
||
// golden-hash generator; 'osm' → the real-data fixture importer. Lane F wires ?plansrc=osm (+ optional
|
||
// &town=<key>) to these args so the shell bootstrap picks the producer/town without B–F caring which
|
||
// ran. Unknown source → synthetic; unknown town → the default town.
|
||
import { generatePlan as _synth } from './plan.js';
|
||
import { generatePlanOSM as _osm } from './plan_osm.js';
|
||
import { withGigs as _withGigs } from './gigs.js';
|
||
export function generatePlanFor(seed, source = 'synthetic', opts = {}) {
|
||
const plan = source === 'osm' ? _osm(seed, opts.town, opts) : _synth(seed);
|
||
// v3 gig layer, gated: ?gigs=1 → shell passes { gigs:true, customBands } and we augment; otherwise
|
||
// the base plan is returned untouched (byte-identical, goldens frozen — CITY_SPEC prime flag law).
|
||
return opts.gigs ? _withGigs(plan, seed, opts) : plan;
|
||
}
|
||
|
||
// Convenience default so `citygen.default` also resolves to the (synthetic) generator.
|
||
export { generatePlan as default } from './plan.js';
|