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>
36 lines
2.4 KiB
JavaScript
36 lines
2.4 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, validateTownCache, MIN_TOWN_SHOPS,
|
||
medianShopSpacing, MAX_MEDIAN_SPACING_M } from './plan_osm.js';
|
||
export { withGigs, gigKeyFor, POSTER_CLEAR } from './gigs.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';
|