1. validate_atlas (the G2a format gate). pipeline/validate_atlas.py, wired into validate_manifest so it
runs in qa gate 3. Judges Lane G's tier-1 atlases from the COMMITTED FILES ALONE -- no dealgod DB, no
network. No atlases yet => clean pass, so it's in place before G's lands. Enforces: provenance
{license, attribution, generator, snapshot} (charter law #3 + determinism); NO private-individual item
fields (law #3); 1 atlas/shop hard cap 2 (C §7.1); <=1024², 2048² only for a 60+ item shop (C §7.2);
declared atlas_px == the atlas's REAL pixels + every listed atlas exists; schema/UV/bands/dup-ids
(C §7.3). The licence string is PRINTED, not pattern-matched -- clearing photos for public release is
a human call, so the gate surfaces it. Dependency-free PNG/WebP header parser. Tested against a
10-case matrix (real 2048² WebP + 1024² PNG): good passes clean, all 9 violations caught.
2. toowoomba RETIRED (my call, counted). Tried the re-bbox FIRST: 3.0km CBD-wide -> 1.6km on the Ruthven
St heart. It fixed the named metric (median NN 395.7 -> 89.4m raw, 'passes') but made the real thing
WORSE: hub density 3/12 -> 2/12 within 160m, the pack's worst (D's alive-darwin is 7/12). Its shops
are PAIRS strung along a road, not a high street; densest 300m cluster is 3 shops (< MIN_TOWN_SHOPS),
so no tighter box exists. Shipping it would have gamed the number while leaving a town nobody shops in
(D: 0 finds / 1417 checks). Config entry kept commented with the rationale. Pack: 21 real + 1 godverse
= 22 rostered, 1180 shops, all 8 states/territories (QLD keeps westend). selfcheck ALL GREEN 152015.
-> Lane A, before you freeze the spacing warn (#5): (a) your validator sees the CACHE (raw lat/lon) but
D's numbers are SEATED (post-lift) -- darwin is 27m seated vs 68.3m RAW, so a threshold picked off D's
table would FALSE-WARN darwin; calibrate on the raw spread (daylesford 11.6 ... braddon 119.2,
ballarat 254.6 -- a ~200m line has daylight). (b) median NN alone is GAMEABLE: my re-bbox improved it
4.4x while the town got worse -- the hub fraction is the discriminator, and my raw hub metric
reproduces D's darwin exactly (7/12), so it's directly encodable (alive ~38-75%, dead <=20%).
(c) ballarat is a SECOND spread town D's two-town comparison missed (NN 254.6m, hub 4/20 = 20%) -- it
will trip your warn, correctly. Not retired: the ledger named toowoomba only.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|---|---|---|
| .. | ||
| _raw | ||
| adelaide_real.json | ||
| ballarat_real.json | ||
| bendigo_real.json | ||
| bowral_real.json | ||
| braddon_real.json | ||
| brunswick_real.json | ||
| castlemaine_real.json | ||
| darwin_real.json | ||
| daylesford_real.json | ||
| fitzroy_real.json | ||
| fremantle_real.json | ||
| geelong_real.json | ||
| glebe_real.json | ||
| hobart_real.json | ||
| index.json | ||
| katoomba_real.json | ||
| launceston_real.json | ||
| marrickville_real.json | ||
| newcastle_real.json | ||
| newtown_godverse.json | ||
| newtown_real.json | ||
| northbridge_real.json | ||
| README.md | ||
| SOURCES.md | ||
| westend_real.json | ||
Real town caches — the home (web/assets/towns/)
Lane A owns the contract; Lane E fills this directory (ROUND17 ledger #6, the real-map scout).
Each real Australian town is one file, <key>.json, in the town-cache contract shape — the processed
output of pipeline/build_towns.py (Overpass → dedupe/suburb/parody-name/seeded-hours). plan_osm.js
marches it exactly like the checked-in fixtures in web/js/citygen/osm_fixture.js; the scout proves the
existing CityPlan contract eats real data — no new plan fields, no new geometry.
The contract (authoritative: validateTownCache() in web/js/citygen/plan_osm.js)
{
"schema": "procity-town-cache/1",
"key": "katoomba_real", // matches the filename; distinct from the hand-made fixture key
"town": "Katoomba", // display name
"source": "osm",
"license": "ODbL 1.0", // REQUIRED — OSM is ODbL
"attribution": "© OpenStreetMap contributors",
"generator": "pipeline/build_towns.py", // optional provenance
"fetchedAt": "2026-07-16", // optional
"center": { "lat": -33.71, "lon": 150.31 },// REQUIRED — the equirectangular projection origin
"bbox": { "minLat": …, "minLon": …, "maxLat": …, "maxLon": … }, // optional metadata
"counts": { "raw": 512, "shops": 96 }, // optional metadata
"shops": [
{ "id": 318855426, "name": "Canty's Bookshop", "type": "book", "lat": -35.32, "lon": 149.18, "suburb": "Fyshwick" }
// …
]
}
Hard requirements (else the cache is rejected and does not boot):
center.{lat,lon}finite numbers.shopsan array of ≥MIN_TOWN_SHOPS(6) entries (up to 4 venues + the one openLate landmark + a spare).- every shop has finite
lat/lon.
Absorbed as warnings (still boots): a blank name defaults to the type label; an unknown type remaps
to opshop; duplicate ids; and — importantly — a cache spanning > 5 km warns "likely more than one
town". A cache MUST be ONE compact town: an over-broad Overpass bbox marches into an unusable multi-km
strip (the R17 mega-strip risk — a whole region of 2,918 shops became an 11.6 km avenue). Bound the query.
type should be a registry SHOP_TYPE (record/opshop/toy/book/video/pawn/milkbar/dept/stall);
build_towns.py maps OSM shop=* tags to these (unknowns land on opshop).
Schema v2 — roads[] (ROUND18, v4.0-alpha REAL ROADS)
A v2 cache adds an optional roads[] — the town's real OSM street geometry. When present, plan_osm
builds the CityPlan street graph from the real roads and seats each shop on its nearest real edge (its
real street, real order, real side) instead of marching shops onto synthetic parallel avenues. roads
absent (or schema procity-town-cache/1) ⇒ the marched fallback — every shipped v1 cache stays valid.
{
"schema": "procity-town-cache/2",
// … all v1 fields (center, shops, license, …) …
"roads": [
{ "id": 12345678, // optional (OSM way id)
"kind": "primary", // REQUIRED — the OSM highway=* class (see the map below)
"name": "Katoomba Street", // optional
"pts": [ [-33.7175, 150.3110], [-33.7140, 150.3112], [-33.7100, 150.3115] ] } // REQUIRED — ≥2 [lat,lon]
// …
]
}
Road validity (a bad road is a hard error; a soft one is absorbed):
- each road is
{ kind: string, pts: [[lat,lon], …] }with ≥ 2 finite[lat,lon]points — else rejected. < 2points → the road is dropped (warning); an unmappedkindfalls toside(warning).
kind → CityPlan edge kind (ROAD_KIND in plan_osm.js; the lift then promotes a shop-dense side
road to the main spine): motorway/trunk/primary/secondary → main; tertiary/unclassified/
residential/living_street/road → side; service/track → lane; pedestrian/footway/path/
steps/cycleway → arcade. Fidelity (charter risk #4, A's knob): ship the geometry roughly as OSM
has it; plan_osm simplifies (Douglas–Peucker) — don't pre-decimate below ~1 point per 5 m.
Bound the roads to the town (the bbox law): fetch highway=* within the same per-town bbox as the shops
so the graph is the town's streets, not a region's.
Provenance (Lane E)
OSM data is ODbL. Ship attribution with the data: this README, a SOURCES.md (E's licence table),
and the license/attribution fields on every cache. Cache the raw Overpass responses too so re-runs
never re-fetch.
Wiring
plan_osm.generatePlanOSM(seed, key, { cache })marches a cache directly;registerTownCache(key, cache)adds it to the runtime registry soosmTownKeys()andgeneratePlanFor(seed, 'osm', { town: key })see it.web/js/citygen/selfcheck.jsauto-loads every*.jsonhere: validates it, runs the full structural + gig suites across the hero seeds, and pins a per-town golden (unpinned → prints the value to paste).