# 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, `.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`) ```jsonc { "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. - `shops` an 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`). ## 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 so `osmTownKeys()` and `generatePlanFor(seed, 'osm', { town: key })` see it. - `web/js/citygen/selfcheck.js` auto-loads every `*.json` here: validates it, runs the full structural + gig suites across the hero seeds, and pins a per-town golden (unpinned → prints the value to paste).