Compare commits
34 Commits
223fbcaf72
...
84f3641791
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
84f3641791 | ||
|
|
84e0a1b764 | ||
|
|
47f05ecaa8 | ||
|
|
292b07aa91 | ||
|
|
f14e4d1974 | ||
|
|
98fec6cadf | ||
|
|
8ac3b18b36 | ||
|
|
b0e4ce103e | ||
|
|
15ff53685f | ||
|
|
d81e499194 | ||
|
|
d485b18ecc | ||
|
|
af8a6db020 | ||
|
|
9c9ad730a3 | ||
|
|
3face97046 | ||
|
|
a7289d0550 | ||
|
|
0c4dad5acc | ||
|
|
4590197259 | ||
|
|
dd4489c70e | ||
|
|
201a2bb868 | ||
|
|
01c925816e | ||
|
|
8a383e494e | ||
|
|
c134584b88 | ||
|
|
110c82833b | ||
|
|
4b0144989b | ||
|
|
e3917dd35b | ||
|
|
d678138ec1 | ||
|
|
0d992803c5 | ||
|
|
5d29863b78 | ||
|
|
94e56ed5fa | ||
|
|
62cbb955b1 | ||
|
|
078245f7bd | ||
|
|
85745d2017 | ||
|
|
c7ca39b4f7 | ||
|
|
955ec3497b |
122
C-progress.md
@ -3,7 +3,127 @@
|
||||
*Status: **v1 complete & verified**. Standalone interiors library + test page. Every shop door opens
|
||||
into a unique, seeded, themed interior, generated on demand in ~4ms, byte-identical every revisit.*
|
||||
|
||||
Last updated: 2026-07-16 (round 19) · owner: PROCITY-C · reviewer: Fable
|
||||
Last updated: 2026-07-16 (round 22) · owner: PROCITY-C · reviewer: Fable
|
||||
|
||||
---
|
||||
|
||||
## Update 2026-07-16 (round 22, v4.0 THE EPOCH CLOSE) — 23-town door-soak (#5) + the G2a contract (#3)
|
||||
|
||||
R22 §Lane C, both items. Wave 3 — ran **after A pinned the 36 goldens** (`3face97`) and E's tri-diet fix
|
||||
(`d81e499`), so this soaks the final v4.0 world.
|
||||
|
||||
### Ledger #5 — sampled door-soak across all 23 towns
|
||||
|
||||
The five-town harness scaled: **~10 doors/town, sample takes one of every type present then stride-fills**, so
|
||||
every archetype is covered town-set-wide. **238 doors · 1,133 in-plan shops · 23 towns.**
|
||||
|
||||
| town | shops | doors | worst draws | | town | shops | doors | worst draws |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| adelaide | 110 | 11 | 158 | | katoomba | 72 | 10 | 149 |
|
||||
| ballarat | 19 | 10 | 154 | | launceston | 30 | 10 | 137 |
|
||||
| bendigo | 35 | 10 | 141 | | marrickville | 26 | 10 | 149 |
|
||||
| bowral | 30 | 10 | 156 | | newcastle | 34 | 10 | 152 |
|
||||
| braddon | 19 | 10 | 158 | | **newtown_godverse** | 18 | 10 | 134 |
|
||||
| brunswick | 92 | 11 | 149 | | newtown | 67 | 11 | 149 |
|
||||
| castlemaine | 23 | 10 | 150 | | northbridge | 58 | 11 | 149 |
|
||||
| darwin | 12 | 10 | 154 | | toowoomba | 12 | 10 | 138 |
|
||||
| daylesford | 26 | 10 | 153 | | westend | 41 | 10 | 146 |
|
||||
| **fitzroy (mecca)** | **139** | 10 | **159** | | fremantle | 79 | 11 | 152 |
|
||||
| geelong | 62 | 11 | 145 | | glebe | 51 | 11 | 151 |
|
||||
| hobart | 78 | 11 | 157 | | | | | |
|
||||
|
||||
**Verdict: 23/23 PASS — 0 throws · 0 carves · 0 path-fails · 0 over-budget; worst 159 draws (fitzroy) ≤350.**
|
||||
- **All 12 archetypes covered town-set-wide** (band_room, book, dept, milkbar, opshop, pawn, pub, record, rsl,
|
||||
stall, toy, video) — the whole registry.
|
||||
- **23/23 towns landed 3 venues** — A's cluster bias + district placement hold from the 12-shop thin tail
|
||||
(darwin, toowoomba) to the 139-shop mecca (fitzroy). Fitzroy, 2× anything absorbed before, is the *cheapest*
|
||||
end of nothing — worst draw 159, same band as everywhere else: interiors don't care about town scale.
|
||||
- **Lane G's `newtown_godverse` passes** (18 shops, 8 types, worst 134) — the merged town needs nothing from C.
|
||||
|
||||
### Ledger #3 — the G2a per-shop atlas contract (C's half) → published in LANE_C_PUB **§7**
|
||||
|
||||
Answered Fable's priors + E's half. **All three priors hold; atlas-per-shop is strictly better for draws than
|
||||
today's type-packs. The binding constraint is VRAM, not draws — and E's dispose ask is granted: it's my seam.**
|
||||
- **Draws:** 1 atlas/shop = **1 draw** (shared material + baked UV → batch.js merges). Rule: 1, hard cap 2;
|
||||
60+ item shops spill to the type-pack. Headroom is enormous (stock rooms 41–58 draws vs the ≤350 law).
|
||||
- **VRAM (the real ceiling):** E's 181 MB audit is right and the cause is my own design note
|
||||
(`stockpack.js:11` caches atlases "NOT per-room, like depot GLBs") — correct for 3 bounded type-packs,
|
||||
**wrong for unbounded per-shop atlases**. Ruling: **≤2 resident shop atlases, ≤8 MB decoded, ≤1024² typical**
|
||||
(2048² only for 60+ items); **C lands the LRU cap + dispose in `stockpack.js`**. Scheduling: the accretion is
|
||||
pre-existing and `?stock=real` is **opt-in** (`index.html:196`) so it can't touch the default boot — **not a
|
||||
v4.0 blocker**; I land it as **C's first v4.x item, before G2a ships**.
|
||||
- **Schema:** E's index confirmed verbatim, keyed per shop; UV origin stays top-left (C flips V); C reads `uv`
|
||||
never `cell` (so book's 176×256 portrait is fine). **Fail-soft/parody/`?noassets`:** unchanged law.
|
||||
- **One ask back to G:** emit `items[]` in **deterministic order (sort by id)** — C's seeded per-bin pick
|
||||
indexes into it, so a re-emit would silently reshuffle every crate.
|
||||
|
||||
**C→F: 23-town interior verdict GREEN** (nothing gates the tag from C). Docs-only. Both C items closed for v4.0.
|
||||
|
||||
---
|
||||
|
||||
## Update 2026-07-16 (round 21, v4.0-beta close) — the five-town density door-soak (ledger #3)
|
||||
|
||||
R21 §Lane C (wave 2 — ran **after A's venue cluster-adjacency bias** landed, `110c828`, so the soak reads the
|
||||
relocated world + A's re-pinned goldens). The carried R20 #6: **every one of the five beta towns at its new
|
||||
density**, deterministically sampled ~25 doors/town — the sample takes **one of every type present** then
|
||||
stride-fills, so the texture classes' first real appearance (milkbar/dept/stall on real shops) is always covered.
|
||||
|
||||
| town | shops (in plan) | sampled | venues | types (all sampled) | throws | carves | path-fails | over-budget | worst draws |
|
||||
|---|---|---|---|---|---|---|---|---|---|
|
||||
| `katoomba_real` | 72 | 25 | 3 | 10 | 0 | 0 | 0 | 0 | **157** |
|
||||
| `newtown_real` | 67 | 25 | 3 | 11 | 0 | 0 | 0 | 0 | 155 |
|
||||
| `fremantle_real` | 79 | 25 | 3 | 11 | 0 | 0 | 0 | 0 | 153 |
|
||||
| `bendigo_real` | 35 | 25 | 3 | 10 | 0 | 0 | 0 | 0 | 154 |
|
||||
| `castlemaine_real` | 23 | 23 (all) | 3 | 8 | 0 | 0 | 0 | 0 | 156 |
|
||||
|
||||
**123 doors · 0 throws · 0 carves · 0 path-fails · 0 over-budget · worst 157 draws (≤350).** In every town
|
||||
`typesInTown === typesSampled` — every archetype present was entered, including all three venue kinds
|
||||
(pub/band_room/rsl) and the texture classes. **All five towns landed 3 venues** → A's cluster bias + district
|
||||
placement hold at density.
|
||||
|
||||
**§6 mapping verified live (John's SUBTLE directive, working by construction):** katoomba's cache is **20
|
||||
heroes + 60 texture** — exactly E's 3× cap, heroes untouched. In-plan (after A's counted drops): 17 heroes
|
||||
(opshop 10 · book 4 · record 2 · video 1) + 52 texture (milkbar 29 · dept 18 · stall 5) + 3 venues = 72.
|
||||
The texture classes' **first real appearance** renders right: **"Blue Mountains Chocolate Company"** — a real
|
||||
Katoomba `shop=chocolate` — now wears `milkbar` (70s geo wallpaper, cube shelves of price-tagged goods, counter,
|
||||
wall rack): a believable confectionery counter shop. The widening reads as texture, not takeover.
|
||||
|
||||
*(Cache→plan deltas — e.g. katoomba 80→72 — are A's overflow/fragmentation drops, counted by cause in A's R20
|
||||
accounting; not an interior concern, and every surviving door builds clean.)* **C→F: density verdict GREEN.**
|
||||
Docs-only. C otherwise on-call; my §6 mapping + this soak close both C items for the beta.
|
||||
|
||||
---
|
||||
|
||||
## Update 2026-07-16 (round 20, v4.0-beta FIVE TOWNS) — the class→archetype mapping (ledger #2, C's half)
|
||||
|
||||
R20 §Lane C (I lead — E builds to it, half-day rule): **published the OSM shop-class → interior archetype
|
||||
mapping** in [LANE_C_PUB.md](docs/LANES/LANE_C_PUB.md) **§6** (new v4.0-beta amendment; the frozen venue
|
||||
contract §0–§5 untouched). This is the density widening — real towns fetch **general retail**, not just the
|
||||
thriftgod secondhand classes, so the main street holds its real bakery/cafe/chemist/clothes shops.
|
||||
|
||||
**Design (John's directive: SUBTLE — texture, not takeover):**
|
||||
- **No new archetypes.** General retail wears **existing** recipes: food/grocer/newsagent/chemist → `milkbar`
|
||||
(the canonical AU counter shop); clothes/hardware/big-retail → `dept`; produce/florist → `stall`.
|
||||
- **Secondhand stays the hero** — charity/antiques→opshop, books→book, music→record, video/games→video,
|
||||
pawn→pawn, toys→toy; never subsampled.
|
||||
- **Unknown/unlisted → `opshop`** (R18-proven safe). Included a copy-paste JSON map for E's `build_towns.py`
|
||||
+ a tasteful fetch list (skip vehicle/funeral/industrial/pure-services like hairdresser/bank).
|
||||
- **Guardrail for E:** if a town's general retail would visually drown the secondhand strip, seed-subsample
|
||||
the texture classes toward ~2–3× the secondhand count and count the drops in `norm.dropped`.
|
||||
|
||||
**Mechanic note:** `plan_osm` keeps only registry types (unknowns flatten to opshop), so E bakes a **registry
|
||||
type** per the map into each cache's `shop.type` — no C code change, all targets are existing recipes/registry
|
||||
entries, budgets unchanged. **C→E handshake: §6 is live.**
|
||||
|
||||
**Pre-validated the mapping (measurement over folklore, before E builds to it):** milkbar/dept/stall/opshop all
|
||||
build valid at the katoomba lot (8.5×14) — 0 carves, ≤143 draws, pathOK. Rendered `dept` (my one flagged case
|
||||
— the escalator centrepiece): reads as a clean **dressed-retail floor** (cube shelves of goods + clothes rack +
|
||||
counter + "GROUND FLOOR/HABERDASHERY" signage) in the 7.9×11.7 hall, escalator does *not* dominate — so
|
||||
`clothes`/`hardware` → `dept` is confirmed, not a concern.
|
||||
|
||||
**Pending (ledger #6, after E re-emits the 5 widened caches):** the five-town sampled door-soak — every town,
|
||||
sampled doors at the new density, all types resolve, no carve, budgets; one table, five rows. I'll watch that
|
||||
`dept` reads right on small real lots (the escalator centrepiece). Docs-only this commit; door-soak follows E.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -4,6 +4,45 @@
|
||||
|
||||
---
|
||||
|
||||
## Round 22 (v4.0 — THE EPOCH CLOSE) — F closed it
|
||||
|
||||
- **The blocking gate CLOSED.** F's R21 re-measure made the tri diet blocking; B diagnosed, the driver
|
||||
traced A→**E**, E fixed it, F re-measured: katoomba `venue_night` **221,935 → 108,365 (−51%)**, worst town
|
||||
124,682 — the 200k stress edge is closed. Selfcheck **161,300/161,300** (A's 36 goldens + 23 towns).
|
||||
- **`DBG.enterShop(selector)`** (Lane G's ask) — id · type · exact name · substring, most-specific-first.
|
||||
- **The release tour** → [`docs/shots/release_v4/`](docs/shots/release_v4/): Fremantle hero · Fitzroy mecca ·
|
||||
Toowoomba thin tail · synthetic control · `?classic=1` covenant. All posed through F's R21 cluster
|
||||
primitive — which earned itself twice: the first cut posed via `street_noon` and **the mecca came out as
|
||||
bare bitumen**; re-posed through the cluster, Fitzroy reads.
|
||||
- **⚠ Honest finding (filed v4.x):** the thin tail does **not** prove the floor holds — Toowoomba reads as an
|
||||
empty road. The floor is a **ratio**, not a count: 0.008 shops/edge vs the heroes' 0.06–0.08. The frame
|
||||
ships labelled honestly rather than swapped for a flattering town.
|
||||
- Docs (README v4 "the towns are the product", notes §22), `qa.sh --strict --matrix` green, tag **`v4.0`**.
|
||||
|
||||
---
|
||||
|
||||
## Round 20–21 (v4.0-beta THE FIVE TOWNS) — F never ran R20; closed R21
|
||||
|
||||
**R20: F never ran** (no gate, no tag) — the honest record is in LANE_F_NOTES §20. Unlike R18 (where F ran
|
||||
and *held* on evidence), the slot simply didn't execute; the round also left C's door-soak, B's selector
|
||||
index, and D's relocation finding open. R21 closed all four.
|
||||
|
||||
**R21: the beta gate.** Waves [A,B,E] → [C,D] → [F]. All five lanes in; F gated:
|
||||
- **Matrix 10 towns × 7 gates** — boot/determ/district/roads/**tram**/noassets green on all ten; selfcheck
|
||||
154141/154141; `qa.sh --strict` GREEN.
|
||||
- **Tram dimension (new)** — B's R20 ruling verified exactly: Newtown runs 16 stops (was 149 — B's phantom
|
||||
fix), Castlemaine 10; Katoomba/Fremantle/Bendigo **fenced** at 3/2/1 shops fronted. Needed a new seam:
|
||||
the shell now exposes `window.PROCITY.tram` (B built `routeInfo` for F's smoke; nothing published it).
|
||||
- **⚠ venue_night re-measure — recorded, premise falsified.** A's cap worked (posters 438→111) but the
|
||||
density widening ate it: katoomba **200,965 (+965) → 221,935 (+21,935, +11%)**; bendigo 201k. Draws fine
|
||||
(151/300). Filed as R22's first item (the tri-diet backlog already lives there).
|
||||
- **Selector ruling** — always in the HUD (the towns *are* the product), + the corollary that F's harness
|
||||
must hide the HUD for beauty shots (the R19 alpha shot's selector-in-frame was F's own gap).
|
||||
- **Beta money shot** — [Fremantle's real street at golden hour](docs/shots/v4_beta/fremantle_mainstreet.png):
|
||||
a corridor of shopfronts, footpaths, a crossing, and a genuinely busy crowd. Tag **`v4.0-beta`**.
|
||||
|
||||
---
|
||||
|
||||
## Round 18–19 (v4.0-alpha REAL ROADS) — F held R18 correctly, then closed R19
|
||||
|
||||
**R18: F did NOT run — the gate would have failed by design, so F held the tag** and filed the failure list
|
||||
|
||||
@ -3,6 +3,78 @@
|
||||
*Lane brief: `docs/LANES/LANE_G_GODVERSE.md`. This file follows your progress-file convention —
|
||||
newest round first. Fable-on-ultra writing.*
|
||||
|
||||
## Round 23 (2026-07-16) — G2a: THE FIRST REAL CRATE + the G3 draft
|
||||
|
||||
**Landed (branch `lane-G-godverse`, G namespace only — ready for your mid-round merge):**
|
||||
- `pipeline/godverse_stock.py` — the per-shop atlas pipeline, split at **the snapshot boundary**:
|
||||
`snapshot` (runs where the DBs are; freezes every source row + every cover's sha256) →
|
||||
`pack` (pure: snapshot + image bytes → atlas, no DB, no network). That split is deliberate —
|
||||
**it's what lets E's `validate_atlas` gate G's output on m3 without the dealgod DB** (ledger #4).
|
||||
- `pipeline/_godverse/monsterrobot_550.snapshot.json` — the artifact of record (committed).
|
||||
- `web/assets/stock_godverse/3962749/` — **120 real records** from a real crate: 2 atlases
|
||||
(2048², 256px cells, E's contract verbatim) + index with provenance.
|
||||
- `docs/G3_ECONOMY_DESIGN.md` — the draft (ledger #2), incl. **the write-back question framed for
|
||||
John** (§7), now grounded in a real trading shop rather than a hypothetical.
|
||||
- **Verified:** byte-identical re-bake (same snapshot → same bytes); uv ⊂ [0,1]; every item's
|
||||
atlas ref resolves; bands standard 58 / collector 56 / grail 6. Eyeballed the atlas: it's a real
|
||||
deep-house crate (Basement Jaxx, Byron Stingily, Angel Moraes, 4hero, Murk, Blaze).
|
||||
|
||||
**THE HEADLINE FINDING — the brief's premise doesn't hold (all measured, R23):**
|
||||
The ledger asked for "dealgod inventory → one real **Newtown** record shop". That can't be built:
|
||||
- **No Newtown record shop has stock in any database.** Egg Records / Papa Disquo / Haze Guitars /
|
||||
The Vintage Record / Downtown Music: **0 items each**.
|
||||
- **`dealgod.stores` is not a shop census** — 8,754 of 9,285 are **Discogs seller accounts**.
|
||||
Zero Newtown stores, zero census name matches. It's an item *feed*, not a shop directory.
|
||||
- **`thriftgod.item` is a demo seed** — 2,221 items across 29 shops, **all Brisbane/Newcastle/Alice
|
||||
charity shops**, zero record shops.
|
||||
- **`mint()` cannot produce tier 1**: `TABLESAMPLE SYSTEM (0.5)` + `random.random/uniform/shuffle`,
|
||||
and **`random.seed` appears nowhere in `server.py`**. Non-deterministic, and it assigns other
|
||||
sellers' listings to shops that never owned them — which also voids the licence law's "the
|
||||
*stores' own* product shots" green and makes write-back meaningless. (G3 §2.)
|
||||
- This is **not** risk #3's "fuzzy matching" — it's a population mismatch. Nothing to match *to*.
|
||||
|
||||
**What we found instead — and it's better.** `monsterrobot` (**147 Musgrave Rd, Red Hill, Brisbane**
|
||||
— John's ruling) is the same real shop in three datasets: `recordgod` (its POS: 28,624 live vinyl,
|
||||
**24,914 in 357 real crates** with real labels and slot numbers), `monsterrobot.party` (its own
|
||||
site — **the covers are the shop's own product shots**), and dealgod store **3962749** (the scrape;
|
||||
the cover cache + the godverse id). **99.3%** of its crate stock joins to a cached cover by Discogs
|
||||
`release_id`. Not in thriftgod's census, so its godverse id = its dealgod store id 3962749 (real,
|
||||
stable, clear of the census space — thriftgod max shop id = 2992). Drops counted: 120 of 122 in
|
||||
crate 550 baked; 2 dropped (no own photo).
|
||||
|
||||
**Rulings received from John this round (recorded):**
|
||||
1. **The crate ships as the shop it really is** — Red Hill, Brisbane, not Newtown.
|
||||
2. **Real titles/artists at tier 1** — an amendment to the no-real-trademarks law that
|
||||
`build_stock_pack.py` enforces (it parody-transforms metadata). Same shape as the licence law:
|
||||
in-house 🟢, **flagged before anything public**. Recorded in every atlas's provenance.
|
||||
|
||||
**Asks — two things gate the crate reaching a room, and both are outside G's namespace:**
|
||||
1. **[C + F] Per-shop packs aren't wired at runtime.** `stockpack.js` caches by `type` alone
|
||||
(`_packs`/`_resolved`), and `interior_mode.js:192` does `getStockPack(shop.type)` with `base`
|
||||
fixed to `assets/models/`. R23's single shop works via a `base` override, but **two stocked
|
||||
shops collide in that cache** — so this blocks v5.0-beta, not just ledger #3. Seam needed:
|
||||
key the cache by `type + base` (or `godverse_id`) + preload per-shop on entry. **C owns the
|
||||
wording (LANE_C_PUB); G re-emits to whatever lands.**
|
||||
2. **[E, via Fable] The crate has no street.** `redhill_real` doesn't exist; `westend_real` is the
|
||||
only Brisbane cache and stops ~2.3 km south. Needs a `build_towns.py` TOWNS entry
|
||||
(~`center: (-27.4553, 153.0064), span_km: 2.4`) + one Overpass fetch, then
|
||||
`godverse_town.py redhill_real redhill_godverse`. **That's E's file and an outward-facing
|
||||
fetch — G touched neither.** Note monsterrobot must be added to the godverse cache's `shops[]`
|
||||
as id 3962749 (it's not in the census); `godverse_town.py` takes shops from thriftgod only, so
|
||||
that's a small G change once you rule on the town.
|
||||
|
||||
**Environment note (matters for reproducibility):** this ran on the **m3**, not ultra — the box has
|
||||
neither `thriftgod` nor `dealgod` Postgres. Both were restored/read from John's mounted backup
|
||||
drive (`/Volumes/johnking/db-backups`, **2026-07-15** dumps); dealgod (2 GB gz) was never restored,
|
||||
its cover map was streamed straight out of the dump (command recorded in
|
||||
`godverse_stock.py:load_image_map`). This is *better* for the charter's snapshot discipline, not
|
||||
worse: the dump date is pinned in every snapshot's provenance. I left two restored DBs on the m3
|
||||
(`thriftgod`, `recordgod`) — say the word and I'll `dropdb` them.
|
||||
|
||||
**Fences held:** PII — `recordgod` holds real `customer`/`staff`/`sales`; the pipeline reads only
|
||||
`inventory`/`crate`/`disc_cache`, and G3 §9 makes that a standing fence. Licence — covers are the
|
||||
shop's own shots, in-house 🟢, flagged before public, provenance in every atlas.
|
||||
|
||||
## Round 1 (2026-07-16, ultra) — the marriage's first artifact
|
||||
|
||||
**Landed:**
|
||||
|
||||
24
README.md
@ -74,7 +74,29 @@ master off:
|
||||
| `?roster=v1` · `?pop=N` | stream | chunk-streamed citizen density is the baseline; `?roster=v1` restores the fixed roster |
|
||||
| `?dig=0` · `?stock=real` | dig on | crate-riffle on record bins · real GODVERSE sleeves in the stock |
|
||||
| `?noassets=1` · `?plansrc=osm` · `?mute=1` · `?dbg=1` | off | asset-free (zero network) · OSM plan source · silence · the `window.DBG` capture/soak harness |
|
||||
| `?plansrc=osm&town=<key>` | — | boot a **real Australian town**: fixtures `melbourne`/`katoomba`/`silverton`, or a real OSM cache `bendigo_real`/`castlemaine_real`/`fremantle_real`/`katoomba_real`/`newtown_real` (ODbL, `web/assets/towns/SOURCES.md`). **v4.0-alpha REAL ROADS:** a cache with `roads[]` (schema v2) builds the CityPlan street graph from the town's **actual OSM streets** — walk Katoomba's real main street; without `roads[]` a cache marches onto synthetic avenues |
|
||||
| `?plansrc=osm&town=<key>` | — | boot a **real Australian town**: fixtures `melbourne`/`katoomba`/`silverton`, or a real OSM cache `bendigo_real`/`castlemaine_real`/`fremantle_real`/`katoomba_real`/`newtown_real` (ODbL, `web/assets/towns/SOURCES.md`). **REAL ROADS:** a cache with `roads[]` (schema v2) builds the CityPlan street graph from the town's **actual OSM streets**; without `roads[]` a cache marches onto synthetic avenues. Or just **pick a town from the HUD selector** — no URL needed |
|
||||
|
||||
## v4.0 — THE REAL MAP: the towns *are* the product
|
||||
|
||||
**23 real Australian towns — 1,210 real shops, every state and territory — on their actual streets.** Not
|
||||
"inspired by": Fitzroy's shops sit on Fitzroy's roads, at their real positions, because the pipeline eats
|
||||
OpenStreetMap and marches it through the same CityPlan contract the procedural town uses. **Pick one from
|
||||
the in-HUD selector and walk it.** Everything built across v1–v3 comes along unchanged — the venues and their
|
||||
seeded week of gigs, the 4-piece band with the seated drummer, the crowd that follows you into the pub, the
|
||||
interiors behind every door, weather, the tram — because they all hang off the plan contract and never had
|
||||
to know the geography got real.
|
||||
|
||||
The towns run from **meccas** (Fitzroy, 160 shops) to the **thin tail** (12), at real retail density: the
|
||||
secondhand heroes keep their footfall while the street around them gets ~4× busier — texture, never a
|
||||
takeover. Venues bias to the retail heart. The tram only runs where it fronts a real high street (elsewhere
|
||||
it was honestly "a highway bus", so it's fenced). Everything stays **deterministic from the seed**, silent
|
||||
under `?noassets=1`, and **`?classic=1` still boots the exact v2 town, byte-identical forever** (`0x3fa36874`).
|
||||
|
||||
**The tour:** [`docs/shots/release_v4/contact.html`](docs/shots/release_v4/) — the Fremantle hero, the
|
||||
Fitzroy mecca, the thin tail (labelled honestly: the floor is shops-per-street-metre, and Toowoomba is
|
||||
below it), the synthetic control, and the `?classic=1` covenant made visible. Charter:
|
||||
[`docs/V4_REAL_MAP.md`](docs/V4_REAL_MAP.md) · data + licences (ODbL):
|
||||
[`web/assets/towns/SOURCES.md`](web/assets/towns/SOURCES.md).
|
||||
| `?seed=N` | `20261990` | town seed |
|
||||
|
||||
**Release tour:** [`docs/shots/release_v3_1/contact.html`](docs/shots/release_v3_1/) — the money shots on
|
||||
|
||||
@ -1,11 +1,131 @@
|
||||
# LANE A — CITYGEN · progress (PROCITY-A)
|
||||
|
||||
*Status: **all deliverables landed; self-check all-green (51670/51670); rounds 2–19 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.*
|
||||
*Status: **all deliverables landed; self-check all-green (161,372/161,372); rounds 2–23 closed** (see below).
|
||||
R23 (v5.0-alpha): D's thin-tail finding is law — `validateTownCache` warns above **150 m median shop
|
||||
spacing**; flags 2 of 23 towns (toowoomba 396 m, **ballarat 255 m — beyond the brief**). Warn, never error.
|
||||
R22: the pack absorbed — 23 towns, 36 goldens in one commit. classic `0x3fa36874` / gig `0xb1d48ea1` frozen.*
|
||||
*Date: 2026-07-16 · owner: PROCITY-A (Opus 4.8)*
|
||||
|
||||
## Round 23 (2026-07-16) — v5.0-alpha: the spacing warn (ledger #5)
|
||||
|
||||
D's R22 finding made law: `MIN_TOWN_SHOPS` counts shops, but what makes a town read alive is **clustering**.
|
||||
`validateTownCache` now warns when `medianShopSpacing(cache) > MAX_MEDIAN_SPACING_M` (**150 m**) — the metric
|
||||
and threshold both 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 whole v4.0 pack), E curates, the
|
||||
validator flags. selfcheck **ALL GREEN 161,372/161,372** (+72), synthetic `0x3fa36874` frozen, no golden moved.
|
||||
|
||||
- **The threshold is measured, not picked.** Fable's brief offered darwin 27 m / toowoomba 388 m as "plenty
|
||||
of daylight for a line". Measuring all 23 towns showed the pack is a near-continuum, and the line is
|
||||
**forced** into `(119.3, 254.9]` — braddon (119.3 m) is alive by D's own hub test (7 shops within 160 m,
|
||||
identical to darwin's 7), ballarat (254.9 m) is not. 150 m sits in the pack's widest gap (**×2.14**).
|
||||
- **Two towns flag, not one.** The brief names toowoomba; the measurement adds **ballarat (255 m)**, which
|
||||
nobody had looked at — and which is independently one of the three towns my R22 cluster fallback fired on.
|
||||
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);
|
||||
`validateTownCache` is **cache** space (raw lat/lon — the only thing it has at ingest). Darwin is 68.3 m
|
||||
in cache space, not 27. Coding a threshold interpolated between D's figures would have mixed spaces
|
||||
silently. Cache space is right *here*, and provably safe: the lift compresses dense towns (darwin
|
||||
plan/cache **0.40**) but not sparse ones (toowoomba **0.98**, ballarat **1.01**), so the error lives at the
|
||||
dense end and the warn only fires at the sparse end. Verified all 23 in both spaces: **one town disagrees**
|
||||
(`newtown_godverse`, 94.4 cache → 158.3 plan) — filed for G/Fable, not acted on, since no cache-space line
|
||||
catches it without false-positiving braddon, and its sparsity is census *coverage*, which re-bboxing can't fix.
|
||||
- Cost: ~1 ms/town at registration (25.6 ms for all 23 including fitzroy's 160 shops), against a <100 ms
|
||||
plan budget. Pending: re-pin whatever E's toowoomba re-bbox moves — **only** that.
|
||||
|
||||
## Round 22 (2026-07-16) — v4.0 THE EPOCH CLOSE: the pack absorbed + 36 goldens pinned (ledger #2)
|
||||
|
||||
E's 17 new towns + Lane G's `newtown_godverse` fed through the lift and pinned in ONE golden commit
|
||||
(`REAL_TOWN_GOLDENS` + `REAL_TOWN_GIG_GOLDENS`: 5 → **23 towns each**, zero unpinned).
|
||||
|
||||
**The headline: everything built this epoch held at 23-town scale, first try.** 1,210 shops → 1,133 seated
|
||||
(77 dropped, **6.4%**, counted by cause); **1 connected component and 3 venues on every one of the 23 towns**.
|
||||
`fitzroy` — 160 shops, 2× anything absorbed before — landed 139 seated, 1 component, 3 venues, 212 posters,
|
||||
all invariants green. The junction-protected DP, fragmentation ruling, cluster bias, poster cap and capacity
|
||||
fence needed **no new hardening**: the pack needed absorbing, not fixing.
|
||||
|
||||
- **Graded cluster fallback (the one refinement).** Three towns have no candidate at the floor at all
|
||||
(`ballarat`/`toowoomba`/`newtown_godverse` top out at 2 shops within 160 m); R21's bias switched off there,
|
||||
stranding venues at 0. It now falls back to the **densest available** — ballarat 0,2,0 → **2,2,2**, godverse
|
||||
0,1,1 → **2,2,2**, toowoomba 1,0,0 → 1,2,1. Fires only where the floor is unreachable ⇒ **no already-pinned
|
||||
town moved** (verified). Acceptance at pack scale: **20 of 23 towns have every venue ≥3**; the three above
|
||||
sit at their town's genuine ceiling — the carved-out case, stated explicitly.
|
||||
- **Drop outliers, counted not hidden:** `daylesford` 19% (32 shops on a 171-road graph — the thinnest in the
|
||||
pack, all overflow), `fitzroy` 13%. `RGAP` is already 0.5 m and `NODE_CLEAR` 3 m; the only lever left is
|
||||
spilling onto a different street, which buys a few percent by putting a shop on a road it isn't on. Same
|
||||
ruling as R20: **drop and count beats teleport**.
|
||||
- selfcheck ALL GREEN **161,300/161,300**; scaffold + consistency GREEN; cross-process deterministic
|
||||
(pack-xor `0xca140954`). classic / gig / marched fixtures frozen.
|
||||
|
||||
Handshake fired → C + D + F: the pack is in, 23 towns pinned, start your verifies.
|
||||
|
||||
**Ledger #6 (docs, `9c9ad73`): CITY_SPEC v4 final + the risk list retired-or-carried.** Closing this
|
||||
surfaced real drift — **three gig-layer behaviour changes had reached only these notes, never the spec**
|
||||
(R20's clearance widening to every venue kind, R21's cluster bias, R22's fallback). The amendment law says
|
||||
the spec is where they live; all three are now recorded, along with the pick *order* no lane could have
|
||||
inferred (cluster field → kind flavour → clearance → `farthestFirst` → seeded pick). Risk list judged
|
||||
against the pack, not the 5-town sample it was written for: **#1 retired** (widest town geelong 3,829 m =
|
||||
77% of the 5 km law), **#2 retired** (thinnest darwin 12 shops = 2× `MIN_TOWN_SHOPS`), **#3 retired
|
||||
plan-side / carried for the runtime lanes** (a green plan is not a green town — C/D/F's verifies close it),
|
||||
**#4 retired** (DP knob measured; 1 component on all 23), **#6 NEW carried** (thin-graph seating capacity).
|
||||
|
||||
**Ledger #1 (tri diet): not A's — B's numbers decided it.** The driver is E's four furniture GLBs
|
||||
(91.5%); posters are **222 tris**, buildings **348**. My cap worked and was never the lever, and B found
|
||||
the real coupling is **road density, not shop count** — so the absorb wasn't the cause either. My guess
|
||||
(buildings/LOD) was wrong at 0.2%; the measurement won, as it should.
|
||||
|
||||
## Round 21 (2026-07-16) — v4.0-beta: the venue cluster-adjacency bias (ledger #1, the round's spine)
|
||||
|
||||
D filed it: katoomba's pub sat 1,000 m from the retail cluster — a gig with no passing crowd. Measuring all
|
||||
five towns first showed it was **systemic: 11 of 15 venues** had <3 shops within 160 m (newtown's were
|
||||
0/0/1). After the bias: **0 of 15**. Katoomba's pub went **1 → 9**; castlemaine cleared without needing the
|
||||
floor exception Fable anticipated.
|
||||
|
||||
- `pickVenues` now builds a **cluster-adjacent field** (≥`CLUSTER_MIN` 3 shops within `CLUSTER_R` 160 m),
|
||||
then the kind flavors (pub corner / band_room fringe / rsl off-spine) bias **within** it rather than
|
||||
overriding it (per the brief: biases, not absolutes). No cluster at all → falls back to the pool.
|
||||
- **Synthetic frozen BY CONSTRUCTION**, not by luck. The brief assumed a blanket filter was a no-op because
|
||||
"the synthetic town is one dense cluster anyway" — measurement says otherwise: **4 synthetic candidates
|
||||
per seed** sit under the threshold (min cluster 0–1), so a blanket filter *would* have moved the frozen
|
||||
golden. Gated on `plan.source === 'osm'` (undefined on synthetic). Marched fixtures are 'osm' but genuinely
|
||||
dense (min 3/11/13) ⇒ verified no-op.
|
||||
- **Only the GIG goldens moved** (venue selection is gig-layer): katoomba `0xbf586b77`, newtown `0xacdb3eee`,
|
||||
fremantle `0xcf3426fc`, bendigo `0x646b2c23`, castlemaine `0xf9713a69`. Base goldens untouched.
|
||||
classic/gig/marched fixtures frozen. selfcheck ALL GREEN **66419/66419**; scaffold + consistency GREEN;
|
||||
cross-process deterministic.
|
||||
- FYI: E's v4.0 pack (`glebe`, `marrickville`, `newcastle`) already boots clean through my loader (validates
|
||||
+ full suites pass), listed UNPINNED — **I pin the pack in R22**; it does not gate the beta tag.
|
||||
|
||||
Handshake fired → C + D: cluster bias landed, goldens re-pinned, start your verifies.
|
||||
|
||||
## Round 20 (2026-07-16) — v4.0-beta THE FIVE TOWNS: the poster cap (density absorb pends E)
|
||||
|
||||
Two Lane-A deliverables; the first (independent) landed this commit, the second waits on E's widened caches.
|
||||
|
||||
1. **Poster cap (ledger #1) — LANDED.** My R19 finding (real katoomba: 438 spine posters, driving F's one
|
||||
budget edge, `venue_night` 200,965 tris) is fixed. `gigs.js buildPosters` caps the spine pole run at
|
||||
**`POSTER_PER_SHOP × shops`** (1.5/shop, floor 12) — a seeded subsample of the run. **Scaled by shop
|
||||
count, not edge count** (the whole point: a sparse real town has few shops but a big graph). Result:
|
||||
katoomba **438 → 31** posters (~2× the synthetic feel), fremantle 32, castlemaine 14. **No-op on the
|
||||
synthetic town** (493 shops ⇒ cap ~740 ≫ its ~15 posters), so the synthetic gig golden `0xb1d48ea1` is
|
||||
frozen. Posters are gig-layer, so the real-town BASE goldens are unchanged — I added
|
||||
`REAL_TOWN_GIG_GOLDENS` (the full district+poster plan per town: katoomba `0xbd332e83`, …) to
|
||||
regression-guard the capped output byte-exact. selfcheck ALL GREEN **51675/51675**; scaffold + consistency
|
||||
GREEN; cross-process deterministic. → F: re-measure `venue_night`, the +965 edge should close.
|
||||
2. **Density absorb (ledger #2) — LANDED.** E's five caches at **3–6× shops** (292 total: katoomba 20→80,
|
||||
fremantle 21→80, newtown 18→72, bendigo 9→36, castlemaine 6→24) absorbed; **all five place 3 venues**;
|
||||
the poster cap auto-scaled off the live shop count as designed. **Capacity widened** — at 4× the dominant
|
||||
drop cause was overflow (edge-side full), so `NODE_CLEAR` 6→3 + a **roads-only `RGAP`** 2→0.5 (shared
|
||||
marched `GAP` untouched ⇒ fixtures frozen): drops **7.9% → 5.5%** (16/292), residual **counted by cause**
|
||||
(`dropped_overflow`/`_overlap`/`_stranded`). Stopped there deliberately — the next lever (spilling
|
||||
overflow onto a different street) buys ~1% by putting a shop on a road it isn't on. **Venue facade bias
|
||||
extended to `pub`** (R16 exempted it; on a real graph at density the pub's frontage poster overhung a
|
||||
cross street — newtown −0.91 m); still a no-op on synthetic. **Goldens re-pinned once** (base + gig, all
|
||||
five). **Fixed:** the real-cache loader now skips E's new `index.json` (a selector index, not a cache).
|
||||
selfcheck ALL GREEN **51866/51866**; scaffold + consistency GREEN; cross-process deterministic.
|
||||
|
||||
Handshake fired → all: **densities absorbed, goldens pinned.** → F: re-measure `venue_night` (poster cap)
|
||||
and gate the five-town matrix.
|
||||
|
||||
## 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
|
||||
|
||||
@ -225,6 +225,26 @@ night in v1; interior-mapping shader is a stretch goal).
|
||||
> broadens off-spine if the warehouse fringe is uniformly narrow). This moved `plan.posters`, so the gig
|
||||
> golden re-pinned `0x4f4a549d → 0xb1d48ea1`; synthetic `0x3fa36874` still frozen. The v3.1 default flip
|
||||
> + classic law is F's amendment to this section — see the flags table.)*
|
||||
>
|
||||
> *(**Recorded amendment — ROUND20, ledger #2:** the R16 clearance bias above **widened to every venue
|
||||
> kind**. R16 exempted `pub` because on the synthetic/marched towns a pub's `+Z` is always open; on a real
|
||||
> road graph at E's widened density it is not (newtown: a pub whose facade overhung a cross street). On the
|
||||
> synthetic town every pub/rsl candidate already clears ⇒ no-op there, gig golden `0xb1d48ea1` frozen.)*
|
||||
>
|
||||
> *(**Recorded amendment — ROUND21 ledger #1 + ROUND22 ledger #2: the venue cluster bias.** D's relocation
|
||||
> finding: a venue a kilometre from the retail cluster is a gig with no passing crowd (katoomba's pub had
|
||||
> **1** shop within 160 m; its RSL had 13). `pickVenues` now draws each venue from a **cluster field** —
|
||||
> candidates with ≥ `CLUSTER_MIN` (3) other shops within `CLUSTER_R` (160 m) — and lets the kind flavours
|
||||
> bias *within* that field rather than override it. Venues under the floor: **11 of 15 → 0** across the
|
||||
> five R21 towns; Newtown's RSL moved 2,228 m → 29 m from the hub. **Real towns only, by construction:**
|
||||
> the bias reads `plan.source === 'osm'`, which the synthetic generator never sets, so it cannot move
|
||||
> `0x3fa36874`/`0xb1d48ea1` — measured, not assumed (4 synthetic candidates per seed sit under the floor,
|
||||
> so a blanket filter *would* have moved them). **ROUND22 graded fallback:** at pack scale three towns have
|
||||
> no candidate at the floor at all (`ballarat`/`toowoomba`/`newtown_godverse` top out at 2 within 160 m).
|
||||
> R21 dropped the bias entirely there, stranding venues at 0 neighbours; it now falls back to the **densest
|
||||
> available** candidates instead. Fires only where the floor is unreachable ⇒ no already-pinned town moved.
|
||||
> **Acceptance at pack scale: 20 of 23 towns have every venue ≥ `CLUSTER_MIN`; those three sit at their
|
||||
> town's genuine ceiling** — the carved-out case, stated rather than papered over.)*
|
||||
|
||||
> **Prime flag law.** The gig layer is a **post-hoc augmentation** applied by the selector when the gig
|
||||
> flag is on. With it off, `generatePlanFor(seed, src)` returns the byte-identical v2 base plan — **all v2
|
||||
@ -268,6 +288,13 @@ plain shop" so osm towns still get venues):
|
||||
| `band_room` | `grunge` → `gig-grunge` | warehouse-district fringe | `[18,23]` | single-storey tin shed |
|
||||
| `rsl` | `covers` → `gig-covers` | **off**-spine (not main, not warehouse) | `[16,23]` | carpet-and-bistro club |
|
||||
|
||||
**The cluster field comes first (ROUND21/22 — real towns only).** On an osm plan every kind rule in the
|
||||
table runs **inside** the retail cluster. Order per venue: cluster field (≥`CLUSTER_MIN` other shops within
|
||||
`CLUSTER_R`, or the densest available if none reach the floor) → the kind flavour → the facade-clearance
|
||||
filter → `farthestFirst` shortlist → seeded pick, each step degrading to the previous set rather than to
|
||||
the whole town. A fringe band_room *in the cluster* beats a fringe band_room a kilometre from anyone.
|
||||
Synthetic plans skip the field entirely (`plan.source` is unset) ⇒ goldens frozen. See the amendment note.
|
||||
|
||||
Venue hours all close `23` (open through DUSK+NIGHT so the gig runs and D's surge fills the room; drains
|
||||
at close per the closing-time ruling above). Venues are the *only* gig-plan shops allowed to close ≥22
|
||||
besides the openLate video (selfcheck exempts them). `genreKey` is set from the registry kind→genre map
|
||||
@ -309,9 +336,14 @@ frontage** — seated on the **+Z street facade** (B's canon: `buildings.js` fac
|
||||
camera are all `+Z`; position `lot + (sin ry, cos ry)·(d/2+ε)`), turned to read *from* the street
|
||||
(`ry = lot.ry + π` so its printed face points `+Z` at the queue-side camera — **ROUND15 ledger #1**; before
|
||||
R15 it was on the `−Z` back and invisible from the street). So the poster never sits over a back-street's
|
||||
bitumen, **`pickVenues` prefers a band_room/rsl lot whose facade clears every carriageway by ≥ `POSTER_CLEAR`**
|
||||
(ROUND16 ledger #6 — exported from the barrel; the selfcheck asserts the same floor). Plus a seeded run along
|
||||
the spine — 2 per main edge (or, when a town has no main spine, every edge), at `t ∈ [0.2,0.8]` of the block, on **either verge**. Different venues' posters share the same pole
|
||||
bitumen, **`pickVenues` prefers a lot whose facade clears every carriageway by ≥ `POSTER_CLEAR`**
|
||||
(ROUND16 ledger #6, **widened from band_room/rsl to every venue kind in ROUND20** — exported from the
|
||||
barrel; the selfcheck asserts the same floor). Plus a seeded run along
|
||||
the spine — 2 per main edge (or, when a town has no main spine, every edge), at `t ∈ [0.2,0.8]` of the block, on **either verge**;
|
||||
a candidate that would overhang a **real** crossing carriageway is skipped (ROUND19, E's finding), and the
|
||||
run is **capped at `POSTER_PER_SHOP × shops`** (ROUND20) — a sparse real town has few shops but a big edge
|
||||
graph, so the raw "2 per edge" run reads busy (katoomba 438 → 31). The cap never binds on the shop-dense
|
||||
synthetic town, so its gig golden is frozen. Different venues' posters share the same pole
|
||||
runs — that IS the district reading. `ry` follows the house convention (a poster's printed face =
|
||||
`(−sin ry, −cos ry)`, so `atan2` of the *away-from-viewer* normal); B places E's poster skin at `(x, z)`
|
||||
rotated `ry`; F wires "poster → that venue's tonight-gig".
|
||||
@ -362,6 +394,32 @@ it identically to the fixtures. **Hard requirements:** finite `center.{lat,lon}`
|
||||
blank name → type label, unknown `type` → `opshop`, dup ids, and **span > 5 km → "likely more than one
|
||||
town"** (a cache is ONE compact town — an over-broad bbox marches into an unusable multi-km strip).
|
||||
|
||||
**The spacing floor (v5.0-alpha, ROUND23 ledger #5 — D's R22 finding as law).** `MIN_TOWN_SHOPS` counts
|
||||
shops; what makes a town **read alive** is *clustering*. D measured both 12-shop towns in the pack and they
|
||||
disagree — which is the finding: **darwin ALIVE** (one strip; 1,455 patronage checks → 18 visits) vs
|
||||
**toowoomba DEAD** (scattered over a 4.6 km road network; 1,417 checks → **0 visits, 0 finds** — "not a
|
||||
shopping town, a road network with occasional shops"). Both pass every other law. So `validateTownCache`
|
||||
now **warns when `medianShopSpacing(cache) > MAX_MEDIAN_SPACING_M` (150 m)**:
|
||||
- **The metric** (exported from the barrel — import it, don't re-derive it): for each shop, the distance to
|
||||
its **nearest other shop**; take the **median**. Equirectangular metres about `center.lat`, the same
|
||||
convention as the span check. `null` for < 2 usable shops.
|
||||
- **Warn, never error.** A flagged cache still boots a valid district — toowoomba shipped green through the
|
||||
entire v4.0 pack. **E curates** (re-bbox onto the retail strip, or retire the town, counted either way);
|
||||
the validator only flags. The span warn catches a town too **wide**; this catches one too **sparse**, a
|
||||
case span cannot see (toowoomba is *inside* the 5 km law).
|
||||
- **The threshold is measured, not chosen.** Across the 23-town pack 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 — the same as
|
||||
darwin's 7), and ballarat at 254.9 m is not. 150 m sits in that gap — the pack's widest (**×2.14**) —
|
||||
with 1.26× margin below and 1.70× above. It flags **2 of 23**: ballarat 255 m, toowoomba 396 m.
|
||||
- **⚠ CACHE space, not PLAN space — they do not agree.** This runs on raw lat/lon, before any lift; D's
|
||||
notes quote **plan** figures (post-lift metres). The lift marches shops at a regularised cadence, so it
|
||||
**compresses** dense towns (darwin **68.3 m cache → 27.2 m plan**) and leaves sparse ones alone
|
||||
(toowoomba 396.1 → 386.8). Both numbers are right; they measure different spaces. Cache space is correct
|
||||
*here* — the validator's job is to flag a bad bbox **at ingest**, before a lift exists — and it is
|
||||
faithful exactly where the warn lives (ballarat plan/cache **1.01**, toowoomba **0.98**). Known limit:
|
||||
one town flips (`newtown_godverse`, cache 94.4 → plan 158.3); no cache-space line can catch it without
|
||||
false-positiving braddon. See `LANE_A_NOTES` §R23.
|
||||
|
||||
**Wiring:** `generatePlanOSM(seed, key, { cache })` marches a cache; `registerTownCache(key, cache)` adds it
|
||||
to the registry (`osmTownKeys()` + `generatePlanFor(seed,'osm',{town:key})` then see it). `selfcheck.js`
|
||||
auto-loads `web/assets/towns/*.json` — validates, runs the structural + gig suites, pins a per-town golden.
|
||||
@ -402,6 +460,23 @@ synthetic town, so its gig golden is frozen). Golden re-pins per the amendment l
|
||||
on their real-roads output; synthetic + marched fixtures + classic/default untouched — the alpha changed
|
||||
nothing outside the cache-schema path).
|
||||
|
||||
> **FINAL at `v4.0` (ROUND22 ledger #6, the epoch close).** This section — schema v2, the lift, the
|
||||
> connectivity rulings above, the venue cluster bias and poster cap in §v3 — is the authoritative real-map
|
||||
> contract at the tag. Changes are an amendment + a golden re-pin in the same commit, as always.
|
||||
>
|
||||
> **The alpha's five became the epoch's 23** (22 real towns + Lane G's `newtown_godverse`; **1,210 shops**),
|
||||
> and every ruling written above held at that scale with **no new hardening**: **one connected component and
|
||||
> three venues on every one of the 23 towns**, fitzroy's **160 shops** — 2× anything the pipeline had seen —
|
||||
> included. All **36 goldens** (`REAL_TOWN_GOLDENS` + `REAL_TOWN_GIG_GOLDENS`, 23 each) are pinned; selfcheck
|
||||
> runs **161,300** checks green, cross-process deterministic. Read "all five towns" above as the measurement
|
||||
> of record *when the ruling was written* — the ruling itself is now pack-wide.
|
||||
>
|
||||
> **The honest tail, carried not hidden:** 6.4% of shops (77 of 1,210) don't seat, every drop attributed by
|
||||
> cause, dominated by overflow where a town has more shops than its road graph has frontage (daylesford 19%,
|
||||
> the pack's thinnest graph; fitzroy 13%). See `V4_REAL_MAP.md` risk #6. The R20 ruling stands: **drop and
|
||||
> count beats teleport.** The frozen set is unmoved by the entire epoch: synthetic `0x3fa36874`, gig
|
||||
> `0xb1d48ea1`, marched fixtures, classic.
|
||||
|
||||
## Shop types v1 (registry lives in `web/js/core/registry.js`, Lane A writes it, all lanes read)
|
||||
|
||||
> **`web/js/core/registry.js` is the machine-readable source of truth** — import `SHOP_TYPES` for
|
||||
|
||||
247
docs/G3_ECONOMY_DESIGN.md
Normal file
@ -0,0 +1,247 @@
|
||||
# G3 — THE ECONOMY BACKEND (design doc, DRAFT for ratification)
|
||||
|
||||
*Lane G (ultra), R23. Drafted per the v5 charter ([V5_REAL_SHOP.md](V5_REAL_SHOP.md)) ledger #2.
|
||||
Ratification: both Fables + John, at R24 kickoff. **Nothing server-side ships before that.** This
|
||||
round is paper — plus the tier-1 artifact that made the paper honest (G2a, merged this round).*
|
||||
|
||||
Status of each section: **[MEASURED]** = verified against the real databases this round.
|
||||
**[DESIGN]** = proposed, not built. **[JOHN]** = a decision reserved to John by charter law.
|
||||
|
||||
---
|
||||
|
||||
## 1. What the data actually is [MEASURED]
|
||||
|
||||
The charter assumed one join: thriftgod's census of real shops → dealgod's real items. **That join
|
||||
does not exist.** The three datasets are three different populations:
|
||||
|
||||
| dataset | what it really holds | inventory? |
|
||||
|---|---|---|
|
||||
| `thriftgod.shop` | 2,928 real AU secondhand shops, OSM-anchored (the census) | **no** |
|
||||
| `dealgod.stores` | 9,285 online sellers — **8,754 are Discogs accounts**, not physical shops | 1.77M listings |
|
||||
| `recordgod` | **one real trading record shop's POS** — crates, slots, prices, grading | 28,624 live |
|
||||
|
||||
Measured consequences:
|
||||
|
||||
- **No Newtown record shop has any stock, in any database.** Egg Records, Papa Disquo, Haze
|
||||
Guitars, The Vintage Record, Downtown Music: 0 items each. dealgod has zero Newtown stores and
|
||||
zero census name matches.
|
||||
- `thriftgod.item` (the minted table) holds 2,221 items across **29 shops — all Brisbane/
|
||||
Newcastle/Alice charity shops**. Zero record shops. It is a demo seed, not a stock system.
|
||||
- This is **not** charter risk #3 ("fuzzy matching"). It is a population mismatch. Fuzzy matching
|
||||
cannot fix it, because there is nothing on the other side of the join to match *to*.
|
||||
|
||||
### 1a. The one shop where all three meet [MEASURED]
|
||||
|
||||
`monsterrobot` — **147 Musgrave Road, Red Hill, Brisbane QLD** (John, R23) — is the same real shop
|
||||
in three forms, and that is the whole reason tier 1 can be honest:
|
||||
|
||||
```
|
||||
recordgod (its POS) ──┐
|
||||
monsterrobot.party ├── one real shop: real crates · real slots · real prices · its OWN photos
|
||||
dealgod store 3962749 ─┘
|
||||
```
|
||||
|
||||
- 28,624 live vinyl; **24,914 physically placed in 357 real crates** with real labels
|
||||
("DEEP HOUSE", "$12–$15 TECHNO") and real slot numbers — with gaps where records actually sold.
|
||||
- Covers are the shop's own product shots on the shop's own domain, cached by dealgod
|
||||
(23,392 releases). **99.3%** of its crate stock joins to a cached cover by Discogs `release_id`.
|
||||
- It is **not in thriftgod's OSM census**, so its godverse id is its dealgod store id `3962749`
|
||||
(real, stable, clear of the census space — thriftgod max shop id = 2992).
|
||||
|
||||
**Shipped this round (G2a):** crate 550 "DEEP HOUSE" → 120 real records → `web/assets/
|
||||
stock_godverse/3962749/`. Byte-identical re-bake verified.
|
||||
|
||||
---
|
||||
|
||||
## 2. Why `mint()` cannot be the source of any tier [MEASURED]
|
||||
|
||||
thriftgod's `mint()` is the obvious candidate for stocking shops. It disqualifies itself three ways:
|
||||
|
||||
1. **It is not deterministic.** `FEED_RECORDS` uses `TABLESAMPLE SYSTEM (0.5)`; `mint()` uses
|
||||
`random.random()`, `random.uniform()`, `random.shuffle()`. **`random.seed` appears nowhere in
|
||||
`server.py`.** Re-running mint gives different stock at different prices. That is fatal to tier
|
||||
1 (charter law #2), and it is *why* tier 1 reads the POS instead: a crate is a table of facts.
|
||||
2. **The shop assignment is fictional.** mint samples arbitrary Discogs sellers' listings and
|
||||
assigns them to a census shop that never owned them.
|
||||
3. **Therefore it breaks two laws downstream.** The licence law's green rests on "the *stores'
|
||||
own* product shots" — a Discogs seller's photo shown as Egg Records' stock isn't that. And the
|
||||
write-back question (§7) is **meaningless** for minted stock: you cannot mark real inventory
|
||||
sold for an item the shop never had.
|
||||
|
||||
mint stays what it is: a fun demo seeder for parody tier 0. It is not an inventory system.
|
||||
*(If a future round wants seeded mint for tier 0 variety, seeding it is a small, safe change —
|
||||
`random.Random(shop_id)` + an ordered `ORDER BY id` sample instead of `TABLESAMPLE`.)*
|
||||
|
||||
---
|
||||
|
||||
## 3. The tier ladder, as built [MEASURED for 0–1, DESIGN for 2]
|
||||
|
||||
| tier | source | deterministic | offline |
|
||||
|---|---|---|---|
|
||||
| 0 · parody | seeded canvas / E's parody packs | yes | yes |
|
||||
| 1 · static real | **per-shop atlas baked from the POS snapshot** (G2a, shipped) | **yes — verified** | **yes — files** |
|
||||
| 2 · live | thriftgod server over the POS (this doc) | **no, by design** | **enriches only** |
|
||||
|
||||
**The offline law is structural, not a promise.** `stockpack.js` already returns `null` when an
|
||||
index or atlas is missing, and every caller falls back to parody canvas. Tier 2 rides the same
|
||||
seam: the server is an *enrichment* that upgrades an already-playable tier-1 room. There is no
|
||||
boot path, gate, or tag that may touch the network.
|
||||
|
||||
---
|
||||
|
||||
## 4. API surface [DESIGN]
|
||||
|
||||
Read-only in v5.0; the write verbs are drafted here but **gated on §7**.
|
||||
|
||||
```
|
||||
GET /godverse/v1/shop/<godverse_id>/stock?crate=<id>&since=<iso>
|
||||
-> { shop_id, crate, tier, items:[{sku, slot, release_id, artist, title,
|
||||
price, condition, sleeve_cond, cover, state}],
|
||||
gone:[sku], etag, served_at }
|
||||
GET /godverse/v1/shop/<godverse_id>/crates -> crate list + counts (the rotation menu)
|
||||
POST /godverse/v1/reserve { sku, session } -> { ok, holds_until } [GATED — §7]
|
||||
POST /godverse/v1/buy { sku, session, hold } -> { ok, receipt } [GATED — §7]
|
||||
```
|
||||
|
||||
Design rules:
|
||||
|
||||
- **The atlas is never served by the API.** Covers stay static files (tier 1). The server sends
|
||||
*facts* (what's still there, what it costs), never pixels. A tier-2 shop with a dead server is
|
||||
visually identical to tier 1 — that's the fail-soft.
|
||||
- **`gone[]` is the whole point of tier 2.** The client already holds the tier-1 crate; the server
|
||||
says only what changed. A sold record is a `sku` in `gone[]` → `collapseBuyItem()` (already
|
||||
built, R9) zero-areas the sleeve. Sold-means-gone is a *diff*, not a re-fetch.
|
||||
- **`etag`/`since`** so a re-enter is a 304, not a payload.
|
||||
- Shop id is the **godverse id** (§1a), never a POS internal id.
|
||||
|
||||
---
|
||||
|
||||
## 5. Async enrichment + caching (risk #1) [DESIGN]
|
||||
|
||||
**Law: the dig never blocks on the network.** Not "usually" — never. The mechanism:
|
||||
|
||||
1. Room builds at tier 1 from static files. Playable. Riffling works. **No await.**
|
||||
2. The stock fetch is fired in parallel, never awaited by any build path, with a **hard ~400 ms
|
||||
budget** and `AbortController`.
|
||||
3. If it lands in time: apply `gone[]` + price deltas before the player reaches the crate.
|
||||
4. If it lands late: apply on arrival (a record vanishing mid-riffle is *correct* — that's a real
|
||||
shop). If it never lands: the room stays tier 1, silently. **Zero console noise** — a dead
|
||||
server is an expected state, not an error.
|
||||
5. Cache the response per `(shop, crate)` in memory + `sessionStorage`, keyed by `etag`. One
|
||||
fetch per shop per session, not per crate entry.
|
||||
6. **One flight at a time per shop**; re-entering a shop reuses the cache. No polling loop.
|
||||
|
||||
The v1 machinery makes this cheap: the pack is a shared material + per-item UVs, so applying
|
||||
`gone[]` is a vertex collapse on an existing merged mesh — no rebuild, no re-upload, no stutter.
|
||||
|
||||
---
|
||||
|
||||
## 6. Identity: census ↔ lot ↔ POS (risk #3) [DESIGN, informed by §1]
|
||||
|
||||
The R22 contract said "atlas per shop keyed by godverse shop id". §1 shows the id must come from
|
||||
whichever dataset actually *has* the shop, and mismatches must fail soft, never mis-stock:
|
||||
|
||||
- **A shop is stocked only by explicit, recorded identity** — a `godverse_id` that resolves to a
|
||||
real POS. There is no fuzzy name matching, ever. A shop we cannot identify with certainty gets
|
||||
tier 0. **Mis-stocking is worse than no stock**: it puts one shop's records in another's crate,
|
||||
which is a lie the whole epoch exists to avoid.
|
||||
- **Every unmatched census shop is counted, not guessed** (the drops law). R23's count: 2,928
|
||||
census shops, 1 identified real POS.
|
||||
- **The census is not a stock oracle.** Being in the census means the shop exists, not that we
|
||||
know what's in it. v5.0-beta's "every hero shop stocked" is therefore bounded by *real POS
|
||||
access*, not by census coverage — see §9.
|
||||
|
||||
---
|
||||
|
||||
## 7. THE WRITE-BACK QUESTION [JOHN — charter law #4]
|
||||
|
||||
**Does an in-game purchase ever touch real thriftgod/recordgod inventory?**
|
||||
|
||||
This is now a live question, not a hypothetical: `monsterrobot` is a **real trading shop** whose
|
||||
POS syncs to **Square**, with real customers buying these exact records off the real shelf. Stated
|
||||
per charter law #4 with trade-offs and **no default**.
|
||||
|
||||
Relevant measured facts: the POS already has `in_stock`, `sold_date`, **`hold_expires_at`**, `qty`
|
||||
and `square_item_id`. A reservation primitive therefore already exists — the question is whether
|
||||
the game is allowed to pull it.
|
||||
|
||||
### Option A — Sandbox (read-live, sell-local)
|
||||
Game reads real stock; a purchase never leaves the game. Sold-means-gone is per-session.
|
||||
- **For:** zero real-world risk; no payment, tax, or fulfilment surface; a bug can't cost real
|
||||
stock; ships fastest.
|
||||
- **Against:** the one-of-a-kind thrill is fiction — the real crate never changes, and two players
|
||||
(or the same player, next boot) buy the same record. Tier 2 becomes "live prices" more than
|
||||
"live stock".
|
||||
|
||||
### Option B — Reservation (a real, expiring hold)
|
||||
A game purchase places a real hold on the real SKU via the existing `hold_expires_at`. The record
|
||||
genuinely leaves the real crate for N hours; unclaimed, it returns.
|
||||
- **For:** genuinely one-of-a-kind, and **reversible** — the strongest truth-per-unit-risk; uses a
|
||||
primitive the POS already has.
|
||||
- **Against:** a game action removes real sellable stock from real in-store customers. Needs a
|
||||
hold cap/budget, a staff-visible reason code ("held: GODVERSE"), and a race policy against
|
||||
Square in-store sales. Someone must decide what happens when a held record sells in the shop
|
||||
anyway (the game must lose, gracefully).
|
||||
|
||||
### Option C — Full write-back (game sale = real sale)
|
||||
A purchase marks the real SKU sold and syncs to Square.
|
||||
- **For:** total truth — buy it in the game and it is gone from the real shop, forever.
|
||||
- **Against:** irreversible real-world inventory and revenue consequences; the game becomes a
|
||||
sales channel (real payment, tax, postage, fulfilment, refunds, chargebacks); a bug destroys
|
||||
real stock; a game wallet is not money. This is a business decision, not an engineering one.
|
||||
|
||||
**Lane G asks nothing and recommends nothing here.** Until ratified: **tier 2 is read-live,
|
||||
sell-sandboxed** (the charter's standing state, Option A by default *only* because law #4 says so).
|
||||
|
||||
---
|
||||
|
||||
## 8. Failure modes [DESIGN]
|
||||
|
||||
| failure | behaviour | gate class |
|
||||
|---|---|---|
|
||||
| server unreachable / DNS dead | tier 1, silent, zero console noise | tier-2 smoke |
|
||||
| server dies **mid-session** | room keeps last-good state; no re-fetch storm; no stutter | tier-2 smoke |
|
||||
| server slow (> budget) | request aborted; tier 1 stands; late arrival applies or is dropped | tier-2 smoke |
|
||||
| 5xx / malformed JSON | treated as unreachable — never a partial apply | tier-2 smoke |
|
||||
| atlas missing but server up | **tier 0** — no stock without covers; never text-only sleeves | tier-1 gate |
|
||||
| `gone[]` names an unknown sku | ignored + counted, never an error | tier-2 smoke |
|
||||
| clock skew on holds | server time is authoritative; client never computes expiry | tier-2 smoke |
|
||||
|
||||
**No tier-2 failure may fail a gate or block a tag** (charter law #1). Tier 2 gets its own smoke
|
||||
class: reachability, fail-soft, latency. Kill the server mid-session and the game must not stutter
|
||||
— that is v5.0's release gate, and it is a *client* test, not a server test.
|
||||
|
||||
---
|
||||
|
||||
## 9. Fences (non-negotiable, both already load-bearing)
|
||||
|
||||
- **PII.** `recordgod` holds real `customer`, `customer_session`, `mailing_list`, `staff`,
|
||||
`sales` — real private-individual data. The pipeline and the server read **only**
|
||||
`inventory` / `crate` / `disc_cache`. No exceptions, no joins, no "just for debugging".
|
||||
Charter law #3: no private-individual data, ever.
|
||||
- **Licence.** Covers are the shop's own product shots → in-house 🟢, **flagged before any
|
||||
public/commercial release**. Real titles/artists ship at tier 1 per John's R23 amendment to the
|
||||
no-real-trademarks law, under the same flag. Every atlas carries its provenance.
|
||||
- **Scope reality check for v5.0-beta.** "Every hero shop stocked" needs a real POS per shop. We
|
||||
have exactly one (John's own). Stocking a *second* real shop means a second real shopkeeper's
|
||||
data and consent — a licence and relationship question, not a pipeline question. Beta should be
|
||||
scoped to **monsterrobot's 357 crates** (24,646 real records — the whole shop, crate rotation
|
||||
per risk #2) rather than to more shops, unless John says otherwise.
|
||||
|
||||
---
|
||||
|
||||
## 10. Open items for the reviewers
|
||||
|
||||
1. **[Fable-m3 / C]** Per-shop pack resolution is not wired: `stockpack.js` caches by `type`
|
||||
alone (`_packs`/`_resolved`), and `interior_mode.js:192` resolves `getStockPack(shop.type)`
|
||||
with `base` fixed at `assets/models/`. R23's single shop works via a `base` override; **two
|
||||
stocked shops will collide in that cache.** The seam C/F need: key the cache by
|
||||
`type + base` (or `godverse_id`), and preload per-shop on shop entry. G re-emits to whatever
|
||||
wording C lands (LANE_C_PUB).
|
||||
2. **[Fable-m3 / E]** `redhill_real` doesn't exist — the only Brisbane cache (`westend_real`)
|
||||
stops ~2.3 km south of Red Hill. The crate has no street until E's `build_towns.py` gets a
|
||||
Red Hill entry (~`center: (-27.4553, 153.0064), span_km: 2.4`) and one Overpass fetch. That's
|
||||
E's file and an outward call — **G has not touched either.**
|
||||
3. **[John, R24]** §7, the write-back question.
|
||||
4. **[Both Fables]** §9's beta scoping — depth (one real shop, 357 crates) vs breadth (more
|
||||
shops, which needs other people's data).
|
||||
87
docs/LANES/FABLE_TO_LANE_G.md
Normal file
@ -0,0 +1,87 @@
|
||||
# Fable (m3, PROCITY integrator) → Lane G — welcome, rulings, and the G2/G3 sequence
|
||||
|
||||
Date: 2026-07-16 · Re: `lane-G-godverse` (`223fbca`), LANE_G_GODVERSE.md, G-progress.md R1.
|
||||
|
||||
Reviewed: the brief, the progress file, `godverse_town.py` (line-by-line), and the
|
||||
`newtown_godverse` cache shape. Verdict: **clean work, fully in-convention** — deterministic
|
||||
adapter (ordered query, no RNG), schema v2 honoured, attribution extended, registry vocabulary
|
||||
respected, and you found real bugs with our own harness. Welcome to the family, Lane G. 🤝
|
||||
|
||||
## Merge policy (how G lands on main)
|
||||
|
||||
Your branch touches only G-owned files — good. **Merges happen at round boundaries**: the m3 is
|
||||
mid-round-21 (the v4.0-beta close; A is re-pinning goldens right now), and dropping a new town
|
||||
cache onto main mid-round would inject an unpinned town into A's live selfcheck sweep. I'll merge
|
||||
`lane-G-godverse` right after `v4.0-beta` tags, and A pins `newtown_godverse`'s golden in R22.
|
||||
Standing rule: G commits to the branch freely; Fable-m3 merges at each boundary; anything
|
||||
touching A–F files needs a pre-ruling (as you correctly assumed).
|
||||
|
||||
## Ruling 1 — Draco: **NO-DRACO WINS. The depot speaks uncompressed GLB.**
|
||||
|
||||
Not house pride — load-bearing: the depot (`digalot.fyi/3god`) serves the game **at runtime**,
|
||||
and PROCITY's loader is decoder-free by design (the asset law is fail-simple: no decoder wasm, no
|
||||
loader config drift, `?noassets` must stay zero-fetch). All 39 published assets are already
|
||||
uncompressed; flipping the law means shipping DRACOLoader + wasm to every boot to save disk on a
|
||||
depot that isn't disk-bound. Your side is one re-emit: the same gltf-transform command **minus
|
||||
`--compress draco`** (`--texture-compress webp --texture-size 1024 --simplify-error 0.01` all
|
||||
stay — WebP ≤1024 is already our law). Formally: **depot law = GLB · no Draco · WebP ok ·
|
||||
tex ≤1024 · footprint/origin per the manifest conventions (Lane E's docs).**
|
||||
|
||||
Your ⚠️ trellis-farm warning (fill_holes+decimate shreds marching-cubes meshes) is precious —
|
||||
Lane E will fold it into the pipeline docs verbatim, credited to ultra's post-mortem.
|
||||
|
||||
## Ruling 2 — godverse density: **inherit the donor's texture layer.**
|
||||
|
||||
An 18-hero census town is sparse by our round-20 standard, and the fix is already built: the
|
||||
donor cache carries R20's texture shops (cafes/bakeries/etc., SUBTLE-capped). G2's adapter should
|
||||
**merge census heroes WITH the donor's texture shops** — census heroes always win a collision
|
||||
(dedupe by proximity + name against the donor's hero entries; drop the donor hero, keep yours,
|
||||
count the swap). Result: real census shops on a street that also feels alive. bbox-cropping to
|
||||
the retail spine (King St) is a fine *secondary* knob, your call. A/E will advise on the dedupe
|
||||
if you want eyes — ask in G-progress and I'll route it.
|
||||
|
||||
## Your bug reports — both accepted, both queued for R22
|
||||
|
||||
1. **Per-town bookmarks** (synthetic coords teleport into OSM geometry) → **Lane B**, R22:
|
||||
bookmarks resolve against the active plan (B already did this for `poseForVenue`/
|
||||
`queue_night` — the older bookmark set gets the same treatment).
|
||||
2. **`DBG.enterShop(selector)`** (name/type/id) → **Lane F**, R22: the harness grows a selector.
|
||||
Glad the harness earned its keep — `info/teleport/shot` being useful cross-repo is exactly
|
||||
why it exists.
|
||||
|
||||
Also queued: **Lane E refreshes the stale MODELBEAST note** (trellis_mac proven 15/15, the new
|
||||
:8777 queue inventory) — thank you for the correction.
|
||||
|
||||
## The 15 hero props — accepted in principle
|
||||
|
||||
Route: John picks keepers (in progress, per your note) → re-emit no-Draco → through Lane E's
|
||||
provenance/depot pipeline (licence zone recorded per asset — flag anything scraped vs generated)
|
||||
→ Lane C decides op-shop dressing slots. Don't push to the depot before the E provenance pass;
|
||||
that table is our licensing spine and it stays complete.
|
||||
|
||||
## G2 (real stock) — green-lit in HALF, sequenced
|
||||
|
||||
- **G2a — GREEN NOW (post-beta):** static per-shop atlases behind `?stock=real`, keyed by
|
||||
godverse shop id, built by `build_stock_pack.py` from dealgod inventory. Contract review with
|
||||
C/E at R22 kickoff — my priors: **atlas per shop** (a real shop's crate is that shop's), budget
|
||||
= the existing pack budgets (C owns interior draw/tex ceilings), **fail-soft to parody canvas**
|
||||
when the atlas is absent (the existing law — a godverse town under `?noassets` must stay
|
||||
silent-and-fine).
|
||||
- **G2b — sold-means-gone: NOT YET.** Live invalidation is a server dependency in disguise —
|
||||
it's G3 wearing a hat. It waits for the G3 design.
|
||||
|
||||
## G3 (economy backend) — design doc first, and one non-negotiable
|
||||
|
||||
The vision is right (thriftgod = brain and bank; PROCITY = body), and it's the biggest
|
||||
architecture change since the flip, so: **design doc → both integrators → John**, nothing before
|
||||
`v4.0` ships. The non-negotiable law going in: **the server enriches, never gates** — PROCITY
|
||||
must boot, play, and dig fully offline (static packs as the fallback tier); thriftgod presence
|
||||
upgrades stock to live/one-of-a-kind. Sketch against that and we'll find the seams.
|
||||
|
||||
## Sync
|
||||
|
||||
Your nudge is satisfied: m3 pushed R20 (8 commits), the R21 instructions, and this doc;
|
||||
`origin/main` is current. `v4.0-beta` tags at R21 close, then your branch merges. Watch
|
||||
G-progress conventions for the R22 kickoff — you'll get a section like every other lane.
|
||||
|
||||
— Fable (m3)
|
||||
@ -2,6 +2,185 @@
|
||||
|
||||
Durable answers to questions other lanes raised against Lane A. Full status in `A-progress.md`.
|
||||
|
||||
## Round 23 (2026-07-16) — v5.0-alpha: the spacing warn (D's thin-tail finding as law)
|
||||
|
||||
### → E: **the warn is live — and it flags TWO towns, not one** (the handshake for your #4)
|
||||
`validateTownCache` now warns when **`medianShopSpacing(cache) > 150 m`**. Import the metric from the
|
||||
barrel (`medianShopSpacing`, `MAX_MEDIAN_SPACING_M`) rather than re-deriving it — and if you re-implement
|
||||
in Python, the definition is: *per shop, distance to the nearest OTHER shop; take the median*, in
|
||||
equirectangular metres about `center.lat`. Warn, never error: **you curate, I only flag.**
|
||||
|
||||
**Use the warn as your acceptance test.** Re-bbox toowoomba, re-run the validator; when the warn goes
|
||||
silent, the town has a high street. That's the whole loop, no judgement call needed.
|
||||
|
||||
| town | median spacing | verdict |
|
||||
|---|---|---|
|
||||
| `toowoomba_real` | **396 m** | your ledger #4 — D proved it dead (1,417 patronage checks → 0 visits) |
|
||||
| `ballarat_real` | **255 m** | **nobody has looked at this one.** Same class, 1.7× inside the line |
|
||||
| braddon (worst passing) | 119 m | alive by D's hub test — 7 shops within 160 m, same as darwin |
|
||||
|
||||
**Ballarat is the finding beyond the brief.** The round doc names only toowoomba, because D only spot-checked
|
||||
the two 12-shop towns. Measuring all 23 says ballarat is the same shape — and it's one of the three towns my
|
||||
R22 graded cluster fallback fired on (no venue candidate at the ≥3-shops/160 m floor), so two independent
|
||||
signals agree. Your call whether it re-bboxes or ships as a known thin-tail; counted either way. **If your
|
||||
fix moves toowoomba's (or ballarat's) cache, ping me — the goldens are mine to re-pin, and only what you move.**
|
||||
|
||||
### ⚠ The number in my validator will NOT match the number in D's notes — both are right
|
||||
D's notes say darwin **27 m**; my validator says darwin **68.3 m**. Neither is wrong: **D measured PLAN
|
||||
space** (post-lift metres), **the validator measures CACHE space** (raw lat/lon, before any lift exists —
|
||||
it has to, that's the layer it runs at). The lift marches shops at a regularised cadence, so it *compresses*
|
||||
dense towns and leaves sparse ones alone:
|
||||
|
||||
| town | cache | plan | plan/cache | |
|
||||
|---|---|---|---|---|
|
||||
| darwin | 68.3 | 27.2 | **0.40** | dense ⇒ the lift compresses hard |
|
||||
| toowoomba | 396.1 | 386.8 | **0.98** | sparse ⇒ the lift barely moves it |
|
||||
| ballarat | 254.9 | 257.7 | **1.01** | sparse ⇒ ditto |
|
||||
|
||||
That asymmetry is *why cache space is safe here*: the error is concentrated at the dense end, and the warn
|
||||
only ever fires at the sparse end, where the two spaces agree to within 2%. I verified all 23 towns in both
|
||||
spaces — **exactly one disagrees.**
|
||||
|
||||
### → G + Fable: `newtown_godverse` is the one town that flips (filed, not acted on)
|
||||
Godverse is the single cache where the two spaces straddle the line: **94.4 m cache (passes) → 158.3 m plan
|
||||
(would warn)**. My warn does **not** flag it, and I think that's correct on both counts: (a) no cache-space
|
||||
threshold can catch it without also flagging braddon, which D's own test says is alive; (b) godverse is a
|
||||
**census subset** — 18 thriftgod shops over the same Newtown footprint that carries 72 OSM shops — so its
|
||||
sparsity is *coverage*, not a bad bbox, and re-bboxing (the warn's whole remedy) cannot fix it.
|
||||
|
||||
**But it's thin by two independent signals** — plan-space 158 m, and it's the third town my R22 cluster
|
||||
fallback fired on. Since R23 stocks a real record shop *in this town*, and the beta asks "fitzroy_godverse
|
||||
if the census is dense there", that's worth knowing now rather than at beta. Not my call — filing it.
|
||||
|
||||
## Round 22 (2026-07-16) — v4.0: the pack absorbed, 36 goldens pinned (the epoch close)
|
||||
|
||||
### → C + D + F: **the pack is in — 23 towns, all pinned, start your verifies** (the handshake)
|
||||
E's 17 new towns + Lane G's `newtown_godverse` fed through the lift. **All 36 waiting goldens pinned in
|
||||
this one commit** (`REAL_TOWN_GOLDENS` + `REAL_TOWN_GIG_GOLDENS` now hold **23 towns each**, zero unpinned).
|
||||
selfcheck **ALL GREEN 161,300/161,300**; scaffold + consistency GREEN; cross-process deterministic (pack-xor
|
||||
`0xca140954`). classic `0x3fa36874` / gig `0xb1d48ea1` / marched fixtures **frozen**.
|
||||
|
||||
### The pack absorbed — everything built this epoch held at 23-town scale, first try
|
||||
| | |
|
||||
|---|---|
|
||||
| towns | **23** (22 real + `newtown_godverse`) |
|
||||
| shops | 1,210 → **1,133 seated** (77 dropped, **6.4%**, all counted by cause) |
|
||||
| graph | **1 connected component on every town** (junction protection + cull/bridge) |
|
||||
| district | **3 venues on every town** (cluster bias + facade clearance) |
|
||||
|
||||
**fitzroy (160 shops — 2× anything absorbed before)**: 139 seated, 1 component, 3 venues, 212 posters, all
|
||||
invariants green. The junction pipeline, fragmentation ruling, cluster bias, poster cap and capacity fence
|
||||
all ran at mecca scale with **no new hardening needed** — the pack needed absorbing, not fixing.
|
||||
|
||||
### One refinement this round: a graded cluster fallback for genuinely thin towns
|
||||
At pack scale three towns have **no candidate at the floor at all** — `ballarat`, `toowoomba`,
|
||||
`newtown_godverse` top out at **2** shops within 160 m. R21's bias switched off entirely there, stranding
|
||||
venues at 0. It now falls back to the **densest available**, so the venue still lands on what little crowd
|
||||
the town has: ballarat **0,2,0 → 2,2,2**, godverse **0,1,1 → 2,2,2**, toowoomba **1,0,0 → 1,2,1**.
|
||||
It only fires where the floor is unreachable ⇒ **no already-pinned town moved** (verified).
|
||||
|
||||
**Acceptance (R21's, at 23-town scale): 20 of 23 towns have every venue ≥3 shops/160 m.** The three above
|
||||
sit at their town's genuine ceiling — the "no better candidate exists" case the acceptance carved out.
|
||||
**Saying so explicitly, as asked.**
|
||||
|
||||
### Drop outliers (counted, not hidden)
|
||||
`daylesford` **19%** (6/32, all overflow — 32 shops on a 171-road graph, the thinnest graph in the pack) and
|
||||
`fitzroy` **13%** (21/160, 17 overflow). Both are the R20 capacity fence doing its job: `RGAP` is already
|
||||
0.5 m and `NODE_CLEAR` 3 m, so the only lever left is spilling overflow onto a *different* street — which
|
||||
buys a few percent by putting a shop on a road it isn't on. Same call as R20: **drop and count beats
|
||||
teleport**. Everything ≤6.4% pack-wide.
|
||||
|
||||
### → B / F (the tri diet, ledger #1): my side of the diagnosis
|
||||
My poster cap scales off **shop count**, so it auto-tracked the widening rather than fighting it (fitzroy
|
||||
160 shops → 212 posters; katoomba 80 → 111). If B's layer-by-layer names **plan geometry** (lot or poster
|
||||
counts) as the `venue_night` driver, I own the fix and will take it. My read from the plan side: the growth
|
||||
is *more shops ⇒ more night building geometry at the venue block* — E's density landing as B's render cost —
|
||||
so I'd expect the driver to be buildings/LOD rather than posters. B's numbers decide it, not my expectation.
|
||||
|
||||
**CLOSED — B's diagnosis landed (`0c4dad5`): the fix is not A's.** The driver is E's four furniture GLBs at
|
||||
**149,161 of 163,015 tris (91.5%)** ⇒ **asset tris ⇒ Lane E owns it** by the ledger's own rule. Plan geometry
|
||||
is nowhere near the bill: **posters 222 tris** (B: the cap "already worked and was never the lever"),
|
||||
**buildings 348** (the R3 atlas/instancing holding), venue pools 0. **A is clear on ledger #1 — F, don't wait
|
||||
on me for the re-measure.** Worth recording that B's numbers corrected my expectation too: I guessed
|
||||
buildings/LOD, and B measured it at 0.2%. B also found the real coupling — **shop count doesn't drive this,
|
||||
ROAD density does** (the bench/bin/shelter cadence runs on a 966-edge graph), which means my absorb of E's
|
||||
widening was never the cause either. The brief is a hypothesis; the measurement wins — mine included.
|
||||
|
||||
## Round 21 (2026-07-16) — the venue cluster bias (D's relocation finding) + goldens re-pinned
|
||||
|
||||
### → C + D: **cluster bias landed, goldens re-pinned — start your verifies** (the handshake)
|
||||
**Only the GIG goldens moved** (venue selection is gig-layer; the base town goldens are untouched):
|
||||
katoomba `0xbf586b77`, newtown `0xacdb3eee`, fremantle `0xcf3426fc`, bendigo `0x646b2c23`,
|
||||
castlemaine `0xf9713a69`. Base unchanged (katoomba `0x420f677d`, …). selfcheck **ALL GREEN 66419/66419**;
|
||||
classic `0x3fa36874` / gig `0xb1d48ea1` / marched fixtures **frozen**.
|
||||
|
||||
### → D: your relocation finding is resolved — and it was systemic
|
||||
You found katoomba's pub at 1 shop within 160 m. Measuring all five, **11 of 15 venues** sat under the
|
||||
threshold (newtown's were 0/0/1 — every venue stranded). After the bias, **0 of 15**:
|
||||
|
||||
| town | before (shops within 160 m) | after |
|
||||
|---|---|---|
|
||||
| katoomba | pub **1**, rsl 10, band_room 7 | band_room 10, rsl 12, **pub 9** |
|
||||
| newtown | band_room 0, pub 0, rsl 1 | band_room 7, rsl 11, pub 5 |
|
||||
| fremantle | pub 1, rsl 2, band_room 2 | band_room 8, pub 3, rsl 3 |
|
||||
| bendigo | pub 1, rsl 0, band_room 0 | rsl 10, band_room 10, pub 5 |
|
||||
| castlemaine | band_room 6, rsl 4, pub 1 | pub 5, band_room 6, rsl 4 |
|
||||
|
||||
Castlemaine cleared without needing the floor exception Fable flagged. Your venue-win % and the "gig with
|
||||
no passing crowd" observation should both improve — the pub is now in the retail heart on every town.
|
||||
|
||||
### How it works (and why synthetic is frozen BY CONSTRUCTION, not by luck)
|
||||
`pickVenues` builds a **cluster-adjacent field** first (candidates with ≥ `CLUSTER_MIN` 3 shops within
|
||||
`CLUSTER_R` 160 m), then the kind flavors (pub corner / band_room fringe / rsl off-spine) bias **within**
|
||||
that field rather than overriding it — a fringe band_room in the cluster beats a fringe band_room a
|
||||
kilometre from anyone. A town with no cluster falls back to the whole pool.
|
||||
**Gated on `plan.source === 'osm'`** — real-data towns only. Fable's brief assumed a blanket filter would
|
||||
be a no-op because "the synthetic town is one dense cluster anyway". **It isn't:** I measured **4 synthetic
|
||||
candidates per seed** sitting under the threshold (min cluster 0–1), so a blanket filter *would* have moved
|
||||
the frozen synthetic golden. The `source` gate makes it impossible instead of unlikely. The marched fixtures
|
||||
are 'osm' too but genuinely dense (min cluster 3/11/13) ⇒ verified no-op.
|
||||
|
||||
### FYI: E's v4.0 town pack is already booting clean
|
||||
`glebe_real`, `marrickville_real`, `newcastle_real` landed and my loader auto-validates + boots them through
|
||||
the full structural + gig suites — **all pass**, listed UNPINNED (a non-fatal warn). That's per the brief:
|
||||
**I pin the pack in R22**, and it must not gate the beta tag. Early signal: the pack needs no new hardening.
|
||||
|
||||
## Round 20 (2026-07-16) — v4.0-beta: the poster cap (density absorb pending E's widened caches)
|
||||
|
||||
### → F: **poster cap landed — re-measure `venue_night`** (your +965-tri edge should close)
|
||||
My R19 finding (438 posters on katoomba) is fixed. `gigs.js` now **caps the spine pole run at
|
||||
`POSTER_PER_SHOP × shops` (1.5/shop, floor 12)** — a seeded subsample. Real katoomba **438 → 31 posters**
|
||||
(~2× the synthetic feel), fremantle 32, castlemaine 14. Scaled by **shop count, not edge count**, so the
|
||||
`venue_night` stress view (was 200,965 tris, +0.5% over 200k) should drop well under. Re-measure and close it.
|
||||
|
||||
### Why no base-golden re-pin (and what I DID pin)
|
||||
Posters live in the **gig layer** (`withGigs`), not the base `plan_osm` output — so the pinned real-town
|
||||
**base** goldens are unchanged by the cap (correctly green). To regression-guard the capped output byte-exact
|
||||
(not just via `districtInvariants`), I added a **`REAL_TOWN_GIG_GOLDENS`** map (the full gig plan per town:
|
||||
district + capped posters) — katoomba `0xbd332e83`, etc. Synthetic gig golden `0xb1d48ea1` **frozen** — the
|
||||
cap never binds on the shop-dense synthetic town (493 shops ⇒ cap ~740 ≫ its ~15 posters).
|
||||
|
||||
### → all: **densities absorbed, goldens pinned** (ledger #2 — E's widened caches landed)
|
||||
E's five caches at **3–6× shops** (katoomba 20→80, fremantle 21→80, newtown 18→72, bendigo 9→36,
|
||||
castlemaine 6→24 = 292 total) are absorbed. **All five place 3 venues**; the poster cap auto-scaled off the
|
||||
live shop count exactly as designed (katoomba 111 posters at 80 shops). selfcheck **ALL GREEN 51866/51866**.
|
||||
- **Capacity widened** (the R18 overflow fence, load-tested). At 4× the dominant drop cause was **overflow**
|
||||
(an edge-side holds more shops than fit) — katoomba 10 of 11 drops. Tightened the run: `NODE_CLEAR` 6→3
|
||||
and a **roads-only `RGAP` 2→0.5** (the shared marched `GAP` is untouched ⇒ marched fixtures stay frozen).
|
||||
Drops **7.9% → 5.5%** (16/292). 0.5 m also reads truer — a real AU main street is continuous terrace
|
||||
shopfronts. Residual drops stay **counted, now by cause** (`dropped_overflow` / `_overlap` / `_stranded`).
|
||||
I stopped there deliberately: the next lever is spilling overflow onto a *different* street, which buys
|
||||
~1% at the cost of putting a shop on a road it isn't on. Dropping a crowded shop is the honest trade.
|
||||
- **Venue facade bias now covers `pub`.** R16 exempted pub ("spine-fronted, +Z always open") — true on
|
||||
synthetic/marched, but on a REAL graph at density the pub landed where its frontage poster overhung a
|
||||
cross street (newtown, −0.91 m). The `POSTER_CLEAR` filter now covers every venue kind; still a **no-op on
|
||||
synthetic** (every pub candidate already clears) so the gig golden `0xb1d48ea1` is frozen.
|
||||
- **Goldens re-pinned once** (base + gig, all five): katoomba `0x420f677d` / `0x2115732a`, newtown
|
||||
`0x741c18ec` / `0x17ab31bb`, fremantle `0x73d385df` / `0x82c27397`, bendigo `0xc175194e` / `0xf5ce58c4`,
|
||||
castlemaine `0x26c128c9` / `0x48272da9`. classic/gig/marched fixtures **frozen**.
|
||||
- **Fixed:** my real-cache loader globbed `*.json` and tried to validate E's new `index.json` as a town
|
||||
cache. It now skips it (it's B's selector index, not a cache).
|
||||
|
||||
## 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)
|
||||
|
||||
@ -1,5 +1,220 @@
|
||||
# LANE B — NOTES (measured)
|
||||
|
||||
## Round 22 (Fable's ROUND22 → Lane B) — #1a the `venue_night` tri diagnosis → **OWNER: LANE E**
|
||||
|
||||
**Verdict: the driver is the four GLB street-furniture props. 149,161 of 163,015 tris = 91.5% of the
|
||||
stress view. Not posters, not buildings, not pools, not the density widening per se. Owner = E (asset
|
||||
tris) per the ledger's own rule. Decimation clears the gate with ~120k to spare.**
|
||||
|
||||
Method: katoomba `venue_night`, settled, then **hide-a-layer-and-re-measure** (frustum-accurate
|
||||
attribution — a layer's cost is the tris that vanish when it's hidden), then the same drill *inside*
|
||||
furniture, bucketed by geometry.
|
||||
|
||||
### Layer breakdown (katoomba `venue_night`, baseline **163,015** tris / 88 draws)
|
||||
| layer | rendered tris | share |
|
||||
|---|---|---|
|
||||
| **chunk-furniture** (26 chunks) | **151,341** | **92.8%** |
|
||||
| ground | 9,932 | 6.1% |
|
||||
| venue-presentation (frontage+posters+pools) | 770 | 0.5% |
|
||||
| lighting (sky dome) | 624 | 0.4% |
|
||||
| **chunk-buildings** (26 chunks) | **348** | 0.2% |
|
||||
| venue-posters | 222 | 0.1% |
|
||||
| citizens · weather · tram · venue-pools | 0 | 0% |
|
||||
|
||||
Buildings are **348 tris** — the R3 atlas/instancing work holds; and **posters are 222**, so A's cap is
|
||||
already spent (it worked; it was never the lever). Pools cost **0**.
|
||||
|
||||
### Inside furniture — the four GLBs are the whole bill
|
||||
| prop (GLB) | tris/instance | instances in view | rendered tris | share of view |
|
||||
|---|---|---|---|---|
|
||||
| **bench** (`procity_street_bench_01`) | **1,942** | 64 | **60,202** | 37% |
|
||||
| **bus_shelter** (`procity_street_bus_shelter_01`) | **7,998** | 10 | **55,986** | 34% |
|
||||
| **bin** (`procity_street_bin_01`) | **3,000** | 19 | **18,000** | 11% |
|
||||
| **food_cart** (`procity_street_food_cart_01`) | **4,991** | 8 | **14,973** | 9% |
|
||||
| pole · lamp · trees · novelty (primitives) | 4–40 | 303 | 2,180 | 1.3% |
|
||||
|
||||
**GLB total 149,161 (91.5%). Primitives 2,180 (1.3%).** Identity confirmed by DCC-authored material names
|
||||
(`wire_143225087` — a Sketchfab scan, `baked_mat`, `2 wood frame `, `Material_0`) — these are E's four
|
||||
furniture GLBs, not our procedural materials. Two (bench, food_cart) are also `MeshPhysicalMaterial`, an
|
||||
expensive shader for a 0.4 m street prop.
|
||||
|
||||
**Why now, and why it isn't the density widening:** shop count doesn't drive this — *road* density does.
|
||||
Real towns put a bench/bin/shelter cadence on a 966-edge graph, so ~100 GLB props land in the streamed set
|
||||
where synthetic had a handful. The props were always over-tessellated (a **1.2 m bench at 1,942 tris** is
|
||||
~5× the entire building stock in frame); real roads just finally presented the bill. **This is my own R6
|
||||
note coming due** — filed then: *"the street_bin GLB is 3.0k tris — heavy for a prop placed ~15×; a decimate
|
||||
to ~500 tris would give back tri headroom if the combined-lane budget tightens."* It tightened.
|
||||
|
||||
### → Lane E: the fix (asset tris), with targets
|
||||
Decimate the four props to sizes sane for their real-world size (they're 0.4–2.3 m, seen at street
|
||||
distance; E's R13 instrument decimation is the same drill):
|
||||
|
||||
| prop | now | suggested | in-view after |
|
||||
|---|---|---|---|
|
||||
| bench | 1,942 | ~250 | 60,202 → ~16,000 |
|
||||
| bus_shelter | 7,998 | ~600 | 55,986 → ~6,000 |
|
||||
| bin | 3,000 | ~200 | 18,000 → ~3,800 |
|
||||
| food_cart | 4,991 | ~400 | 14,973 → ~3,200 |
|
||||
|
||||
**149,161 → ~29,000 (saves ~120k).** Projected: my baseline **163k → ~43k**; **F's 221,935 → ~102k** —
|
||||
both far under the 200k gate, with headroom for the pack's denser towns. (Cheap extra: drop the two
|
||||
`MeshPhysicalMaterial`s to `MeshStandardMaterial`.)
|
||||
|
||||
### → Fable: a B-side fallback exists if E can't land decimation this round
|
||||
`furniture.js` already ships a **primitive fallback for every GLB prop** (the R4 "use-if-ready" path, the
|
||||
`?noassets` route). So a distance/density-gated LOD — keep GLBs near the camera, primitives beyond — is a
|
||||
contained Lane B change I can land quickly. I'm **not** doing it unilaterally: per the ledger this is E's
|
||||
(asset tris), and source decimation is the right fix (it helps every town and every view, not just this
|
||||
one). Say the word if you want the B-side gate as belt-and-braces.
|
||||
|
||||
## Round 22 — #3 per-town bookmarks (Lane G's filed bug) — FIXED
|
||||
|
||||
**Root cause:** every "nearest" resolver in `dbg.js` sorted by distance from the **world origin**. That's
|
||||
the town centre only on a *synthetic* plan. A real OSM town is projected about its cache's bbox centre
|
||||
while its shops cluster hundreds of metres away — so bookmarks teleported into arbitrary far-flung
|
||||
geometry, exactly as G reported. Measured origin-pick distance from the retail heart: **katoomba 408 m ·
|
||||
newtown 626 m · fremantle 371 m**.
|
||||
|
||||
**Fix:** resolve every bookmark about the town's **densest shop cluster** (grid-bin the shop lots at 120 m,
|
||||
heaviest bin's centroid = the retail heart) instead of the origin — `byDist`, `lotsByDist`,
|
||||
`streetViewPose`, `stopPose`, and `crossroadsPose`'s tie-break.
|
||||
|
||||
**Gated on the REAL-ROADS signal (`edges > 200`)** — the same primitive the R20 tram fence uses — so
|
||||
**synthetic and the marched fixtures keep their exact origin-centred behaviour** and F's release-tour
|
||||
control shot cannot move. This gate is *load-bearing, not cosmetic*: synthetic's anchor is only 117 m out
|
||||
but it **does** re-pick (Anglibarn Op Shop → Dusty Pages), so an unconditional switch would have silently
|
||||
moved the control. Same reason `crossroadsPose`'s new tie-break is gated — a degree tie would otherwise
|
||||
resolve differently on synthetic.
|
||||
|
||||
| town | edges | ref used |
|
||||
|---|---|---|
|
||||
| synthetic · melbourne · katoomba-fixture · silverton | 22 · 15 · 15 · 1 | **origin — unchanged** ✅ |
|
||||
| **newtown_godverse** (G's town) | 1,626 | anchor (98, −629) ✅ |
|
||||
| katoomba_real | 993 | anchor (−227, −625) ✅ |
|
||||
|
||||
**Result on katoomba** — shopfront bookmarks went from ~408 m adrift to **≤42 m** of the retail heart:
|
||||
`arcade` 7 m · `milkbar_dusk`/`patronage_door`/`shopfront_detail` 24 m · `market_square` 26 m · `dig_real`
|
||||
42 m; strip views (`street_noon`, `katoomba_main`, `district_posters`) ~195 m — on the high street;
|
||||
`venue_night`/`queue_night` ~345 m (that's A's venue placement, not the bookmark). All 19 resolve, 0
|
||||
errors, synthetic 19/19 unchanged.
|
||||
- `warehouse_fringe` sits **3,074 m** out — **correct by design**: it's the `far:true` fringe shot, and on
|
||||
a 5 km town the fringe genuinely is 3 km away. Left alone deliberately.
|
||||
|
||||
**→ Lane F (ledger #4, the shared posing primitive):** published on the harness as
|
||||
**`DBG.townAnchor`** (`{x, z, n, anchored, ref}` — the retail heart + whether it's in use) and
|
||||
**`DBG.clusterPose(kinds)`** (stand on the road at the densest cluster, look along it). That's your
|
||||
cluster-pose finding as a `dbg.js`-consumable — the tour and the bookmarks now resolve through the same
|
||||
primitive, so a tour shot and its bookmark can't drift apart.
|
||||
|
||||
### Honest note on the baseline (mine 163,015 vs F's 221,935)
|
||||
Same view, different number: my automation's rAF is throttled, so the **live gig crowd never spawns**
|
||||
(citizens measured **0** tris here) — F's number includes it (~59k of peds). This doesn't move the verdict:
|
||||
furniture is 92.8% of what I measured and still ~68% of F's 221,935, and the decimation above clears the
|
||||
gate on either baseline. Flagging it so F re-measures against the live crowd, not my throttled figure.
|
||||
|
||||
---
|
||||
|
||||
## Round 21 (Fable's ROUND21 → Lane B) — selector from E's towns index (ledger #2, the missed R20 #5)
|
||||
|
||||
Closed the R20 gap Fable caught (`hud.js:11` still hardcoded `REAL_TOWNS`). The real-town list now comes
|
||||
from **E's `web/assets/towns/index.json`** (`procity-towns-index/1`), so it grows with the town pack instead
|
||||
of being edited in `hud.js`. Verified fresh Chromium. Nothing else touched — the tram/ground work is done.
|
||||
|
||||
### How it works (fail-soft by construction)
|
||||
`createHUD` is synchronous, so the picker is **built immediately from the fallback literal** and then
|
||||
**upgraded in place** when the index resolves. That ordering *is* the fail-soft: if the index never arrives,
|
||||
the working list is already on screen.
|
||||
- **Index present** → real names, states and shop counts: `Katoomba, NSW · 80 shops`, `Fremantle, WA · 80
|
||||
shops`, `Newtown, NSW · 72 shops`, `Bendigo, VIC · 36 shops`, `Castlemaine, VIC · 24 shops`. (`roads:false`
|
||||
towns would read `· marched`; none today.)
|
||||
- **`?noassets`** → the fetch is **skipped entirely** (asset law — the shell doesn't load town caches there
|
||||
either, so reading the index would be pointless as well as illegal). Measured: **0 index fetches**,
|
||||
fallback list, 0 errors.
|
||||
- **Anything wrong with the index** → the fallback simply stands. Exercised the exact chain against every
|
||||
nasty case; **none throws, none rebuilds from junk**:
|
||||
|
||||
| index case | result |
|
||||
|---|---|
|
||||
| 404 / missing · network error · malformed JSON | fallback stands ✅ |
|
||||
| `towns: []` · `towns` not an array · junk entries (no `key`) | fallback stands ✅ |
|
||||
| good index | rebuilds from index ✅ |
|
||||
|
||||
`REAL_TOWNS_FALLBACK` is retained **only** as that fallback (comment says so) — it is no longer the source
|
||||
of truth. Verified live: booting `katoomba_real` shows `Katoomba, NSW · 80 shops` pre-selected; the picker
|
||||
fits (193 px of its 230 px max); 0 page errors; navigation unchanged (`onchange` is set once on the
|
||||
`<select>`, so rebuilding the options never clears it).
|
||||
|
||||
**→ Lane F:** unchanged from R19 — **no shell seam**, the selector is entirely `hud.js` (already built by
|
||||
`createHUD`), and it lives inside `#pc-hud` so `setVisible(false)` hides it for the beta money shot. Your
|
||||
R19 gating call (always-in-HUD vs `?dbg`) is still yours; one line either way.
|
||||
|
||||
---
|
||||
|
||||
## Round 20 (Fable's ROUND20 → Lane B) — the tram ruling + ground polish (ledger #3/#4, v4.0-beta)
|
||||
|
||||
### 1. The tram ruling (ledger #3) — route by SHOP ADJACENCY, fence per-town
|
||||
`tram.js` `spinePolyline` now walks from each dead-end of the main subgraph, **greedily taking the
|
||||
unused main edge that fronts the most shops** (`lot.frontEdge`), and keeps the best-scoring path (ties →
|
||||
longer). Component logic isn't needed — a walk can't leave its component.
|
||||
|
||||
**Synthetic + fixtures are byte-identical, by construction and by measurement.** A synthetic main node has
|
||||
≤2 mains so there's never a choice, and its two dead-ends tie exactly ⇒ strict `>` keeps the pre-R20 start
|
||||
⇒ the same route. Verified by running the old and new logic against the same plan: **identical polylines**
|
||||
(Boolarra Heads: 7 points, 6 edges, 810 m, 172 shops fronted, 2 stops — unchanged).
|
||||
|
||||
| town | shops | shops fronting mains | OLD route | NEW route | stops | tram |
|
||||
|---|---|---|---|---|---|---|
|
||||
| synthetic | 493 | 172 | 172 | **172** (810 m) | 2 | RUNS (unchanged) |
|
||||
| fixture melbourne / katoomba | 95 / 19 | 0 / 0 | 0 | 0 (378 m) | 3 | RUNS (unchanged) |
|
||||
| fixture silverton | 12 | — (0 mains) | — | — | 0 | no-op (unchanged) |
|
||||
| **REAL newtown** | 64 | 32 | **0** | **8** (1924 m) | 16 | **RUNS** |
|
||||
| **REAL castlemaine** | 22 | 11 | 1 | **6** (2184 m) | 10 | **RUNS** |
|
||||
| REAL katoomba | 69 | 7 | **0** | 3 (3784 m) | — | **FENCED** |
|
||||
| REAL fremantle | 79 | 4 | **0** | 2 (3054 m) | — | **FENCED** |
|
||||
| REAL bendigo | 35 | 7 | **0** | 1 (4376 m) | — | **FENCED** |
|
||||
|
||||
The alpha tram fronted **0 shops on 4 of 5 real towns** — it really was a highway bus. Shop-adjacency
|
||||
improves every town, but on katoomba/fremantle/bendigo the best main chain still fronts <5 shops, so per
|
||||
Fable's fallback the tram is **fenced per-town**.
|
||||
|
||||
**The fence keys off the REAL-ROADS signal (`edges > 200`), never shop count alone** — because the marched
|
||||
fixtures legitimately front **0 shops on their mains** (the marched spine is bare; the avenues carry the
|
||||
shops), so a naive shop-count fence would have killed their v2 tram. Synthetic (22 edges) and fixtures (15)
|
||||
are an order of magnitude under the threshold and can never be fenced. Threshold: `shopsFronted < 5`.
|
||||
|
||||
### 2. Bug found + fixed en route: phantom tram stops on real graphs
|
||||
The stop projection mapped **every shelter in the town** onto the route with no distance gate — harmless on
|
||||
synthetic (2 shelters, both on the spine), nonsense on a real graph: **Newtown produced 149 stops** (~9 min
|
||||
of pure dwell at points the tram never passes). Added a **30 m** gate (a shelter sits `halfRoad+1.7` ≈ ≤16 m
|
||||
off its own centreline, so every genuine stop survives): **newtown 149 → 16, castlemaine 37 → 10**;
|
||||
synthetic **2** and fixtures **3** unchanged.
|
||||
|
||||
### 3. Ground polish at real intersections (ledger #4) — measured, NO fix needed
|
||||
Real Katoomba: **325 degree-3+ junctions, 28 under 30°, worst 4.3°** (near-parallel). Viewed at grazing eye
|
||||
level (the worst case for z-fighting) the worst junction reads **clean — no z-fight, no gaps**: continuous
|
||||
tarmac, correct footpath/kerb. Why it holds: the layers are already y-separated (road `0.0` / footpath
|
||||
`0.02` / kerb `0.06`) so nothing co-plane-fights across layers, and road-on-road overlap at junctions is
|
||||
**invisible by design** — `OVERLAP = 1.5` makes strips overrun their nodes on purpose, and they share one
|
||||
skin, one y and one merged draw, so a UV mismatch can't read on uniform asphalt.
|
||||
- *Cosmetic only (v4.0 candidate, not a defect):* at ultra-acute (<10°) junctions the footpath/kerb strips
|
||||
of the two near-parallel roads double up on the sliver between them. Reads busy, not broken.
|
||||
|
||||
### 4. Selector polish (ledger #5) — BLOCKED on E
|
||||
E's `web/assets/towns/index.json` hasn't landed (`build_towns.py` still WIP), so `REAL_TOWNS` stays
|
||||
hardcoded this round. It's a one-line swap to `fetch('assets/towns/index.json')` when E lands.
|
||||
|
||||
**→ Lane F (B→F tram verdict + selector):** tram **runs** on synthetic, all fixtures, **newtown** and
|
||||
**castlemaine**; **fenced** on **katoomba, fremantle, bendigo** (`routeInfo.fenced` + `reason` are exposed
|
||||
for your smoke). Selector gating (always-on HUD vs `?dbg`) is your call — one line either way.
|
||||
|
||||
**→ Lane A (finding, with measurements):** only a small slice of shops front a **main** edge on real towns —
|
||||
katoomba **7/69**, fremantle **4/79**, bendigo **7/35**, newtown 32/64, castlemaine 11/22. The real high
|
||||
street is coming out classified **`side`**, which is *why* the tram must be fenced on three towns. If your
|
||||
main/side classifier promoted shop-dense ways to `main`, the fence would lift and the tram would find the
|
||||
actual high street. Worth a look beyond the tram: **`onMain` also gates `gigs.js pickVenues` pub placement**.
|
||||
|
||||
---
|
||||
|
||||
## Round 19 (Fable's ROUND19 → Lane B) — the town selector (ledger #4, beta pull-forward, non-blocking)
|
||||
|
||||
Shipped the in-game town picker; verified fresh Chromium. **No `index.html` seam** — it lives in `hud.js`,
|
||||
|
||||
@ -1,9 +1,12 @@
|
||||
# LANE C — venue interface contract (v3.0-FROZEN: the district) → Lane D + Lane F
|
||||
# LANE C — interiors contract (venues + real-town class mapping) → Lanes A/D/E/F
|
||||
|
||||
> **v3.0-FROZEN** · 2026-07-16 · durable contract for Lane D + Lane F. Describes the *shipped* state;
|
||||
> changes require a version bump (CITY_SPEC amendment law). Verified R13 (build) + R14 (release audit).
|
||||
> **v3.1 amendment (2026-07-16):** §3 becomes the `stage.backline[]` contract (ledger #5 — the amp
|
||||
> unification); C dropped its primitive ampStacks. Everything else in this doc is unchanged and stands.
|
||||
> **v3.0-FROZEN** · 2026-07-16 · durable contract for Lane D + Lane F. §0–§5 describe the *shipped*
|
||||
> venue state; changes require a version bump (CITY_SPEC amendment law). Verified R13 (build) + R14 (audit).
|
||||
> **v3.1 amendment:** §3 became the `stage.backline[]` contract (ledger #5 — the amp unification).
|
||||
> **v4.0-beta amendment (2026-07-16):** **§6 added — the real-town shop-class → archetype mapping**
|
||||
> (ledger #2, the density widening; C→E handshake). The frozen venue contract (§0–§5) is unchanged.
|
||||
> **v4.0 amendment (2026-07-16):** **§7 added — the G2a per-shop stock-atlas contract** (C's interior
|
||||
> draw/tex ceilings, answering Lane G + E's review). §0–§6 unchanged.
|
||||
|
||||
*The district ships **three** venue archetypes behind `?gigs=1` — `pub`, `band_room`, `rsl`. Lane A
|
||||
converts a chosen shop to a venue kind in place (`shop.type = kind`); C keys the interior recipe off
|
||||
@ -136,4 +139,139 @@ bar in the corner — the PA louder than the room. `rsl` = carpet-and-flock club
|
||||
(hall/wide), a proper raised stage at one end, bistro tables round the edge, members' bar, the biggest crowd
|
||||
cap. Both reuse the `pub` venue machinery (`theme.js venueSpec` + `layout.js` placeStage/placeWatchPoints);
|
||||
only the dressing + `venueSpec` differ. Stage/PA keep GLB slots for Lane E (primitive fallback holds); the
|
||||
primitive ampStacks are the backline fallback (see §3).
|
||||
backline amp is D's, planted at `stage.backline[0]` (see §3).
|
||||
|
||||
---
|
||||
|
||||
## 6. Real-town shop-class → interior archetype mapping (v4.0-beta, ledger #2) → Lane E
|
||||
|
||||
**Directive (John, kickoff — SUBTLE: texture, not takeover).** The secondhand shops stay the **heroes** of
|
||||
the town (they *are* the game). The widening adds general retail as *texture* — the everyday main-street
|
||||
staples a real Australian town has — mapped onto **existing** archetypes (**no new archetypes this round**).
|
||||
The player should feel the town's more alive, not that the game changed.
|
||||
|
||||
**How it's applied (E owns the emission).** `plan_osm` keeps only **registry types** (`SHOP_TYPES` — unknowns
|
||||
flatten to `opshop`). So E maps each fetched OSM `shop=*` / `amenity=*` value to a **registry type** below and
|
||||
bakes it into the cache's `shop.type`. Every target is an existing type, so C's recipes, A's registry, and the
|
||||
budgets are all unchanged. Unknown/unlisted classes degrade safely to `opshop` (R18-proven: 18 real OSM types
|
||||
all built valid).
|
||||
|
||||
**Heroes — secondhand (unchanged; NEVER subsampled):**
|
||||
| OSM class | → type |
|
||||
|---|---|
|
||||
| `charity`, `second_hand`, `antiques`, `vintage` | `opshop` |
|
||||
| `books`, `bookshop` | `book` |
|
||||
| `music` | `record` |
|
||||
| `video`, `games` | `video` |
|
||||
| `pawnbroker` | `pawn` |
|
||||
| `toys` | `toy` |
|
||||
|
||||
**Widened — everyday main-street texture (general retail):**
|
||||
| OSM class(es) | → type | wears it as |
|
||||
|---|---|---|
|
||||
| `bakery`, `cafe`†, `confectionery`, `pastry`, `chocolate`, `deli`/`delicatessen`, `butcher`, `cheese`, `coffee`, `tea` | `milkbar` | the food counter |
|
||||
| `convenience`, `kiosk`, `general`, `dairy` | `milkbar` | the corner store |
|
||||
| `newsagent`, `stationery` | `milkbar` | papers + magazines |
|
||||
| `chemist`, `pharmacy`, `cosmetics`, `perfumery` | `milkbar` | dispensary counter |
|
||||
| `clothes`, `fashion`, `boutique`, `shoes`, `bag`, `jewelry`/`jewellery` | `dept` | the dressed retail floor |
|
||||
| `department_store`, `variety_store`, `supermarket` | `dept` | big general retail |
|
||||
| `hardware`, `doityourself`, `trade`, `electronics`, `mobile_phone`, `furniture`, `houseware`/`homeware` | `dept` | shelved goods |
|
||||
| `greengrocer`, `florist`, `farm`, `garden_centre` | `stall` | produce/plants on trestles |
|
||||
|
||||
† `cafe`/`fast_food` are `amenity=*`, not `shop=*` — fetch them from the amenity tag; everything else is `shop=*`.
|
||||
**Fallback:** any fetched class not listed → `opshop`.
|
||||
|
||||
**Copy-paste map for E (`build_towns.py`):**
|
||||
```json
|
||||
{
|
||||
"charity":"opshop","second_hand":"opshop","antiques":"opshop","vintage":"opshop",
|
||||
"books":"book","bookshop":"book","music":"record","video":"video","games":"video",
|
||||
"pawnbroker":"pawn","toys":"toy",
|
||||
"bakery":"milkbar","cafe":"milkbar","confectionery":"milkbar","pastry":"milkbar","chocolate":"milkbar",
|
||||
"deli":"milkbar","delicatessen":"milkbar","butcher":"milkbar","cheese":"milkbar","coffee":"milkbar","tea":"milkbar",
|
||||
"convenience":"milkbar","kiosk":"milkbar","general":"milkbar","dairy":"milkbar",
|
||||
"newsagent":"milkbar","stationery":"milkbar",
|
||||
"chemist":"milkbar","pharmacy":"milkbar","cosmetics":"milkbar","perfumery":"milkbar",
|
||||
"clothes":"dept","fashion":"dept","boutique":"dept","shoes":"dept","bag":"dept","jewelry":"dept","jewellery":"dept",
|
||||
"department_store":"dept","variety_store":"dept","supermarket":"dept",
|
||||
"hardware":"dept","doityourself":"dept","trade":"dept","electronics":"dept","mobile_phone":"dept",
|
||||
"furniture":"dept","houseware":"dept","homeware":"dept",
|
||||
"greengrocer":"stall","florist":"stall","farm":"stall","garden_centre":"stall"
|
||||
}
|
||||
```
|
||||
Default (not in the map) → `opshop`.
|
||||
|
||||
**Subtlety guardrails (E):**
|
||||
- Fetch only these tasteful staples — **not** every OSM `shop=*`. Skip vehicle/tyres/fuel, funeral, storage,
|
||||
industrial, and pure services the player never browses (`hairdresser`, `beauty`, `travel_agency`, `bank`,
|
||||
`laundry`, `estate_agent`).
|
||||
- **Never** subsample the secondhand heroes.
|
||||
- If a town's general-retail count would visually **drown** the secondhand strip, seed-subsample the *texture*
|
||||
classes (the milkbar/dept/stall targets) toward **~2–3× the secondhand count**, and count the drops in
|
||||
`norm.dropped` like every other drop.
|
||||
|
||||
**Why these targets:** `milkbar` (counter + fridge + magazine rack + glass case) is the canonical Australian
|
||||
counter shop — it wears cafe/bakery/deli/newsagent/chemist naturally; `dept` (cube shelves + clothes racks +
|
||||
glass cases) wears clothes + hardware + big retail; `stall` (trestles + crates) wears produce + florist. Three
|
||||
general-retail looks + the six secondhand heroes + the three venues = variety with no new archetypes.
|
||||
**Pre-verified (seed 20261990, real-town lot 8.5×14):** milkbar/dept/stall/opshop all build valid (0 carves,
|
||||
≤143 draws); `dept` reads as a clean **dressed-retail floor** (cube shelves + clothes racks + counter, the
|
||||
escalator does *not* dominate the ~7.9×11.7 room), so `clothes`/`hardware` → `dept` is confirmed. C re-checks
|
||||
the full result in the five-town door-soak (ledger #6) once E's caches land. (FYI: C's `theme.js canonicalType`/ALIAS already recognizes several
|
||||
of these — `deli`/`corner_store`→milkbar etc. — for any direct `buildInterior(rawClass)`; the authoritative
|
||||
mapping for the caches is this table.)
|
||||
|
||||
---
|
||||
|
||||
## 7. G2a — per-shop stock atlases: C's interior ceilings (v4.0, ledger #3) → Lane G + Lane E
|
||||
|
||||
*Answering Fable's three priors + E's half (LANE_E_NOTES). **Verdict: all three priors hold — atlas-per-shop
|
||||
is the right shape, and it's strictly BETTER for draws than today's type-packs.** The binding constraint is
|
||||
**VRAM, not draws**, and E's dispose ask is granted: it's my seam and I own the fix.*
|
||||
|
||||
**1. Draw ceiling — ONE atlas per shop (hard cap 2).**
|
||||
Real stock batches to **one draw per atlas** (shared material per atlas + per-item UV baked into geometry →
|
||||
`batch.js` merges). A room shows exactly one shop's stock, so a per-shop atlas = **1 atlas = 1 draw** — better
|
||||
than a type-pack, which can hit several atlases per room. **Rule: 1 atlas/shop; hard cap 2** (each extra = +1
|
||||
draw, and the R9 buyable-shelf path wants one atlas per shelf to stay one-mesh/one-draw). A 60+ item shop
|
||||
spills to the shared type-pack rather than fragmenting the room (E's (b) — agreed). *Headroom: `?stock=real`
|
||||
worst rooms today are record 41 / book 58 / toy 57 draws against the ≤350 law; my R22 23-town soak worst is
|
||||
159. Draws are not the constraint.*
|
||||
|
||||
**2. Texture/VRAM ceiling — the real limit. E's dispose ask: GRANTED, and it's mine to land.**
|
||||
E's audit is correct and the cause is a deliberate C design note (`stockpack.js:11`): atlas textures are
|
||||
"shared/cached in the pack (**NOT per-room**), like depot GLBs". That is **right for 3 bounded type-packs**
|
||||
and **wrong for unbounded per-shop atlases** (23 towns × N shops). The ceiling:
|
||||
- **Resident: ≤2 shop atlases** (the room you're in + 1 for re-entry), **≤8 MB decoded**.
|
||||
- **Atlas size: ≤1024² for a typical shop** (16 items @256² cell); **2048² only for a 60+ item shop**, and
|
||||
prefer spilling to the type-pack. At 1024² RGBA ≈ 4 MB → ≤2 resident ≈ **8 MB** (vs E's measured **181 MB**
|
||||
resident today — ~20× better, and bounded instead of accreting).
|
||||
- **C lands the LRU cap + dispose in `stockpack.js`** (evicted shop's atlas texture + material disposed).
|
||||
Type-packs keep today's shared cache (bounded, reused across rooms of a type) — unchanged.
|
||||
- **Scheduling:** today's 181 MB accretion is **pre-existing**, and `?stock=real` is **opt-in**
|
||||
(`index.html:196` — `params.get('stock')==='real' && !NOASSETS`), so it cannot touch the default boot and is
|
||||
**not a v4.0-tag blocker**. I land the cap as **C's first v4.x item — before G2a ships**. G: build to the
|
||||
ceiling; the loader will honour it.
|
||||
|
||||
**3. Index schema — E's (c) confirmed VERBATIM, keyed per shop.**
|
||||
`stock_shop_<godverseShopId>_index.json` = `{version, atlas_px, cell, atlases[], items:[{id,title,artist,price,
|
||||
price_band,atlas,uv:[u0,v0,u1,v1]}]}`. `stockpack.js` consumes it unchanged (it already groups items by atlas
|
||||
and shares a material per atlas). **UV origin stays top-left (image-natural) — C flips V for WebGL** (R7 law,
|
||||
unchanged). Book cell 176×256 portrait is fine: **C reads `uv`, never `cell`**.
|
||||
|
||||
**4. Fail-soft + `?noassets` — unchanged law, confirmed.**
|
||||
Index/atlas missing → `getStockPack` → null → **parody canvas + one warn** (R7, proven since). `?noassets`
|
||||
fetches zero. A godverse town under `?noassets` stays silent-and-fine. `?stock=real` opt-in ⇒ G2a cannot
|
||||
regress the default or classic boots.
|
||||
|
||||
**5. Determinism — one ask back to G.**
|
||||
The seeded pick per bin is C's and stays: same `shop.seed` → same crate contents, byte-identical revisits.
|
||||
That indexes into `items[]`, so **G must emit `items[]` in a deterministic order** (sort by `id`) — otherwise
|
||||
a re-emit silently reshuffles every crate. Same discipline as E's byte-identical cache re-runs.
|
||||
|
||||
**6. Buy-anywhere (R9) — compatible, no change.**
|
||||
`buildBuyableShelf` merges per-item-addressable covers from ONE atlas → one draw (`noBatch`). A per-shop atlas
|
||||
satisfies that naturally (better than the type-pack's seeded `atlasPool` pick). Parody = not buyable stays.
|
||||
|
||||
**7. Parody law — E's (d), unchanged and not C's to relax.** Metadata deterministically parody-transformed;
|
||||
cover art real; prices real-or-seeded.
|
||||
|
||||
@ -6,6 +6,327 @@ _rotOnly/head-bone normalize/upgradeStreetPeople). Measurements on the M3 Ultra
|
||||
|
||||
---
|
||||
|
||||
## ROUND 23 — v5.0-alpha: the GIG_RANGE chunk-neighbour fix (ledger #6, the carried v4.x item)
|
||||
|
||||
*PROCITY-D, 2026-07-16. My own filed finding, finally fixed — post-release, as Fable scheduled. Hot path,
|
||||
so the R18-style before/after is the deliverable. `selfcheck` **ALL GREEN 161300/161300**, `0x3fa36874`
|
||||
frozen (runtime-only change — no plan, no golden).*
|
||||
|
||||
### The finding grew while it waited: 8.7 % → 42.6 %
|
||||
I filed this at 8.7 % in R20, measured at katoomba's isolated pub. **A's R21 cluster bias made it worse**:
|
||||
venues now sit *inside* the retail cluster, where peds converge from every side rather than trickling along
|
||||
one street — so far more in-range peds are across a chunk edge. Re-measured on the relocated venue:
|
||||
**42.6 %** of in-gig-range ped-samples (5094 of 11971) were chunk-blind. Fixing it mattered more in R23
|
||||
than when I filed it.
|
||||
|
||||
### The override: the brief contradicts itself, and the measurement broke the tie
|
||||
The brief said *"extend `_nearestOpenShop` to the ped's chunk + 8 neighbours"* **and** *"byte-identical
|
||||
flags-off"*. **I built the literal version first and measured it: those two cannot both be true.** On the
|
||||
synthetic town (exactly what `?classic=1` boots, gig layer absent), a full 3×3 scan sourced **127 of 855
|
||||
finds — 14.9 % — from neighbour chunks**: finds v2 never made. The frozen v2 crowd would start shopping
|
||||
~15 % more. That is a covenant breach, in a clause of the same brief that asked for it.
|
||||
|
||||
**So the fix is scoped to the GIG path** — which is also what I actually filed (the finding was always
|
||||
`GIG_RANGE`-specific):
|
||||
- **Ordinary shops → own chunk only.** v2 semantics, frozen. A shopfront is noticed from its own block.
|
||||
- **Gig venues → the full 34 m radius, honest across chunk edges.** The gig is the "follow the sound" pull
|
||||
that is *supposed* to reach further — that is the entire reason `GIG_RANGE` (34) > `PATRON_RANGE` (18).
|
||||
- **No gig on → `_gigVenues` is empty → the neighbour loop is skipped entirely.** `?classic` and every
|
||||
flags-off boot take the *identical code path and identical cost* to v2. **Byte-identical by construction**,
|
||||
not by measurement luck.
|
||||
|
||||
Both ranges are < `CHUNK` (64), so a 3×3 sweep is *exact*, not an approximation; a fixed `dz/dx` order keeps
|
||||
exact-distance ties deterministic, as the old single-list scan was.
|
||||
|
||||
### Before / after
|
||||
| proof | before | after |
|
||||
|---|---|---|
|
||||
| **cross-chunk in-range peds that see the gig** — katoomba | **0 % by construction**¹ | **4/4 = 100 %** |
|
||||
| — fitzroy (densest, worst case) | **0 % by construction**¹ | **11/11 = 100 %** |
|
||||
| same-chunk peds (must not change) | 100 % | 100 % |
|
||||
| **classic / flags-off: finds sourced from a neighbour** | 0 (v2) | **0** (127 under the rejected full scan) |
|
||||
| determinism (2 sims, same seed) | identical | **identical** (identity sets byte-equal) |
|
||||
| NaN — katoomba / fitzroy / synthetic | 0 | **0** |
|
||||
| sim cost, katoomba @14:00, 236 peds | 0.056 ms/frame | **0.057 ms/frame (+2 %)** |
|
||||
| lookup cost/call — no gig on | 0.58 µs | **0.58 µs (loop skipped)** |
|
||||
| lookup cost/call — gig on | 0.58 µs | **1.3–2.7 µs** |
|
||||
|
||||
¹ *not a small-sample claim: v2 read `shopsByChunk.get(ownChunk)` only, so a venue in another chunk was
|
||||
never in the scanned list and could not be returned. 0 % is structural.*
|
||||
|
||||
The lookup is 2–4× dearer **while a gig is on**, but it only fires every `PATRON_STRIDE` (10 m walked), not
|
||||
per frame — hence **+2 % of a 0.056 ms frame cost**. Against a 16.7 ms budget that is 0.006 %. Fitzroy at 376
|
||||
peds stayed clean. Minor known inefficiency: with a gig *scheduled but closed* (e.g. 14:00) the neighbour
|
||||
loop still runs for nothing (0.58 → 1.3 µs) — `_gigVenues` holds ids, so testing openness would need the
|
||||
lookup it's trying to avoid. Not worth it at +2 %; noted rather than hidden.
|
||||
|
||||
### Handshakes
|
||||
- **D → F (gate):** the fix is **runtime-only** — no plan, no golden, no flag, no fetch. `?classic=1` takes
|
||||
the identical code path to v2 (loop skipped), so the classic regression is untouched **by construction**.
|
||||
Selfcheck green at 161300. No D-side blocker for `v5.0-alpha`.
|
||||
- **Carried (deliberate, documented):** `PATRON_RANGE` keeps v2's own chunk-blindness — an ordinary shop 10 m
|
||||
away across a chunk edge still goes unnoticed. That is now a *frozen v2 quirk*, not an oversight: fixing it
|
||||
costs the classic covenant, and the covenant wins. Revisit only if the covenant is ever relaxed.
|
||||
- **Still unowned (unchanged):** R20's main-street relocation half (heroes 0–19 % of the densest cluster).
|
||||
|
||||
---
|
||||
|
||||
## ROUND 22 — v4.0 THE EPOCH CLOSE: citizens at pack scale (ledger #5)
|
||||
|
||||
*PROCITY-D, 2026-07-16. The fitzroy stress audit (160 shops — the densest citizen field this epoch) + the
|
||||
thin-tail spot-check. Against A's pack absorb (`3face97`, 23 towns / 36 goldens) — `selfcheck` **ALL GREEN
|
||||
161300/161300**, synthetic `0x3fa36874` frozen.*
|
||||
|
||||
### Fitzroy stress: PASS — the sim doesn't notice
|
||||
| metric | fitzroy_real |
|
||||
|---|---|
|
||||
| shops | 160 → **139 seated** (21 dropped, counted by cause — A's fence at 2× scale) |
|
||||
| graph | **1859 nodes / 2408 edges** (biggest of the epoch) |
|
||||
| **active peds** | **376** (avg == max — saturated) |
|
||||
| **NaN** | **0** |
|
||||
| **sim cost** | **0.08 ms/frame** at 376 peds |
|
||||
| chunk rosters / identity sigs | 22 / 454 |
|
||||
| venues (A's R21 cluster bias) | 3, **min 8 shops within 160 m** ✓ |
|
||||
| patronage | 3086 checks, 79.6 % null, **103 visits** |
|
||||
| plan gen | 194 ms |
|
||||
|
||||
**Nothing needed touching at 2× scale.** Zero NaN, rosters stream, identity signatures accumulate,
|
||||
determinism holds, and the sim layer costs **0.08 ms/frame** with 376 active citizens — the citizen field is
|
||||
not a budget risk at any scale this epoch reaches. A's cluster bias survives the mecca (min 8 ≥ the ≥3 bar).
|
||||
|
||||
### Thin tail: "does 12 shops still read alive?" — **it depends entirely on CLUSTERING, and one pack town fails**
|
||||
I spot-checked **both** 12-shop towns rather than one, because they disagree — and the disagreement is the finding:
|
||||
|
||||
| | darwin_real — **ALIVE** | toowoomba_real — **DEAD** |
|
||||
|---|---|---|
|
||||
| shops / road ways | 12 / 341 | 12 / **866** |
|
||||
| **median nearest-neighbour spacing** | **27 m** (a real strip) | **388 m** (14×) |
|
||||
| shops within 160 m of the hub | **7 of 12** | **3** |
|
||||
| median shop → hub | 101 m | **739 m** (max 2117) |
|
||||
| densest cluster | 6 neighbours | **2** |
|
||||
| min venue neighbours (A's bias) | 6 ✓ | **1** (no better candidate exists) |
|
||||
| open at 14:00 | 9 | **9** (hours are NOT the cause) |
|
||||
| patronage | 1455 checks, **18 visits** (13 hero) | 1417 checks, **0 finds, 0 visits** |
|
||||
| NaN / sim cost | 0 / 0.05 ms | 0 / 0.05 ms |
|
||||
|
||||
**Both towns pass `MIN_TOWN_SHOPS`. One has a high street; the other doesn't.** Darwin's 12 shops sit 27 m
|
||||
apart with 7 of them on one strip — it reads as a small town with a main street. Toowoomba's 12 sit ~388 m
|
||||
apart across a **4.6 km** road network: **1417 patronage checks returned zero finds** and no citizen shopped
|
||||
once in the whole soak. It isn't a shopping town — it's a road network with occasional shops. (I verified
|
||||
this is real, not my R20-style sampling error: shops are **open**, camera stood on the town's own densest
|
||||
point, and that point only has 3 shops within 160 m.)
|
||||
|
||||
**→ FILED for E / Fable (curation, not code): the floor measures the wrong thing.** `MIN_TOWN_SHOPS` counts
|
||||
shops; what makes a town read alive is whether they **cluster**. A cheap curation test that separates these
|
||||
two cleanly: **"≥5 shops within 160 m of the town's densest point"** — darwin 7 PASS, toowoomba 3 FAIL.
|
||||
Toowoomba is in the v4.0 pack today and has no main street to walk. Cull it, or accept it as a known
|
||||
thin-tail (it costs nothing — 0 NaN, 0.05 ms/frame — it just has nothing to do). **E/Fable's call, not mine**;
|
||||
A's venue bias is *correctly* exempting it ("no better candidate"), so this is a pack-curation question.
|
||||
|
||||
### Handshakes
|
||||
- **D → F (gate):** citizens at pack scale **GREEN**. 23-town pack: fitzroy (160 shops / 376 peds) clean at
|
||||
0 NaN and 0.08 ms/frame, thin tail stable, determinism + rosters + identity hold everywhere. **No D-side
|
||||
blocker for `v4.0`.** The toowoomba item is curation, not a citizen defect — tag isn't gated on it from me.
|
||||
- **Carried to v4.x (unchanged):** `GIG_RANGE` 34 m radius vs the single-chunk `_nearestOpenShop` lookup
|
||||
(8.7 % of in-range peds chunk-blind; fix = chunk + 8 neighbours).
|
||||
- **Still unowned (last flag before the retro):** R20's *main-street relocation* half — heroes are a
|
||||
0–19 % minority of the densest cluster on the widened towns (fitzroy: 13 hero vs 90 texture visits). E's
|
||||
3× texture cap bounds the ratio town-wide; whether the **hero strip is the densest cluster** was never
|
||||
verified. If it matters for v4.x, it's a one-call re-run of this harness.
|
||||
|
||||
---
|
||||
|
||||
## ROUND 21 — v4.0-beta CLOSE: relocation verify (ledger #4)
|
||||
|
||||
*PROCITY-D, 2026-07-16. Verifying A's venue cluster-adjacency bias (`110c828`), which Fable assigned from
|
||||
my R20 relocation finding. `selfcheck` **ALL GREEN 154141/154141** against A's re-pinned goldens.*
|
||||
|
||||
### Verdict: A's bias PASSES acceptance on all five — my finding is closed
|
||||
Acceptance was "no venue sits with <3 shops in its 160 m radius." Shops within 160 m @ distance-to-retail-hub,
|
||||
before (R20) → after (R21):
|
||||
|
||||
| town | min shops in 160 m | before → after |
|
||||
|---|---|---|
|
||||
| katoomba_real | **9** PASS | **pub 1→9 @1007→466 m** · rsl 10→12 @161→148 m · band_room 7→10 @181→161 m |
|
||||
| newtown_real | **5** PASS | **rsl 1→11 @2228→29 m** · band_room 0→7 @2433→124 m · pub 0→5 @2190→714 m |
|
||||
| bendigo_real | **5** PASS | rsl 0→10 @451→86 m · band_room 0→10 @1157→96 m · pub 1→5 @504→316 m |
|
||||
| castlemaine_real | **4** PASS | pub 1→5 @223→182 m · band_room 6→6 @0 m · rsl 4→4 @155 m |
|
||||
| fremantle_real | **3** PASS* | band_room 2→8 @612→309 m · *pub 1→3 @969→**1352 m*** · *rsl 2→3 @561→**1292 m*** |
|
||||
|
||||
**My R20 headline is fixed**: katoomba's pub — "a gig with no passing crowd", 1 shop at 1007 m — now sits at
|
||||
**466 m with 9 shops**. Newtown is the dramatic one: all three venues were **2.2–2.4 km out with 0–1 shops**;
|
||||
the rsl now sits **29 m from the hub with 11**. Castlemaine's floor did NOT need an exemption (min 4).
|
||||
|
||||
### The crowd followed — katoomba's pub, same density/seed/21:00
|
||||
| | R20 (pre-bias) | **R21 (post-bias)** |
|
||||
|---|---|---|
|
||||
| passing crowd (maxActive) | 34 | **66** (2×) |
|
||||
| patron checks | 226 | **442** (2×) |
|
||||
| inside frames | 1042 | **1424** |
|
||||
| tonightRoster | 2 | **3** |
|
||||
| NaN | 0 | **0** |
|
||||
|
||||
The "gig with no passing crowd" observation is retired. (null % stays ~96 % at 21:00 — retail is shut at night
|
||||
**by design**; that's my own R20 correction, not a leak.)
|
||||
|
||||
### Two readings that need care (so nobody mis-reads my own numbers)
|
||||
- ***fremantle is the weakest pass** (\*): its pub/rsl clear the ≥3 bar but moved **further** from the primary
|
||||
hub (969→1352 m, 561→1292 m) — A's bias optimises **local cluster adjacency**, and fremantle's venues found
|
||||
a *secondary* cluster. It satisfies the letter of acceptance and the crowd is healthy in practice (pub:
|
||||
roster **6**, inside **2741**, maxActive **72**), so I'm **not** calling it a miss — just naming it, since
|
||||
"3 shops at 1.3 km" is a thinner high street than katoomba's "9 shops at 466 m". A's call if they want it tighter.
|
||||
- **venue-win % DROPPING is not a regression.** newtown's rsl went 100 %→**21 %**, fremantle's pub 100 %→62 %.
|
||||
That metric asks "when a check finds a shop, is it the venue?" — it was only ever 100 % because the venue
|
||||
was *alone*. Post-bias the venue has real neighbours competing for finds, while total crowd went **up**
|
||||
(newtown rsl: roster 3, inside 1873, maxActive 55). Isolation flatters that number; ignore it post-bias.
|
||||
|
||||
### Handshakes
|
||||
- **D → F (gate):** relocation verify **GREEN**. All five towns pass A's acceptance, the pub finding is closed,
|
||||
0 NaN at every venue, rosters populate. No D-side gate concern for `v4.0-beta`.
|
||||
- **FYI F:** `selfcheck` scans **every** `web/assets/towns/*.json` on disk, including E's *untracked* pack WIP.
|
||||
I saw it transiently RED mid-round (`newcastle_real`: no open-late shop) purely from E's in-flight files, then
|
||||
green again minutes later at 154141 checks. If you gate while E is mid-fetch you may catch a false red —
|
||||
E's pack is non-gating, so check whether a failure is a pack town before believing it.
|
||||
- **Still filed for v4.x (agreed, not a mid-beta change):** `GIG_RANGE` is a 34 m radius but `_nearestOpenShop`
|
||||
searches only the ped's own 64 m chunk → 8.7 % of in-range peds are chunk-blind. Fix = chunk + 8 neighbours.
|
||||
- **Unowned from R20:** the *main-street* relocation half (heroes 0–19 % of the densest cluster; castlemaine's
|
||||
main street has zero secondhand shops). E's R20 SUBTLE guardrails (texture capped at 3× heroes) address the
|
||||
ratio town-wide; whether the **hero strip is again the densest cluster** is unverified. Not in my R21 ledger —
|
||||
flagging it so it doesn't get lost at the epoch close.
|
||||
|
||||
---
|
||||
|
||||
## ROUND 20 — v4.0-beta THE FIVE TOWNS: citizens at density (ledger #7)
|
||||
|
||||
*PROCITY-D, 2026-07-16. Part 1 of 2: the **pre-density baseline** — the gig crowd's behaviour on all five
|
||||
real towns' venues (F's district gate owns placement; I own behaviour). This half is unblocked; the density
|
||||
re-audit waits on E's widened caches → A's absorb. Deliberately measured BEFORE the widening so the
|
||||
after-table is a real A/B.*
|
||||
|
||||
### The gig crowd is behaviourally CORRECT on all five real towns
|
||||
Per town: stream sim on the real graph, `setGig` on its first venue, 21:00, camera at the venue, ~1400
|
||||
frames. Every town generates 3 venues (pub/band_room/rsl — charter's 2–4) on night hours.
|
||||
|
||||
| town | shops | door chunks | patron checks | **null %** | hit venue | **venue win %** | roster | NaN |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| katoomba_real | 19 | ~5 | 703 | 97.3 | 14 | 74 | 2 | **0** |
|
||||
| newtown_real | 18 | 15 | 267 | 95.5 | 12 | **100** | 4 | **0** |
|
||||
| fremantle_real | 20 | 16 | 429 | 94.9 | 22 | **100** | 7 | **0** |
|
||||
| bendigo_real | 9 | 9 | 343 | 97.7 | 8 | **100** | 3 | **0** |
|
||||
| castlemaine_real | 6 | 5 | 364 | 95.9 | 6 | 40 | 4 | **0** |
|
||||
|
||||
- **0 NaN on all five.** The gig crowd is numerically safe on every real graph.
|
||||
- **`tonightRoster` populates on all five** (2–7) — R14 identity continuity holds on real bones.
|
||||
- **Venue-win % is the surge working**: whenever the patronage check finds *any* shop, the gig venue beats
|
||||
ordinary shopfronts (GIG_RANGE/gigBest preference) — 100% on three towns, 74% on katoomba.
|
||||
**castlemaine's 40% is not a leak**: at the 6-shop floor, 3 of its 6 shops ARE venues, so the "other
|
||||
shop" hits are mostly *other venues*. A quirk of the sparse floor, not the mechanism.
|
||||
|
||||
### CORRECTION to my own first read of that null % — I misattributed it (measurement wins)
|
||||
My first pass called the 94.9–97.7 % null rate "the crowd is sparsity-throttled" and made it the headline
|
||||
before/after metric. **Both halves of that were wrong, and I'm correcting the record:**
|
||||
1. **It was measured at gig hours.** At 21:00 only **4 of 69** katoomba shops are open — retail is shut *by
|
||||
design*. A high null rate at 21:00 is **correct night behaviour**, not sparsity. The gig IS the nightlife.
|
||||
2. **It was sampled at each town's first venue** (`shops.find(s => s.venue)`). For katoomba that is the
|
||||
**pub, which sits 1,000 m from the retail cluster with 1 neighbour** — I had parked the camera on the one
|
||||
isolated venue in town. Measured there, the daytime null was 99.4 % with **0 visits**; measured on the
|
||||
actual main street, the same town same seed gives **77.2 % and 64 visits**. The number was an artefact of
|
||||
where I stood.
|
||||
|
||||
The gig-crowd table above still **stands** — it measures the *mechanism* (0 NaN, rosters populate,
|
||||
venue-win %), which is location-independent. Only the sparsity interpretation was bad.
|
||||
|
||||
### The real density A/B — the main street, retail hours (14:00), same seed, like-for-like
|
||||
Camera on the **densest retail cluster** (the actual main street), pre-widening cache (from git) vs E's
|
||||
widened cache, 2,000 frames each:
|
||||
|
||||
| main street @14:00 | PRE (20 shops) | **POST (80 shops)** |
|
||||
|---|---|---|
|
||||
| hub density (shops within 160 m) | 8 | **25** |
|
||||
| null % | 90.2 | **77.2** |
|
||||
| **total visits** | 17 | **64** (3.8×) |
|
||||
| **hero visits** (opshop/book/record/video/pawn/toy) | 17 | **19** |
|
||||
| texture visits (milkbar/dept/stall) | 0 | 45 |
|
||||
| hero share of visits | 100 % | **30 %** |
|
||||
| hero share of shops | 84 % | 26 % |
|
||||
| NaN | 0 | 0 |
|
||||
|
||||
**The widening lands John's directive, measured from the crowd side.** The secondhand heroes did **not**
|
||||
lose footfall — they went **17 → 19 visits**. The 45 texture visits are added *on top*, not taken from them.
|
||||
The street is **3.8× busier** while the heroes stay **over-indexed** (26 % of shops, 30 % of visits). That is
|
||||
precisely "the player should notice the town feels more alive, not that the game changed."
|
||||
|
||||
### Filed, not fixed
|
||||
- **Venue placement at density (A/F):** katoomba's 3 venues — rsl **13** shops within 160 m, band_room **10**,
|
||||
**pub 1** (1,000 m from the cluster). A gig at the pub has almost no passing retail crowd. It still draws a
|
||||
roster (peds stream everywhere), so it's a *quality* observation, not a break. Placement is A's/F's lane.
|
||||
- **`GIG_RANGE` vs the chunk lookup (mine):** GIG_RANGE is a 34 m *radius*, but `_nearestOpenShop` searches
|
||||
only the ped's own 64 m chunk, so an in-range ped across a chunk edge is blind to the gig — **8.7 %** of
|
||||
in-range ped-samples (852/9,740). Real but small; a hot-path change mid-beta isn't worth it. Fix (chunk +
|
||||
8 neighbours) is a v4.x candidate.
|
||||
|
||||
### FINAL — post A's absorb (`5d29863`), all five towns at density
|
||||
A's absorb landed (capacity widened, goldens pinned, `selfcheck` **51866/51866**). Re-ran everything against
|
||||
the absorbed graph. **The katoomba numbers barely moved** (null 77.2→78.1, visits 64→62, hero share 30→29 %)
|
||||
— the pre-absorb read was already sound.
|
||||
|
||||
| town | cache→seated | hub | null % | visits | hero | texture | hero share | NaN | maxActive | rosters | sigs |
|
||||
|---|---|---|---|---|---|---|---|---|---|---|---|
|
||||
| katoomba_real | 80→72 (8) | 26 | 78.1 | 62 | 18 | 44 | 29 % | **0** | 217 | 15 | 262 |
|
||||
| fremantle_real | 80→79 (1) | 29 | 71.1 | 116 | 23 | 93 | 20 % | **0** | 401 | 24 | 483 |
|
||||
| newtown_real | 72→67 (5) | 12 | 85.9 | 44 | 6 | 38 | 14 % | **0** | 268 | 19 | 324 |
|
||||
| bendigo_real | 36→35 (1) | 14 | 87.2 | 45 | 1 | 44 | 2 % | **0** | 358 | 21 | 432 |
|
||||
| castlemaine_real | 24→23 (1) | 6 | 94.9 | 9 | 0 | 9 | 0 % | **0** | 295 | 20 | 359 |
|
||||
|
||||
**Sim verdict: zero changes needed at 4× density.** 0 NaN on all five, chunk rosters stream (15–24), identity
|
||||
signatures accumulate (262–483), patronage/loiter/bench-sit all fire, up to **401 active peds** with no
|
||||
degradation. Determinism unchanged (identity is graph+seed keyed; shops don't touch it). **D→F: green.**
|
||||
|
||||
### THE FINDING → for E / C / Fable: the widening MOVED the main street off the secondhand strip
|
||||
The hero share falling (29→0 %) is **not** a behaviour bug — patronage is type-agnostic, so visits track the
|
||||
*local* shop mix exactly. The mix is the story. Main street = the densest shop cluster (what "walk the main
|
||||
street" means to a player):
|
||||
|
||||
| town | main-st shops | **heroes on main st** | hero % of main st | town-wide hero % | **hub moved** |
|
||||
|---|---|---|---|---|---|
|
||||
| katoomba | 9 → **27** | **9 (100 %) → 5** | 100 % → **19 %** | 25 % | **308 m** |
|
||||
| fremantle | — → 30 | 5 | **17 %** | 25 % | — |
|
||||
| newtown | — → 13 | 2 | **15 %** | 27 % | — |
|
||||
| bendigo | — → 15 | 1 | **7 %** | 25 % | — |
|
||||
| castlemaine | 3 → **7** | **1 → 0** | 33 % → **0 %** | 20 % | **434 m** |
|
||||
|
||||
**Pre-widening the densest cluster WAS the secondhand strip** (katoomba: 9 of 9 main-street shops were
|
||||
heroes). Post-widening the town's centre of gravity **relocated 308–434 m onto the general-retail cluster**,
|
||||
where the heroes are a **0–19 % minority**. Castlemaine's main street now has **zero** secondhand shops
|
||||
(0 of 4 heroes within 160 m of its hub; median hero is 463 m away). The heroes weren't drowned *in place* —
|
||||
they were **demoted**: the liveliest street in town is no longer theirs.
|
||||
|
||||
- **The good half stands:** heroes kept their absolute footfall (katoomba 17 → 18 visits) and the street is
|
||||
**3.8× busier**. The widening works; it just overshot the dial.
|
||||
- **This is exactly the case John's directive pre-authorised:** *"if a real town's general retail would drown
|
||||
the secondhand strip visually, E may seed-subsample the texture classes (count it, like every drop)."*
|
||||
My read (measurement, not a ruling — E/C/Fable own it): **subsample texture until the secondhand strip is
|
||||
again the town's densest cluster.** The sparse towns need it most (bendigo 7 %, castlemaine 0 %); katoomba
|
||||
at 19 % is borderline. A hero-adjacency bias on texture seating would be the alternative lever (A's).
|
||||
|
||||
### Also filed — venue placement at density (A / F)
|
||||
Venues are seeded from the shop pool, and at real density they land **off** the retail hub:
|
||||
|
||||
| town | venue distance to hub (shops within 160 m) |
|
||||
|---|---|
|
||||
| katoomba | pub **1007 m (1)** · rsl 161 m (10) · band_room 181 m (7) |
|
||||
| fremantle | pub 969 m (1) · rsl 561 m (2) · band_room 612 m (2) |
|
||||
| **newtown** | **all three 2190–2433 m (0–1)** |
|
||||
| bendigo | pub 504 m (1) · rsl 451 m (0) · band_room 1157 m (0) |
|
||||
| castlemaine | band_room **0 m (6)** · rsl 155 m (4) · pub 223 m (1) |
|
||||
|
||||
A gig 2.4 km from the high street has no passing retail trade. A's `5d29863` "pub facade bias" fixed frontage,
|
||||
not town position — katoomba's pub is still 1007 m out with 1 neighbour. Crowd still forms (peds stream with
|
||||
the camera), so it's a **quality** call, not a break — but "venue placement still lands" is in A's ledger and
|
||||
F's district gate, so it's theirs. Castlemaine shows the good case (band_room *on* the hub).
|
||||
|
||||
---
|
||||
|
||||
## ROUND 19 — v4.0-alpha CLOSE: the fragmentation re-measure (ledger #3)
|
||||
|
||||
*PROCITY-D, 2026-07-16. A resolved my R18 finding (commit `6fc4b40`: junction-protected DP + bridge-joins
|
||||
|
||||
@ -1,5 +1,263 @@
|
||||
# LANE E — cross-lane notes
|
||||
|
||||
## Round 23 — v5.0-alpha: the atlas validator + the toowoomba call ⟨v5.0-alpha⟩
|
||||
|
||||
**1. `validate_atlas` — the G2a format gate (ledger #4).** `pipeline/validate_atlas.py`, wired into
|
||||
`validate_manifest` so it runs in qa gate 3. It judges G's tier-1 atlases **from the committed files
|
||||
alone — no dealgod DB, no network** (that's the point: the gate must run on m3). No atlases yet ⇒ clean
|
||||
pass, so it's in place *before* G's lands. **→ Lane G: this is the format you build to.** It enforces:
|
||||
|
||||
| rule | source |
|
||||
|---|---|
|
||||
| provenance `{license, attribution, generator, snapshot}` present | charter law #3 ("every atlas carries provenance") + `snapshot` pins determinism |
|
||||
| **no private-individual fields** on items (`seller`/`owner`/`email`/`phone`/…) | charter law #3 ("no private-individual data, ever") |
|
||||
| 1 atlas/shop, **hard cap 2** | LANE_C_PUB §7.1 |
|
||||
| **≤1024²**; 2048² only for a **60+ item** shop | LANE_C_PUB §7.2 |
|
||||
| declared `atlas_px` == the atlas's **real** pixels; every listed atlas exists | index↔atlas consistency |
|
||||
| schema + UV in range/not inverted, bands, dup ids, empty title/artist, atlas∈atlases[] | §7.3 (schema confirmed verbatim) |
|
||||
|
||||
The licence string is **printed** by the gate, not pattern-matched — clearing photos for public release is
|
||||
a human call (charter law #3), so the gate surfaces it rather than pretending to rule on it. **Tested
|
||||
against a 10-case matrix** (real 2048² WebP + 1024² PNG headers, dependency-free parser): good passes
|
||||
clean; all 9 violations caught. A gate that's never caught anything isn't a gate.
|
||||
|
||||
**2. Toowoomba — RETIRED (my call, counted). I tried the re-bbox first; the data said retire.**
|
||||
D's finding: DEAD — 0 patronage finds in 1417 checks. I re-bboxed 3.0 km CBD-wide → 1.6 km onto the
|
||||
Ruthven St heart. Result:
|
||||
|
||||
| | median NN (the named metric) | hub ≤160 m (D's real test) |
|
||||
|---|---|---|
|
||||
| toowoomba @3.0 km | 395.7 m ❌ | 3/12 |
|
||||
| **toowoomba @1.6 km (re-bbox)** | **89.4 m ✅ "passes"** | **2/12 (17%) — worst in the pack** |
|
||||
| darwin (D: ALIVE) | 68.3 m | 7/12 (58%) |
|
||||
|
||||
**The re-bbox fixed the metric and made the town worse.** Toowoomba's shops are *pairs* strung along a
|
||||
road, not a high street — close-in-pairs flatters median NN while no hub exists. Its densest 300 m
|
||||
cluster is **3 shops** (below `MIN_TOWN_SHOPS`), so no tighter box exists either. Shipping it would have
|
||||
gamed the number while leaving a town nobody shops in. **Retired** (config entry kept commented with the
|
||||
rationale so nobody re-adds it blindly). Pack: **21 real + 1 godverse = 22 rostered · 1180 shops · all 8
|
||||
states/territories** (QLD keeps westend, 11 heroes/44 shops). selfcheck **ALL GREEN 152015/152015**.
|
||||
|
||||
### ⚠️ → Lane A: two things before you freeze the spacing warn (#5)
|
||||
1. **Your validator sees the CACHE (raw lat/lon); D's numbers are SEATED (post-lift).** They differ:
|
||||
**darwin — D 27 m seated vs 68.3 m raw**; toowoomba 388 seated vs 395.7 raw (spread towns barely move,
|
||||
clustered ones move a lot). **Calibrate off the raw spread, not D's table** — a threshold picked near
|
||||
"darwin 27 = alive" would **false-warn darwin at 68.3**. Raw spread now: daylesford 11.6 · katoomba
|
||||
22.4 · bowral 27.9 · … · launceston 76.8 · braddon 119.2 · **ballarat 254.6**. A ~200 m line has
|
||||
daylight on both sides.
|
||||
2. **Median NN alone is gameable — the hub fraction is the discriminator.** My re-bbox is the proof: NN
|
||||
improved 4.4× while the town got worse. Consider warning on **hub density** too (shops within 160 m of
|
||||
the densest shop). **My raw hub metric reproduces D's darwin exactly (7/12)**, so it's directly
|
||||
encodable: alive ≈ 38–75% (castlemaine/darwin/launceston/daylesford), dead ≈ ≤20% (ballarat 20%,
|
||||
retired-toowoomba 17%).
|
||||
3. **ballarat is a second spread town** D's two-town comparison never covered: **NN 254.6 m, hub 4/20
|
||||
(20%)** — it *will* trip your warn (correctly). Not retired: the ledger named toowoomba only, and a
|
||||
warn is A's to raise, curation is mine to act on. Flagging for Fable's next-round call.
|
||||
|
||||
## Round 22 — v4.0 THE EPOCH CLOSE: the tri diet (mine) · notes · provenance · the G2a review ⟨v4.0⟩
|
||||
|
||||
### ⚠️ Ledger #1b — the `venue_night` tri diet: **MY four furniture GLBs. Fixed. → F: RE-MEASURE.**
|
||||
|
||||
B's #1a diagnosis named it exactly right and I own it: **149,161 of 163,015 tris (91.5%)** were four
|
||||
GLB street props of mine. Buildings 348, posters 222 (A's cap worked — never the lever), pools 0. B's
|
||||
sharpest point: **it isn't the density widening** — *road* density drives the bench/bin/shelter cadence
|
||||
(~100 GLB props on a 966-edge real graph). Great call; the shop count was a red herring.
|
||||
|
||||
**The fix** — collapse-decimate via the R13 instrument tool (`decimate_props.py`), originals kept as `_hi`:
|
||||
|
||||
| prop | tris/inst | → | rendered (B's frustum-accurate) | → | saved |
|
||||
|---|---|---|---|---|---|
|
||||
| bench | 1,942 | **500** | 60,202 | 15,500 | **44,702** |
|
||||
| bus_shelter | 7,998 | **2,500** | 55,986 | 17,500 | **38,486** |
|
||||
| bin | 3,000 | **800** | 18,000 | 4,800 | **13,200** |
|
||||
| food_cart | 4,991 | **1,000** | 14,973 | 3,000 | **11,973** |
|
||||
| | | | **149,161** | **40,800** | **−108,361** |
|
||||
|
||||
**Projected view: ~54.7k** (B's 163,015 baseline) or **~113.6k** (vs F's 221,935 re-measure) — **PASS
|
||||
the 200k gate either way**, 86–145k to spare. **F: the numbers are projections from B's per-instance
|
||||
attribution — yours is the real measure.**
|
||||
|
||||
**Eyeballed every one (house law), and two needed re-cutting:** bus_shelter @1,000 **shredded** (its
|
||||
thin roof plate is a voxel-remeshed shell — collapse tore it into spikes) → re-cut **@2,500**, clean;
|
||||
bin @400 creased → **@800**, clean. bench @500 is perfect. food_cart @1,000 matches its original —
|
||||
including the black base slab, which I confirmed against the pre-diet thumb from git: **that slab is in
|
||||
the source model**, not a decimation artifact.
|
||||
|
||||
**Zero blast radius:** dims/footprints/heights **identical** → `manifest.json` **byte-identical** → B's
|
||||
placement and A's goldens untouched. Published to the depot (4 diet + 4 `_hi` archives) — the GLBs are
|
||||
git-ignored, so the depot IS the archive.
|
||||
|
||||
**→ Lane B, your second note, cause found (NOT fixed — deliberate).** The MeshPhysicalMaterial you spotted
|
||||
on bench + food_cart is **`KHR_materials_specular`** in the glTF, and it **survives a Blender re-export**.
|
||||
Fix = neutralise specular pre-export (or strip the extension) → three.js falls back to MeshStandardMaterial.
|
||||
I did **not** do it this round: the gate is tris (cleared with margin), and changing a shipped prop's
|
||||
shading in a release round without a visual re-verify is risk for no gate benefit. **Filed for v4.x** with
|
||||
the cause. Same list: **food_cart is 3.1 MB on the depot — 28 textures now dominate** (tris are 1,000);
|
||||
a texture-merge item, not a tri item.
|
||||
|
||||
**Process note on myself:** the depot push was blocked, correctly — I'd been carrying John's **R13**
|
||||
approval (which named only the *instruments*) forward across rounds as if standing, because the briefs
|
||||
kept saying "John's R13 authorization stands". Briefs aren't the user. John re-authorized explicitly and
|
||||
granted **standing for Lane E assets** (stating what/where each time), so it's clean now — but the drift
|
||||
was mine, not the classifier's.
|
||||
|
||||
## Round 22 (cont.) — notes refresh · provenance v4-final · the G2a review
|
||||
|
||||
**1. MODELBEAST notes refreshed (ledger #3).** `pipeline/README.md`'s toolchain row said
|
||||
`sf3d`/`trellis_mac` were *"local but HF-gated (setup needed)"* — **ultra was right, that was stale**
|
||||
(weights cached; `trellis_mac` went **15/15** on thriftgod's Tier-1 props; the `:8777` queue also runs
|
||||
`bg_remove_local`/`mflux_image_edit`/`seedvr2_upscale`). Corrected, and **ultra's ⚠️ finish-farm
|
||||
post-mortem is folded into the props section verbatim + credited** — with Fable's Ruling 1 amendment
|
||||
(**NO-DRACO**: drop `--compress draco`, the rest of ultra's command stands; the depot holds ONE law and
|
||||
our loader stays decoder-free). Our own `bake_lowpoly.py` / `decimate_props.py` are noted as *not* the
|
||||
finish farm, so nobody conflates them.
|
||||
|
||||
**2. Provenance table → v4-FINAL (ledger #6).** The frozen table now carries the pack (**22 real AU
|
||||
towns · all 8 states/territories · 1192 shops + `roads[]`**, ODbL, raw snapshots committed) and — for a
|
||||
**complete licence spine** — a row for **Lane G's `newtown_godverse`**, explicitly attributed to G
|
||||
(GODVERSE census + OSM roads, their `godverse_town.py`, their fields). Nothing shipped is unaccounted.
|
||||
|
||||
**3. 🐛 The index was missing Lane G's town.** `index.json` still listed **22** — it was generated
|
||||
before the `dd4489c` merge, so `newtown_godverse` was invisible to **B's selector**, and R22 verifies at
|
||||
**23-town scale**. Regenerated → **23 towns**, selfcheck **ALL GREEN 161300/161300**. I also made the
|
||||
index **self-describing**: `state` now reads from the cache first, then my config — so any lane's town
|
||||
rosters itself. **→ Lane G: add `"state": "NSW"` to `newtown_godverse.json` and the index picks it up**
|
||||
(it's the one town showing a blank state).
|
||||
|
||||
### → LANE G: the **G2a atlas contract — E's half** (C answers the interior ceilings in LANE_C_PUB)
|
||||
|
||||
**Verdict: Fable's three priors hold. Atlas-per-shop isn't just viable — done right it FIXES a VRAM
|
||||
problem we already have.** Two conditions and one reuse:
|
||||
|
||||
**a. Atlas per shop — YES, with one hard condition: dispose on exit.** Today `stockpack.js` caches
|
||||
type-pack atlases and **never disposes** (my R9 audit: record+book+toy = **181 MB resident**, accreting
|
||||
as you visit types). Per-shop makes that much worse *or* much better, on one line:
|
||||
- **Without dispose:** every shop entered strands its atlas — a 12-shop town ⇒ ~12 atlases ⇒ at 2048²
|
||||
(~16 MB each) **~190 MB per town visited**, unbounded across 23 towns. Non-starter.
|
||||
- **With dispose-on-exit** (the R9 cache-cap I flagged to C back then — **now load-bearing**): only the
|
||||
shop you're standing in is resident ⇒ **~4 MB** at 1024². That's **~45× better than today's 181 MB**.
|
||||
G2a is the reason to finally land that cap. **Ask: the loader disposes the previous shop's atlas on
|
||||
room exit** (C's interiors seam).
|
||||
|
||||
**b. Size the atlas to the shop, not to the format.** A real crate is ~20–60 items; at our **256² cell**,
|
||||
1024² holds 16 and 2048² holds 64. **Ship ≤1024² for a typical shop** — don't hand a 6-item shop a
|
||||
2048². Spill to a second atlas (or fall back to the shared type-pack) for the rare 60+ shop. Book cell
|
||||
stays **176×256** (portrait cover-face — a real constraint: I have cover art, no spines).
|
||||
|
||||
**c. Reuse my index schema verbatim — then nothing downstream changes.**
|
||||
`{version, atlas_px, cell, atlases[], items:[{id,title,artist,price,price_band,atlas,uv:[u0,v0,u1,v1]}]}`,
|
||||
keyed per shop (e.g. `stock_shop_<godverseShopId>_index.json`). You get for free: `stockpack.js` consumes
|
||||
it unchanged · **fail-soft to parody is already law** (pack absent ⇒ parody canvas, proven since R7) ·
|
||||
`?noassets` already fetches zero · and **`validate_pack.py` gates your packs** (UV range, price bands,
|
||||
dangling atlas, dup ids) — already wired into qa gate 3 through `validate_manifest`. Add index fields and
|
||||
tell me; I'll extend `REQUIRED`.
|
||||
|
||||
**d. The parody law is unchanged and non-negotiable** (R8, John's): metadata **deterministically
|
||||
parody-transformed** (sha256-seeded word-banks, no real trademarks), **cover art is the real image**,
|
||||
prices real-or-seeded. Real dealgod inventory changes nothing here.
|
||||
|
||||
**e. Interior draw/tex ceilings are C's call** — my only input to C is the number in (a): per-shop @1024²
|
||||
**with dispose** is ~4 MB resident, i.e. *cheaper than the status quo*, not more expensive.
|
||||
|
||||
## Round 21 — the v4.0 town pack (pull-forward, DATA-ONLY) ⟨v4.0⟩
|
||||
|
||||
> Ledger #5 — **NON-GATING**: nothing here touches a golden until A absorbs in R22, and F tags
|
||||
> `v4.0-beta` with or without it. **The pack: 22 towns · 1192 shops · 298 heroes · all 8 states/
|
||||
> territories.** selfcheck **ALL GREEN 154141/154141**, synthetic `0x3fa36874` unmoved, all 22
|
||||
> byte-identical on re-run. → **Lane A (R22): 34 goldens await your pin** (17 new towns × base+gig).
|
||||
|
||||
**Curation.** Picked on the instincts the first five earned: **secondhand density first** (the heroes
|
||||
are the game — a town with no op shops isn't a PROCITY town), a walkable centre inside a 2.0–3.4 km
|
||||
bbox (the mega-strip law), and a national spread. 18 candidates proposed, 17 survived.
|
||||
|
||||
| tier | towns (heroes/shops) |
|
||||
|---|---|
|
||||
| meccas | **fitzroy 40/160** · adelaide 29/116 · brunswick 24/96 · katoomba 20/80 · hobart 20/80 · fremantle 20/80 |
|
||||
| strong | newtown 18/72 · geelong 17/68 · northbridge 15/60 · glebe 13/52 · westend 11/44 |
|
||||
| solid | newcastle 9/36 · bendigo 9/36 · launceston 8/32 · daylesford 8/32 · bowral 8/32 · marrickville 7/28 · castlemaine 6/24 |
|
||||
| thin tail | braddon 5/20 · ballarat 5/20 · toowoomba 3/12 · darwin 3/12 |
|
||||
|
||||
**The cap is a curation filter, not just a density knob (the round's nicest finding).** Because texture
|
||||
is pegged at **3× heroes**, a town without a secondhand strip *cannot* inflate into a retail strip.
|
||||
**Wollongong** had ~62 real shops but only **1 hero** → capped to 4 → **dropped by the ≥6 floor**. It
|
||||
curated itself out. John's "subtle" directive is doing quality control for free: a town has to earn its
|
||||
place with real op shops. The thin tail (toowoomba/darwin, 3 heroes) is the same signal, weaker —
|
||||
**A/Fable may want to drop them in R22**; I've left them in as honest data rather than pad or hide.
|
||||
|
||||
**Newcastle: my centre was wrong, not the town.** The CBD bbox held 2 heroes; Newcastle's real
|
||||
secondhand strip is **Hamilton / Beaumont St**. Re-centred → **2 → 9 heroes, 8 → 36 shops**.
|
||||
|
||||
### 🐛 The bug I shipped in R18 and caught here — stale roads on a re-centre
|
||||
Re-centring Newcastle refetched the **shops** (my query-comparison caught the bbox change) but
|
||||
**silently reused the old CBD roads** — `fetch_roads_raw` had **no** query-comparison, only
|
||||
`fetch_raw` did. Result: Hamilton shops seated against a road graph **2.5 km away** → A's lift
|
||||
marched the town out with **ZERO shops**, which surfaced as 32 bogus "open-late invariant" failures
|
||||
(a symptom — the real cause was `shops=0`). Ten other towns lack a video shop and pass fine, which is
|
||||
what proved it wasn't the open-late rule. **Fixed**: `fetch_roads_raw` now compares its query like the
|
||||
shops path — the bbox lives in the query, so a re-centre must invalidate **both** snapshots. Newcastle
|
||||
rebuilt: 34 shops seated, openLate=1. *Lesson: a cache-validity check applied to one of two symmetric
|
||||
paths is a trap with a fuse on it.*
|
||||
|
||||
**Discipline unchanged:** bounded bboxes, raw snapshots committed (shops + roads per town), C's §6
|
||||
mapping, SUBTLE guardrails (heroes never subsampled, texture 3×, **2392 drops counted**), deterministic
|
||||
emit, ODbL (`SOURCES.md` + per-cache fields). Overpass: batch 1 died on a fatal 504 after 25 retries
|
||||
(pack scale genuinely rate-limits) — courtesy sleep raised to 4 s, retry/backoff carried the rest; I
|
||||
split into two polite batches rather than hammer a public API.
|
||||
|
||||
**→ Lane A (R22 absorb):** 17 new towns to pin (34 goldens). Sizes range 12 → 160 shops (fitzroy is
|
||||
2× katoomba — your capacity/overflow fences get a real load test). **→ Lane B:** `index.json` now has
|
||||
22 towns; your selector reads it already (thanks — R20 #5 closed). Also: thanks for the selfcheck
|
||||
`index.json` skip, A — my R20 seam flag landed clean.
|
||||
|
||||
## Round 20 — v4.0-beta THE FIVE TOWNS: the density widening ⟨v4-beta⟩
|
||||
|
||||
> Ledger #2, E's half — built to **C's LANE_C_PUB §6 table** under **John's SUBTLE directive**
|
||||
> ("texture, not takeover"). Widened fetch → re-emitted 5 caches → `towns/index.json`.
|
||||
> → **Lane A: caches re-emitted, densities are yours to absorb + re-pin** (see the two flags below).
|
||||
> → **Lane B: `index.json` is live — drop the `REAL_TOWNS` hardcode in `hud.js:11`.**
|
||||
|
||||
**The widening.** `build_towns.py` now fetches C's tasteful staple list (`shop=*` + `amenity=cafe`,
|
||||
per C's †) and maps every class through C's copy-paste table onto **existing** registry types — no new
|
||||
archetypes. Guardrails honoured: **no** vehicle/tyres/fuel, funeral, storage, industrial, or pure
|
||||
services (hairdresser/beauty/bank/laundry/estate_agent). Raw is a **new snapshot** (the fetcher
|
||||
auto-refetches when the class list changes — the query is recorded in each snapshot, so nobody has to
|
||||
remember `--refetch`), committed like the first. Deterministic: same raw → byte-identical cache.
|
||||
|
||||
**SUBTLE, enforced by construction.** The secondhand **heroes are never subsampled** (they're the
|
||||
game). Texture is capped at **3× the hero count** (the top of C's ~2–3× band — keeps the town alive
|
||||
without letting retail take over); over-cap towns are **seed-subsampled deterministically**
|
||||
(`sha256(townKey:osmId)` rank — stable across rebuilds so A's goldens hold) and **every drop counted**
|
||||
in `counts.textureDropped`:
|
||||
|
||||
| town | heroes | texture | ratio | dropped | total | shop mix |
|
||||
|---|---|---|---|---|---|---|
|
||||
| katoomba_real | 20 | 60 | 3.0× | 4 | **80** | milkbar 33 · dept 22 · opshop 13 · stall 5 · book 4 · record 2 · video 1 |
|
||||
| newtown_real | 18 | 54 | 3.0× | 156 | **72** | milkbar 30 · dept 21 · opshop 6 · record 5 · book 4 · stall 3 · toy 2 · video 1 |
|
||||
| fremantle_real | 20 | 60 | 3.0× | 81 | **80** | milkbar 43 · dept 16 · opshop 9 · book 5 · toy 2 · video 2 · pawn 1 · record 1 · stall 1 |
|
||||
| bendigo_real | 9 | 27 | 3.0× | 81 | **36** | milkbar 14 · dept 12 · opshop 3 · book 3 · stall 1 · record 1 · video 1 · pawn 1 |
|
||||
| castlemaine_real | 6 | 18 | 3.0× | 43 | **24** | milkbar 9 · dept 6 · opshop 4 · stall 3 · book 2 |
|
||||
|
||||
Density is now **3–4× pre-widening** (6–21 → 24–80 shops) — inside the ledger's 3–6× expectation for A.
|
||||
Katoomba reads right: the op shops (Cancer Help, Tanglecare, Cat Defence, Red Shield) still anchor the
|
||||
strip, now with The Hatters Cafe, Blue Mountains Chocolate Co, K Hub and Bunnings as texture around them.
|
||||
|
||||
**`web/assets/towns/index.json`** (new, E-owned): `{schema:"procity-towns-index/1", generated, license,
|
||||
attribution, towns:[{key, town, state, shops, roads}]}` — 5 towns, built from every cache on disk.
|
||||
|
||||
### ⚠ Two flags for Lane A (both land in your absorb pass)
|
||||
1. **10 expected golden re-pins** — the widening moves every real town's base + gig golden (5 × 2).
|
||||
That's the designed E→A handoff (ledger #2). **Synthetic `0x3fa36874` is UNMOVED**; fixtures frozen.
|
||||
2. **`index.json` trips selfcheck's town glob** (a real seam — `selfcheck.js:467` takes *every*
|
||||
top-level `towns/*.json` as a cache, so the index validates as a town and fails). **One-line fix in
|
||||
your file:** `.filter(f => f.endsWith('.json') && f !== 'index.json')`. I didn't touch `selfcheck.js`
|
||||
— it's yours and you're mid-round in it. Fable spec'd the index at this path (ledger #2/#5, B reads
|
||||
it there), so the path stays; the glob is what needs to learn about it.
|
||||
|
||||
### → Lane C (one-line map addition)
|
||||
`video_games` wasn't in your §6 table (you listed `video`/`games` → `video`). It was already shipping as
|
||||
`video` pre-widening, so I kept **`video_games` → `video`** rather than regress those shops to `opshop`
|
||||
via the fallback. Same family, same target — flagging so your table and my emit stay the one truth.
|
||||
|
||||
## Round 18 — v4.0-alpha REAL ROADS: the road-geometry fetch ⟨v4-alpha⟩
|
||||
|
||||
> Ledger #3 — E fetches the real street geometry; A's lift builds the CityPlan graph from it.
|
||||
@ -154,7 +412,8 @@ loader wiring (`spawnRig`/fleet). Bench-sit loiter (Fable's stretch goal) reuses
|
||||
| Audio pack | 28 | 100% procedural numpy synthesis (`gen_audio.py`) | oscillator/noise render → ffmpeg OGG+M4A | 🟢 CC0-equiv, parody-safe |
|
||||
| Stock packs | 3 (350/311/273) | GODVERSE `dealgod` Postgres — **real** cover art | `build_stock_pack.py`: parody-transform metadata + atlas | 🟢 real-art / parody-metadata |
|
||||
| Ped clip **⟨v3.1⟩** | `sit.glb` (E R16; + pre-existing `walk`/`idle` from D/90sDJsim) | Adobe **Mixamo** `Sitting_Idle.fbx` | `fbx_to_clip.py` (Blender, mesh-strip) | 🟢 Mixamo royalty-free (motion only, mesh stripped) |
|
||||
| Town caches **⟨v3.2⟩** | 5 real AU towns (Katoomba/Newtown/Fremantle/Bendigo/Castlemaine) | **OpenStreetMap** via Overpass API | `build_towns.py` (fetch → dedupe/suburb/parody/type-map) | 🟢 **ODbL 1.0** (© OSM contributors; `web/assets/towns/SOURCES.md`) |
|
||||
| Town pack **⟨v4.0-FINAL⟩** | **22 real AU towns, all 8 states/territories** — 1192 shops + real street geometry (`roads[]`) | **OpenStreetMap** via Overpass API — bounded per-town bbox; raw shops+roads snapshots committed in `_raw/` | `build_towns.py`: fetch → dedupe/suburb-fill/parody → C's §6 class map → 3× texture cap (drops counted) → v2 cache + roads | 🟢 **ODbL 1.0** (© OSM contributors; `web/assets/towns/SOURCES.md` + `license`/`attribution` on every cache) |
|
||||
| Godverse town *(Lane G's, listed for completeness)* | `newtown_godverse` — 18 shops, rostered in E's `index.json` | GODVERSE/thriftgod shop census + OSM road geometry | `pipeline/godverse_town.py` (**Lane G owns this provenance**) | 🟢 ODbL 1.0 + GODVERSE census (G's own `license`/`attribution` fields) |
|
||||
|
||||
**GLB fleet = 33 manifest (23 fit + 10 furniture) + 5 `_hi` = 38 published.** Only external spend ever:
|
||||
~$0.64 (fal-Hunyuan, R5). Everything else is $0 / on-device (electricity only).
|
||||
|
||||
@ -728,3 +728,115 @@ town selector. F ran two things:
|
||||
the opt-in `--matrix` warn-gate, not a blocking gate. Beta hardening: A caps posters per real town.
|
||||
The alpha's job was **real roads that boot, stream in budget in play, place a district, and read as a real
|
||||
place** — that shipped; the venue_night stress view is the one filed edge.
|
||||
|
||||
---
|
||||
|
||||
## 20. Round 20 — v4.0-beta THE FIVE TOWNS (density). **F never ran — the honest record.**
|
||||
|
||||
R20 widened the towns (E: katoomba 20→80, five towns to 292 shops, "subtle" enforced by construction),
|
||||
proved the directive from the crowd side (D's A/B: heroes kept footfall 17→19 while the street got 3.8×
|
||||
busier), capped posters 438→31 (A), and ruled the tram by shop adjacency (B: the alpha tram fronted **zero
|
||||
shops on 4 of 5 real towns** — "it really was a highway bus"). Excellent round.
|
||||
|
||||
**F did not run, and there was no tag.** Unlike R18 — where F ran the gate and *held* on evidence — R20's F
|
||||
slot simply never executed. The round also left C's density door-soak undone, B's selector still hardcoding
|
||||
`hud.js:11` instead of E's index, and D's relocation finding (katoomba's pub 1,000 m from the retail cluster
|
||||
— a gig with no passing crowd) without an owner. R21 closed all four.
|
||||
|
||||
## 21. Round 21 — the v4.0-beta CLOSE. F: the beta gate + the tag (ledger #6).
|
||||
|
||||
Waves ran [A, B, E] → [C, D] → [F]. A biased `pickVenues` toward retail-cluster adjacency (D's finding) and
|
||||
re-pinned once; B derived the selector from E's `index.json`; C soaked the five towns' doors at density; D
|
||||
verified the relocation (**A's bias PASSES**); E pulled 22 towns / 1192 shops forward as non-gating data.
|
||||
|
||||
### The matrix — 10 towns × 7 gates (`--matrix`)
|
||||
`boot · determ · district · roads · tram · noassets` **green on all ten**. Two budget flags (below).
|
||||
Selfcheck **154141/154141**, `qa.sh --strict` GREEN.
|
||||
|
||||
### Tram (#6c) — B's R20 ruling verified exactly
|
||||
| town | verdict |
|
||||
|---|---|
|
||||
| newtown_real | **runs, 16 stops** (B's phantom-stop fix: was 149) |
|
||||
| castlemaine_real | runs, 10 stops |
|
||||
| katoomba / fremantle / bendigo | **fenced** — 3 / 2 / 1 shops fronted, all < the 5-shop floor |
|
||||
|
||||
New seam: the shell now exposes `window.PROCITY.tram` — B built `routeInfo` *for F's smoke* but nothing
|
||||
published it. F owns index.html, so F wired it. The matrix reads `{fenced, stops, shopsFronted}`.
|
||||
|
||||
### ⚠ The venue_night re-measure (#6b) — RECORDED, and the premise came out false
|
||||
The brief expected A's poster cap to close F's R19 `+965`-tri edge. **It did not — it widened:**
|
||||
| | R19 | R21 |
|
||||
|---|---|---|
|
||||
| katoomba_real venue_night | 200,965 (+965, **+0.5%**) | **221,935 (+21,935, +11%)** |
|
||||
| bendigo_real venue_night | — | 201k (**+0.5%**) |
|
||||
| fremantle / newtown / castlemaine | — | 184k / 179k / 112k ✓ |
|
||||
|
||||
**A's cap worked** (posters 438→111). The cause is the *density widening*: katoomba 19→72 shops + D's 3.8×
|
||||
busier street more than ate the savings. **Draws are fine everywhere** (151/300 — half budget); this is
|
||||
tris-only, at the worst night stress view. Not a blocking gate (it rides the opt-in `--matrix` warn), and
|
||||
the brief's instruction was "record the new number" — recorded. **Filed as R22's first item**, where the
|
||||
tri-diet backlog (instrument LOD · tram tri diet) already lives. katoomba is the outlier: a 966-edge graph
|
||||
carrying 72 shops.
|
||||
|
||||
### Ruling (#6d) — selector gating: **always in the HUD**, not `?dbg`-gated
|
||||
THE REAL MAP's product *is* the towns; gating their discovery behind a debug flag would hide the epoch's
|
||||
headline feature. It's unobtrusive, shows friendly names, and lives in the HUD overlay. **Corollary (F's own
|
||||
gap):** money shots must hide the HUD — the R19 alpha shot had the selector in-frame because F's harness
|
||||
screenshotted the full viewport. The beta shot hides it.
|
||||
|
||||
### The beta money shot (#6e)
|
||||
[`docs/shots/v4_beta/fremantle_mainstreet.png`](../shots/v4_beta/fremantle_mainstreet.png) — Fremantle's real
|
||||
street at golden hour: buildings lining both sides down the corridor, footpaths, a crossing, gum trees,
|
||||
streetlamps, and a genuinely busy crowd (the density widening, visible). 126 draws / 167k tris, HUD hidden.
|
||||
Posed by projecting the densest **spatial** shop cluster (10 shops within 60 m) onto its front edge and
|
||||
standing on the road — per-edge shop counts are ~1 on a real graph, so "densest edge" is the wrong pose
|
||||
heuristic for real towns.
|
||||
|
||||
---
|
||||
|
||||
## 22. Round 22 — **v4.0: THE EPOCH CLOSE.** F: the re-measure, the selector, the tour, the tag.
|
||||
|
||||
### The blocking gate — CLOSED (#1b)
|
||||
F's R21 re-measure made the tri diet blocking ("release rounds don't ship known breaches"). B diagnosed
|
||||
layer-by-layer, the driver traced **A → E** (A's own commit said so: "B's numbers say the fix is E's"), and
|
||||
**E fixed it**. F re-measured:
|
||||
|
||||
| `venue_night` | R21 | R22 |
|
||||
|---|---|---|
|
||||
| katoomba_real | 221,935 (+11%) | **108,365** (−51%) |
|
||||
| bendigo_real | 201k (+0.5%) | **124,682** |
|
||||
| fremantle_real | 184k | **88,397** |
|
||||
|
||||
**Worst 124,682 — the 200k stress edge is closed.** Selfcheck **161,300/161,300** (A's 36 goldens + the
|
||||
23-town pack). The measure-first loop worked end to end: F measured, B diagnosed, E owned, F re-measured.
|
||||
|
||||
### `DBG.enterShop(selector)` (#3 — Lane G's ask)
|
||||
Grew from id-only to a selector: id · registry type · exact name · name substring, resolved most-specific
|
||||
first so an id-shaped string can't be shadowed. `DBG.enterShop('record')` / `('The Exchange Hotel')` / `(116)`
|
||||
— cross-repo QA one-liners on any of 23 towns without an id lookup.
|
||||
|
||||
### The release tour (#4) — `docs/shots/release_v4/`
|
||||
Five frames: the Fremantle golden-hour hero · the Fitzroy mecca · the Toowoomba thin tail · the synthetic
|
||||
control · the `?classic=1` covenant. Every real-town frame poses through **F's R21 cluster primitive**
|
||||
(project the densest SPATIAL cluster onto its front edge, stand on the road); B's R22 bookmark fix anchors
|
||||
on the same retail heart, so tour and bookmarks agree by construction. HUD hidden throughout.
|
||||
|
||||
**The primitive earned itself twice.** First cut of the tour posed via `DBG.shot('street_noon')` and the
|
||||
**mecca came out as bare bitumen** — the same trap as R21. Re-posed through the cluster primitive, Fitzroy
|
||||
reads (7 shops/60 m retail heart, trees, benches, crowd). *Per-edge is always the wrong heuristic on a real
|
||||
graph.*
|
||||
|
||||
### ⚠ The honest thin-tail finding (filed for v4.x)
|
||||
The brief asked for a thin-tail frame as **"proof the floor holds."** It doesn't, and the frame says so:
|
||||
**Toowoomba reads as an empty road, not a town.** The cause is a RATIO, not a count —
|
||||
|
||||
| town | shops / edges | shops per edge |
|
||||
|---|---|---|
|
||||
| fremantle (hero) | 79 / 1,056 | 0.075 |
|
||||
| fitzroy (mecca) | 139 / 2,408 | 0.058 |
|
||||
| **toowoomba (thin tail)** | **12 / 1,459** | **0.008** — 7–9× sparser |
|
||||
|
||||
Toowoomba is a real city whose *secondhand* cache is 12 shops, spread across its full road graph. The floor
|
||||
isn't "≥N shops" — it's **shops-per-street-metre**. v4.x options: clip the graph to the retail core for
|
||||
thin caches, or set a ratio floor in the pack. The frame ships **labelled honestly** rather than swapped for
|
||||
a flattering town — same discipline as the R21 re-measure that got worse and said so.
|
||||
|
||||
91
docs/LANES/ROUND21_INSTRUCTIONS.md
Normal file
@ -0,0 +1,91 @@
|
||||
# PROCITY — Round 21 lane instructions (from Fable, integrator)
|
||||
|
||||
Date: 2026-07-16 · **R20 verdict: the density round delivered — but the beta is NOT tagged.**
|
||||
What landed (all verified in notes): **A** capped posters 438→31 (+ a new `REAL_TOWN_GIG_GOLDENS`
|
||||
guard) and absorbed the 3–6× densities (292 shops across five towns, drops down to 5.5% and now
|
||||
counted BY CAUSE, pub facade bias extended, goldens re-pinned once); **C** published the
|
||||
class→archetype mapping; **E** widened the fetch under John's SUBTLE directive — enforced by
|
||||
construction (heroes never subsampled, texture capped at 3× heroes, deterministic seed-subsample,
|
||||
every drop counted); **B** ruled the tram by shop adjacency (the alpha tram fronted 0 shops on 4
|
||||
of 5 real towns — "it really was a highway bus"), fenced katoomba/fremantle/bendigo per the
|
||||
fallback, and found+fixed the phantom-stop bug (Newtown: 149 stops → 16); **D** delivered the
|
||||
density A/B — heroes kept footfall (17→19 visits) while the street got 3.8× busier — and published
|
||||
a model self-correction (the sparsity null% was an artefact of measuring at 21:00 from the one
|
||||
isolated venue; the record is corrected in place). Quality is at series-best.
|
||||
|
||||
**Open (why there's no tag):** F never ran (gate + tag), C's density door-soak (R20 ledger #6)
|
||||
wasn't done, B never consumed E's `index.json` (`hud.js:11` still hardcodes `REAL_TOWNS`), and D
|
||||
filed the **main-street relocation finding**: katoomba's pub sits 1,000 m from the retail cluster
|
||||
(1 shop within 160 m vs the RSL's 13) — a gig with no passing crowd. Round 21 closes all of it.
|
||||
Tag **`v4.0-beta`** at close.
|
||||
|
||||
## The ledger
|
||||
|
||||
1. **Venue cluster bias (A — D's relocation finding).** At real-town density, venue candidates
|
||||
should prefer retail-cluster adjacency: bias `pickVenues` toward candidates with ≥N shops
|
||||
within ~160 m (you own N and the weighting; keep the kind placement flavors — pub corner,
|
||||
band_room fringe, rsl off-spine — as biases within the cluster-adjacent set, not absolutes).
|
||||
**Synthetic + fixtures must be a no-op** (goldens frozen — the synthetic town is one dense
|
||||
cluster anyway); real-town goldens re-pin ONCE (base+gig, amendment law). Acceptance: no venue
|
||||
in any of the five towns sits with <3 shops in its 160 m radius unless the town genuinely has
|
||||
no better candidate (castlemaine's floor may — say so if it does).
|
||||
2. **Selector from the index (B — the missed R20 #5).** Drop the `hud.js:11` hardcode; fetch
|
||||
E's `web/assets/towns/index.json` (fail-soft: index missing → current hardcode as fallback,
|
||||
zero errors). Selector shows shop counts from the index if it's cheap. Nothing else — your
|
||||
tram/ground work is done and verified.
|
||||
3. **Density door-soak (C — the missed R20 #6).** The five towns at their new densities, sampled
|
||||
doors per town (~25/town covers every archetype incl. the texture classes' first real
|
||||
appearance — milkbar/dept/stall interiors on real shops), assert valid interiors / no carve /
|
||||
budgets / types resolve per your §6 table. One table, five rows. **Run AFTER A lands** (venue
|
||||
relocation changes which lots are venues).
|
||||
4. **Relocation verify (D).** After A: re-run the katoomba venue-adjacency numbers (the pub
|
||||
should now sit in/near the cluster; your venue-win % and the "gig with no passing crowd"
|
||||
observation should improve). One before/after line per town. Your GIG_RANGE chunk-blindness
|
||||
finding stays filed for v4.x (agreed — not a mid-beta hot-path change).
|
||||
5. **Town-pack pull-forward (E — NON-GATING for the tag).** Begin v4.0's curated pack: propose
|
||||
the candidate list (~15–25 AU towns with real secondhand density — you have the data instincts
|
||||
from the first five), fetch + emit as **data-only commits** (same discipline: bounded bbox,
|
||||
raw snapshots, C's mapping, SUBTLE guardrails, index.json grows). A absorbs + pins in R22 —
|
||||
do NOT block the beta tag on any of it. If Overpass rate-limits at pack scale, batch politely
|
||||
(sleep between towns) — the standing auth covers the fetches, courtesy covers the cadence.
|
||||
6. **The beta gate (F, last).**
|
||||
a. Five-town matrix at density (boot/determinism/budget/district/noassets/roads), against A's
|
||||
R21 re-pinned goldens.
|
||||
b. **`venue_night` re-measure** — A's poster cap must close your +965-tri edge; record the
|
||||
new number.
|
||||
c. Tram in the matrix: newtown + castlemaine RUN (stops sane), the three fenced towns show
|
||||
no tram and no errors (B's fence signal).
|
||||
d. **Selector gating ruling** (your R19 open call): always-in-HUD vs `?dbg`-gated — rule it,
|
||||
one line, done.
|
||||
e. Beta money shot: the densest real main street at golden hour (fremantle or katoomba
|
||||
post-widening — continuous shopfronts, crowd, heroes among texture), into
|
||||
`docs/shots/v4_beta/`.
|
||||
f. Docs (README beta paragraph, LANE_F_NOTES §20/§21 incl. the honest why-R20-had-no-tag
|
||||
record), tag **`v4.0-beta`**, push (standing authorization).
|
||||
|
||||
## Laws
|
||||
|
||||
Charter unchanged. One golden move this round (A's #1, batched base+gig, once). Synthetic
|
||||
`0x3fa36874`, gig `0xb1d48ea1`, fixtures, classic — frozen. E's pack commits are data-only and
|
||||
touch no golden until A absorbs them (R22).
|
||||
|
||||
## THE EXACT RUN ORDER (John asked)
|
||||
|
||||
1. **Start together: A + B + E.** (A: venue bias — the only golden-moving item, the round's
|
||||
spine. B: selector-from-index — independent. E: town-pack fetches — independent, non-gating.)
|
||||
2. **When A commits (goldens re-pinned): start C + D.** (C: door-soak on the relocated world.
|
||||
D: relocation verify. Both read A's handshake note for the new goldens.)
|
||||
3. **When C + D have committed (and B is in): start F.** (E does NOT gate F — if E's pack is
|
||||
still fetching, F tags without it.)
|
||||
|
||||
Three waves: **[A, B, E] → [C, D] → [F].**
|
||||
|
||||
## Standing
|
||||
|
||||
- **John — nothing blocks this round.** FYI: Fable assigned D's relocation finding to A as a
|
||||
cluster-adjacency bias (real towns only, synthetic untouched); E begins the v4.0 town pack
|
||||
early as data-only commits. Both are course-corrections inside the chartered epoch.
|
||||
- **v4.0 preview (R22, do not start beyond E's data):** A absorbs + pins the pack · the release
|
||||
tour on real streets · docs freeze across lanes · the `v4.0` tag · then the epoch retro.
|
||||
- **Backlog:** GIG_RANGE chunk-neighbour lookup (D, v4.x) · instrument LOD · tram tri diet ·
|
||||
audioEmitter · flag retirement · awning ripple · editor · multiplayer.
|
||||
96
docs/LANES/ROUND22_INSTRUCTIONS.md
Normal file
@ -0,0 +1,96 @@
|
||||
# PROCITY — Round 22 lane instructions (from Fable, integrator)
|
||||
|
||||
Date: 2026-07-16 · **v4.0-beta SHIPPED** (`201a2bb`, tag on origin; matrix 10 towns × 7 gates
|
||||
green; qa `--strict` GREEN; selfcheck now 161k). R21 review, short: A's cluster bias turned out
|
||||
to fix a **systemic** stranding (11 of 15 venues under threshold → 0 of 15; Newtown's RSL moved
|
||||
2,228 m → 29 m from the retail hub), D closed its own finding with the before/after table, B and
|
||||
C cleared the missed R20 items, E pulled the entire v4.0 town pack forward (**22 towns · 1,192
|
||||
shops · 298 heroes · all 8 states/territories**, byte-identical re-runs, non-gating as briefed),
|
||||
and F closed the gate honestly — including recording that the one budget edge got WORSE, and
|
||||
correcting its own first bad money shot instead of shipping it. **Lane G is merged** at the
|
||||
boundary as ruled (`dd4489c`); the godverse town passes the sweep (161,264 green).
|
||||
|
||||
Round 22 is **v4.0: THE EPOCH CLOSE** — the tri diet first, the pack absorbed, the release tour
|
||||
on real streets, the Lane G items, docs freeze, the tag. Tag **`v4.0`** at close.
|
||||
|
||||
## The ledger
|
||||
|
||||
1. **The `venue_night` tri diet (FIRST — the one open gate edge).** F's re-measure: katoomba
|
||||
200,965 → **221,935 tris (+11%)**, bendigo ~201k. A's poster cap worked (438→111); **E's
|
||||
density widening ate the savings** — more shops = more night geometry at the venue block.
|
||||
Two steps, measure-first:
|
||||
a. **B diagnoses** (streetscape budget is B's): a layer-by-layer `renderer.info` breakdown at
|
||||
katoomba `venue_night` (buildings / posters / furniture / pools / peds / interiors-spill) —
|
||||
name the driver with numbers, half a day.
|
||||
b. **The named owner fixes** (A if it's plan geometry (lot/poster counts), B if render
|
||||
(merge/instance/LOD), E if asset tris), F re-measures. **Must land < 200k** — this is a
|
||||
release round; the stress view IS the gate.
|
||||
2. **A absorbs the pack (the round's spine).** E's 17 new towns + Lane G's `newtown_godverse`:
|
||||
feed all through the lift, pin the **36 waiting goldens** (selfcheck prints them), and absorb
|
||||
what the meccas surface — **fitzroy is 160 shops, 2× anything absorbed so far**; expect
|
||||
overflow/capacity work (your R20 fence, now at scale; drops stay counted by cause). The
|
||||
cluster bias + poster cap + junction pipeline all run at 23-town scale for the first time —
|
||||
anything that breaks gets the usual treatment: fix or fence, written down. Batch the pins:
|
||||
ONE golden commit at the end.
|
||||
3. **The Lane G items (queued from FABLE_TO_LANE_G):**
|
||||
- **B**: per-town bookmarks — resolve bookmark poses against the ACTIVE plan (the
|
||||
synthetic-coord teleports land inside OSM geometry; you did this for `poseForVenue` — the
|
||||
old set gets the same treatment, plus F's R21 cluster-pose finding below is the right
|
||||
primitive to share).
|
||||
- **F**: `DBG.enterShop(selector)` grows name/type/id matching (cross-repo QA one-liners).
|
||||
- **E**: refresh the stale MODELBEAST notes (trellis_mac proven, the :8777 queue, ultra's
|
||||
⚠️ finish-farm warning folded into pipeline docs verbatim, credited).
|
||||
- **Lane G (on ultra, their branch): G2a is GREEN as of the beta tag** — static per-shop
|
||||
real-stock atlases behind `?stock=real`, per the ruling. **C + E: the contract review is
|
||||
due this round** (atlas per shop · interior budget ceilings are C's · fail-soft to parody).
|
||||
Answer in G-progress or LANE_C_PUB; G builds to it next.
|
||||
4. **The release tour (F).** `tour_shots.py` grows the v4 set → `docs/shots/release_v4/`:
|
||||
the Fremantle golden-hour hero (have it), a mecca main street (fitzroy, once A absorbs), a
|
||||
thin-tail town (proof the floor holds), the synthetic town (unchanged, the control), and one
|
||||
`?classic=1` frame (the covenant, visible). **Use your cluster-pose finding as the tour's
|
||||
posing primitive** (project the densest spatial cluster onto its front edge, stand on the
|
||||
road) — and publish it as a `dbg.js`-consumable so B's bookmark fix (#3) shares it.
|
||||
5. **Interiors + citizens at pack scale (C + D, after A).**
|
||||
- **C**: sampled door-soak across all 23 towns (your five-town harness, scaled — sample
|
||||
~10 doors/town, every archetype covered town-set-wide). One table.
|
||||
- **D**: the fitzroy stress audit (160 shops — the densest citizen field yet: patronage,
|
||||
rosters, determinism) + spot-check one thin-tail town (does 12 shops still read alive?).
|
||||
6. **Docs freeze + the tag (F last, with A).**
|
||||
- A: CITY_SPEC v4 amendments final (schema v2, the lift, connectivity rulings, cluster bias,
|
||||
poster cap), charter risk-list retired-or-carried line by line.
|
||||
- E: provenance table v4-final (22 towns ODbL + the pack's SOURCES.md; the licence spine
|
||||
stays complete).
|
||||
- F: README v4 section (the towns ARE the product — say it with the selector + tour), notes
|
||||
§22, `qa.sh --strict --matrix` green, tag **`v4.0`**, push. **After the tag: Fable writes
|
||||
the epoch retro** (not a lane deliverable).
|
||||
|
||||
## Laws
|
||||
|
||||
Charter laws hold. Golden discipline: A pins the 36 in ONE commit; the tri-diet fix may move
|
||||
real-town goldens if it's plan-side (amendment law, same commit as the fix); synthetic
|
||||
`0x3fa36874`, gig `0xb1d48ea1`, fixtures, classic — frozen, as always. The 200k stress edge is a
|
||||
BLOCKING gate this round (release rounds don't ship known breaches — the R19 precedent).
|
||||
|
||||
## THE EXACT RUN ORDER
|
||||
|
||||
1. **Start together: B (tri diagnosis, #1a) + A (pack absorb, #2) + E (#3 notes + #6 provenance
|
||||
prep).** B's diagnosis is half a day — it lands while A grinds.
|
||||
2. **When B names the tri driver: the owner fixes (#1b)** — if it's A, fold into the absorb; if
|
||||
B/E, parallel commit. **F re-measures immediately.**
|
||||
3. **When A pins the goldens: start C (#5 soak) + D (#5 audits) + F (#3 enterShop + #4 tour
|
||||
build-out).** C+E answer G2a's contract review (#3) in this window too.
|
||||
4. **When C, D, and the tri re-measure are green: F closes (#6)** — docs, matrix, tag `v4.0`,
|
||||
push. Fable retro follows.
|
||||
|
||||
Waves: **[B, A, E] → [tri-fix owner] → [C, D, F-prep] → [F close]**.
|
||||
|
||||
## Standing
|
||||
|
||||
- **John — nothing blocks this round.** FYI: Lane G is merged and their G2a (real stock atlases,
|
||||
static half) is green to start on their branch per the standing ruling; the C/E contract
|
||||
review happens this round. The tri-diet is blocking for the tag — if the fix turns out to need
|
||||
scope you'll hear it loudly, not silently.
|
||||
- **After v4.0 (the retro decides, do not start):** G2b/G3 design (the economy — both
|
||||
integrators + you) · the v4.x backlog (GIG_RANGE chunk lookup · instrument LOD · tram tri diet
|
||||
· flag retirement · awning ripple · fabric canopies) · and the next epoch conversation
|
||||
(editor vs multiplayer vs the GODVERSE deepening).
|
||||
89
docs/LANES/ROUND23_INSTRUCTIONS.md
Normal file
@ -0,0 +1,89 @@
|
||||
# PROCITY — Round 23 lane instructions (from Fable, integrator)
|
||||
|
||||
Date: 2026-07-16 · **v4.0 SHIPPED, the epoch retro is written ([RETRO_V4.md](../RETRO_V4.md)),
|
||||
and John has chartered v5: [THE REAL SHOP](../V5_REAL_SHOP.md).** Read the charter first — the
|
||||
tier ladder (parody → static real → live GODVERSE) and the five laws are the epoch's spine.
|
||||
|
||||
Round 23 is **v5.0-alpha: THE FIRST REAL CRATE** — one real shop's real stock, riffled and
|
||||
bought, end-to-end, plus the G3 design doc drafted for ratification. The v4.x tail runs as the
|
||||
light parallel lane the retro promised. Tag **`v5.0-alpha`** at close.
|
||||
|
||||
## The ledger
|
||||
|
||||
1. **G2a: the first real atlas (Lane G, on ultra — the round's headline).** Build the per-shop
|
||||
atlas pipeline per the R22-reviewed contract (C's ceilings · atlas-per-shop keyed by godverse
|
||||
shop id · fail-soft to parody): dealgod inventory → a static atlas + index for ONE real
|
||||
Newtown record shop in `newtown_godverse`. Files stay in the G namespace
|
||||
(`pipeline/godverse_*`, `web/assets/stock_godverse/**`). Deterministic (byte-identical
|
||||
re-run against the same inventory snapshot — snapshot the query result like you snapshot
|
||||
Overpass raw). Provenance per the licence law (charter law #3). **This deliverable is
|
||||
gating, so per the merge amendment (charter law #5) your branch merges mid-round once
|
||||
Fable-m3 reviews.** Ping via G-progress when it's up.
|
||||
2. **G3: the design doc draft (Lane G + both Fables).** `docs/G3_ECONOMY_DESIGN.md` covering:
|
||||
API surface (read stock / price / reserve / buy) · async-enrichment caching (risk #1 — the
|
||||
dig never blocks on network) · the determinism boundary (charter law #2) · failure modes
|
||||
(server dies mid-session → tier 1 without a stutter) · census↔lot identity matching (risk
|
||||
#3, mismatches counted) · **the write-back question stated as a decision for John** (charter
|
||||
law #4 — options with trade-offs, no default). Draft this round; ratification is R24's
|
||||
kickoff. Nothing server-side ships before it.
|
||||
3. **The interior side of the real crate (C).** When G's atlas merges: verify the contract in
|
||||
the room — the godverse record shop's crates resolve the per-shop atlas through the existing
|
||||
`stockAdapter` path, interior draw/tex ceilings hold with real covers, `?noassets` and
|
||||
missing-atlas boots fail soft to parody with zero errors. If the contract needs an
|
||||
amendment, you own the wording (LANE_C_PUB), G re-emits.
|
||||
4. **The atlas validator (E).** The format side: a `validate_atlas` check in the pipeline
|
||||
(dimensions ≤ C's ceilings, provenance fields present, index↔atlas consistency, determinism
|
||||
note) so G's output is gate-checkable on m3 without the dealgod DB. Plus the retro's
|
||||
curation item: **the thin-tail clustering floor** — re-bbox toowoomba onto its actual retail
|
||||
strip (or retire it, your call, counted either way) using D's median nearest-neighbour
|
||||
spacing as the curation metric.
|
||||
5. **The spacing warn (A).** D's metric becomes law: `validateTownCache` warns when median
|
||||
nearest-neighbour spacing exceeds a threshold you pick from the pack's measured spread
|
||||
(darwin 27 m alive, toowoomba 388 m dead — plenty of daylight for a line). Warn, not error —
|
||||
E curates, the validator flags. Re-pin only what E's toowoomba fix moves.
|
||||
6. **GIG_RANGE chunk-neighbour fix (D — the carried v4.x item).** The 8.7% blind spot: extend
|
||||
`_nearestOpenShop` to the ped's chunk + 8 neighbours. Hot path, so bring the R18-style
|
||||
before/after (perf cost, blind-spot elimination, determinism unchanged, byte-identical
|
||||
flags-off). Post-release is exactly when this change is safe.
|
||||
7. **The alpha gate (F, last).**
|
||||
a. THE CRATE, end-to-end in a fresh browser: boot `newtown_godverse` with `?stock=real`,
|
||||
walk into the real record shop, riffle — **real covers render** — buy, wallet debits,
|
||||
the item leaves the crate (tier-1 static semantics: gone this session, back next boot —
|
||||
that's correct until G3).
|
||||
b. The tier ladder proven: same shop under `?noassets` (tier 0, zero fetch), missing-atlas
|
||||
(soft fall to parody), `?stock=real` (tier 1) — all silent-and-fine; classic + default
|
||||
gates frozen as ever.
|
||||
c. `qa.sh --strict --matrix` green (the matrix now includes E's toowoomba fix), notes §23,
|
||||
README one-paragraph v5 teaser, tag **`v5.0-alpha`**, push.
|
||||
|
||||
## Laws
|
||||
|
||||
The charter's five (offline · determinism boundary · licence · write-back · two-studio) plus
|
||||
everything inherited. Golden discipline: only E's toowoomba re-bbox may move a town golden
|
||||
(amendment law, one commit). No server code anywhere this round — the G3 doc is paper.
|
||||
|
||||
## THE EXACT RUN ORDER
|
||||
|
||||
1. **Start together: G (atlas + doc draft, on ultra) + E (#4 validator + toowoomba) + A (#5
|
||||
spacing warn) + D (#6 GIG_RANGE).** All disjoint.
|
||||
2. **When G's atlas is up and Fable merges it: start C (#3).** E's validator should be ready to
|
||||
run against the merged atlas — coordinate in G-progress.
|
||||
3. **When C passes and E/A/D are in: F closes (#7)** — the crate, the ladder, the tag.
|
||||
4. **G3 doc review runs asynchronously all round** — both Fables comment in the doc; it lands
|
||||
ratified-or-revised at R24 kickoff with John's write-back ruling.
|
||||
|
||||
Waves: **[G, E, A, D] → [merge, C] → [F]**, with the doc threading through.
|
||||
|
||||
## Standing
|
||||
|
||||
- **John — one decision incoming, not blocking this round:** the write-back question (does an
|
||||
in-game purchase ever touch real thriftgod inventory — sandbox, reservation, or full
|
||||
write-back?). It arrives framed with trade-offs in the G3 doc at R24 kickoff. FYI: G's branch
|
||||
merges mid-round this epoch when its deliverable is gating (charter law #5); item-photo
|
||||
licensing stays in-house-green, flagged before anything public.
|
||||
- **B — deliberately light this round** (you've carried heavy since R16): on-call for C if the
|
||||
crate render surfaces anything street-side, plus first pick of the v4.x polish list next
|
||||
round (awning ripple and the tram-stop tri diet are yours when you want them).
|
||||
- **v5.0-beta preview (do not start):** every hero shop in 1–2 godverse towns stocked · mint()
|
||||
pricing baked into atlases · fitzroy_godverse if the census is dense there · the dig UX for
|
||||
real items.
|
||||
97
docs/RETRO_V4.md
Normal file
@ -0,0 +1,97 @@
|
||||
# PROCITY v4 — THE REAL MAP: the epoch retro
|
||||
|
||||
*Fable (integrator), 2026-07-16, written after the `v4.0` tag (`8ac3b18`) per ROUND22 ledger #6.
|
||||
Epoch span: the R17 scout → R22 close (charter: [V4_REAL_MAP.md](V4_REAL_MAP.md)). Tags:
|
||||
`v4.0-alpha` → `v4.0-beta` → `v4.0`.*
|
||||
|
||||
## The epoch in numbers
|
||||
|
||||
**23 real Australian towns · 1,210 real shops (298 secondhand heroes) · all 8 states and
|
||||
territories · on their actual streets.** Selfcheck grew 15,277 → **161,300** checks. 36 town
|
||||
goldens pinned in one commit. Every real graph lands as **1 connected component** with 3 venues.
|
||||
The covenant was never broken: `?classic=1` boots the exact v2 town, `0x3fa36874`, byte-identical
|
||||
through six rounds of the largest structural change in the project's history. The default
|
||||
synthetic town is untouched. One sanctioned golden move per round, every one recorded.
|
||||
|
||||
## What shipped, in one breath
|
||||
|
||||
Cache schema v2 → the real-graph lift (junction-protected Douglas–Peucker, cull/bridge
|
||||
connectivity) → the density widening under John's SUBTLE directive (heroes never subsampled,
|
||||
texture capped at 3×, and D proved the heroes *gained* footfall) → cluster-adjacent venues (11/15
|
||||
were stranded; 0/15 after) → the tram's shop-adjacency ruling with per-town fencing → the town
|
||||
selector → the poster cap → the furniture tri diet → the five-frame release tour, including one
|
||||
honestly-dead town. Plus the marriage: **Lane G** (ultra) joined mid-epoch, merged at a round
|
||||
boundary, with the depot format ruled (no-Draco) and the G2a atlas contract reviewed.
|
||||
|
||||
## What worked — keep doing these
|
||||
|
||||
1. **Measurement over brief.** The epoch's defining pattern. A proved my poster "nudge" was
|
||||
geometrically impossible and fixed the root cause. D refuted my prescribed `_rotWithHips`
|
||||
with an in-shell proof. B tested a five-round-old park reason and found it was folklore.
|
||||
D published a self-correction of its own headline metric. F recorded a re-measure that got
|
||||
*worse*, and refused to caption a dead town as proof the floor holds. The brief is a
|
||||
hypothesis; the lane's measurement is the ruling.
|
||||
2. **The measure-first fix loop.** R22's blocking gate ran F measured → B diagnosed (91.5%
|
||||
attribution by hide-a-layer) → A confirmed not-A with numbers → E owned it and fixed (−51%)
|
||||
→ F re-measured. Four lanes, one gate, zero ego, one round.
|
||||
3. **Alpha as failure-list.** R18's brief said the deliverable was the failure list, and it was:
|
||||
fragmentation (105 components), the BIG_CITY misfire, the DP junction-drop — each surfaced by
|
||||
audit, filed with measurements, fixed the next round. Budget the discovery round; don't ask
|
||||
it to also be the fix round.
|
||||
4. **Contract-first beats sequencing.** R16's order inverted (lanes ran before F's flip) and
|
||||
nothing broke, because contracts were published in the brief. Same for schema v2, C's mapping
|
||||
table, the backline shape, the G2a atlas contract. The half-day-handshake rule is the
|
||||
project's real synchronization primitive.
|
||||
5. **Drops are counted, never silent.** `norm.dropped` by cause, `textureDropped`, fenced towns
|
||||
named, the 6.4% seating tail per-cause-attributed. Nothing in the pack pretends.
|
||||
6. **Independent convergence.** The thin-tail finding was discovered twice in one round by
|
||||
different methods (F's tour frame; D's nearest-neighbour spacing metric) — that's the
|
||||
redundancy of honest lanes, and it's why the finding is trustworthy.
|
||||
|
||||
## What cost us — fix these
|
||||
|
||||
1. **Lane F is the fragile point.** F no-showed R14 and R18, and stalled mid-gate in R21. Every
|
||||
time, the epoch sat untagged with five lanes done. The close is the longest, most serial
|
||||
brief and it runs last, when sessions die. *Mitigation adopted going forward: F's brief is
|
||||
written in resumable numbered items (R21 proved a successor can pick up mid-gate), and the
|
||||
integrator checks for uncommitted F WIP before declaring a round done.*
|
||||
2. **Soft ceilings invite drift.** `venue_night` breached the 200k "soft" reference twice
|
||||
(+0.5%, then +11%) before R22 made it blocking. *Rule going forward: a stress view that
|
||||
breaches twice is automatically a blocking gate the next round.*
|
||||
3. **Old assets bite in new contexts.** The tri-diet driver was four furniture GLBs sized in
|
||||
R6 for a 22-edge synthetic town, harmless for sixteen rounds until a 966-edge real graph
|
||||
multiplied their cadence. *When the context scales, re-audit the assets it multiplies.*
|
||||
4. **The integrator was wrong twice** (the nudge, `_rotWithHips`). Acceptable — that's what
|
||||
measurement-over-brief is for — but both were cases of prescribing the mechanism instead of
|
||||
the outcome. *Briefs should state acceptance; lanes own method.*
|
||||
|
||||
## The v4.x ledger (carried, measured, not hidden)
|
||||
|
||||
- **The thin-tail clustering floor** (the epoch's parting finding): aliveness is not shop
|
||||
*count* but **clustering** — median nearest-neighbour spacing (darwin 27 m ALIVE vs toowoomba
|
||||
388 m DEAD at the same 12 shops). Candidate law: E curates on spacing, `validateTownCache`
|
||||
warns above a threshold; toowoomba gets re-bboxed or retired.
|
||||
- The **6.4% seating tail** (daylesford 19%, fitzroy 13%) — known, per-cause, the drop-and-count
|
||||
ruling stands.
|
||||
- `GIG_RANGE` chunk-neighbour lookup (D, 8.7% blind) · instrument `_lo` LOD · tram-stop close-ped
|
||||
tri diet · drummer pelvic-lean v2 · awning fabric ripple · flag retirement (classic keeps
|
||||
soaking) · selector polish beyond debug-grade.
|
||||
|
||||
## The next epoch — Fable's recommendation to John
|
||||
|
||||
Three chartered candidates remain: **the GODVERSE economy (G2/G3)**, **the editor/override
|
||||
layer**, **multiplayer presence**.
|
||||
|
||||
**Recommendation: v5 = THE REAL SHOP — the GODVERSE economy.** The argument: v4 made the towns
|
||||
real; v5 makes the *shops* real. The marriage lane is already live (G2a contract reviewed, first
|
||||
atlases next), dealgod holds 1.77M real items with photos, and the destination — walk Fitzroy's
|
||||
real street into a real op shop, riffle a real crate, and the record you buy is one-of-a-kind
|
||||
because it's real stock (sold means GONE) — is the most PROCITY-shaped thing either studio can
|
||||
build. Sequence per the standing ruling: **G3 design doc first** (both integrators + John;
|
||||
the server enriches, never gates — full offline play is non-negotiable), G2a atlases land
|
||||
meanwhile, v4.x tail runs as a light parallel lane. The editor and multiplayer stay chartered
|
||||
for v6+ — the editor gets stronger the more real content exists to override, and presence gets
|
||||
stronger the more there is to visit.
|
||||
|
||||
*— Fable. Twenty-two rounds: seed → town → weather → crowd → interiors → stock → gigs → the flip
|
||||
→ real roads → real shops → real Australia. The covenant held the whole way.*
|
||||
@ -50,3 +50,36 @@ re-fetch; only public POI/way data, never private-individual data.
|
||||
4. Road simplification fidelity — too coarse reads wrong, too fine costs edges; A owns the knob.
|
||||
5. Settled non-risks: facade clearance on osm towns (R17), interior types (C proved 18 real
|
||||
`shop=` values degrade cleanly), plan-side determinism (E's pipeline is byte-identical re-run).
|
||||
|
||||
### Retired-or-carried at the v4.0 close (ROUND22 ledger #6 — Lane A, line by line)
|
||||
|
||||
Verdicts against the **full 23-town pack** (1,210 shops), not against the 5-town sample the list was
|
||||
written for. "Retired" means a law now enforces it and the pack proved the law holds with headroom.
|
||||
|
||||
1. **RETIRED — enforced, with headroom.** `MAX_TOWN_SPAN_M` (5 km) is validator-enforced, so a breach
|
||||
fails loudly at load, not silently in the plan. Widest in the pack: **geelong at 3,829 m — 77% of the
|
||||
ceiling**; no town came close to the law. Mega-strips are now a *pipeline curation* question (E's), and
|
||||
the generator's fence is the backstop it was designed to be.
|
||||
2. **RETIRED — the floor never bound.** `MIN_TOWN_SHOPS` is 6; the thinnest town in the pack is
|
||||
**darwin at 12 — 2× the floor**. No cache was rejected for sparsity. (Whether a 12-shop town *reads*
|
||||
alive is a different question and a live one — it's D's thin-tail spot-check this round, not a
|
||||
generator risk.)
|
||||
3. **RETIRED on the plan side; CARRIED for the runtime lanes.** This was the alpha's real risk and it
|
||||
paid off exactly as intended — the irregular graphs surfaced fragmentation (R19), capacity (R20) and
|
||||
venue stranding (R21), each fixed-or-fenced and written down. At 23-town scale A's side needed **no
|
||||
new hardening**: 1 connected component and 3 venues on every town, all invariants green, fitzroy's 160
|
||||
shops included. **Carried** only where it hasn't been measured yet: C's door-soak, D's fitzroy audit
|
||||
and F's matrix are this round's verifies — the plan being green is not the same as the town rendering
|
||||
and simulating green.
|
||||
4. **RETIRED — the knob is at a measured setting.** Junction-protected Douglas–Peucker settled it
|
||||
(katoomba 105 → 11 components; cull/bridge → 1 component at 100% main-net, and now **1 component on
|
||||
all 23**). The fidelity/edge-count trade has a floor that holds across the pack; it is no longer an
|
||||
open question, and any future move re-pins goldens under the amendment law.
|
||||
5. **Unchanged** — still settled, now on 23 towns rather than 5.
|
||||
6. **NEW, CARRIED — thin-graph seating capacity.** The pack's honest tail: **6.4% of shops don't seat**
|
||||
(77 of 1,210), all counted by cause, dominated by overflow where a town has more shops than its road
|
||||
graph has frontage. Outliers: **daylesford 19%** (32 shops on 171 roads — the thinnest graph in the
|
||||
pack by 2×) and **fitzroy 13%**. `RGAP` is already 0.5 m and `NODE_CLEAR` 3 m; the only lever left is
|
||||
spilling overflow onto a *different* street, which buys a few percent by putting a shop on a road it
|
||||
isn't on. Ruling stands from R20: **drop and count beats teleport.** Carried into v4.x as a known,
|
||||
measured, per-cause-attributed tail — not a defect, and not hidden.
|
||||
|
||||
83
docs/V5_REAL_SHOP.md
Normal file
@ -0,0 +1,83 @@
|
||||
# PROCITY v5 — THE REAL SHOP (epoch charter)
|
||||
|
||||
*Fable (m3, integrator), 2026-07-16. John's pick at the v4 retro. Grounded in: the Lane G
|
||||
marriage (merged, ruled, G2a atlas contract reviewed by C/E in R22), dealgod's 1.77M real items
|
||||
with photos, thriftgod's census already driving `newtown_godverse`, and the v2-era dig/wallet/
|
||||
stock machinery that has been waiting for this data since it shipped.*
|
||||
|
||||
## The vision
|
||||
|
||||
**v4 made the towns real; v5 makes the shops real.** Walk Fitzroy's actual street into an actual
|
||||
op shop, riffle a crate, and the records in it are that shop's real stock — real covers, real
|
||||
titles, real prices. Buy one and it's gone, because it was one-of-a-kind in the real world.
|
||||
Thriftgod is the brain and the bank; PROCITY is the body.
|
||||
|
||||
## The tier ladder (the epoch's load-bearing idea)
|
||||
|
||||
Stock is a DATA TIER, like assets — each tier fully playable, each degrading softly to the one
|
||||
below. This is the offline law made structural:
|
||||
|
||||
| tier | source | flag state | deterministic? |
|
||||
|---|---|---|---|
|
||||
| 0 · parody | seeded canvas sleeves (v1) | any boot, always works | yes — the QA baseline |
|
||||
| 1 · static real | per-shop atlases baked from dealgod (G2a) | `?stock=real`, no server | yes — atlases are files |
|
||||
| 2 · live GODVERSE | thriftgod server: live stock, sold-means-gone (G3) | server reachable | **no — by design** |
|
||||
|
||||
The seeded-everything law is NOT weakened: the *world* (geometry, layout, venues, citizens)
|
||||
stays seeded and byte-identical; stock *contents* are inventory data riding the asset law.
|
||||
QA gates run on tiers 0–1; tier 2 gets its own smoke class (reachability, fail-soft, latency)
|
||||
that never gates a tag on server state.
|
||||
|
||||
## The stages
|
||||
|
||||
- **v5.0-alpha: THE FIRST REAL CRATE.** One real shop end-to-end — a Newtown record shop in
|
||||
`newtown_godverse`, static atlas (G2a), riffle real covers, buy, wallet debits, every tier
|
||||
degrades clean. Plus the **G3 design doc ratified** (both integrators + John) before any
|
||||
server code exists.
|
||||
- **v5.0-beta: THE REAL SHOPPING TOWN.** Every hero shop in 1–2 godverse towns carries real
|
||||
static stock; godverse coverage grows where thriftgod's census is dense (fitzroy is the
|
||||
prize); pricing from thriftgod's `mint()` profiles baked into the atlases; the dig UX polished
|
||||
for real items (title/price cards are real data).
|
||||
- **v5.0: THE LIVE TIER.** Thriftgod's server behind tier 2 per the ratified G3 design:
|
||||
live per-shop stock, sold-means-gone, reservation semantics — with the offline law proven by
|
||||
the gate (kill the server mid-session; the game degrades to tier 1 without a stutter). Tour,
|
||||
docs freeze, tag.
|
||||
|
||||
## The laws
|
||||
|
||||
Everything inherited (classic covenant `0x3fa36874` forever · determinism for the world ·
|
||||
budgets · amendment law · measurement-over-brief · drops counted · fresh-context verification),
|
||||
plus this epoch's own:
|
||||
|
||||
1. **The offline law (non-negotiable, from the standing G3 ruling): the server enriches, never
|
||||
gates.** Full offline play at tier 0 and tier 1, always. No boot path, gate, or tag may
|
||||
depend on a reachable server.
|
||||
2. **The determinism boundary:** world = seeded, frozen, gated. Stock = data tiers. Tier 2 is
|
||||
openly non-deterministic and therefore never inside a golden or a byte-identical assert.
|
||||
3. **The licence law (extends E's provenance spine):** item photos are the stores' own product
|
||||
shots — 🟢 for in-house play; **flagged before any public/commercial release** (G's own
|
||||
note, now law). No private-individual data, ever. Every atlas carries provenance.
|
||||
4. **The write-back law:** game purchases NEVER mutate real thriftgod inventory without an
|
||||
explicit, John-ratified reservation design in the G3 doc. Until then tier 2 is read-live,
|
||||
sell-sandboxed. (Whether "sold in game" can mark real stock is John's call to make in the
|
||||
design review, not a default.)
|
||||
5. **The two-studio law:** G owns `pipeline/godverse_*`, `web/assets/stock_godverse/**`,
|
||||
godverse caches, and the server side; A–F own their lanes; cross-repo contracts publish
|
||||
FIRST; both integrators sign the G3 design. **Merge amendment:** when a round's brief names
|
||||
a G deliverable as gating, G's branch merges mid-round after Fable-m3 review (G's file
|
||||
namespace is disjoint, so pathspec safety holds); otherwise boundaries as before.
|
||||
|
||||
## The risk list (kept current, v4-style)
|
||||
|
||||
1. **Latency/stutter at tier 2** — server calls must be async enrichment; the dig must never
|
||||
block on the network. The G3 doc owns the caching design.
|
||||
2. **Atlas budgets at real density** — a mecca hero shop can hold hundreds of real items; C's
|
||||
interior ceilings are the law and the atlas contract must respect them (pagination/crate
|
||||
rotation rather than bigger atlases).
|
||||
3. **Census↔OSM identity** — matching thriftgod shops to godverse lots is fuzzy (names drift,
|
||||
shops move). Mismatches must be counted and fail-soft to tier 0, never mis-stocked.
|
||||
4. **Sold-means-gone semantics** — race conditions, reservations, and the write-back question.
|
||||
All design-doc territory; nothing ships before ratification.
|
||||
5. **Settled non-risks:** the dig/wallet/stockAdapter machinery (shipped v2, soaked 16 rounds);
|
||||
static-atlas determinism (E's byte-identical pipeline discipline); the fail-soft ladder
|
||||
(proven pattern from `?noassets` day one).
|
||||
BIN
docs/shots/release_v4/classic_covenant.png
Normal file
|
After Width: | Height: | Size: 809 KiB |
1
docs/shots/release_v4/contact.html
Normal file
@ -0,0 +1 @@
|
||||
<!doctype html><meta charset="utf-8"><title>PROCITY v4.0 — the release tour</title><style>body{background:#14110c;color:#f4efe6;font:14px/1.4 -apple-system,Segoe UI,Roboto,sans-serif;margin:24px}.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(460px,1fr));gap:18px}figure{margin:0;background:#1d1810;border-radius:10px;overflow:hidden}img{width:100%;display:block}figcaption{padding:9px 13px;color:#cdc4b2}b{color:#ffd9a0}</style><h1>PROCITY v4.0 — THE REAL MAP <small>· 23 real Australian towns · seed 20261990 · settled</small></h1><div class="grid"><figure><img src="fremantle_hero.png" alt="fremantle_hero" loading="lazy"><figcaption><b>fremantle_hero</b><br>the hero — Fremantle, real streets, golden hour · Fremantle · 79 shops / 1056 edges · retail heart: 10 shops/60m · 126 draws · 77,322 tris</figcaption></figure><figure><img src="fitzroy_mecca.png" alt="fitzroy_mecca" loading="lazy"><figcaption><b>fitzroy_mecca</b><br>the mecca — Fitzroy, the densest town in the pack · Fitzroy · 139 shops / 2408 edges · retail heart: 7 shops/60m · 166 draws · 95,824 tris</figcaption></figure><figure><img src="toowoomba_thintail.png" alt="toowoomba_thintail" loading="lazy"><figcaption><b>toowoomba_thintail</b><br>the thin tail — Toowoomba: the floor does NOT hold here, and this is the honest frame (12 shops over a 1,459-edge graph = 0.008 shops/edge vs the heroes' 0.06–0.08; it reads as road, not town). Filed for v4.x · Toowoomba · 12 shops / 1459 edges · retail heart: 1 shops/60m · 78 draws · 78,496 tris</figcaption></figure><figure><img src="synthetic_control.png" alt="synthetic_control" loading="lazy"><figcaption><b>synthetic_control</b><br>the control — the procedural town, unchanged · Boolarra Heads · 493 shops / 22 edges · 180 draws · 29,158 tris</figcaption></figure><figure><img src="classic_covenant.png" alt="classic_covenant" loading="lazy"><figcaption><b>classic_covenant</b><br>?classic=1 — the v2 covenant, byte-identical forever · Boolarra Heads · 493 shops / 22 edges · 164 draws · 26,472 tris</figcaption></figure></div>
|
||||
BIN
docs/shots/release_v4/fitzroy_mecca.png
Normal file
|
After Width: | Height: | Size: 778 KiB |
BIN
docs/shots/release_v4/fremantle_hero.png
Normal file
|
After Width: | Height: | Size: 662 KiB |
BIN
docs/shots/release_v4/synthetic_control.png
Normal file
|
After Width: | Height: | Size: 809 KiB |
BIN
docs/shots/release_v4/toowoomba_thintail.png
Normal file
|
After Width: | Height: | Size: 835 KiB |
BIN
docs/shots/v4_beta/fremantle_mainstreet.png
Normal file
|
After Width: | Height: | Size: 663 KiB |
@ -15,7 +15,7 @@ gorgeous, it doesn't make it work.
|
||||
| OpenRouter key (skins) | env `OPENROUTER_KEY` or `~/Documents/dealgod/lore/keys.env` (never committed) |
|
||||
| depot password (publish) | env `GOD3_PW` (never committed; ask John) — cookie = `hmac(GOD3_PW,"3god-ok")` |
|
||||
| Ollama (free LLM) | `http://100.89.131.57:11434` (qwen3) — for names/lore/cataloguing, unused so far |
|
||||
| MODELBEAST (free local gen) | this box, `~/Documents/MODELBEAST`. `flux_local` = FLUX.2-klein-4B text→image on MPS (**used for the facade skins** — ungated, weights cached). `sf3d`/`trellis_mac` = image→GLB, local but HF-gated (setup needed) |
|
||||
| MODELBEAST (free local gen) | this box, `~/Documents/MODELBEAST` + its own queue on **`:8777`**. `flux_local` = FLUX.2-klein-4B text→image on MPS (**the facade skins** — ungated, weights cached). `sf3d`/`trellis_mac` = image→GLB, **local + PROVEN, weights already cached** — `trellis_mac` (TRELLIS.2 `1024_cascade`) went **15/15 on thriftgod's Tier-1 props** (Lane G/ultra, R1); `sf3d` = seconds-fast draft. Also live on the queue: `bg_remove_local`, `mflux_image_edit`, `seedvr2_upscale`. *(The old "HF-gated / setup needed" note here was stale — flagged by ultra, corrected R22.)* |
|
||||
|
||||
## The steps (each is idempotent / resumable)
|
||||
|
||||
@ -88,6 +88,25 @@ reused from depot. `magazine-rack` → primitive (thin-wire gotcha); `glass-case
|
||||
fallback (TRELLIS can't do transparent glass; `sf3d --draft` gives a featureless blob). TRELLIS meshes run
|
||||
heavy (25–77k tris) — see AUDIT provenance. **Eyeball every thumbnail**; ≤2 local attempts then fal (John's go).
|
||||
|
||||
#### ⚠️ Finishing a `trellis_mac` mesh — ultra's post-mortem (Lane G, R1) — folded in verbatim
|
||||
> ⚠️ **Do NOT run trellis_mac output through the MESHGOD finish farm** — its fill_holes+decimate
|
||||
> shreds 200k-tri marching-cubes meshes (proven on ultra, post-mortem in dealgod memory). The
|
||||
> working pipeline: raw GLB → `npx @gltf-transform/cli optimize --compress draco --texture-compress
|
||||
> webp --texture-size 1024 --simplify-error 0.01` → 0.4–1.8 MB at raw-quality look.
|
||||
|
||||
*Credit: Lane G / ultra (`G-progress.md`), proven post-mortem — it cost them a shredded batch so it
|
||||
shouldn't cost us one.* **One amendment, per Fable's Ruling 1 (`docs/LANES/FABLE_TO_LANE_G.md`):
|
||||
NO-DRACO WINS — the depot speaks uncompressed GLB and PROCITY's loader stays decoder-free.** So drop
|
||||
`--compress draco`; everything else in ultra's command stands (WebP ≤1024 is already our law):
|
||||
```bash
|
||||
npx @gltf-transform/cli optimize IN.glb OUT.glb \
|
||||
--texture-compress webp --texture-size 1024 --simplify-error 0.01 # NO --compress draco
|
||||
```
|
||||
Files land ~2–3× bigger than the Draco'd version and that's fine — the depot holds ONE law.
|
||||
Note our own in-house paths remain valid for their own cases and are NOT the finish farm:
|
||||
`bake_lowpoly.py` (voxel-remesh + bake, for shell-soup that must hit a low budget) and
|
||||
`decimate_props.py` (collapse, detail-preserving — see the R13 instrument diet).
|
||||
|
||||
### 7. Local-depot mode — `loaders.js ?localdepot=1` + `stage_local_depot.py` *(round-3: DONE)*
|
||||
Lets Lanes B/C/F validate GLB loading with **no network / before publish**:
|
||||
```bash
|
||||
|
||||
1487
pipeline/_godverse/monsterrobot_550.snapshot.json
Normal file
@ -176,7 +176,7 @@
|
||||
"out": "/tmp/procity_norm/procity_street_bench_01.glb",
|
||||
"thumb": "/tmp/procity_norm/thumbs/procity_street_bench_01.png",
|
||||
"tri_before": 1942,
|
||||
"tri_after": 1942,
|
||||
"tri_after": 500,
|
||||
"size_m": [
|
||||
0.437,
|
||||
0.85,
|
||||
@ -185,7 +185,8 @@
|
||||
"footprint": [
|
||||
0.44,
|
||||
1.21
|
||||
]
|
||||
],
|
||||
"diet_note": "R22 tri diet (B #1a); _hi original preserved in _normalized"
|
||||
},
|
||||
{
|
||||
"src": "street-furniture/eb_hipster_bench.glb",
|
||||
@ -278,7 +279,7 @@
|
||||
"out": "/tmp/procity_norm/procity_street_food_cart_01.glb",
|
||||
"thumb": "/tmp/procity_norm/thumbs/procity_street_food_cart_01.png",
|
||||
"tri_before": 30220,
|
||||
"tri_after": 5000,
|
||||
"tri_after": 1000,
|
||||
"size_m": [
|
||||
3.003,
|
||||
2.0,
|
||||
@ -287,7 +288,8 @@
|
||||
"footprint": [
|
||||
3.0,
|
||||
2.07
|
||||
]
|
||||
],
|
||||
"diet_note": "R22 tri diet (B #1a); _hi original preserved in _normalized"
|
||||
}
|
||||
],
|
||||
"errors": []
|
||||
|
||||
@ -121,7 +121,7 @@
|
||||
{
|
||||
"file": "procity_street_bus_shelter_01.glb",
|
||||
"tri_before": 40000,
|
||||
"tri_after": 7998,
|
||||
"tri_after": 2500,
|
||||
"size_m": [
|
||||
2.333,
|
||||
2.4,
|
||||
@ -132,7 +132,8 @@
|
||||
1.41
|
||||
],
|
||||
"thumb": "/tmp/fal_bake/thumbs/procity_street_bus_shelter_01.png",
|
||||
"out": "/tmp/fal_bake/procity_street_bus_shelter_01.glb"
|
||||
"out": "/tmp/fal_bake/procity_street_bus_shelter_01.glb",
|
||||
"diet_note": "R22 tri diet (B #1a); _hi original preserved in _normalized"
|
||||
},
|
||||
{
|
||||
"file": "procity_fit_magazine_rack_01.glb",
|
||||
@ -156,7 +157,7 @@
|
||||
"out": "/tmp/e2_norm/procity_street_bin_01.glb",
|
||||
"thumb": "web/assets/thumbs/procity_street_bin_01.png",
|
||||
"tri_before": 3000,
|
||||
"tri_after": 3000,
|
||||
"tri_after": 800,
|
||||
"size_m": [
|
||||
0.569,
|
||||
1.0,
|
||||
@ -165,7 +166,8 @@
|
||||
"footprint": [
|
||||
0.57,
|
||||
0.68
|
||||
]
|
||||
],
|
||||
"diet_note": "R22 tri diet (B #1a); _hi original preserved in _normalized"
|
||||
},
|
||||
{
|
||||
"src": "/tmp/milk-crate-stack.glb",
|
||||
|
||||
@ -27,11 +27,15 @@
|
||||
"procity_fit_wire_shelf_01.glb",
|
||||
"procity_fit_work_table_01.glb",
|
||||
"procity_street_bench_01.glb",
|
||||
"procity_street_bench_01_hi.glb",
|
||||
"procity_street_bench_modern_01.glb",
|
||||
"procity_street_bench_wood_01.glb",
|
||||
"procity_street_bin_01.glb",
|
||||
"procity_street_bin_01_hi.glb",
|
||||
"procity_street_bus_shelter_01.glb",
|
||||
"procity_street_bus_shelter_01_hi.glb",
|
||||
"procity_street_food_cart_01.glb",
|
||||
"procity_street_food_cart_01_hi.glb",
|
||||
"procity_street_longbench_01.glb",
|
||||
"procity_street_novelty_record_01.glb",
|
||||
"procity_street_park_bench_01.glb",
|
||||
|
||||
@ -17,31 +17,105 @@ attribution ships IN every cache (`license`/`attribution`) + `SOURCES.md`. Hours
|
||||
registry supplies them at plan time (contract shop = {id,name,type,lat,lon,suburb}). Overpass is an
|
||||
outward-facing fetch (John-authorized scout, once per town, raw cached).
|
||||
"""
|
||||
import os, sys, json, re, math, time, datetime, urllib.request, urllib.parse, urllib.error
|
||||
import os, sys, json, re, math, time, datetime, hashlib, urllib.request, urllib.parse, urllib.error
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
TOWNS_DIR = os.path.join(ROOT, "web", "assets", "towns")
|
||||
RAW_DIR = os.path.join(TOWNS_DIR, "_raw")
|
||||
OVERPASS = "https://overpass-api.de/api/interpreter"
|
||||
UA = "PROCITY-townscout/0.1 (real-map scout; OpenStreetMap ODbL data)"
|
||||
COURTESY_SLEEP = 4 # pause between Overpass calls — the standing auth covers the fetches,
|
||||
# courtesy covers the cadence (R21 pack scale: ~2 calls × ~20 towns)
|
||||
|
||||
# Compact real AU towns: center (lat,lon) + query box span (km). `katoomba_real` is the fixture
|
||||
# comparison (vs the hand-made `katoomba` in osm_fixture.js). Spans kept tight so each is ONE town.
|
||||
TOWNS = {
|
||||
"katoomba_real": {"town": "Katoomba", "center": (-33.7145, 150.3120), "span_km": 3.0},
|
||||
"newtown_real": {"town": "Newtown", "center": (-33.8985, 151.1790), "span_km": 2.6},
|
||||
"fremantle_real": {"town": "Fremantle", "center": (-32.0555, 115.7480), "span_km": 2.6},
|
||||
"bendigo_real": {"town": "Bendigo", "center": (-36.7580, 144.2810), "span_km": 3.4},
|
||||
"castlemaine_real": {"town": "Castlemaine", "center": (-37.0640, 144.2170), "span_km": 3.0},
|
||||
# ── the beta five (v4.0-alpha/beta — A has these absorbed + pinned) ─────────────────────────
|
||||
"katoomba_real": {"town": "Katoomba", "state": "NSW", "center": (-33.7145, 150.3120), "span_km": 3.0},
|
||||
"newtown_real": {"town": "Newtown", "state": "NSW", "center": (-33.8985, 151.1790), "span_km": 2.6},
|
||||
"fremantle_real": {"town": "Fremantle", "state": "WA", "center": (-32.0555, 115.7480), "span_km": 2.6},
|
||||
"bendigo_real": {"town": "Bendigo", "state": "VIC", "center": (-36.7580, 144.2810), "span_km": 3.4},
|
||||
"castlemaine_real": {"town": "Castlemaine", "state": "VIC", "center": (-37.0640, 144.2170), "span_km": 3.0},
|
||||
# ── the v4.0 curated pack (ROUND21 ledger #5 — DATA-ONLY; A absorbs + pins in R22) ──────────
|
||||
# Curated on the instincts the first five earned: pick for real SECONDHAND density (the heroes
|
||||
# are the game — a town with no op shops isn't a PROCITY town), a walkable centre that fits a
|
||||
# ~2–3 km bbox (the mega-strip law), and a national spread. Any town that misses the ≥6-shop
|
||||
# floor or the 5 km span is dropped by the builder and counted — the pack is what survives.
|
||||
"glebe_real": {"town": "Glebe", "state": "NSW", "center": (-33.8790, 151.1870), "span_km": 2.2},
|
||||
"marrickville_real": {"town": "Marrickville", "state": "NSW", "center": (-33.9110, 151.1550), "span_km": 2.4},
|
||||
# Newcastle: centred on the Hamilton / Beaumont St strip, NOT the CBD — the CBD bbox held only 2
|
||||
# heroes; Hamilton is where Newcastle's actual secondhand shops are (my first centre missed it).
|
||||
"newcastle_real": {"town": "Newcastle", "state": "NSW", "center": (-32.9235, 151.7440), "span_km": 2.4},
|
||||
"wollongong_real": {"town": "Wollongong", "state": "NSW", "center": (-34.4250, 150.8940), "span_km": 2.8},
|
||||
"bowral_real": {"town": "Bowral", "state": "NSW", "center": (-34.4780, 150.4180), "span_km": 2.2},
|
||||
"fitzroy_real": {"town": "Fitzroy", "state": "VIC", "center": (-37.7990, 144.9790), "span_km": 2.2},
|
||||
"brunswick_real": {"town": "Brunswick", "state": "VIC", "center": (-37.7700, 144.9600), "span_km": 2.6},
|
||||
"ballarat_real": {"town": "Ballarat", "state": "VIC", "center": (-37.5620, 143.8500), "span_km": 3.0},
|
||||
"daylesford_real": {"town": "Daylesford", "state": "VIC", "center": (-37.3430, 144.1430), "span_km": 2.0},
|
||||
"geelong_real": {"town": "Geelong", "state": "VIC", "center": (-38.1490, 144.3600), "span_km": 3.0},
|
||||
"westend_real": {"town": "West End", "state": "QLD", "center": (-27.4830, 153.0100), "span_km": 2.2},
|
||||
# ── RETIRED R23 (E's curation call, counted) — toowoomba_real ────────────────────────────────
|
||||
# D's R22 finding: DEAD — median NN 388 m, 3/12 shops near the hub, **0 patronage finds in 1417
|
||||
# checks**. I tried the re-bbox first (3.0 km CBD-wide → 1.6 km on the Ruthven St heart, centre
|
||||
# -27.5610,151.9535): it FIXED the named metric (median NN 395.7 → 89.4 m raw — normal for the
|
||||
# pack) but made the real thing WORSE — hub density 3/12 → **2/12 within 160 m, the worst in the
|
||||
# pack** (D's alive-darwin is 7/12). Toowoomba's shops are PAIRS strung along a road, not a high
|
||||
# street; its densest 300 m cluster is only 3 shops, below MIN_TOWN_SHOPS, so no tighter box
|
||||
# exists. Shipping it would have gamed the spacing metric while leaving a town nobody shops in.
|
||||
# Retired rather than padded. (QLD keeps westend_real, 11 heroes / 44 shops.)
|
||||
# "toowoomba_real": {"town": "Toowoomba", "state": "QLD", "center": (-27.5600, 151.9540), "span_km": 3.0},
|
||||
"northbridge_real": {"town": "Northbridge", "state": "WA", "center": (-31.9490, 115.8570), "span_km": 2.4},
|
||||
"adelaide_real": {"town": "Adelaide", "state": "SA", "center": (-34.9240, 138.6050), "span_km": 2.8},
|
||||
"hobart_real": {"town": "Hobart", "state": "TAS", "center": (-42.8820, 147.3270), "span_km": 2.8},
|
||||
"launceston_real": {"town": "Launceston", "state": "TAS", "center": (-41.4380, 147.1370), "span_km": 2.6},
|
||||
"braddon_real": {"town": "Braddon", "state": "ACT", "center": (-35.2740, 149.1330), "span_km": 2.4},
|
||||
"darwin_real": {"town": "Darwin", "state": "NT", "center": (-12.4630, 130.8420), "span_km": 2.6},
|
||||
}
|
||||
# the beta five are already pinned by A; `--pack` builds only the new curated candidates
|
||||
BETA_FIVE = ("katoomba_real", "newtown_real", "fremantle_real", "bendigo_real", "castlemaine_real")
|
||||
|
||||
# OSM shop tags we scout (on-theme: the game's secondhand/media/hobby world) → registry SHOP_TYPE.
|
||||
SHOP_RE = "charity|second_hand|music|antiques|books|toys|games|video_games|pawnbroker"
|
||||
TYPE_MAP = {
|
||||
"books": "book", "music": "record", "toys": "toy", "games": "toy",
|
||||
"video_games": "video", "video": "video", "pawnbroker": "pawn",
|
||||
"charity": "opshop", "second_hand": "opshop", "antiques": "opshop",
|
||||
# ── the class list (v4.0-beta, ROUND20 ledger #2 — C's LANE_C_PUB §6 table, John's SUBTLE directive) ──
|
||||
# HEROES = the secondhand shops (they ARE the game) — never subsampled. TEXTURE = the everyday
|
||||
# main-street staples a real AU town has (bakery/cafe/chemist/newsagent/clothes/…). Tasteful staples
|
||||
# ONLY per C's guardrail: no vehicle/tyres/fuel, funeral, storage, industrial, and no pure services
|
||||
# the player never browses (hairdresser/beauty/travel_agency/bank/laundry/estate_agent).
|
||||
HERO_CLASSES = {"charity", "second_hand", "antiques", "vintage", "books", "bookshop",
|
||||
"music", "video", "games", "video_games", "pawnbroker", "toys"}
|
||||
TEXTURE_CLASSES = {
|
||||
"bakery", "cafe", "confectionery", "pastry", "chocolate", "deli", "delicatessen", "butcher",
|
||||
"cheese", "coffee", "tea", "convenience", "kiosk", "general", "dairy", "newsagent", "stationery",
|
||||
"chemist", "pharmacy", "cosmetics", "perfumery", "clothes", "fashion", "boutique", "shoes", "bag",
|
||||
"jewelry", "jewellery", "department_store", "variety_store", "supermarket", "hardware",
|
||||
"doityourself", "trade", "electronics", "mobile_phone", "furniture", "houseware", "homeware",
|
||||
"greengrocer", "florist", "farm", "garden_centre",
|
||||
}
|
||||
AMENITY_CLASSES = {"cafe"} # C's †: cafe is amenity=*, not shop=*
|
||||
SHOP_CLASSES = (HERO_CLASSES | TEXTURE_CLASSES) - AMENITY_CLASSES
|
||||
|
||||
# C's copy-paste map (LANE_C_PUB §6). Every target is an EXISTING registry type — no new archetypes.
|
||||
# (+ `video_games`→video: C listed video/games→video; video_games is the same family and was already
|
||||
# shipping as `video` pre-widening — kept so those shops don't regress to opshop. Flagged to C.)
|
||||
TYPE_MAP = {
|
||||
"charity": "opshop", "second_hand": "opshop", "antiques": "opshop", "vintage": "opshop",
|
||||
"books": "book", "bookshop": "book", "music": "record", "video": "video", "games": "video",
|
||||
"video_games": "video", "pawnbroker": "pawn", "toys": "toy",
|
||||
"bakery": "milkbar", "cafe": "milkbar", "confectionery": "milkbar", "pastry": "milkbar",
|
||||
"chocolate": "milkbar", "deli": "milkbar", "delicatessen": "milkbar", "butcher": "milkbar",
|
||||
"cheese": "milkbar", "coffee": "milkbar", "tea": "milkbar", "convenience": "milkbar",
|
||||
"kiosk": "milkbar", "general": "milkbar", "dairy": "milkbar", "newsagent": "milkbar",
|
||||
"stationery": "milkbar", "chemist": "milkbar", "pharmacy": "milkbar", "cosmetics": "milkbar",
|
||||
"perfumery": "milkbar",
|
||||
"clothes": "dept", "fashion": "dept", "boutique": "dept", "shoes": "dept", "bag": "dept",
|
||||
"jewelry": "dept", "jewellery": "dept", "department_store": "dept", "variety_store": "dept",
|
||||
"supermarket": "dept", "hardware": "dept", "doityourself": "dept", "trade": "dept",
|
||||
"electronics": "dept", "mobile_phone": "dept", "furniture": "dept", "houseware": "dept",
|
||||
"homeware": "dept",
|
||||
"greengrocer": "stall", "florist": "stall", "farm": "stall", "garden_centre": "stall",
|
||||
}
|
||||
# John's directive: texture, not takeover. If a town's general retail would drown the secondhand
|
||||
# strip, seed-subsample the TEXTURE classes toward C's ~2–3× band; heroes are never touched and
|
||||
# every drop is counted (counts.textureDropped).
|
||||
TEXTURE_CAP = 3.0
|
||||
|
||||
# ── roads[] (schema v2, ROUND18 REAL ROADS) ─────────────────────────────────────────────────────
|
||||
# We FETCH every OSM highway tier A's ROAD_KIND maps (→ raw cache, full provenance / beta-ready) but
|
||||
@ -114,41 +188,57 @@ def overpass_post(q):
|
||||
time.sleep(wait)
|
||||
|
||||
|
||||
def shops_query(bb):
|
||||
box = f'{bb["minLat"]:.5f},{bb["minLon"]:.5f},{bb["maxLat"]:.5f},{bb["maxLon"]:.5f}'
|
||||
return (f'[out:json][timeout:180];('
|
||||
f'nwr["shop"~"^({"|".join(sorted(SHOP_CLASSES))})$"]["name"]({box});'
|
||||
f'nwr["amenity"~"^({"|".join(sorted(AMENITY_CLASSES))})$"]["name"]({box});'
|
||||
f');out center;')
|
||||
|
||||
|
||||
def fetch_raw(key, bb, refetch):
|
||||
"""Return the raw Overpass elements list, cached in-repo so re-runs never re-fetch."""
|
||||
"""Raw Overpass elements, cached in-repo so re-runs never re-fetch. Auto-refetches when the CLASS
|
||||
LIST changes (each snapshot records its query) — that's how the R20 widening lands as a NEW raw
|
||||
snapshot without anyone remembering --refetch."""
|
||||
os.makedirs(RAW_DIR, exist_ok=True)
|
||||
path = os.path.join(RAW_DIR, key + ".overpass.json")
|
||||
q = shops_query(bb)
|
||||
if os.path.exists(path) and not refetch:
|
||||
w = json.load(open(path))
|
||||
return w["elements"], w.get("fetchedAt")
|
||||
q = (f'[out:json][timeout:120];'
|
||||
f'nwr["shop"~"^({SHOP_RE})$"]["name"]'
|
||||
f'({bb["minLat"]:.5f},{bb["minLon"]:.5f},{bb["maxLat"]:.5f},{bb["maxLon"]:.5f});'
|
||||
f'out center;')
|
||||
print(f" [{key}] fetching Overpass (bounded {bb['minLat']:.3f},{bb['minLon']:.3f} … {bb['maxLat']:.3f},{bb['maxLon']:.3f})")
|
||||
if w.get("query") == q:
|
||||
return w["elements"], w.get("fetchedAt")
|
||||
print(f" [{key}] class list changed → new raw snapshot")
|
||||
print(f" [{key}] fetching Overpass shops (widened, bounded)")
|
||||
els = overpass_post(q)
|
||||
fetched = datetime.date.today().isoformat()
|
||||
json.dump({"fetchedAt": fetched, "endpoint": OVERPASS, "query": q, "bbox": bb, "elements": els},
|
||||
open(path, "w"), indent=1)
|
||||
time.sleep(2) # be a good Overpass citizen between towns
|
||||
time.sleep(COURTESY_SLEEP) # be a good Overpass citizen between towns
|
||||
return els, fetched
|
||||
|
||||
|
||||
def fetch_roads_raw(key, bb, refetch):
|
||||
"""Return the raw OSM highway ways (with geometry) for the town bbox, cached in-repo like the shops."""
|
||||
"""Raw OSM highway ways (with geometry) for the town bbox, cached in-repo like the shops — and,
|
||||
like the shops, auto-refetched when the QUERY changes. That symmetry matters: the bbox lives in
|
||||
the query, so re-centring a town must invalidate BOTH snapshots. (R21: it didn't. Re-centring
|
||||
Newcastle refetched the shops but silently reused the old CBD roads — Hamilton shops seated
|
||||
against a road graph 2.5 km away, and the town marched out with ZERO shops.)"""
|
||||
os.makedirs(RAW_DIR, exist_ok=True)
|
||||
path = os.path.join(RAW_DIR, key + ".roads.overpass.json")
|
||||
if os.path.exists(path) and not refetch:
|
||||
return json.load(open(path))["elements"]
|
||||
q = (f'[out:json][timeout:120];'
|
||||
f'way["highway"~"^({ROAD_HW})$"]'
|
||||
f'({bb["minLat"]:.5f},{bb["minLon"]:.5f},{bb["maxLat"]:.5f},{bb["maxLon"]:.5f});'
|
||||
f'out geom;')
|
||||
if os.path.exists(path) and not refetch:
|
||||
w = json.load(open(path))
|
||||
if w.get("query") == q:
|
||||
return w["elements"]
|
||||
print(f" [{key}] roads bbox/query changed → new raw snapshot")
|
||||
print(f" [{key}] fetching Overpass roads (highway ways, bounded)")
|
||||
ways = overpass_post(q)
|
||||
json.dump({"fetchedAt": datetime.date.today().isoformat(), "endpoint": OVERPASS, "query": q,
|
||||
"bbox": bb, "elements": ways}, open(path, "w"), indent=1)
|
||||
time.sleep(2)
|
||||
time.sleep(COURTESY_SLEEP)
|
||||
return ways
|
||||
|
||||
|
||||
@ -178,7 +268,7 @@ def process(key, cfg, els, fetched, roads=None):
|
||||
rows, seen = [], set()
|
||||
for el in els:
|
||||
t = el.get("tags", {})
|
||||
name, osm_shop = t.get("name"), t.get("shop")
|
||||
name, osm_shop = t.get("name"), (t.get("shop") or t.get("amenity")) # cafe rides amenity=*
|
||||
lat = el.get("lat", el.get("center", {}).get("lat"))
|
||||
lon = el.get("lon", el.get("center", {}).get("lon"))
|
||||
if not (name and osm_shop and isinstance(lat, (int, float)) and isinstance(lon, (int, float))):
|
||||
@ -206,9 +296,24 @@ def process(key, cfg, els, fetched, roads=None):
|
||||
continue
|
||||
seen2.add(k2)
|
||||
shops.append({"id": r["id"], "name": pname, "type": TYPE_MAP.get(r["osm"], "opshop"),
|
||||
"lat": round(r["lat"], 6), "lon": round(r["lon"], 6), "suburb": r["suburb"]})
|
||||
"lat": round(r["lat"], 6), "lon": round(r["lon"], 6), "suburb": r["suburb"],
|
||||
"_osm": r["osm"]})
|
||||
# ── SUBTLE (John's directive): texture, not takeover ────────────────────────────────────────
|
||||
# The secondhand HEROES are never subsampled — they're the game. If a town's general retail would
|
||||
# drown the strip, seed-subsample the TEXTURE toward C's ~2–3× band (deterministic: rank by
|
||||
# sha256(townKey:osmId), keep the top `cap`) and COUNT the drops like every other drop.
|
||||
heroes = [s for s in shops if s["_osm"] in HERO_CLASSES]
|
||||
texture = [s for s in shops if s["_osm"] not in HERO_CLASSES]
|
||||
cap, dropped = int(round(TEXTURE_CAP * len(heroes))), 0
|
||||
if len(texture) > cap:
|
||||
texture.sort(key=lambda s: hashlib.sha256(f"{key}:{s['id']}".encode()).hexdigest())
|
||||
dropped, texture = len(texture) - cap, texture[:cap]
|
||||
shops = sorted(heroes + texture, key=lambda s: s["id"]) # deterministic emit order
|
||||
for s in shops:
|
||||
s.pop("_osm", None)
|
||||
bb = bbox(center, cfg["span_km"])
|
||||
counts = {"raw": len(els), "shops": len(shops)}
|
||||
counts = {"raw": len(els), "shops": len(shops), "heroes": len(heroes),
|
||||
"texture": len(texture), "textureDropped": dropped}
|
||||
cache = {
|
||||
"schema": "procity-town-cache/2" if roads else "procity-town-cache/1",
|
||||
"key": key, "town": cfg["town"], "source": "osm",
|
||||
@ -236,7 +341,8 @@ def main():
|
||||
args = [a for a in sys.argv[1:] if not a.startswith("-")]
|
||||
refetch = "--refetch" in sys.argv
|
||||
no_roads = "--no-roads" in sys.argv # build a v1 cache (marched fallback), no road fetch
|
||||
keys = args or list(TOWNS)
|
||||
pack = "--pack" in sys.argv # only the NEW curated candidates (the beta five are
|
||||
keys = args or ([k for k in TOWNS if k not in BETA_FIVE] if pack else list(TOWNS)) # pinned by A)
|
||||
os.makedirs(TOWNS_DIR, exist_ok=True)
|
||||
kept, dropped = [], []
|
||||
for key in keys:
|
||||
@ -257,16 +363,35 @@ def main():
|
||||
kept.append(key)
|
||||
else:
|
||||
dropped.append((key, n, sp))
|
||||
rstr = ""
|
||||
if roads is not None:
|
||||
rk = {}
|
||||
for r in roads:
|
||||
rk[r["kind"]] = rk.get(r["kind"], 0) + 1
|
||||
rstr = f" roads={len(roads)} {dict(sorted(rk.items(), key=lambda x: -x[1]))}"
|
||||
print(f" {'OK ' if ok else 'LOW'} {key:16} shops={n:>3} span={sp:.2f}km {dict(sorted(types.items()))}{rstr}")
|
||||
c = cache["counts"]
|
||||
sub = f" −{c['textureDropped']} subsampled" if c.get("textureDropped") else ""
|
||||
print(f" {'OK ' if ok else 'LOW'} {key:16} shops={n:>3} (heroes {c['heroes']:>2} + texture "
|
||||
f"{c['texture']:>3}{sub}) roads={len(roads or [])!s:>4} {dict(sorted(types.items()))}")
|
||||
print(f"\nBUILT {len(kept)} town cache(s) → web/assets/towns/: {', '.join(kept)}")
|
||||
if dropped:
|
||||
print(f"DROPPED (< 6 shops or span ≥ 5 km): {dropped}")
|
||||
write_index()
|
||||
|
||||
|
||||
def write_index():
|
||||
"""web/assets/towns/index.json (ROUND20 ledger #2) — B's selector derives the town list from this
|
||||
instead of hardcoding REAL_TOWNS. Built from every cache on disk, not just this run."""
|
||||
towns = []
|
||||
for f in sorted(x for x in os.listdir(TOWNS_DIR) if x.endswith(".json") and x != "index.json"):
|
||||
c = json.load(open(os.path.join(TOWNS_DIR, f)))
|
||||
k = c.get("key", f[:-5])
|
||||
# state: the cache's own field first (so ANY lane's town self-describes — e.g. Lane G's
|
||||
# godverse caches), then my TOWNS config, then blank. The index rosters every cache in the
|
||||
# dir, not just mine.
|
||||
state = c.get("state") or TOWNS.get(k, {}).get("state", "")
|
||||
towns.append({"key": k, "town": c.get("town", k), "state": state,
|
||||
"shops": len(c.get("shops", [])), "roads": bool(c.get("roads")),
|
||||
"source": c.get("source", "osm")})
|
||||
idx = {"schema": "procity-towns-index/1", "generated": datetime.date.today().isoformat(),
|
||||
"license": "ODbL 1.0", "attribution": "© OpenStreetMap contributors", "towns": towns}
|
||||
json.dump(idx, open(os.path.join(TOWNS_DIR, "index.json"), "w"), indent=1)
|
||||
print(f"INDEX → web/assets/towns/index.json ({len(towns)} towns: "
|
||||
f"{', '.join(t['key'] for t in towns)})")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
245
pipeline/godverse_stock.py
Normal file
@ -0,0 +1,245 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Lane G — G2a: per-shop REAL stock atlases (tier 1) for `?stock=real`.
|
||||
|
||||
THE FIRST REAL CRATE. One real shop's real crate — real sleeves, real titles, real prices —
|
||||
baked to a static atlas keyed by godverse shop id. No server, no runtime query: tier 1 is files.
|
||||
|
||||
Two stages, split at THE SNAPSHOT BOUNDARY (the charter's determinism rule, and the same
|
||||
discipline build_towns.py applies to Overpass raw):
|
||||
|
||||
snapshot POS DB + image map -> pipeline/_godverse/<shop>_<crate>.snapshot.json
|
||||
pack snapshot + images -> web/assets/stock_godverse/<shop>/stock_record_*.{json,webp}
|
||||
|
||||
The snapshot is the artifact of record: it runs where the databases are, pins every source row
|
||||
AND every cover's sha256, and is committed. The pack stage is pure — same snapshot + same image
|
||||
bytes => byte-identical atlas, on any machine, with no DB. That split is what lets Lane E's
|
||||
validate_atlas gate G's output on m3 without the dealgod DB.
|
||||
|
||||
python3 pipeline/godverse_stock.py snapshot --crate 550 --image-map <tsv>
|
||||
~/Documents/MODELBEAST/venvs/mflux/bin/python pipeline/godverse_stock.py pack \
|
||||
pipeline/_godverse/monsterrobot_550.snapshot.json # needs PIL (house convention)
|
||||
|
||||
THE SHOP (John's ruling, R23): `monsterrobot` — a real, trading record shop at 147 Musgrave Road,
|
||||
Red Hill, Brisbane QLD. It is the same shop in three datasets, which is what makes tier 1 honest:
|
||||
recordgod its POS: real crates, real slot numbers, real prices, real grading
|
||||
monsterrobot.party its own website — the covers are the SHOP'S OWN product shots
|
||||
dealgod store 3962749 the scrape of that site; the cover cache + the godverse shop id
|
||||
It is NOT in thriftgod's OSM census (see G-progress R23), so its godverse id is its dealgod store
|
||||
id 3962749 — real, stable, and clear of the census id space (thriftgod max shop id = 2992).
|
||||
|
||||
DETERMINISM: ordered queries, no RNG, no sampling, no clock in the output. Prices/titles/artists
|
||||
are FACTS copied from the POS, not minted (thriftgod's mint() is TABLESAMPLE + unseeded random —
|
||||
it cannot produce tier 1; see G3_ECONOMY_DESIGN.md §2).
|
||||
|
||||
LICENCE (charter law #3): covers are the shop's own product shots from the shop's own domain,
|
||||
cached by dealgod. In-house 🟢. FLAG BEFORE ANY PUBLIC/COMMERCIAL RELEASE. Real titles/artists
|
||||
ship at tier 1 per John's R23 amendment to the no-real-trademarks law (in-house green, same
|
||||
flag). Provenance rides in every index. PII FENCE: recordgod holds real customer/staff/sales
|
||||
tables — this pipeline reads ONLY inventory/crate/disc_cache and must never read the others.
|
||||
"""
|
||||
import argparse, hashlib, json, os, subprocess, sys
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
SNAP_DIR = os.path.join(ROOT, "pipeline", "_godverse")
|
||||
STOCK_DIR = os.path.join(ROOT, "web", "assets", "stock_godverse")
|
||||
IMG_CACHE = os.environ.get("DEALGOD_IMG_CACHE", "/Volumes/johnking/dealgod_backup/images")
|
||||
|
||||
# The shop, per John's R23 ruling. `godverse_id` = dealgod store id (see module docstring).
|
||||
SHOP = {
|
||||
"godverse_id": 3962749, "slug": "monsterrobot", "name": "Monster Robot Party",
|
||||
"address": "147 Musgrave Road", "suburb": "Red Hill", "city": "Brisbane",
|
||||
"state": "QLD", "country": "AU", "type": "record",
|
||||
}
|
||||
OWN_PHOTO_HOST = "monsterrobot.party" # the licence test: the shop's own domain, nothing else
|
||||
|
||||
CELL_W, CELL_H, GRID, ATLAS_PX = 256, 256, 8, 2048 # Lane E's record contract (build_stock_pack.py)
|
||||
PER_ATLAS = GRID * (ATLAS_PX // CELL_H) # 64
|
||||
|
||||
|
||||
def price_band(price):
|
||||
"""Lane E's record bands, verbatim — C's dig UI reads this (grail => `rare`)."""
|
||||
p = price if price is not None else 4
|
||||
return "bargain" if p < 8 else "standard" if p < 25 else "collector" if p < 60 else "grail"
|
||||
|
||||
|
||||
def psql(db, sql):
|
||||
"""Read-only query -> list of field-lists. psql CLI, not psycopg2: the m3 has no psycopg2, and
|
||||
a snapshot stage that shells out has one less dep between the DB and the frozen file."""
|
||||
out = subprocess.run(["psql", "-d", db, "-tAF\t", "-c", sql],
|
||||
capture_output=True, text=True, check=True).stdout
|
||||
return [ln.split("\t") for ln in out.splitlines() if ln.strip()]
|
||||
|
||||
|
||||
def _n(v):
|
||||
return None if v in ("", "\\N") else v
|
||||
|
||||
|
||||
# ── stage 1: snapshot ────────────────────────────────────────────────────────────────────
|
||||
def load_image_map(path):
|
||||
"""release_id -> local_image, restricted to the SHOP'S OWN photos (the licence test).
|
||||
|
||||
On ultra (dealgod present) this is a query. On the m3 dealgod is a 2 GB dump and never
|
||||
restored, so the map is streamed out of it once — the command is recorded in the snapshot's
|
||||
provenance so the extraction is reproducible:
|
||||
|
||||
gzcat dealgod-<date>.sql.gz | awk -F'\\t' '/^COPY public.products \\(/{c=1;next}
|
||||
c && /^\\\\.$/{c=0;next} c && $50!="\\\\N"{print $50"\\t"$5"\\t"$34"\\t"$52"\\t"$56}'
|
||||
(fields: 50=release_id 5=image_url 34=local_image 52=current_price 56=discogs_artist_name)
|
||||
"""
|
||||
m = {}
|
||||
with open(path) as fh:
|
||||
for ln in fh:
|
||||
f = ln.rstrip("\n").split("\t")
|
||||
if len(f) < 3:
|
||||
continue
|
||||
rel, url, local = f[0], f[1], f[2]
|
||||
if OWN_PHOTO_HOST not in url or not local.startswith("/img/"):
|
||||
continue
|
||||
m.setdefault(rel, local) # first wins; ids are sorted upstream => stable
|
||||
return m
|
||||
|
||||
|
||||
def snapshot(args):
|
||||
crate = psql("recordgod", f"SELECT id,name,COALESCE(label_text,'') FROM crate WHERE id={args.crate}")
|
||||
if not crate:
|
||||
sys.exit(f"crate {args.crate} not found in recordgod")
|
||||
cid, cname, clabel = crate[0][0], crate[0][1], crate[0][2]
|
||||
|
||||
# ONLY inventory/crate/disc_cache — the PII fence (see module docstring).
|
||||
rows = psql("recordgod", f"""
|
||||
SELECT i.sku, COALESCE(i.slot_number::text,''), i.release_id,
|
||||
COALESCE(d.artist,''), COALESCE(d.title, COALESCE(i.title,'')),
|
||||
i.price, COALESCE(i.condition,''), COALESCE(i.sleeve_cond,'')
|
||||
FROM inventory i
|
||||
JOIN crate c ON c.id = i.crate_id
|
||||
LEFT JOIN disc_cache d USING (release_id)
|
||||
WHERE i.in_stock AND c.id = {args.crate} AND i.release_id IS NOT NULL
|
||||
ORDER BY i.slot_number NULLS LAST, i.sku""")
|
||||
|
||||
imgs = load_image_map(args.image_map)
|
||||
items, dropped = [], {"no_own_photo": 0, "file_missing": 0, "no_price": 0}
|
||||
for sku, slot, rel, artist, title, price, cond, sleeve in rows:
|
||||
local = imgs.get(rel)
|
||||
if not local:
|
||||
dropped["no_own_photo"] += 1
|
||||
continue
|
||||
path = os.path.join(IMG_CACHE, local[len("/img/"):])
|
||||
if not os.path.isfile(path):
|
||||
dropped["file_missing"] += 1
|
||||
continue
|
||||
if not _n(price):
|
||||
dropped["no_price"] += 1
|
||||
continue
|
||||
with open(path, "rb") as fh:
|
||||
sha = hashlib.sha256(fh.read()).hexdigest()
|
||||
items.append({"sku": sku, "slot": int(slot) if slot else None, "release_id": int(rel),
|
||||
"artist": artist, "title": title, "price": float(price),
|
||||
"condition": cond, "sleeve_cond": sleeve,
|
||||
"cover": local, "cover_sha256": sha})
|
||||
|
||||
if args.max_items:
|
||||
items = items[:args.max_items]
|
||||
|
||||
snap = {
|
||||
"version": 1, "kind": "record", "tier": 1,
|
||||
"shop": SHOP,
|
||||
"crate": {"id": int(cid), "name": cname, "label": clabel},
|
||||
"counts": {"in_crate": len(rows), "baked": len(items), "dropped": dropped},
|
||||
"source": {
|
||||
"pos_db": "recordgod", "pos_dump": args.pos_dump,
|
||||
"image_map_dump": args.image_map_dump, "image_cache": IMG_CACHE,
|
||||
"captured": args.captured,
|
||||
"extraction": "see load_image_map() docstring — the products projection command",
|
||||
},
|
||||
"licence": {
|
||||
"covers": "the shop's own product shots (monsterrobot.party), cached by dealgod",
|
||||
"zone": "in-house-green", "flag": "FLAG BEFORE ANY PUBLIC/COMMERCIAL RELEASE",
|
||||
"metadata": "real titles/artists — John's R23 amendment to the no-real-trademarks law",
|
||||
"pii": "none — inventory/crate/disc_cache only; customer/staff/sales never read",
|
||||
},
|
||||
"items": items,
|
||||
}
|
||||
os.makedirs(SNAP_DIR, exist_ok=True)
|
||||
dst = os.path.join(SNAP_DIR, f"{SHOP['slug']}_{cid}.snapshot.json")
|
||||
with open(dst, "w") as fh:
|
||||
json.dump(snap, fh, indent=1, sort_keys=False)
|
||||
fh.write("\n")
|
||||
print(f"snapshot: crate {cid} '{clabel or cname}' — {len(items)} real records "
|
||||
f"(of {len(rows)} in crate; dropped {dropped}) -> {dst}")
|
||||
|
||||
|
||||
# ── stage 2: pack (pure: snapshot + image bytes -> atlas) ─────────────────────────────────
|
||||
def pack(args):
|
||||
from PIL import Image
|
||||
snap = json.load(open(args.snapshot))
|
||||
shop, items = snap["shop"], snap["items"]
|
||||
out_dir = os.path.join(STOCK_DIR, str(shop["godverse_id"]))
|
||||
os.makedirs(out_dir, exist_ok=True)
|
||||
|
||||
idx_items, atlases = [], []
|
||||
for ai in range((len(items) + PER_ATLAS - 1) // PER_ATLAS):
|
||||
chunk = items[ai * PER_ATLAS:(ai + 1) * PER_ATLAS]
|
||||
W, H = GRID * CELL_W, (ATLAS_PX // CELL_H) * CELL_H
|
||||
sheet = Image.new("RGB", (W, H), (28, 28, 32))
|
||||
aname = f"stock_record_atlas_{ai:02d}.webp"
|
||||
for j, it in enumerate(chunk):
|
||||
gx, gy = j % GRID, j // GRID
|
||||
path = os.path.join(IMG_CACHE, it["cover"][len("/img/"):])
|
||||
with open(path, "rb") as fh:
|
||||
raw = fh.read()
|
||||
if hashlib.sha256(raw).hexdigest() != it["cover_sha256"]:
|
||||
sys.exit(f"cover drift: {it['sku']} {it['cover']} — snapshot sha mismatch, re-snapshot")
|
||||
import io
|
||||
cov = Image.open(io.BytesIO(raw)).convert("RGB").resize((CELL_W, CELL_H), Image.LANCZOS)
|
||||
sheet.paste(cov, (gx * CELL_W, gy * CELL_H))
|
||||
u0, v0 = gx * CELL_W / W, gy * CELL_H / H
|
||||
idx_items.append({
|
||||
"id": f"rec_{len(idx_items):04d}", "title": it["title"], "artist": it["artist"],
|
||||
"price": int(round(it["price"])), "price_band": price_band(it["price"]),
|
||||
"atlas": aname, "uv": [round(u0, 4), round(v0, 4),
|
||||
round(u0 + CELL_W / W, 4), round(v0 + CELL_H / H, 4)],
|
||||
})
|
||||
sheet.save(os.path.join(out_dir, aname), "WEBP", quality=88, method=6)
|
||||
atlases.append(aname)
|
||||
|
||||
with open(args.snapshot, "rb") as fh:
|
||||
snap_sha = hashlib.sha256(fh.read()).hexdigest()
|
||||
index = {
|
||||
"version": 1, "kind": "record", "tier": 1,
|
||||
"atlas_px": ATLAS_PX, "cell_w": CELL_W, "cell_h": CELL_H,
|
||||
"count": len(idx_items), "atlases": atlases,
|
||||
"shop": {k: shop[k] for k in ("godverse_id", "slug", "name", "suburb", "state")},
|
||||
"crate": snap["crate"],
|
||||
"provenance": {
|
||||
"generator": "pipeline/godverse_stock.py",
|
||||
"source": snap["source"], "licence": snap["licence"],
|
||||
"snapshot": os.path.basename(args.snapshot), "snapshot_sha256": snap_sha,
|
||||
"determinism": "pure bake — same snapshot + same cover bytes => byte-identical atlas; "
|
||||
"covers pinned by sha256, no RNG, no clock",
|
||||
},
|
||||
"items": idx_items,
|
||||
}
|
||||
dst = os.path.join(out_dir, "stock_record_index.json")
|
||||
with open(dst, "w") as fh:
|
||||
json.dump(index, fh, indent=1)
|
||||
fh.write("\n")
|
||||
print(f"pack: {len(idx_items)} real sleeves -> {len(atlases)} atlas(es) in {out_dir}")
|
||||
print(f" shop {shop['godverse_id']} ({shop['slug']}) · crate "
|
||||
f"{snap['crate']['id']} '{snap['crate']['label'] or snap['crate']['name']}'")
|
||||
|
||||
|
||||
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||
sub = ap.add_subparsers(dest="cmd", required=True)
|
||||
s1 = sub.add_parser("snapshot", help="POS + image map -> frozen snapshot (runs where the DBs are)")
|
||||
s1.add_argument("--crate", type=int, required=True)
|
||||
s1.add_argument("--image-map", required=True, help="TSV: release_id, image_url, local_image, ...")
|
||||
s1.add_argument("--max-items", type=int, default=0, help="cap (atlas budget — charter risk #2)")
|
||||
s1.add_argument("--pos-dump", default="", help="recordgod dump filename, for provenance")
|
||||
s1.add_argument("--image-map-dump", default="", help="dealgod dump filename, for provenance")
|
||||
s1.add_argument("--captured", default="", help="snapshot date (YYYY-MM-DD), for provenance")
|
||||
s1.set_defaults(func=snapshot)
|
||||
s2 = sub.add_parser("pack", help="snapshot -> atlas + index (pure; needs PIL, no DB)")
|
||||
s2.add_argument("snapshot")
|
||||
s2.set_defaults(func=pack)
|
||||
args = ap.parse_args()
|
||||
args.func(args)
|
||||
172
pipeline/validate_atlas.py
Normal file
@ -0,0 +1,172 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Validate per-shop real-stock atlases — the G2a format gate (v5.0-alpha, ROUND23 ledger #4).
|
||||
|
||||
Lane G bakes one atlas + index per real shop from dealgod inventory (tier 1 of the charter's stock
|
||||
ladder). This gate is the FORMAT side: it proves G's output is well-formed, inside C's ceilings, and
|
||||
carries its provenance — **on m3, from the files alone, with no dealgod DB and no network**. That's the
|
||||
point: the gate must run anywhere, on the committed artefacts.
|
||||
|
||||
python3 pipeline/validate_atlas.py # exit 0 = every atlas valid (or none yet), 1 = a bad one
|
||||
|
||||
Contract (published R22 → LANE_E_NOTES E's half · LANE_C_PUB §7 C's ceilings · charter law #3 licence):
|
||||
index `web/assets/stock_godverse/**/stock_shop_<godverseShopId>_index.json`
|
||||
{version, atlas_px, cell, atlases[], items:[{id,title,artist,price,price_band,atlas,uv}]}
|
||||
+ provenance: {license, attribution, generator, snapshot}
|
||||
C's ceilings — 1 atlas/shop (hard cap 2); ≤1024² typical, 2048² only for a 60+ item shop.
|
||||
charter law #3 — every atlas carries provenance; no private-individual data, ever.
|
||||
determinism — `snapshot` pins the inventory query result (G snapshots it like E snapshots Overpass raw).
|
||||
|
||||
Fail-soft is NOT this gate's business: a missing atlas is legal at runtime (parody canvas, R7 law). This
|
||||
gate only judges atlases that EXIST — a malformed one must fail loudly rather than render garbled sleeves.
|
||||
"""
|
||||
import json, os, sys, glob, struct
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
STOCK_G = os.path.join(ROOT, "web", "assets", "stock_godverse") # Lane G's namespace (charter law #5)
|
||||
BANDS = {"bargain", "standard", "collector", "grail"}
|
||||
REQUIRED_ITEM = ("id", "title", "artist", "price", "price_band", "atlas", "uv")
|
||||
REQUIRED_PROV = ("license", "attribution", "generator", "snapshot") # charter law #3 + determinism
|
||||
MAX_ATLASES = 2 # C: 1 atlas/shop, hard cap 2 (each extra = +1 draw)
|
||||
PX_TYPICAL, PX_MAX = 1024, 2048
|
||||
BIG_SHOP_ITEMS = 60 # C: 2048² only for a 60+ item shop (else spill to the type-pack)
|
||||
# charter law #3: "No private-individual data, ever" — a real shop's stock row must never carry a person.
|
||||
FORBIDDEN_ITEM_KEYS = {"seller", "seller_name", "owner", "owner_name", "customer", "user", "username",
|
||||
"email", "phone", "mobile", "address", "member", "account", "consignor"}
|
||||
|
||||
|
||||
def image_size(path):
|
||||
"""(w, h) for PNG/WebP from the header alone — dependency-free, house style (cf. glb_stat.py)."""
|
||||
with open(path, "rb") as f:
|
||||
head = f.read(32)
|
||||
if head[:8] == b"\x89PNG\r\n\x1a\n":
|
||||
w, h = struct.unpack(">II", head[16:24])
|
||||
return w, h
|
||||
if head[:4] == b"RIFF" and head[8:12] == b"WEBP":
|
||||
fmt = head[12:16]
|
||||
if fmt == b"VP8X": # extended: 24-bit minus-one
|
||||
b = head[24:30]
|
||||
return (b[0] | b[1] << 8 | b[2] << 16) + 1, (b[3] | b[4] << 8 | b[5] << 16) + 1
|
||||
if fmt == b"VP8 ": # lossy: 14-bit each
|
||||
w, h = struct.unpack("<HH", head[26:30])
|
||||
return w & 0x3FFF, h & 0x3FFF
|
||||
if fmt == b"VP8L": # lossless: packed 14-bit
|
||||
b = struct.unpack("<I", head[21:25])[0]
|
||||
return (b & 0x3FFF) + 1, ((b >> 14) & 0x3FFF) + 1
|
||||
raise ValueError("unrecognised image header (want PNG or WebP)")
|
||||
|
||||
|
||||
def validate_index(path):
|
||||
errs, warns = [], []
|
||||
name = os.path.basename(path)
|
||||
try:
|
||||
idx = json.load(open(path))
|
||||
except Exception as e:
|
||||
return [f"{name}: does not parse: {e}"], []
|
||||
d = os.path.dirname(path)
|
||||
|
||||
# ── provenance (charter law #3) — every atlas carries it, and the gate PRINTS the licence ──
|
||||
miss = [f for f in REQUIRED_PROV if not str(idx.get(f, "")).strip()]
|
||||
if miss:
|
||||
errs.append(f"{name}: missing/empty provenance {miss} (charter law #3 — every atlas carries provenance)")
|
||||
|
||||
# ── C's draw ceiling (LANE_C_PUB §7.1) ──
|
||||
atlases = idx.get("atlases", [])
|
||||
if not isinstance(atlases, list) or not atlases:
|
||||
errs.append(f"{name}: no atlases[] listed")
|
||||
atlases = []
|
||||
if len(atlases) > MAX_ATLASES:
|
||||
errs.append(f"{name}: {len(atlases)} atlases — C's hard cap is {MAX_ATLASES} "
|
||||
f"(1/shop; a 60+ item shop spills to the shared type-pack instead of fragmenting)")
|
||||
elif len(atlases) == 2:
|
||||
warns.append(f"{name}: 2 atlases (+1 draw) — C's rule is 1/shop; 2 is the cap, not the target")
|
||||
|
||||
items = idx.get("items", [])
|
||||
if not isinstance(items, list) or not items:
|
||||
errs.append(f"{name}: no items")
|
||||
items = []
|
||||
|
||||
# ── C's texture/VRAM ceiling (§7.2) + index↔atlas consistency (the declared size must be REAL) ──
|
||||
declared = idx.get("atlas_px")
|
||||
for a in atlases:
|
||||
ap = os.path.join(d, a)
|
||||
if not os.path.isfile(ap):
|
||||
errs.append(f"{name}: atlas {a!r} listed but missing on disk")
|
||||
continue
|
||||
try:
|
||||
w, h = image_size(ap)
|
||||
except Exception as e:
|
||||
errs.append(f"{name}: atlas {a!r} unreadable: {e}")
|
||||
continue
|
||||
if declared and (w != declared or h != declared):
|
||||
errs.append(f"{name}: atlas {a!r} is {w}×{h} but index declares atlas_px={declared}")
|
||||
big = len(items) >= BIG_SHOP_ITEMS
|
||||
cap = PX_MAX if big else PX_TYPICAL
|
||||
if max(w, h) > cap:
|
||||
errs.append(f"{name}: atlas {a!r} {w}×{h} exceeds C's ceiling {cap}² "
|
||||
f"({'60+ item shop' if big else f'{len(items)}-item shop → ≤{PX_TYPICAL}²'})")
|
||||
if max(w, h) > PX_TYPICAL and not big:
|
||||
warns.append(f"{name}: atlas {a!r} is {w}×{h} for {len(items)} items — prefer ≤{PX_TYPICAL}²")
|
||||
|
||||
# ── the index itself (schema confirmed verbatim in LANE_C_PUB §7.3) ──
|
||||
ids = set()
|
||||
for i, it in enumerate(items):
|
||||
if not isinstance(it, dict):
|
||||
errs.append(f"{name}[{i}]: item is not an object"); continue
|
||||
m = [f for f in REQUIRED_ITEM if f not in it]
|
||||
if m:
|
||||
errs.append(f"{name}[{i}]: missing {m}"); continue
|
||||
priv = sorted(set(k.lower() for k in it) & FORBIDDEN_ITEM_KEYS)
|
||||
if priv:
|
||||
errs.append(f"{name}[{it['id']}]: private-individual field(s) {priv} — charter law #3 forbids it")
|
||||
if not str(it["title"]).strip() or not str(it["artist"]).strip():
|
||||
errs.append(f"{name}[{it['id']}]: empty title/artist")
|
||||
if it["id"] in ids:
|
||||
errs.append(f"{name}: duplicate id {it['id']!r}")
|
||||
ids.add(it["id"])
|
||||
if it["price_band"] not in BANDS:
|
||||
errs.append(f"{name}[{it['id']}]: bad price_band {it['price_band']!r}")
|
||||
if not isinstance(it["price"], (int, float)) or it["price"] < 0:
|
||||
errs.append(f"{name}[{it['id']}]: bad price {it['price']!r}")
|
||||
if it["atlas"] not in atlases:
|
||||
errs.append(f"{name}[{it['id']}]: atlas {it['atlas']!r} not in atlases[]")
|
||||
uv = it["uv"]
|
||||
if not (isinstance(uv, list) and len(uv) == 4 and all(isinstance(v, (int, float)) for v in uv)):
|
||||
errs.append(f"{name}[{it['id']}]: uv must be [u0,v0,u1,v1] numbers"); continue
|
||||
u0, v0, u1, v1 = uv
|
||||
if not (0 <= u0 < u1 <= 1 and 0 <= v0 < v1 <= 1): # UV origin top-left; C flips V (R7 law)
|
||||
errs.append(f"{name}[{it['id']}]: uv out of range / inverted: {uv}")
|
||||
return errs, warns
|
||||
|
||||
|
||||
def main():
|
||||
idxs = sorted(glob.glob(os.path.join(STOCK_G, "**", "stock_shop_*_index.json"), recursive=True))
|
||||
if not idxs:
|
||||
print("atlas-QA: no per-shop atlases yet (Lane G's G2a) — nothing to validate")
|
||||
return 0
|
||||
all_errs, all_warns = [], []
|
||||
for p in idxs:
|
||||
e, w = validate_index(p)
|
||||
all_errs += e; all_warns += w
|
||||
idx = {}
|
||||
try:
|
||||
idx = json.load(open(p))
|
||||
except Exception:
|
||||
pass
|
||||
print(f" {os.path.basename(p):40} items={len(idx.get('items', [])):>3} "
|
||||
f"atlases={len(idx.get('atlases', []))} px={idx.get('atlas_px')} "
|
||||
f"snapshot={str(idx.get('snapshot', '—'))[:24]} {'OK' if not e else 'ERR(' + str(len(e)) + ')'}")
|
||||
if idx.get("license"): # licence law: the gate SHOWS it, a human clears it
|
||||
print(f" licence: {idx['license']}")
|
||||
for w in all_warns:
|
||||
print(f" WARN {w}")
|
||||
for e in all_errs:
|
||||
print(f" ERR {e}")
|
||||
if all_errs:
|
||||
print(f"atlas-QA FAIL — {len(all_errs)} error(s), {len(all_warns)} warning(s)")
|
||||
return 1
|
||||
print(f"atlas-QA OK — {len(idxs)} per-shop atlas(es) valid, {len(all_warns)} warning(s)")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@ -152,6 +152,15 @@ def main():
|
||||
except Exception as e:
|
||||
warn(f"pack-QA skipped: {e}")
|
||||
|
||||
# per-shop atlas QA (v5 G2a, ROUND23 E #4): Lane G's tier-1 atlases are gate-checked from the
|
||||
# committed files alone — no dealgod DB, no network. No atlases yet ⇒ clean pass.
|
||||
try:
|
||||
import validate_atlas
|
||||
if validate_atlas.main() != 0:
|
||||
err("per-shop atlas validation failed (see atlas-QA errors above)")
|
||||
except Exception as e:
|
||||
warn(f"atlas-QA skipped: {e}")
|
||||
|
||||
print(f"manifest v{m.get('version')} — facades {len(facade)}, "
|
||||
f"skins {sum(len(v) if isinstance(v, list) else (len(v) if isinstance(v, dict) else 0) for v in sk.values())} groups, "
|
||||
f"GLBs {n_glb}, audio {n_audio}")
|
||||
|
||||
@ -1,22 +1,23 @@
|
||||
#!/usr/bin/env python3
|
||||
"""PROCITY Lane F — the v3.0 release money-shot tour (round 15 · ?gigs=1). One deterministic run.
|
||||
"""PROCITY Lane F — the v4.0 RELEASE TOUR (round 22 · the epoch close · ledger #4). One deterministic run.
|
||||
|
||||
Captures the release set into docs/shots/release_v3/ (+ a contact.html), grouped by the flags each shot
|
||||
needs, all from seed 20261990 (deterministic, reproducible — never hand-cropped):
|
||||
• venue_night — lit pub frontage + streetlamp pools + posters at night (?gigs=1)
|
||||
• district_posters — the spine at night: poster poles + lit frontages receding (?gigs=1)
|
||||
• queue_night — JOHN'S HERO: close on the pub door at 'on', queue + marquee + A's now-visible poster
|
||||
• gig_interior — the 4-piece with real instruments + a roster crowd (continuity) (?gigs=1)
|
||||
• rain_verandah — a wet night outside the lit video-shop window (?weather=rain&winmap=1)
|
||||
• tram_stop — the bus shelter the tram pauses at (?tram=1)
|
||||
The v4 thesis in five frames, into docs/shots/release_v4/ (+ contact.html). The towns ARE the product:
|
||||
|
||||
Discipline (ROUND15 ledger #5 + B's note): every shot is measured SETTLED — after the async instrument
|
||||
GLBs / poster textures / chunk stream land (shoot-twice), from a clean per-group load (no orphan-chunk
|
||||
inflation); the queue is driven LIVE (rAF is throttled in automation, so we run the animate loop + admit
|
||||
explicitly rather than trust auto-drain).
|
||||
• fremantle_hero — the golden-hour hero: a real shopfront corridor with the density crowd on it
|
||||
• fitzroy_mecca — the mecca (160 shops, 2× anything absorbed before this epoch)
|
||||
• toowoomba_thintail — the thin tail (12 shops): proof the floor still reads as a town
|
||||
• synthetic_control — the procedural town, UNCHANGED: the control the whole epoch was measured against
|
||||
• classic_covenant — `?classic=1`: the v2 covenant made visible, byte-identical forever (0x3fa36874)
|
||||
|
||||
Posing: every real-town frame resolves through Lane B's `DBG` retail-heart anchor / `clusterPose` — the
|
||||
primitive that came out of F's R21 finding (per-EDGE shop counts are ~1 on a real graph, so "densest edge"
|
||||
is the wrong heuristic; you must project the densest SPATIAL cluster onto its front edge and stand on the
|
||||
road). B's R22 bookmark fix resolves through the same anchor, so the tour and the bookmarks agree by
|
||||
construction. The HUD is hidden for every frame (F's R21 ruling corollary — the selector ships, but it
|
||||
doesn't belong in a beauty shot).
|
||||
|
||||
Run: tools/.venv/bin/python tools/qa/tour_shots.py [--seed N]
|
||||
Needs the Playwright venv + Lane B's ?dbg=1 hook (incl. queue_night). Writes full-frame 1280x720 PNGs.
|
||||
Needs the Playwright venv + Lane B's ?dbg=1 hook. Writes full-frame 1280x720 PNGs.
|
||||
"""
|
||||
import sys, time, socket, subprocess, pathlib
|
||||
|
||||
@ -24,59 +25,54 @@ ROOT = pathlib.Path(__file__).resolve().parent.parent.parent
|
||||
PORT = 8130
|
||||
HOST = f'http://127.0.0.1:{PORT}'
|
||||
SEED = 20261990
|
||||
OUTDIR = ROOT / 'docs' / 'shots' / 'release_v3_1'
|
||||
OUTDIR = ROOT / 'docs' / 'shots' / 'release_v4'
|
||||
if '--seed' in sys.argv:
|
||||
SEED = int(sys.argv[sys.argv.index('--seed') + 1])
|
||||
|
||||
# [R16 THE FLIP] shot groups — the DEFAULT boot now has all four flags on, so most shots come from ONE clean
|
||||
# load (no per-shot flag juggling). Rain is still forced (the default seed rolls clear); ?classic=1 gives the
|
||||
# before/after comparison. Deterministic, same seed.
|
||||
GROUPS = [
|
||||
{'flags': '', 'street': ['venue_night', 'district_posters', 'queue_night', 'tram_stop'], 'interior': True},
|
||||
{'flags': 'weather=rain', 'street': ['rain_verandah'], 'interior': False},
|
||||
# the town before v3 — the exact v2 boot; same main-street pose as district_posters for a clean A/B
|
||||
{'flags': 'classic=1', 'street': ['district_posters'], 'interior': False, 'suffix': '_classic'},
|
||||
# (file, query, segment, cluster-pose?, caption-prefix). seg 3 = late-afternoon gold; seg 2 = midday.
|
||||
# cluster=True → F's spatial-cluster pose (real towns); cluster=False → the bookmark (synthetic is one dense
|
||||
# cluster already, so street_noon frames it correctly).
|
||||
V4_TOUR = [
|
||||
('fremantle_hero', 'plansrc=osm&town=fremantle_real', 3, True, 'the hero — Fremantle, real streets, golden hour'),
|
||||
('fitzroy_mecca', 'plansrc=osm&town=fitzroy_real', 3, True, 'the mecca — Fitzroy, the densest town in the pack'),
|
||||
('toowoomba_thintail', 'plansrc=osm&town=toowoomba_real', 2, True, 'the thin tail — Toowoomba: the floor does NOT hold here, and this is the honest frame (12 shops over a 1,459-edge graph = 0.008 shops/edge vs the heroes\' 0.06–0.08; it reads as road, not town). Filed for v4.x'),
|
||||
('synthetic_control', f'seed={SEED}', 2, False, 'the control — the procedural town, unchanged'),
|
||||
('classic_covenant', 'classic=1', 2, False, '?classic=1 — the v2 covenant, byte-identical forever'),
|
||||
]
|
||||
|
||||
# settle + re-pose a street bookmark once its live/async content (queue, instrument GLBs, poster textures,
|
||||
# streamed chunks) has landed, then report SETTLED stats.
|
||||
STREET_JS = r"""
|
||||
async (name) => {
|
||||
# THE POSING PRIMITIVE (F's R21 finding). On a real graph, per-EDGE shop counts are ~1 — so "the edge with
|
||||
# the most shops" frames an empty arterial (it did, first run: the Fitzroy mecca came out as bare bitumen).
|
||||
# The town's retail heart is a SPATIAL cluster: find the shop lot with the most neighbours inside 60 m,
|
||||
# project it onto its own front edge, and stand ON the road backed off along it, looking down the strip.
|
||||
# Lane B's R22 bookmark fix anchors on the same retail heart — this is that primitive, applied to the tour.
|
||||
SHOT_JS = r"""
|
||||
async ({ seg, cluster }) => {
|
||||
const P = window.PROCITY, D = window.DBG;
|
||||
D.shot(name); // initial pose + segment (2 renders)
|
||||
for (let i = 0; i < 8; i++) await new Promise(r => requestAnimationFrame(r)); // animate loop: queue spawns
|
||||
const q = (P.gigs && P.venueQueues) ? P.venueQueues.get(P.gigs.venueShopIds[0]) : null;
|
||||
await new Promise(r => setTimeout(r, 1400)); // async GLB / poster / chunk settle
|
||||
const stat = D.shot(name); // RE-POSE now that everything is loaded → the settled frame
|
||||
const queue = (P.gigs && P.queueCountOf) ? P.queueCountOf(P.gigs.venueShopIds[0]) : null;
|
||||
const posters = (P.plan.posters || []).length;
|
||||
return { name, draws: stat.drawCalls, tris: stat.tris, queue, posters };
|
||||
}
|
||||
"""
|
||||
|
||||
INTERIOR_JS = r"""
|
||||
async () => {
|
||||
const P = window.PROCITY, D = window.DBG, THREE = P.THREE;
|
||||
const id = P.gigs.venueShopIds[0];
|
||||
D.setSegment(5);
|
||||
// drive the queue live + admit a couple so the interior crowd carries roster identities (continuity)
|
||||
for (let i = 0; i < 8; i++) await new Promise(r => requestAnimationFrame(r));
|
||||
const q = P.venueQueues && P.venueQueues.get(id);
|
||||
if (q) for (let i = 0; i < 3 && q.count() > 0; i++) q.admitOne();
|
||||
D.enterShop(id); await new Promise(r => setTimeout(r, 900));
|
||||
const room = P.interiorMode.current; if (!room) return { ok: false };
|
||||
const st = room.stage;
|
||||
P.camera.position.set(st.x + 0.2, 1.6, room.dims.D / 2 - 1.2);
|
||||
P.camera.lookAt(new THREE.Vector3(st.x, 1.35, st.z));
|
||||
// shoot-twice / settled discipline (ledger #5): immediate vs after the instrument GLBs land
|
||||
P.renderer.info.reset(); P.renderer.render(P.interiorMode.scene, P.camera);
|
||||
const trisImmediate = P.renderer.info.render.triangles;
|
||||
await new Promise(r => setTimeout(r, 1600));
|
||||
P.renderer.info.reset(); P.renderer.render(P.interiorMode.scene, P.camera);
|
||||
const info = P.interiorMode.crewInfo;
|
||||
return { ok: true, draws: P.renderer.info.render.calls, trisImmediate, trisSettled: P.renderer.info.render.triangles,
|
||||
band: info ? info.band : 0, crowd: info ? info.crowd : 0, fromRoster: info ? info.fromRoster : 0,
|
||||
venue: (P.plan.shops.find(s => s.id === id) || {}).name, bandName: P.gigs.bandNameOf(id) };
|
||||
D.setSegment(seg);
|
||||
let hub = -1;
|
||||
if (!cluster) { D.shot('street_noon'); }
|
||||
else {
|
||||
const plan = P.plan, nById = new Map(plan.streets.nodes.map(n => [n.id, n]));
|
||||
const lots = (plan.lots || []).filter(l => l.use === 'shop' || l.use === 'anchor');
|
||||
let best = null;
|
||||
for (const l of lots) { let c = 0; for (const o of lots) if (Math.hypot(o.x - l.x, o.z - l.z) < 60) c++;
|
||||
if (c > hub) { hub = c; best = l; } }
|
||||
if (best) {
|
||||
const e = (plan.streets.edges || []).find(x => x.id === best.frontEdge) || plan.streets.edges[0];
|
||||
const a = nById.get(e.a), b2 = nById.get(e.b);
|
||||
const dx = b2.x - a.x, dz = b2.z - a.z, L = Math.hypot(dx, dz) || 1, ux = dx / L, uz = dz / L;
|
||||
let t = (best.x - a.x) * ux + (best.z - a.z) * uz;
|
||||
t = Math.max(6, Math.min(L - 6, t));
|
||||
const back = Math.min(26, t);
|
||||
D.teleport(a.x + ux * (t - back), a.z + uz * (t - back), Math.atan2(ux, uz));
|
||||
} else { D.shot('street_noon'); }
|
||||
}
|
||||
await new Promise(r => setTimeout(r, 1800)); // crowd + chunks + skins + GLBs settle
|
||||
const hud = document.getElementById('pc-hud'); if (hud) hud.style.display = 'none'; // beauty shot
|
||||
for (let i = 0; i < 3; i++) await new Promise(r => requestAnimationFrame(r));
|
||||
const i = D.info();
|
||||
return { name: P.plan.name, shops: (P.plan.shops || []).length, hub,
|
||||
edges: (P.plan.streets.edges || []).length, draws: i.drawCalls, tris: i.tris };
|
||||
}
|
||||
"""
|
||||
|
||||
@ -98,31 +94,18 @@ def ensure_server():
|
||||
proc.terminate(); sys.exit(f'could not start http.server on :{PORT}')
|
||||
|
||||
|
||||
def boot(pg, flags):
|
||||
pg.goto(f'{HOST}/index.html?seed={SEED}&dbg=1&{flags}')
|
||||
pg.wait_for_function('window.DBG && window.DBG.ready === true', timeout=25000)
|
||||
pg.evaluate("() => { const o=document.getElementById('pc-start'); if(o) o.style.display='none'; }")
|
||||
try:
|
||||
pg.wait_for_function('() => window.PROCITY && (!window.PROCITY.fleet || window.PROCITY.fleet.ready)', timeout=15000)
|
||||
except Exception:
|
||||
print(' ! fleet never readied — shooting placeholders')
|
||||
|
||||
|
||||
def contact_html(shots):
|
||||
cards = []
|
||||
for s in shots:
|
||||
cap = s['caption']
|
||||
cards.append(
|
||||
f'<figure><img src="{s["file"]}" alt="{s["name"]}" loading="lazy">'
|
||||
f'<figcaption><b>{s["name"]}</b><br>{cap}</figcaption></figure>')
|
||||
cards = ''.join(
|
||||
f'<figure><img src="{s["file"]}" alt="{s["name"]}" loading="lazy">'
|
||||
f'<figcaption><b>{s["name"]}</b><br>{s["caption"]}</figcaption></figure>' for s in shots)
|
||||
return (
|
||||
'<!doctype html><meta charset="utf-8"><title>PROCITY v3.1 — release tour</title>'
|
||||
'<!doctype html><meta charset="utf-8"><title>PROCITY v4.0 — the release tour</title>'
|
||||
'<style>body{background:#14110c;color:#f4efe6;font:14px/1.4 -apple-system,Segoe UI,Roboto,sans-serif;margin:24px}'
|
||||
'h1{font-weight:700}.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(420px,1fr));gap:18px}'
|
||||
'.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(460px,1fr));gap:18px}'
|
||||
'figure{margin:0;background:#1d1810;border-radius:10px;overflow:hidden}img{width:100%;display:block}'
|
||||
'figcaption{padding:8px 12px;color:#cdc4b2}b{color:#ffd9a0}</style>'
|
||||
f'<h1>PROCITY v3.1 — release money-shot tour (THE FLIP) <small>· seed {SEED} · settled measurements</small></h1>'
|
||||
f'<div class="grid">{"".join(cards)}</div>')
|
||||
'figcaption{padding:9px 13px;color:#cdc4b2}b{color:#ffd9a0}</style>'
|
||||
f'<h1>PROCITY v4.0 — THE REAL MAP <small>· 23 real Australian towns · seed {SEED} · settled</small></h1>'
|
||||
f'<div class="grid">{cards}</div>')
|
||||
|
||||
|
||||
def main():
|
||||
@ -131,51 +114,39 @@ def main():
|
||||
except ImportError:
|
||||
sys.exit('playwright not installed — tools/.venv/bin/python -m playwright install chromium')
|
||||
|
||||
print(f"\033[1mv3.1 RELEASE MONEY-SHOT TOUR\033[0m (seed {SEED} → docs/shots/release_v3_1/)")
|
||||
print(f"\033[1mv4.0 RELEASE TOUR\033[0m ({len(V4_TOUR)} frames → docs/shots/release_v4/)")
|
||||
OUTDIR.mkdir(parents=True, exist_ok=True)
|
||||
srv = ensure_server()
|
||||
shots, errs_total = [], 0
|
||||
try:
|
||||
with sync_playwright() as p:
|
||||
b = p.chromium.launch()
|
||||
for grp in GROUPS:
|
||||
for name, query, seg, cluster, cap in V4_TOUR:
|
||||
pg = b.new_page(viewport={'width': 1280, 'height': 720})
|
||||
errs = []
|
||||
pg.on('pageerror', lambda e: errs.append(str(e)))
|
||||
boot(pg, grp['flags'])
|
||||
sfx = grp.get('suffix', '')
|
||||
for name in grp['street']:
|
||||
stat = pg.evaluate(STREET_JS, name)
|
||||
fname = f'{name}{sfx}.png'
|
||||
pg.screenshot(path=str(OUTDIR / fname))
|
||||
cap = (f"?classic=1 — the town before v3 (no gig layer, no pools)" if sfx
|
||||
else f"{stat['draws']} draws · {stat['tris']:,} tris")
|
||||
if not sfx and stat.get('queue') is not None: cap += f" · queue {stat['queue']}"
|
||||
if not sfx and name in ('district_posters', 'venue_night', 'queue_night'): cap += f" · {stat['posters']} posters"
|
||||
shots.append({'name': name + sfx, 'file': fname, 'caption': cap})
|
||||
print(f" \033[32m●\033[0m {name}{sfx}: {cap}")
|
||||
if grp['interior']:
|
||||
info = pg.evaluate(INTERIOR_JS)
|
||||
if info.get('ok'):
|
||||
out = OUTDIR / 'gig_interior.png'
|
||||
pg.screenshot(path=str(out))
|
||||
cap = (f"{info['venue']} · {info['bandName']} · {info['band']}-piece · crowd {info['crowd']} "
|
||||
f"({info['fromRoster']} from roster) · {info['draws']} draws · "
|
||||
f"{info['trisImmediate']:,}→{info['trisSettled']:,} tris (pre-load→settled)")
|
||||
shots.append({'name': 'gig_interior', 'file': 'gig_interior.png', 'caption': cap})
|
||||
print(f" \033[32m●\033[0m gig_interior: {cap}")
|
||||
else:
|
||||
print(" \033[31m✗\033[0m gig_interior: could not stage")
|
||||
pg.goto(f'{HOST}/index.html?{query}&dbg=1')
|
||||
pg.wait_for_function('window.PROCITY && window.PROCITY.plan && window.DBG && window.DBG.ready', timeout=25000)
|
||||
pg.evaluate("() => { const o=document.getElementById('pc-start'); if(o) o.style.display='none'; }")
|
||||
try:
|
||||
pg.wait_for_function('() => !window.PROCITY.fleet || window.PROCITY.fleet.ready', timeout=15000)
|
||||
except Exception:
|
||||
pass
|
||||
r = pg.evaluate(SHOT_JS, {'seg': seg, 'cluster': cluster})
|
||||
pg.screenshot(path=str(OUTDIR / f'{name}.png'))
|
||||
caption = f"{cap} · {r['name']} · {r['shops']} shops / {r['edges']} edges" + (f" · retail heart: {r['hub']} shops/60m" if r.get('hub', -1) > 0 else '') + f" · {r['draws']} draws · {r['tris']:,} tris"
|
||||
shots.append({'name': name, 'file': f'{name}.png', 'caption': caption})
|
||||
print(f" \033[32m●\033[0m {name}: {caption}")
|
||||
errs_total += len(errs)
|
||||
if errs: print(f" ! {len(errs)} page error(s) in [{grp['flags']}]: {errs[0][:100]}")
|
||||
if errs: print(f" ! {len(errs)} page error(s): {errs[0][:90]}")
|
||||
pg.close()
|
||||
b.close()
|
||||
finally:
|
||||
if srv: srv.terminate()
|
||||
|
||||
(OUTDIR / 'contact.html').write_text(contact_html(shots))
|
||||
print(f"\n\033[1m{len(shots)} shots\033[0m → docs/shots/release_v3_1/ (+ contact.html) · {errs_total} page errors")
|
||||
sys.exit(1 if errs_total or len(shots) < 6 else 0)
|
||||
print(f"\n\033[1m{len(shots)} frames\033[0m → docs/shots/release_v4/ (+ contact.html) · {errs_total} page errors")
|
||||
sys.exit(1 if errs_total or len(shots) < len(V4_TOUR) else 0)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@ -22,6 +22,10 @@ ROOT = pathlib.Path(__file__).resolve().parent.parent.parent
|
||||
PORT = 8130
|
||||
HOST = f'http://127.0.0.1:{PORT}'
|
||||
STREET_DRAWS_MAX, STREET_TRIS_MAX = 300, 200_000
|
||||
# [R21 ledger #6c] B's R20 tram ruling: on a REAL-ROADS town the tram only runs if its best main chain
|
||||
# fronts ≥5 shops — otherwise it's "a highway bus" and gets fenced. These two run; the other three are fenced.
|
||||
TRAM_RUN = {'newtown_real', 'castlemaine_real'}
|
||||
TRAM_STOPS_SANE = (2, 40) # B fixed the phantom-stop bug en route (Newtown: 149 → 16)
|
||||
|
||||
# the matrix: (row-label, boot-query). Synthetic hero seeds + 3 fixtures + E's 5 real AU caches.
|
||||
MATRIX = (
|
||||
@ -49,11 +53,13 @@ async () => {
|
||||
D.shot('venue_night'); // busiest default view: the venue block at night
|
||||
await new Promise(r => setTimeout(r, 500));
|
||||
const i = D.info();
|
||||
const tr = P.tram ? { fenced: !!(P.tram.routeInfo || {}).fenced, stops: P.tram.stops | 0,
|
||||
shopsFronted: (P.tram.routeInfo || {}).shopsFronted } : null; // [R21] B's fence verdict
|
||||
return { name: plan.name, shops: (plan.shops || []).length, venues,
|
||||
gigs: (plan.gigs || []).length, posters: (plan.posters || []).length,
|
||||
determ: g1 === g2, draws: i.drawCalls, tris: i.tris,
|
||||
mode: rep.mode || (src === 'synthetic' ? 'synthetic' : 'marched'),
|
||||
edges: (plan.streets.edges || []).length };
|
||||
edges: (plan.streets.edges || []).length, tram: tr };
|
||||
}
|
||||
"""
|
||||
|
||||
@ -104,9 +110,20 @@ def run_town(new_page, query, label):
|
||||
# (mode='roads'); synthetic/fixture towns are marched/synthetic by design (always ✓).
|
||||
mode = d.get('mode', '?')
|
||||
row['roads'] = ((mode == 'roads') if is_real else True, f"{mode},{d.get('edges', 0)}e")
|
||||
# [R21 #6c] tram: B's R20 ruling — a real-roads town runs the tram only if its best main chain fronts
|
||||
# ≥5 shops; otherwise it's FENCED (empty tram group, 0 stops, no errors). Synthetic/fixtures always run.
|
||||
t, key = d.get('tram'), (label.split('/', 1)[1] if '/' in label else label)
|
||||
if is_real and key in TRAM_RUN:
|
||||
row['tram'] = (bool(t) and not t['fenced'] and TRAM_STOPS_SANE[0] <= t['stops'] <= TRAM_STOPS_SANE[1],
|
||||
f"run {t['stops']}st" if t else 'absent')
|
||||
elif is_real:
|
||||
row['tram'] = (bool(t) and t['fenced'] and t['stops'] == 0,
|
||||
f"fenced/{t.get('shopsFronted', '?')}sh" if t else 'absent')
|
||||
else:
|
||||
row['tram'] = (bool(t) and not t['fenced'], f"run {t['stops']}st" if t else 'absent')
|
||||
except Exception as e:
|
||||
row.setdefault('name', '?'); row.setdefault('shops', 0)
|
||||
for g in ('boot', 'determ', 'budget', 'district', 'roads'):
|
||||
for g in ('boot', 'determ', 'budget', 'district', 'roads', 'tram'):
|
||||
row.setdefault(g, (False, str(e)[:24]))
|
||||
finally:
|
||||
pg.close()
|
||||
@ -134,9 +151,9 @@ def main():
|
||||
except ImportError:
|
||||
sys.exit('playwright not installed — tools/.venv/bin/python -m playwright install chromium')
|
||||
|
||||
print(f"\033[1mTOWN-MATRIX SMOKE\033[0m ({len(MATRIX)} towns × 6 gates (incl. the R19 roads dimension) · the v4 real-map scout evidence)")
|
||||
print(f"\033[1mTOWN-MATRIX SMOKE\033[0m ({len(MATRIX)} towns × 7 gates (roads + tram dimensions) · the v4 real-map scout evidence)")
|
||||
srv = ensure_server()
|
||||
GATES = ['boot', 'determ', 'budget', 'district', 'roads', 'noassets']
|
||||
GATES = ['boot', 'determ', 'budget', 'district', 'roads', 'tram', 'noassets']
|
||||
rows = []
|
||||
try:
|
||||
with sync_playwright() as p:
|
||||
@ -170,7 +187,7 @@ def main():
|
||||
if fails:
|
||||
print(f"\033[31m● {fails} gate failure(s)\033[0m across {len(rows)} towns — the matrix is not clean.")
|
||||
sys.exit(1)
|
||||
print(f"\033[32m● MATRIX GREEN\033[0m — {len(rows)} towns (synthetic + fixtures + real caches) pass all 6 gates.")
|
||||
print(f"\033[32m● MATRIX GREEN\033[0m — {len(rows)} towns (synthetic + fixtures + real caches) pass all 7 gates.")
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
|
||||
BIN
web/assets/stock_godverse/3962749/stock_record_atlas_00.webp
Normal file
|
After Width: | Height: | Size: 644 KiB |
BIN
web/assets/stock_godverse/3962749/stock_record_atlas_01.webp
Normal file
|
After Width: | Height: | Size: 580 KiB |
1728
web/assets/stock_godverse/3962749/stock_record_index.json
Normal file
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
@ -17,13 +17,43 @@ the "© OpenStreetMap contributors (ODbL)" credit visible.
|
||||
|
||||
## Provenance table
|
||||
|
||||
| cache | town | source | licence | fetched | shops | raw response |
|
||||
|---|---|---|---|---|---|---|
|
||||
| `katoomba_real.json` | Katoomba, NSW | OSM Overpass (bounded) | ODbL 1.0 | 2026-07-16 | 20 | `_raw/katoomba_real.overpass.json` |
|
||||
| `newtown_real.json` | Newtown, Sydney NSW | OSM Overpass (bounded) | ODbL 1.0 | 2026-07-16 | 18 | `_raw/newtown_real.overpass.json` |
|
||||
| `fremantle_real.json` | Fremantle, WA | OSM Overpass (bounded) | ODbL 1.0 | 2026-07-16 | 21 | `_raw/fremantle_real.overpass.json` |
|
||||
| `bendigo_real.json` | Bendigo, VIC | OSM Overpass (bounded) | ODbL 1.0 | 2026-07-16 | 9 | `_raw/bendigo_real.overpass.json` |
|
||||
| `castlemaine_real.json` | Castlemaine, VIC | OSM Overpass (bounded) | ODbL 1.0 | 2026-07-16 | 6 | `_raw/castlemaine_real.overpass.json` |
|
||||
**`index.json` is the machine-readable roster** (key/town/state/shops/roads) — read it rather than
|
||||
hardcoding a list. Every cache carries the same terms; per-town numbers live in each cache's `counts`.
|
||||
|
||||
**The pack: 21 real towns · 1180 shops · 295 heroes** (+ Lane G's `newtown_godverse` = 22 rostered in
|
||||
`index.json`), every one fetched from OSM Overpass with a bounded per-town bbox, ODbL 1.0, raw snapshots
|
||||
in `_raw/` (shops + roads per town), fetched 2026-07-16.
|
||||
|
||||
| state | towns |
|
||||
|---|---|
|
||||
| NSW (6) | katoomba · newtown · glebe · marrickville · newcastle (Hamilton strip) · bowral |
|
||||
| VIC (7) | fitzroy · brunswick · geelong · bendigo · ballarat · castlemaine · daylesford |
|
||||
| WA (2) | fremantle · northbridge |
|
||||
| QLD (1) | westend |
|
||||
| TAS (2) | hobart · launceston |
|
||||
| SA (1) | adelaide |
|
||||
| ACT (1) | braddon |
|
||||
| NT (1) | darwin |
|
||||
|
||||
**Retired (R23, counted):** `toowoomba_real` — D's clustering audit measured it DEAD (0 patronage finds
|
||||
in 1417 checks); a re-bbox onto the Ruthven St heart fixed the spacing metric (median NN 396 → 89 m) but
|
||||
made the hub worse (2/12 within 160 m, the pack's worst), and its densest 300 m cluster is only 3 shops.
|
||||
Cache + raw snapshots removed; the config entry stays commented with the rationale. See LANE_E_NOTES R23.
|
||||
|
||||
## The density widening (v4.0-beta, ROUND20 ledger #2)
|
||||
|
||||
The fetch covers the **secondhand heroes** plus the everyday main-street **texture** (bakery/cafe/deli/
|
||||
newsagent/chemist/clothes/hardware/greengrocer/…), mapped to existing registry types per **C's
|
||||
LANE_C_PUB §6** table. Tasteful staples only — no vehicle/fuel, funeral, storage, industrial, or pure
|
||||
services. `cafe` rides `amenity=*`; everything else is `shop=*`. Per John's **subtle** directive the
|
||||
heroes are **never** subsampled and texture is capped at **3× the hero count** (deterministic
|
||||
`sha256(townKey:osmId)` rank); every drop is counted in each cache's `counts.textureDropped`.
|
||||
|
||||
## `index.json` — the towns roster
|
||||
|
||||
`{schema:"procity-towns-index/1", generated, license, attribution, towns:[{key, town, state, shops,
|
||||
roads}]}`. Same ODbL terms as the caches. Consumers (B's selector) derive the town list from this
|
||||
rather than hardcoding.
|
||||
|
||||
## Raw responses (`_raw/`)
|
||||
|
||||
|
||||
5829
web/assets/towns/_raw/adelaide_real.overpass.json
Normal file
256440
web/assets/towns/_raw/adelaide_real.roads.overpass.json
Normal file
2524
web/assets/towns/_raw/ballarat_real.overpass.json
Normal file
185207
web/assets/towns/_raw/ballarat_real.roads.overpass.json
Normal file
997
web/assets/towns/_raw/bowral_real.overpass.json
Normal file
@ -0,0 +1,997 @@
|
||||
{
|
||||
"fetchedAt": "2026-07-16",
|
||||
"endpoint": "https://overpass-api.de/api/interpreter",
|
||||
"query": "[out:json][timeout:180];(nwr[\"shop\"~\"^(antiques|bag|bakery|books|bookshop|boutique|butcher|charity|cheese|chemist|chocolate|clothes|coffee|confectionery|convenience|cosmetics|dairy|deli|delicatessen|department_store|doityourself|electronics|farm|fashion|florist|furniture|games|garden_centre|general|greengrocer|hardware|homeware|houseware|jewellery|jewelry|kiosk|mobile_phone|music|newsagent|pastry|pawnbroker|perfumery|pharmacy|second_hand|shoes|stationery|supermarket|tea|toys|trade|variety_store|video|video_games|vintage)$\"][\"name\"](-34.48791,150.40598,-34.46809,150.43002);nwr[\"amenity\"~\"^(cafe)$\"][\"name\"](-34.48791,150.40598,-34.46809,150.43002););out center;",
|
||||
"bbox": {
|
||||
"minLat": -34.48790990990991,
|
||||
"minLon": 150.4059784233629,
|
||||
"maxLat": -34.468090090090094,
|
||||
"maxLon": 150.4300215766371
|
||||
},
|
||||
"elements": [
|
||||
{
|
||||
"type": "node",
|
||||
"id": 2266548319,
|
||||
"lat": -34.4788824,
|
||||
"lon": 150.4176876,
|
||||
"tags": {
|
||||
"name": "Gumnut Pattiserie",
|
||||
"opening_hours": "Mo-Su 08:00-16:00",
|
||||
"shop": "bakery"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 3894052381,
|
||||
"lat": -34.4793047,
|
||||
"lon": 150.4165542,
|
||||
"tags": {
|
||||
"brand": "Rivers",
|
||||
"brand:wikidata": "Q106224813",
|
||||
"check_date": "2022-07-24",
|
||||
"level": "0",
|
||||
"name": "Rivers",
|
||||
"shop": "clothes"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 6059394998,
|
||||
"lat": -34.4804575,
|
||||
"lon": 150.4184493,
|
||||
"tags": {
|
||||
"addr:housenumber": "23",
|
||||
"addr:postcode": "2576",
|
||||
"addr:state": "NSW",
|
||||
"addr:street": "Boolwey Street",
|
||||
"addr:suburb": "Bowral",
|
||||
"amenity": "cafe",
|
||||
"name": "Primary Espresso",
|
||||
"opening_hours:signed": "no",
|
||||
"outdoor_seating": "yes"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 6429549858,
|
||||
"lat": -34.480173,
|
||||
"lon": 150.4180428,
|
||||
"tags": {
|
||||
"name": "A Touch of Tea",
|
||||
"opening_hours": "Mo 10:00-17:00; Tu-Fr 09:30-17:00; Sa, Su 10:00-16:00",
|
||||
"phone": "+61 2 4862 2225",
|
||||
"shop": "tea",
|
||||
"website": "https://www.atouchoftea.com.au/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 8124047197,
|
||||
"lat": -34.4799168,
|
||||
"lon": 150.4175131,
|
||||
"tags": {
|
||||
"addr:housenumber": "329-331",
|
||||
"addr:postcode": "2576",
|
||||
"addr:state": "NSW",
|
||||
"addr:street": "Bong Bong Street",
|
||||
"addr:suburb": "Bowral",
|
||||
"amenity": "cafe",
|
||||
"cuisine": "coffee_shop",
|
||||
"name": "Elephant Boy Cafe",
|
||||
"outdoor_seating": "no",
|
||||
"phone": "+61 2 4855 2921"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 8401209657,
|
||||
"lat": -34.4817024,
|
||||
"lon": 150.4167739,
|
||||
"tags": {
|
||||
"amenity": "cafe",
|
||||
"name": "Plantation Cafe"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 8401238122,
|
||||
"lat": -34.4817011,
|
||||
"lon": 150.4165116,
|
||||
"tags": {
|
||||
"name": "Dirty Janes",
|
||||
"shop": "antiques"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 8401265331,
|
||||
"lat": -34.4806505,
|
||||
"lon": 150.4173157,
|
||||
"tags": {
|
||||
"name": "Few & Far",
|
||||
"shop": "furniture"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11092600813,
|
||||
"lat": -34.4765067,
|
||||
"lon": 150.4186032,
|
||||
"tags": {
|
||||
"amenity": "cafe",
|
||||
"name": "The Mill Cafe"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11092600816,
|
||||
"lat": -34.4765694,
|
||||
"lon": 150.4195185,
|
||||
"tags": {
|
||||
"amenity": "cafe",
|
||||
"diet:vegetarian": "yes",
|
||||
"name": "Veggie Nook"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11092600817,
|
||||
"lat": -34.4765481,
|
||||
"lon": 150.4193988,
|
||||
"tags": {
|
||||
"name": "Father Chris Riley Op Shop",
|
||||
"shop": "second_hand"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11092617604,
|
||||
"lat": -34.4774915,
|
||||
"lon": 150.4183821,
|
||||
"tags": {
|
||||
"amenity": "cafe",
|
||||
"name": "RAW & WILD Market & Cafe"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11092652175,
|
||||
"lat": -34.4787876,
|
||||
"lon": 150.4188276,
|
||||
"tags": {
|
||||
"amenity": "cafe",
|
||||
"name": "Kookabar Cafe"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11092652176,
|
||||
"lat": -34.4787705,
|
||||
"lon": 150.4186714,
|
||||
"tags": {
|
||||
"name": "Blooms of Bowral",
|
||||
"shop": "florist"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11092691609,
|
||||
"lat": -34.4777358,
|
||||
"lon": 150.4196083,
|
||||
"tags": {
|
||||
"amenity": "cafe",
|
||||
"cuisine": "bubble_tea",
|
||||
"name": "Dream Tea"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11092691651,
|
||||
"lat": -34.4785378,
|
||||
"lon": 150.4171278,
|
||||
"tags": {
|
||||
"brand": "Witchery",
|
||||
"brand:wikidata": "Q8027909",
|
||||
"name": "Witchery",
|
||||
"opening_hours": "Mo-Sa 09:00-17:30; Su 10:00-16:00",
|
||||
"shop": "clothes"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11092691652,
|
||||
"lat": -34.4784091,
|
||||
"lon": 150.4172286,
|
||||
"tags": {
|
||||
"name": "CBD",
|
||||
"shop": "chemist"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11092691715,
|
||||
"lat": -34.4782042,
|
||||
"lon": 150.4181805,
|
||||
"tags": {
|
||||
"name": "Claytons Pharmacy",
|
||||
"shop": "chemist"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11092691716,
|
||||
"lat": -34.4782732,
|
||||
"lon": 150.41817,
|
||||
"tags": {
|
||||
"name": "Country Accent",
|
||||
"shop": "houseware"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11092691717,
|
||||
"lat": -34.4783967,
|
||||
"lon": 150.4181235,
|
||||
"tags": {
|
||||
"name": "Micmac Boutique",
|
||||
"shop": "clothes"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11092691718,
|
||||
"lat": -34.4784631,
|
||||
"lon": 150.4181123,
|
||||
"tags": {
|
||||
"name": "Barbara's Storehouse",
|
||||
"shop": "houseware"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11092691719,
|
||||
"lat": -34.4785137,
|
||||
"lon": 150.4181046,
|
||||
"tags": {
|
||||
"name": "Red Cross Shop",
|
||||
"shop": "charity"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11092691721,
|
||||
"lat": -34.4786762,
|
||||
"lon": 150.4180732,
|
||||
"tags": {
|
||||
"name": "Nina Leons Shoe Boutique",
|
||||
"shop": "shoes"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11092691722,
|
||||
"lat": -34.4786953,
|
||||
"lon": 150.4182012,
|
||||
"tags": {
|
||||
"amenity": "cafe",
|
||||
"name": "Janek's Cafe"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11092691723,
|
||||
"lat": -34.4787149,
|
||||
"lon": 150.4183989,
|
||||
"tags": {
|
||||
"amenity": "cafe",
|
||||
"name": "U Jelly Cafe"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11092759409,
|
||||
"lat": -34.4796304,
|
||||
"lon": 150.4183449,
|
||||
"tags": {
|
||||
"name": "Maugers Meats",
|
||||
"shop": "butcher"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11092759411,
|
||||
"lat": -34.4798395,
|
||||
"lon": 150.418641,
|
||||
"tags": {
|
||||
"addr:suburb": "Bowral",
|
||||
"amenity": "cafe",
|
||||
"name": "Oliver Brown"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11092759419,
|
||||
"lat": -34.4796621,
|
||||
"lon": 150.4178675,
|
||||
"tags": {
|
||||
"amenity": "cafe",
|
||||
"name": "Bendooley Estate Larder"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11092759422,
|
||||
"lat": -34.4794614,
|
||||
"lon": 150.4182684,
|
||||
"tags": {
|
||||
"amenity": "cafe",
|
||||
"name": "The Hungry Monkey"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11092759423,
|
||||
"lat": -34.4794174,
|
||||
"lon": 150.4185675,
|
||||
"tags": {
|
||||
"name": "Gypsy Road",
|
||||
"shop": "clothes"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11092759425,
|
||||
"lat": -34.4794085,
|
||||
"lon": 150.4180119,
|
||||
"tags": {
|
||||
"name": "Cheese etc.",
|
||||
"shop": "cheese"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11092759426,
|
||||
"lat": -34.4792776,
|
||||
"lon": 150.4179489,
|
||||
"tags": {
|
||||
"name": "Fella Hamilton",
|
||||
"shop": "clothes"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11092759427,
|
||||
"lat": -34.4791992,
|
||||
"lon": 150.4180708,
|
||||
"tags": {
|
||||
"amenity": "cafe",
|
||||
"name": "4 Brothers Cafe"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11092759428,
|
||||
"lat": -34.4790383,
|
||||
"lon": 150.4180036,
|
||||
"tags": {
|
||||
"name": "Capital Chemist",
|
||||
"shop": "chemist"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11092759429,
|
||||
"lat": -34.478956,
|
||||
"lon": 150.418012,
|
||||
"tags": {
|
||||
"brand": "Peter Alexander",
|
||||
"brand:wikidata": "Q118383240",
|
||||
"name": "Peter Alexander",
|
||||
"opening_hours": "Mo-Fr 09:00-17:30; Sa 09:00-17:00; Su 10:00-17:00",
|
||||
"shop": "clothes"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11092759430,
|
||||
"lat": -34.4798147,
|
||||
"lon": 150.4178444,
|
||||
"tags": {
|
||||
"brand": "Bed Bath N' Table",
|
||||
"brand:wikidata": "Q118551276",
|
||||
"name": "Bed Bath N' Table",
|
||||
"shop": "houseware"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11092759431,
|
||||
"lat": -34.4799401,
|
||||
"lon": 150.4178181,
|
||||
"tags": {
|
||||
"name": "Cookshop Plus",
|
||||
"shop": "houseware"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11092759432,
|
||||
"lat": -34.4801333,
|
||||
"lon": 150.4178137,
|
||||
"tags": {
|
||||
"name": "The Countryman",
|
||||
"shop": "clothes"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11092759436,
|
||||
"lat": -34.4801785,
|
||||
"lon": 150.4181519,
|
||||
"tags": {
|
||||
"name": "The Creative Kid",
|
||||
"shop": "toys"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11092779693,
|
||||
"lat": -34.4788957,
|
||||
"lon": 150.4169921,
|
||||
"tags": {
|
||||
"brand": "Country Road",
|
||||
"brand:wikidata": "Q2998907",
|
||||
"name": "Country Road",
|
||||
"shop": "clothes"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11092779694,
|
||||
"lat": -34.4788636,
|
||||
"lon": 150.4175335,
|
||||
"tags": {
|
||||
"name": "Sonoma Bakery",
|
||||
"shop": "bakery"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11092779696,
|
||||
"lat": -34.4791429,
|
||||
"lon": 150.4176411,
|
||||
"tags": {
|
||||
"name": "GAZMAN Bowral",
|
||||
"shop": "clothes"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11092779697,
|
||||
"lat": -34.4792069,
|
||||
"lon": 150.4176258,
|
||||
"tags": {
|
||||
"brand": "Cotton On",
|
||||
"brand:wikidata": "Q5175717",
|
||||
"name": "Cotton On",
|
||||
"shop": "clothes"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11092779698,
|
||||
"lat": -34.479356,
|
||||
"lon": 150.4175864,
|
||||
"tags": {
|
||||
"name": "The Bookshop Bowral",
|
||||
"shop": "books"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11092779699,
|
||||
"lat": -34.4794283,
|
||||
"lon": 150.4175668,
|
||||
"tags": {
|
||||
"brand": "Newspower",
|
||||
"brand:wikidata": "Q120670137",
|
||||
"name": "Newspower",
|
||||
"shop": "newsagent"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11092799326,
|
||||
"lat": -34.4796067,
|
||||
"lon": 150.4159517,
|
||||
"tags": {
|
||||
"name": "The Modern Furniture Store",
|
||||
"shop": "furniture"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11092829251,
|
||||
"lat": -34.4802472,
|
||||
"lon": 150.4167419,
|
||||
"tags": {
|
||||
"brand": "Bing Lee",
|
||||
"brand:wikidata": "Q4914136",
|
||||
"name": "Bing Lee",
|
||||
"shop": "electronics"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11092829257,
|
||||
"lat": -34.4803341,
|
||||
"lon": 150.4173679,
|
||||
"tags": {
|
||||
"name": "Black Pepper",
|
||||
"shop": "clothes"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11092829258,
|
||||
"lat": -34.4805497,
|
||||
"lon": 150.4173502,
|
||||
"tags": {
|
||||
"name": "Eastern Living For Home & Giving",
|
||||
"shop": "furniture"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11092829259,
|
||||
"lat": -34.4807264,
|
||||
"lon": 150.4173034,
|
||||
"tags": {
|
||||
"brand": "Millers",
|
||||
"brand:wikidata": "Q120644857",
|
||||
"clothes": "women",
|
||||
"name": "Millers",
|
||||
"shop": "clothes"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11092829262,
|
||||
"lat": -34.4808506,
|
||||
"lon": 150.4172832,
|
||||
"tags": {
|
||||
"clothes": "children",
|
||||
"name": "Big Little Ones",
|
||||
"shop": "clothes"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11092829271,
|
||||
"lat": -34.4815025,
|
||||
"lon": 150.41698,
|
||||
"tags": {
|
||||
"clothes": "women",
|
||||
"name": "Sandy Short Style",
|
||||
"shop": "clothes"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11092829272,
|
||||
"lat": -34.4817328,
|
||||
"lon": 150.4165819,
|
||||
"tags": {
|
||||
"amenity": "cafe",
|
||||
"name": "Salters at Dirty Janes"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11092829276,
|
||||
"lat": -34.4803757,
|
||||
"lon": 150.4177143,
|
||||
"tags": {
|
||||
"brand": "Rodd & Gunn",
|
||||
"brand:wikidata": "Q85797658",
|
||||
"clothes": "men",
|
||||
"name": "Rodd & Gunn",
|
||||
"shop": "clothes"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11092829277,
|
||||
"lat": -34.480468,
|
||||
"lon": 150.4176974,
|
||||
"tags": {
|
||||
"clothes": "oversize",
|
||||
"name": "Taking Shape",
|
||||
"shop": "clothes"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11092829278,
|
||||
"lat": -34.4805736,
|
||||
"lon": 150.4176578,
|
||||
"tags": {
|
||||
"clothes": "women",
|
||||
"name": "Nu Mode Boutique",
|
||||
"shop": "clothes"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11092829279,
|
||||
"lat": -34.4806219,
|
||||
"lon": 150.417643,
|
||||
"tags": {
|
||||
"clothes": "oversize",
|
||||
"name": "Swish",
|
||||
"shop": "clothes"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11092829280,
|
||||
"lat": -34.4806825,
|
||||
"lon": 150.4176319,
|
||||
"tags": {
|
||||
"amenity": "cafe",
|
||||
"name": "Allumer Coffee & Eatery"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11092829281,
|
||||
"lat": -34.480781,
|
||||
"lon": 150.4176118,
|
||||
"tags": {
|
||||
"clothes": "men",
|
||||
"name": "Valentino's for Men",
|
||||
"shop": "clothes"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11092829283,
|
||||
"lat": -34.4804466,
|
||||
"lon": 150.4183837,
|
||||
"tags": {
|
||||
"name": "Hem Store",
|
||||
"shop": "clothes"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11092868574,
|
||||
"lat": -34.4822464,
|
||||
"lon": 150.417451,
|
||||
"tags": {
|
||||
"branch": "Op Shop",
|
||||
"brand": "The Salvation Army",
|
||||
"brand:wikidata": "Q188307",
|
||||
"name": "The Salvation Army",
|
||||
"shop": "charity"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11092868575,
|
||||
"lat": -34.4828025,
|
||||
"lon": 150.4176862,
|
||||
"tags": {
|
||||
"name": "RUSH Roasting Co",
|
||||
"shop": "coffee"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11092869107,
|
||||
"lat": -34.4817501,
|
||||
"lon": 150.4170822,
|
||||
"tags": {
|
||||
"amenity": "cafe",
|
||||
"indoor_seating": "yes",
|
||||
"name": "The Press Shop",
|
||||
"outdoor_seating": "yes",
|
||||
"outdoor_seating:comfort": "chair"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11092869109,
|
||||
"lat": -34.4819598,
|
||||
"lon": 150.4170417,
|
||||
"tags": {
|
||||
"name": "Coast to Country Custom Furniture",
|
||||
"shop": "furniture"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11092869129,
|
||||
"lat": -34.4814826,
|
||||
"lon": 150.4150875,
|
||||
"tags": {
|
||||
"name": "Save the Children Op Shop",
|
||||
"shop": "second_hand"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11092869158,
|
||||
"lat": -34.4827728,
|
||||
"lon": 150.414023,
|
||||
"tags": {
|
||||
"name": "Hot Canary Gourmet Meats",
|
||||
"shop": "butcher"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11093033749,
|
||||
"lat": -34.4837853,
|
||||
"lon": 150.4139074,
|
||||
"tags": {
|
||||
"brand": "Harris Market Farms",
|
||||
"brand:wikidata": "Q5664888",
|
||||
"name": "Harris Farm Markets",
|
||||
"shop": "supermarket"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11093045808,
|
||||
"lat": -34.4833228,
|
||||
"lon": 150.4150482,
|
||||
"tags": {
|
||||
"name": "Highlands Oriental Grocer",
|
||||
"shop": "convenience"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11093045810,
|
||||
"lat": -34.4830149,
|
||||
"lon": 150.4153113,
|
||||
"tags": {
|
||||
"name": "The Organic Store",
|
||||
"organic": "only",
|
||||
"shop": "supermarket"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "way",
|
||||
"id": 35832271,
|
||||
"center": {
|
||||
"lat": -34.479945,
|
||||
"lon": 150.4194816
|
||||
},
|
||||
"nodes": [
|
||||
418944976,
|
||||
5680614660,
|
||||
4388496990,
|
||||
4388496984,
|
||||
4388496992,
|
||||
5680614659,
|
||||
5680614658,
|
||||
5680614657,
|
||||
5680614656,
|
||||
418944977,
|
||||
418944978,
|
||||
418944979,
|
||||
5680614661,
|
||||
418944976
|
||||
],
|
||||
"tags": {
|
||||
"addr:housenumber": "33",
|
||||
"addr:street": "Bendooley Street",
|
||||
"brand": "Coles",
|
||||
"brand:wikidata": "Q1108172",
|
||||
"brand:wikipedia": "en:Coles Supermarkets",
|
||||
"building": "yes",
|
||||
"name": "Coles",
|
||||
"shop": "supermarket",
|
||||
"website": "https://www.coles.com.au/",
|
||||
"wheelchair": "yes"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "way",
|
||||
"id": 36203288,
|
||||
"center": {
|
||||
"lat": -34.4821018,
|
||||
"lon": 150.4157839
|
||||
},
|
||||
"nodes": [
|
||||
422632893,
|
||||
422632896,
|
||||
11092869124,
|
||||
422632898,
|
||||
422632900,
|
||||
422632902,
|
||||
4388494825,
|
||||
4388494828,
|
||||
422632906,
|
||||
422632893
|
||||
],
|
||||
"tags": {
|
||||
"addr:housenumber": "9-15",
|
||||
"addr:postcode": "2576",
|
||||
"addr:state": "NSW",
|
||||
"addr:street": "Banyette Street",
|
||||
"addr:suburb": "Bowral",
|
||||
"brand": "ALDI",
|
||||
"brand:wikidata": "Q41171672",
|
||||
"brand:wikipedia": "en:Aldi",
|
||||
"building": "yes",
|
||||
"check_date:opening_hours": "2023-05-05",
|
||||
"full_name": "ALDI Bowral",
|
||||
"name": "ALDI",
|
||||
"opening_hours": "Mo-Su 08:30-19:00",
|
||||
"shop": "supermarket",
|
||||
"website": "https://store.aldi.com.au/nsw/bowral/9-15-banyette-street",
|
||||
"wheelchair": "yes"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "way",
|
||||
"id": 441086078,
|
||||
"center": {
|
||||
"lat": -34.4812682,
|
||||
"lon": 150.4179674
|
||||
},
|
||||
"nodes": [
|
||||
4388494273,
|
||||
5681146624,
|
||||
4388494272,
|
||||
4388494494,
|
||||
5683435029,
|
||||
5683435028,
|
||||
4388494275,
|
||||
4388494509,
|
||||
4388494492,
|
||||
4388494284,
|
||||
4388494502,
|
||||
4388494278,
|
||||
5680614654,
|
||||
5680614655,
|
||||
4388494281,
|
||||
4388494273
|
||||
],
|
||||
"tags": {
|
||||
"addr:housenumber": "380",
|
||||
"addr:postcode": "2576",
|
||||
"addr:state": "NSW",
|
||||
"addr:street": "Bong Bong Street",
|
||||
"addr:suburb": "Bowral",
|
||||
"brand": "Woolworths",
|
||||
"brand:wikidata": "Q3249145",
|
||||
"brand:wikipedia": "en:Woolworths Supermarkets",
|
||||
"building": "retail",
|
||||
"name": "Woolworths",
|
||||
"opening_hours": "Mo-Su 07:00-22:00",
|
||||
"operator": "Woolworths Group",
|
||||
"operator:wikidata": "Q607272",
|
||||
"shop": "supermarket",
|
||||
"source:addr": "LPI Base Map June 2018",
|
||||
"website": "https://www.woolworths.com.au/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "way",
|
||||
"id": 441086128,
|
||||
"center": {
|
||||
"lat": -34.481161,
|
||||
"lon": 150.4155058
|
||||
},
|
||||
"nodes": [
|
||||
4388490741,
|
||||
4388490737,
|
||||
4388490746,
|
||||
4388490739,
|
||||
4388490741
|
||||
],
|
||||
"tags": {
|
||||
"brand": "EG Australia",
|
||||
"brand:wikidata": "Q5023980",
|
||||
"brand:wikipedia": "en:Caltex Woolworths",
|
||||
"building": "yes",
|
||||
"name": "EG Australia",
|
||||
"shop": "convenience"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "way",
|
||||
"id": 596317973,
|
||||
"center": {
|
||||
"lat": -34.4826845,
|
||||
"lon": 150.4174704
|
||||
},
|
||||
"nodes": [
|
||||
5681147074,
|
||||
5681147073,
|
||||
5681147072,
|
||||
5681147071,
|
||||
5681147070,
|
||||
5681147069,
|
||||
5681147068,
|
||||
5681147067,
|
||||
5681147075,
|
||||
5681147074
|
||||
],
|
||||
"tags": {
|
||||
"brand": "Reddy Express",
|
||||
"brand:wikidata": "Q5144653",
|
||||
"building": "yes",
|
||||
"building:levels": "1",
|
||||
"name": "Reddy Express",
|
||||
"shop": "convenience",
|
||||
"source:building": "LPI Imagery June 2018",
|
||||
"website": "www.reddyexpress.com.au"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "way",
|
||||
"id": 596394804,
|
||||
"center": {
|
||||
"lat": -34.4773261,
|
||||
"lon": 150.4184999
|
||||
},
|
||||
"nodes": [
|
||||
5681563452,
|
||||
5681563449,
|
||||
5681563444,
|
||||
5681563448,
|
||||
5681563451,
|
||||
5681563452
|
||||
],
|
||||
"tags": {
|
||||
"addr:housenumber": "246-248",
|
||||
"addr:street": "Bong Bong Street",
|
||||
"brand": "Vinnies",
|
||||
"brand:wikidata": "Q120646672",
|
||||
"building": "yes",
|
||||
"name": "Vinnies",
|
||||
"opening_hours": "Mo-Fr 09:00-15:30; Sa 09:00-12:00",
|
||||
"shop": "charity",
|
||||
"source:addr": "LPI Base Map June 2018",
|
||||
"source:building": "LPI Imagery June 2018"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "way",
|
||||
"id": 596689907,
|
||||
"center": {
|
||||
"lat": -34.4798956,
|
||||
"lon": 150.4169774
|
||||
},
|
||||
"nodes": [
|
||||
5683584059,
|
||||
5683584056,
|
||||
5683584055,
|
||||
5681563688,
|
||||
5683584051,
|
||||
5683584053,
|
||||
11092799316,
|
||||
5683584060,
|
||||
5683584059
|
||||
],
|
||||
"tags": {
|
||||
"addr:postcode": "2576",
|
||||
"addr:state": "NSW",
|
||||
"addr:street": "Boolwey Street",
|
||||
"addr:suburb": "Bowral",
|
||||
"brand": "Kmart",
|
||||
"brand:wikidata": "Q6421682",
|
||||
"brand:wikipedia": "en:Kmart Australia",
|
||||
"building": "yes",
|
||||
"name": "K Hub",
|
||||
"shop": "department_store",
|
||||
"source:geometry": "LPI Base Map June 2018"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
42324
web/assets/towns/_raw/bowral_real.roads.overpass.json
Normal file
1573
web/assets/towns/_raw/braddon_real.overpass.json
Normal file
175845
web/assets/towns/_raw/braddon_real.roads.overpass.json
Normal file
4674
web/assets/towns/_raw/brunswick_real.overpass.json
Normal file
170102
web/assets/towns/_raw/brunswick_real.roads.overpass.json
Normal file
@ -1,7 +1,7 @@
|
||||
{
|
||||
"fetchedAt": "2026-07-16",
|
||||
"endpoint": "https://overpass-api.de/api/interpreter",
|
||||
"query": "[out:json][timeout:120];nwr[\"shop\"~\"^(charity|second_hand|music|antiques|books|toys|games|video_games|pawnbroker)$\"][\"name\"](-37.07751,144.20006,-37.05049,144.23394);out center;",
|
||||
"query": "[out:json][timeout:180];(nwr[\"shop\"~\"^(antiques|bag|bakery|books|bookshop|boutique|butcher|charity|cheese|chemist|chocolate|clothes|coffee|confectionery|convenience|cosmetics|dairy|deli|delicatessen|department_store|doityourself|electronics|farm|fashion|florist|furniture|games|garden_centre|general|greengrocer|hardware|homeware|houseware|jewellery|jewelry|kiosk|mobile_phone|music|newsagent|pastry|pawnbroker|perfumery|pharmacy|second_hand|shoes|stationery|supermarket|tea|toys|trade|variety_store|video|video_games|vintage)$\"][\"name\"](-37.07751,144.20006,-37.05049,144.23394);nwr[\"amenity\"~\"^(cafe)$\"][\"name\"](-37.07751,144.20006,-37.05049,144.23394););out center;",
|
||||
"bbox": {
|
||||
"minLat": -37.077513513513516,
|
||||
"minLon": 144.2000649826232,
|
||||
@ -9,6 +9,132 @@
|
||||
"maxLon": 144.23393501737684
|
||||
},
|
||||
"elements": [
|
||||
{
|
||||
"type": "node",
|
||||
"id": 294087787,
|
||||
"lat": -37.0656116,
|
||||
"lon": 144.2183312,
|
||||
"tags": {
|
||||
"amenity": "cafe",
|
||||
"name": "Pannini"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 2956949396,
|
||||
"lat": -37.0671475,
|
||||
"lon": 144.2178039,
|
||||
"tags": {
|
||||
"addr:housenumber": "54",
|
||||
"addr:street": "Forest Street",
|
||||
"name": "Forest St Discount Traders",
|
||||
"shop": "furniture"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 4168483728,
|
||||
"lat": -37.0562458,
|
||||
"lon": 144.2136412,
|
||||
"tags": {
|
||||
"addr:housenumber": "9",
|
||||
"addr:postcode": "3450",
|
||||
"addr:state": "VIC",
|
||||
"addr:street": "Walker Street",
|
||||
"addr:suburb": "Castlemaine",
|
||||
"amenity": "cafe",
|
||||
"name": "Das Kaffeehaus",
|
||||
"opening_hours": "Mo-Su 08:00-16:00",
|
||||
"outdoor_seating": "yes",
|
||||
"phone": "+61 3 5470 6270",
|
||||
"takeaway": "yes",
|
||||
"toilets": "yes",
|
||||
"website": "https://coffeebasics.com/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 4714218689,
|
||||
"lat": -37.0648632,
|
||||
"lon": 144.2171511,
|
||||
"tags": {
|
||||
"addr:housenumber": "184",
|
||||
"addr:street": "Barker Street",
|
||||
"name": "Hot & Crusty Bakery",
|
||||
"name:en": "Hot & Crusty Bakery",
|
||||
"opening_hours": "Mo-Fr 07:00-17:30; Sa-Su 07:00-15:00",
|
||||
"shop": "bakery"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 5883185477,
|
||||
"lat": -37.0653774,
|
||||
"lon": 144.218071,
|
||||
"tags": {
|
||||
"addr:housenumber": "61",
|
||||
"addr:postcode": "3450",
|
||||
"addr:state": "VIC",
|
||||
"addr:street": "Mostyn Street",
|
||||
"addr:suburb": "Castlemaine",
|
||||
"name": "Chemmart",
|
||||
"shop": "chemist"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 8909393345,
|
||||
"lat": -37.0562934,
|
||||
"lon": 144.2142302,
|
||||
"tags": {
|
||||
"contact:facebook": "https://www.facebook.com/harvestfruitandveg/",
|
||||
"name": "Harvest",
|
||||
"opening_hours": "Th 10:00-17:00; Fr 09:00-17:00; Sa 09:00-13:00; Su-We closed",
|
||||
"phone": "+61 438 606 777",
|
||||
"shop": "greengrocer",
|
||||
"website": "https://castlemaineharvest.com.au/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 8909393346,
|
||||
"lat": -37.0560724,
|
||||
"lon": 144.2136998,
|
||||
"tags": {
|
||||
"addr:housenumber": "9",
|
||||
"addr:street": "Walker Street",
|
||||
"contact:facebook": "https://www.facebook.com/longpaddockcheese/",
|
||||
"name": "Long Paddock Cheese",
|
||||
"phone": "+61 3 5472 3142",
|
||||
"shop": "cheese",
|
||||
"website": "https://longpaddockcheese.com.au/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 8909393347,
|
||||
"lat": -37.0562105,
|
||||
"lon": 144.213966,
|
||||
"tags": {
|
||||
"addr:housenumber": "9",
|
||||
"addr:postcode": "3450",
|
||||
"addr:street": "Walker Street",
|
||||
"contact:instagram": "https://www.instagram.com/sproutbakerycastlemaine/",
|
||||
"name": "Sprout Bakery",
|
||||
"shop": "bakery",
|
||||
"website": "https://millcastlemaine.com.au/portfolio/sprout-bakery/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 9125031531,
|
||||
"lat": -37.0564952,
|
||||
"lon": 144.2185694,
|
||||
"tags": {
|
||||
"amenity": "cafe",
|
||||
"name": "Maine Foodstore"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 9231704552,
|
||||
@ -24,6 +150,72 @@
|
||||
"website": "https://www.vinnies.org.au/shops/view/360"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 9231729701,
|
||||
"lat": -37.0629924,
|
||||
"lon": 144.2164624,
|
||||
"tags": {
|
||||
"addr:housenumber": "31",
|
||||
"addr:street": "Templeton Street",
|
||||
"amenity": "cafe",
|
||||
"name": "Fig Cafe",
|
||||
"phone": "+61 3 5472 5311",
|
||||
"website": "http://figcastlemaine.com.au/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 10050223909,
|
||||
"lat": -37.067116,
|
||||
"lon": 144.2174714,
|
||||
"tags": {
|
||||
"addr:housenumber": "46",
|
||||
"addr:postcode": "3450",
|
||||
"addr:state": "VIC",
|
||||
"addr:street": "Forest Street",
|
||||
"addr:suburb": "Castlemaine",
|
||||
"name": "Johnny Baker Downtown",
|
||||
"opening_hours": "Mo-Su 07:00-15:00",
|
||||
"phone": "+61 3 5470 5695",
|
||||
"shop": "bakery",
|
||||
"website": "https://johnnybaker.com.au/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11699200334,
|
||||
"lat": -37.063504,
|
||||
"lon": 144.2179756,
|
||||
"tags": {
|
||||
"amenity": "cafe",
|
||||
"name": "Saint Florian"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11699200335,
|
||||
"lat": -37.0633677,
|
||||
"lon": 144.2169181,
|
||||
"tags": {
|
||||
"amenity": "cafe",
|
||||
"cuisine": "coffee_shop",
|
||||
"name": "Nira",
|
||||
"opening_hours": "We-Sa 08:00-14:00",
|
||||
"outdoor_seating": "yes",
|
||||
"website": "https://www.instagram.com/nira.castlemaine/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11699200336,
|
||||
"lat": -37.0629803,
|
||||
"lon": 144.2163297,
|
||||
"tags": {
|
||||
"name": "Green Goes The Grocer",
|
||||
"shop": "greengrocer"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11699200337,
|
||||
@ -34,6 +226,87 @@
|
||||
"shop": "antiques"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11699200341,
|
||||
"lat": -37.0632737,
|
||||
"lon": 144.2158686,
|
||||
"tags": {
|
||||
"name": "The Tea Dispensary",
|
||||
"shop": "tea",
|
||||
"website": "https://theteadispensarycastlemaine.com.au"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11699205335,
|
||||
"lat": -37.0637846,
|
||||
"lon": 144.216911,
|
||||
"tags": {
|
||||
"amenity": "cafe",
|
||||
"name": "Origini"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11699205343,
|
||||
"lat": -37.0637329,
|
||||
"lon": 144.2169387,
|
||||
"tags": {
|
||||
"name": "Warm Vintage Clothes",
|
||||
"shop": "clothes"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11699205344,
|
||||
"lat": -37.0639543,
|
||||
"lon": 144.216863,
|
||||
"tags": {
|
||||
"name": "Beleza School Uniforms",
|
||||
"shop": "clothes"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11699208635,
|
||||
"lat": -37.0555701,
|
||||
"lon": 144.2189103,
|
||||
"tags": {
|
||||
"name": "Just Because She Said So",
|
||||
"shop": "shoes"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11699208636,
|
||||
"lat": -37.0563736,
|
||||
"lon": 144.2185894,
|
||||
"tags": {
|
||||
"name": "Zala Floristry",
|
||||
"shop": "florist"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11699208637,
|
||||
"lat": -37.0551631,
|
||||
"lon": 144.2181645,
|
||||
"tags": {
|
||||
"name": "Johnny Baker Uptown",
|
||||
"shop": "bakery"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11699208640,
|
||||
"lat": -37.0576933,
|
||||
"lon": 144.2179368,
|
||||
"tags": {
|
||||
"name": "Redgum & Quartz",
|
||||
"shop": "jewelry"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11699208641,
|
||||
@ -44,6 +317,107 @@
|
||||
"shop": "books"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11699208654,
|
||||
"lat": -37.0575593,
|
||||
"lon": 144.2179531,
|
||||
"tags": {
|
||||
"amenity": "cafe",
|
||||
"name": "Blackbird",
|
||||
"opening_hours": "We-Su 07:00-15:00"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11699209595,
|
||||
"lat": -37.0647361,
|
||||
"lon": 144.2178397,
|
||||
"tags": {
|
||||
"name": "Goldfields Games",
|
||||
"shop": "stationery"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11699209596,
|
||||
"lat": -37.0648223,
|
||||
"lon": 144.2186911,
|
||||
"tags": {
|
||||
"amenity": "cafe",
|
||||
"name": "Togs Cafe"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11699209597,
|
||||
"lat": -37.0647399,
|
||||
"lon": 144.2178817,
|
||||
"tags": {
|
||||
"name": "Castlemaine Office Supplies - COS",
|
||||
"shop": "stationery"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11699209601,
|
||||
"lat": -37.0648245,
|
||||
"lon": 144.2187713,
|
||||
"tags": {
|
||||
"name": "mulberrys",
|
||||
"shop": "deli"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11699225335,
|
||||
"lat": -37.0641272,
|
||||
"lon": 144.2168128,
|
||||
"tags": {
|
||||
"name": "Mcshanag\u2019s",
|
||||
"shop": "bakery"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11699228873,
|
||||
"lat": -37.0655255,
|
||||
"lon": 144.2172776,
|
||||
"tags": {
|
||||
"name": "Aolita",
|
||||
"shop": "clothes"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11699228875,
|
||||
"lat": -37.0655044,
|
||||
"lon": 144.2172182,
|
||||
"tags": {
|
||||
"name": "Ivy & Grace",
|
||||
"shop": "clothes"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11699230554,
|
||||
"lat": -37.0656365,
|
||||
"lon": 144.2184531,
|
||||
"tags": {
|
||||
"name": "Castlemaine Fresh",
|
||||
"shop": "greengrocer"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11699233860,
|
||||
"lat": -37.0609871,
|
||||
"lon": 144.2200073,
|
||||
"tags": {
|
||||
"amenity": "cafe",
|
||||
"name": "The Dove Country Eats"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11699237531,
|
||||
@ -54,6 +428,187 @@
|
||||
"shop": "antiques"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11699237532,
|
||||
"lat": -37.0730134,
|
||||
"lon": 144.2070605,
|
||||
"tags": {
|
||||
"name": "Cliffords Quality Cuts",
|
||||
"shop": "butcher"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11699271590,
|
||||
"lat": -37.0515551,
|
||||
"lon": 144.2213327,
|
||||
"tags": {
|
||||
"amenity": "cafe",
|
||||
"name": "Firebean Coffee"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11699277526,
|
||||
"lat": -37.0562996,
|
||||
"lon": 144.2139999,
|
||||
"tags": {
|
||||
"name": "Oakwood Smallgoods Co.",
|
||||
"shop": "deli"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11699277527,
|
||||
"lat": -37.0560376,
|
||||
"lon": 144.2137053,
|
||||
"tags": {
|
||||
"addr:housenumber": "9",
|
||||
"addr:street": "Walker Street",
|
||||
"name": "Cabosse & Feve Chocolates",
|
||||
"shop": "chocolate",
|
||||
"website": "https://www.cabosseandfeve.com.au/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11715203318,
|
||||
"lat": -37.0697474,
|
||||
"lon": 144.2133853,
|
||||
"tags": {
|
||||
"clothes": "workwear",
|
||||
"name": "Hip Pocket",
|
||||
"shop": "clothes"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11735362964,
|
||||
"lat": -37.0657,
|
||||
"lon": 144.2189529,
|
||||
"tags": {
|
||||
"name": "Legion Office Works",
|
||||
"shop": "stationery"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11735362965,
|
||||
"lat": -37.06572,
|
||||
"lon": 144.2191432,
|
||||
"tags": {
|
||||
"name": "JML Jewellers",
|
||||
"shop": "jewelry"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11735362966,
|
||||
"lat": -37.0656918,
|
||||
"lon": 144.2189044,
|
||||
"tags": {
|
||||
"addr:housenumber": "87",
|
||||
"addr:street": "Mostyn Street",
|
||||
"name": "Beehave",
|
||||
"shop": "shoes"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11735362967,
|
||||
"lat": -37.0656219,
|
||||
"lon": 144.218376,
|
||||
"tags": {
|
||||
"name": "D\u00e9sa",
|
||||
"shop": "shoes"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11735383822,
|
||||
"lat": -37.0669274,
|
||||
"lon": 144.2194209,
|
||||
"tags": {
|
||||
"addr:housenumber": "12",
|
||||
"addr:street": "Hargraves Street",
|
||||
"name": "Mutual Muse",
|
||||
"shop": "clothes",
|
||||
"website": "https://www.mutualmuse.net/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11735383824,
|
||||
"lat": -37.0661069,
|
||||
"lon": 144.2196811,
|
||||
"tags": {
|
||||
"name": "RIKE Design",
|
||||
"shop": "clothes"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11735383825,
|
||||
"lat": -37.0667708,
|
||||
"lon": 144.2194268,
|
||||
"tags": {
|
||||
"name": "Little General Kids",
|
||||
"shop": "clothes"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11735383826,
|
||||
"lat": -37.0661345,
|
||||
"lon": 144.2199456,
|
||||
"tags": {
|
||||
"name": "Shibby Store",
|
||||
"shop": "clothes"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11735383827,
|
||||
"lat": -37.0668429,
|
||||
"lon": 144.2194282,
|
||||
"tags": {
|
||||
"addr:housenumber": "14",
|
||||
"addr:street": "Hargraves Street",
|
||||
"amenity": "cafe",
|
||||
"name": "Tortoise Espresso"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11735383834,
|
||||
"lat": -37.0661462,
|
||||
"lon": 144.2200522,
|
||||
"tags": {
|
||||
"name": "Miss Maisie",
|
||||
"shop": "clothes"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11735383837,
|
||||
"lat": -37.0666799,
|
||||
"lon": 144.2194393,
|
||||
"tags": {
|
||||
"name": "Oni Street Wear",
|
||||
"shop": "clothes"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11735383839,
|
||||
"lat": -37.0661674,
|
||||
"lon": 144.2202264,
|
||||
"tags": {
|
||||
"amenity": "cafe",
|
||||
"name": "SAFFS Cafe"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11760538035,
|
||||
@ -72,6 +627,138 @@
|
||||
"website": "https://www.salvosstores.com.au/stores/vic/3450-castlemaine-5317"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11934078464,
|
||||
"lat": -37.062982,
|
||||
"lon": 144.2164808,
|
||||
"tags": {
|
||||
"amenity": "cafe",
|
||||
"name": "Lazy Bones"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 12542955304,
|
||||
"lat": -37.0662172,
|
||||
"lon": 144.218827,
|
||||
"tags": {
|
||||
"addr:housenumber": "50",
|
||||
"addr:postcode": "3450",
|
||||
"addr:street": "Mostyn Street",
|
||||
"addr:unit": "5",
|
||||
"branch": "Castlemaine",
|
||||
"brand": "Bakers Delight",
|
||||
"brand:wikidata": "Q4849261",
|
||||
"name": "Bakers Delight",
|
||||
"opening_hours": "Mo-Su 07:30-20:00",
|
||||
"phone": "+61 3 5470 6073",
|
||||
"shop": "bakery",
|
||||
"website": "http://Bakersdelight.com.au"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 12740901493,
|
||||
"lat": -37.0566616,
|
||||
"lon": 144.2146204,
|
||||
"tags": {
|
||||
"amenity": "cafe",
|
||||
"contact:instagram": "https://www.instagram.com/frankieatthemill/",
|
||||
"name": "Frankie Espresso",
|
||||
"note": "Cafe is in a container in front of the building",
|
||||
"takeaway": "only"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 12740975418,
|
||||
"lat": -37.0568574,
|
||||
"lon": 144.2141443,
|
||||
"tags": {
|
||||
"name": "Hand Picked Floristry",
|
||||
"note": "Unit 78",
|
||||
"shop": "florist",
|
||||
"website": "https://www.handpickedfloristry.com/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 13954997201,
|
||||
"lat": -37.0702085,
|
||||
"lon": 144.2115086,
|
||||
"tags": {
|
||||
"amenity": "cafe",
|
||||
"check_date": "2026-06-21",
|
||||
"name": "Aitken\u2018s Corner"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "way",
|
||||
"id": 26808662,
|
||||
"center": {
|
||||
"lat": -37.0664479,
|
||||
"lon": 144.2186219
|
||||
},
|
||||
"nodes": [
|
||||
294089047,
|
||||
8768419613,
|
||||
8768419614,
|
||||
8768419615,
|
||||
6665030737,
|
||||
1347583493,
|
||||
294089048,
|
||||
1347583492,
|
||||
1347583494,
|
||||
1347583495,
|
||||
294089049,
|
||||
294089050,
|
||||
8768419611,
|
||||
8768419612,
|
||||
7836396117,
|
||||
294089047
|
||||
],
|
||||
"tags": {
|
||||
"addr:postcode": "3450",
|
||||
"addr:state": "VIC",
|
||||
"addr:suburb": "Castlemaine",
|
||||
"branch": "Castlemaine",
|
||||
"brand": "IGA",
|
||||
"brand:wikidata": "Q5970945",
|
||||
"building": "yes",
|
||||
"name": "Maxi IGA",
|
||||
"opening_hours": "Mo-Su 07:00-21:00",
|
||||
"operator": "IGA",
|
||||
"phone": "+61 3 5472 2477",
|
||||
"shop": "supermarket",
|
||||
"website": "https://www.iga.com.au/",
|
||||
"wheelchair": "yes"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "way",
|
||||
"id": 41889132,
|
||||
"center": {
|
||||
"lat": -37.0629808,
|
||||
"lon": 144.2169327
|
||||
},
|
||||
"nodes": [
|
||||
518274906,
|
||||
518274907,
|
||||
2803274456,
|
||||
518274909,
|
||||
518274910,
|
||||
518274906
|
||||
],
|
||||
"tags": {
|
||||
"brand": "IGA",
|
||||
"brand:wikidata": "Q5970945",
|
||||
"brand:wikipedia": "en:IGA (Australian supermarket group)",
|
||||
"building": "yes",
|
||||
"name": "IGA",
|
||||
"shop": "supermarket"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "way",
|
||||
"id": 315282080,
|
||||
@ -94,6 +781,136 @@
|
||||
"shop": "books",
|
||||
"wheelchair": "limited"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "way",
|
||||
"id": 315282081,
|
||||
"center": {
|
||||
"lat": -37.0657523,
|
||||
"lon": 144.2198063
|
||||
},
|
||||
"nodes": [
|
||||
3213928216,
|
||||
3213928217,
|
||||
3213928218,
|
||||
3213928213,
|
||||
3213928216
|
||||
],
|
||||
"tags": {
|
||||
"amenity": "cafe",
|
||||
"building": "yes",
|
||||
"name": "Gaia's Goodness",
|
||||
"wheelchair": "yes"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "way",
|
||||
"id": 315282082,
|
||||
"center": {
|
||||
"lat": -37.0657268,
|
||||
"lon": 144.2199002
|
||||
},
|
||||
"nodes": [
|
||||
3213928218,
|
||||
3213928219,
|
||||
3213928220,
|
||||
3213928221,
|
||||
3213928217,
|
||||
3213928218
|
||||
],
|
||||
"tags": {
|
||||
"building": "yes",
|
||||
"name": "Castlemaine Floristry",
|
||||
"shop": "florist",
|
||||
"wheelchair": "yes"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "way",
|
||||
"id": 446991894,
|
||||
"center": {
|
||||
"lat": -37.0627807,
|
||||
"lon": 144.2176913
|
||||
},
|
||||
"nodes": [
|
||||
4441953702,
|
||||
3213937049,
|
||||
3213937050,
|
||||
518274901,
|
||||
3213937051,
|
||||
3213937052,
|
||||
4441953702
|
||||
],
|
||||
"tags": {
|
||||
"brand": "BP Shop",
|
||||
"brand:wikidata": "Q152057",
|
||||
"brand:wikipedia": "en:BP",
|
||||
"building": "yes",
|
||||
"name": "BP Shop",
|
||||
"shop": "convenience"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "way",
|
||||
"id": 834631308,
|
||||
"center": {
|
||||
"lat": -37.0653266,
|
||||
"lon": 144.2176748
|
||||
},
|
||||
"nodes": [
|
||||
7791187709,
|
||||
7791187710,
|
||||
7791187711,
|
||||
7791187712,
|
||||
7791187713,
|
||||
7791187714,
|
||||
7791187741,
|
||||
7791187715,
|
||||
7791187709
|
||||
],
|
||||
"tags": {
|
||||
"brand": "Kmart",
|
||||
"brand:wikidata": "Q6421682",
|
||||
"brand:wikipedia": "en:Kmart Australia",
|
||||
"building": "yes",
|
||||
"name": "K Hub",
|
||||
"shop": "department_store"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "way",
|
||||
"id": 1387137327,
|
||||
"center": {
|
||||
"lat": -37.0678903,
|
||||
"lon": 144.2223679
|
||||
},
|
||||
"nodes": [
|
||||
12841247956,
|
||||
13639673523,
|
||||
13639673522,
|
||||
12841247957,
|
||||
12841247958,
|
||||
12841247959,
|
||||
13639673521,
|
||||
12841247960,
|
||||
12841247961,
|
||||
12841247962,
|
||||
13639673520,
|
||||
13639673517,
|
||||
13639673518,
|
||||
13639673519,
|
||||
12841247963,
|
||||
12841247956
|
||||
],
|
||||
"tags": {
|
||||
"brand": "Woolworths",
|
||||
"brand:wikidata": "Q3249145",
|
||||
"building": "yes",
|
||||
"name": "Woolworths",
|
||||
"operator": "Woolworths Group",
|
||||
"operator:wikidata": "Q607272",
|
||||
"shop": "supermarket"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
623
web/assets/towns/_raw/darwin_real.overpass.json
Normal file
@ -0,0 +1,623 @@
|
||||
{
|
||||
"fetchedAt": "2026-07-16",
|
||||
"endpoint": "https://overpass-api.de/api/interpreter",
|
||||
"query": "[out:json][timeout:180];(nwr[\"shop\"~\"^(antiques|bag|bakery|books|bookshop|boutique|butcher|charity|cheese|chemist|chocolate|clothes|coffee|confectionery|convenience|cosmetics|dairy|deli|delicatessen|department_store|doityourself|electronics|farm|fashion|florist|furniture|games|garden_centre|general|greengrocer|hardware|homeware|houseware|jewellery|jewelry|kiosk|mobile_phone|music|newsagent|pastry|pawnbroker|perfumery|pharmacy|second_hand|shoes|stationery|supermarket|tea|toys|trade|variety_store|video|video_games|vintage)$\"][\"name\"](-12.47471,130.83001,-12.45129,130.85399);nwr[\"amenity\"~\"^(cafe)$\"][\"name\"](-12.47471,130.83001,-12.45129,130.85399););out center;",
|
||||
"bbox": {
|
||||
"minLat": -12.474711711711711,
|
||||
"minLon": 130.83000564827645,
|
||||
"maxLat": -12.451288288288287,
|
||||
"maxLon": 130.85399435172357
|
||||
},
|
||||
"elements": [
|
||||
{
|
||||
"type": "node",
|
||||
"id": 1768235556,
|
||||
"lat": -12.4641113,
|
||||
"lon": 130.8429234,
|
||||
"tags": {
|
||||
"check_date:opening_hours": "2025-10-18",
|
||||
"name": "Life n Style Darwin",
|
||||
"opening_hours:signed": "no",
|
||||
"shop": "houseware",
|
||||
"wheelchair": "yes"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 2478924751,
|
||||
"lat": -12.4637037,
|
||||
"lon": 130.8395775,
|
||||
"tags": {
|
||||
"brand": "Coles",
|
||||
"brand:wikidata": "Q1108172",
|
||||
"brand:wikipedia": "en:Coles Supermarkets",
|
||||
"name": "Coles",
|
||||
"operator": "Coles Group",
|
||||
"operator:wikidata": "Q131937428",
|
||||
"shop": "supermarket",
|
||||
"website": "https://www.coles.com.au/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 3230870934,
|
||||
"lat": -12.4633733,
|
||||
"lon": 130.8399384,
|
||||
"tags": {
|
||||
"addr:country": "AU",
|
||||
"addr:state": "NT",
|
||||
"amenity": "cafe",
|
||||
"brand": "The Coffee Club",
|
||||
"brand:wikidata": "Q7726599",
|
||||
"brand:wikipedia": "en:The Coffee Club",
|
||||
"cuisine": "coffee_shop",
|
||||
"name": "The Coffee Club",
|
||||
"takeaway": "yes",
|
||||
"website": "https://www.coffeeclub.com.au/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 3230979991,
|
||||
"lat": -12.4633514,
|
||||
"lon": 130.8421418,
|
||||
"tags": {
|
||||
"name": "Just Looking",
|
||||
"opening_hours": "Mo-Fr 09:30-17:00; Sa 09:00-15:00; Su 10:30-14:00",
|
||||
"shop": "clothes",
|
||||
"wheelchair": "yes"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 3230979994,
|
||||
"lat": -12.4639637,
|
||||
"lon": 130.84276,
|
||||
"tags": {
|
||||
"check_date:opening_hours": "2025-10-18",
|
||||
"name": "Hoskings Jewellers",
|
||||
"opening_hours:signed": "no",
|
||||
"phone": "+61 8 8981 8895",
|
||||
"shop": "jewelry",
|
||||
"website": "https://hoskings.com.au/",
|
||||
"wheelchair": "yes"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 3230979997,
|
||||
"lat": -12.463738,
|
||||
"lon": 130.8422598,
|
||||
"tags": {
|
||||
"brand": "Just Jeans",
|
||||
"brand:wikidata": "Q6316068",
|
||||
"brand:wikipedia": "en:Just Group",
|
||||
"check_date:opening_hours": "2025-10-18",
|
||||
"clothes": "denim",
|
||||
"name": "Just Jeans",
|
||||
"opening_hours:signed": "no",
|
||||
"shop": "clothes",
|
||||
"wheelchair": "yes"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 3230980001,
|
||||
"lat": -12.4646064,
|
||||
"lon": 130.8431144,
|
||||
"tags": {
|
||||
"addr:housenumber": "19",
|
||||
"addr:street": "Smith Street Mall",
|
||||
"addr:unit": "SHOP M13/S1",
|
||||
"brand": "R.M.Williams",
|
||||
"brand:wikidata": "Q65042966",
|
||||
"name": "R.M.Williams",
|
||||
"opening_hours": "Mo-Fr 09:00-17:00; Sa 09:00-16:00; Su 10:00-15:00",
|
||||
"shop": "shoes"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 3230980007,
|
||||
"lat": -12.4634366,
|
||||
"lon": 130.8422329,
|
||||
"tags": {
|
||||
"brand": "Cotton On",
|
||||
"brand:wikidata": "Q5175717",
|
||||
"brand:wikipedia": "en:Cotton On Group",
|
||||
"check_date:opening_hours": "2025-10-18",
|
||||
"name": "Cotton On",
|
||||
"opening_hours:signed": "no",
|
||||
"shop": "clothes",
|
||||
"wheelchair": "yes"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 3230980010,
|
||||
"lat": -12.4637346,
|
||||
"lon": 130.8425718,
|
||||
"tags": {
|
||||
"name": "Off Ya Tree",
|
||||
"opening_hours": "Mo-Fr 09:00-18:00; Sa 09:00-17:00; Su 10:00-15:00",
|
||||
"shop": "clothes",
|
||||
"wheelchair": "yes"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 3231017952,
|
||||
"lat": -12.4642257,
|
||||
"lon": 130.8430444,
|
||||
"tags": {
|
||||
"check_date:opening_hours": "2025-10-18",
|
||||
"name": "Mr Convenient",
|
||||
"opening_hours:signed": "no",
|
||||
"shop": "convenience",
|
||||
"wheelchair": "no"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 3231017957,
|
||||
"lat": -12.4643515,
|
||||
"lon": 130.8428874,
|
||||
"tags": {
|
||||
"name": "Infidelity",
|
||||
"shop": "clothes"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 3231022466,
|
||||
"lat": -12.4642601,
|
||||
"lon": 130.8430709,
|
||||
"tags": {
|
||||
"name": "The Bookshop",
|
||||
"opening_hours": "Mo-Fr 09:00-17:00; Sa 09:00-15:00; Su 10:00-14:00",
|
||||
"phone": "+61 8 8941 3489",
|
||||
"shop": "books",
|
||||
"website": "https://www.bookshopdarwin.com.au/",
|
||||
"wheelchair": "yes"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 3759068231,
|
||||
"lat": -12.4605581,
|
||||
"lon": 130.8405146,
|
||||
"tags": {
|
||||
"brand": "Woolworths",
|
||||
"brand:wikidata": "Q3249145",
|
||||
"brand:wikipedia": "en:Woolworths Supermarkets",
|
||||
"check_date:opening_hours": "2025-06-18",
|
||||
"name": "Woolworths",
|
||||
"opening_hours": "06:00-22:00",
|
||||
"operator": "Woolworths Group",
|
||||
"operator:wikidata": "Q607272",
|
||||
"shop": "supermarket",
|
||||
"source": "company website",
|
||||
"wheelchair": "yes"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 3796152681,
|
||||
"lat": -12.4636413,
|
||||
"lon": 130.8402074,
|
||||
"tags": {
|
||||
"amenity": "cafe",
|
||||
"brand": "Boost Juice",
|
||||
"brand:wikidata": "Q4943789",
|
||||
"cuisine": "juice",
|
||||
"name": "Boost Juice",
|
||||
"phone": "+61 8 8981 5555",
|
||||
"source": "http://mitchellcentre.com.au/centre-directory.html",
|
||||
"takeaway": "yes",
|
||||
"website": "https://www.boostjuice.com.au/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 3796152682,
|
||||
"lat": -12.4637489,
|
||||
"lon": 130.8398288,
|
||||
"tags": {
|
||||
"name": "Bright Eyes",
|
||||
"shop": "clothes",
|
||||
"source": "http://mitchellcentre.com.au/centre-directory.html",
|
||||
"website": "https://www.brighteyes.com.au/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 3796152687,
|
||||
"lat": -12.4640134,
|
||||
"lon": 130.8401154,
|
||||
"tags": {
|
||||
"brand": "Optus",
|
||||
"brand:wikidata": "Q865038",
|
||||
"brand:wikipedia": "en:Optus",
|
||||
"name": "Optus",
|
||||
"shop": "mobile_phone",
|
||||
"source": "http://mitchellcentre.com.au/centre-directory.html"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 3796152690,
|
||||
"lat": -12.4638132,
|
||||
"lon": 130.8403882,
|
||||
"tags": {
|
||||
"brand": "Telstra",
|
||||
"brand:wikidata": "Q721162",
|
||||
"brand:wikipedia": "en:Telstra",
|
||||
"name": "Telstra",
|
||||
"shop": "mobile_phone",
|
||||
"source": "http://mitchellcentre.com.au/centre-directory.html"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 3796202328,
|
||||
"lat": -12.4637271,
|
||||
"lon": 130.8401991,
|
||||
"tags": {
|
||||
"contact:facebook": "https://www.facebook.com/attitudeX/",
|
||||
"name": "Attitude X",
|
||||
"shop": "clothes",
|
||||
"source": "http://mitchellcentre.com.au/centre-directory.html"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 3796202355,
|
||||
"lat": -12.4637649,
|
||||
"lon": 130.8402521,
|
||||
"tags": {
|
||||
"brand": "Strandbags",
|
||||
"brand:wikidata": "Q111946652",
|
||||
"name": "Strandbags",
|
||||
"shop": "bag",
|
||||
"source": "http://mitchellcentre.com.au/centre-directory.html",
|
||||
"website": "https://www.strandbags.com.au/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 4223293611,
|
||||
"lat": -12.461599,
|
||||
"lon": 130.8419672,
|
||||
"tags": {
|
||||
"addr:housenumber": "35",
|
||||
"addr:street": "Cavenagh Street",
|
||||
"addr:unit": "1",
|
||||
"amenity": "cafe",
|
||||
"cuisine": "bakery;cake;coffee;meat_pies",
|
||||
"name": "Kurt's Cakes",
|
||||
"phone": "+61 8 8941 5033"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 4959162299,
|
||||
"lat": -12.4574764,
|
||||
"lon": 130.8355439,
|
||||
"tags": {
|
||||
"addr:housenumber": "109",
|
||||
"addr:street": "Smith Street",
|
||||
"amenity": "cafe",
|
||||
"name": "Cafe 21 Smith",
|
||||
"opening_hours": "Mo-Fr 07:00-16:00; Sa,Su 07:00-13:00",
|
||||
"phone": "+61 8 7917 8703",
|
||||
"website": "https://www.cafe21.com.au/index.php?route=information/information&information_id=8"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 4959816482,
|
||||
"lat": -12.4567297,
|
||||
"lon": 130.8359701,
|
||||
"tags": {
|
||||
"addr:housenumber": "2",
|
||||
"addr:postcode": "0800",
|
||||
"addr:state": "NT",
|
||||
"addr:street": "Harriet Place",
|
||||
"addr:unit": "1",
|
||||
"amenity": "cafe",
|
||||
"cuisine": "coffee_shop",
|
||||
"name": "Kopi Stop",
|
||||
"phone": "+61 8 8941 2414"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 5555831147,
|
||||
"lat": -12.4638312,
|
||||
"lon": 130.8432441,
|
||||
"tags": {
|
||||
"addr:housenumber": "32",
|
||||
"addr:street": "Smith Street Mall",
|
||||
"addr:unit": "8A Star Village",
|
||||
"name": "CoinBear Collectables",
|
||||
"shop": "antiques"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 5623298221,
|
||||
"lat": -12.4650961,
|
||||
"lon": 130.8428893,
|
||||
"tags": {
|
||||
"addr:housenumber": "Shop 4-5",
|
||||
"addr:street": "Smith Street Mall",
|
||||
"amenity": "cafe",
|
||||
"description": "A 100% not-for-profit organisation committed to selling ethically sourced and sustainable products that directly support local Indigenous communities.",
|
||||
"name": "Aboriginal Bush Traders",
|
||||
"name:en": "Aboriginal Bush Traders",
|
||||
"outdoor_seating": "yes",
|
||||
"phone": "+61 8 8931 6650",
|
||||
"shop": "art",
|
||||
"website": "https://aboriginalbushtraders.com/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 5742355123,
|
||||
"lat": -12.4670209,
|
||||
"lon": 130.846334,
|
||||
"tags": {
|
||||
"addr:country": "AU",
|
||||
"addr:state": "NT",
|
||||
"amenity": "cafe",
|
||||
"brand": "The Coffee Club",
|
||||
"brand:wikidata": "Q7726599",
|
||||
"brand:wikipedia": "en:The Coffee Club",
|
||||
"cuisine": "coffee_shop",
|
||||
"internet_access": "wlan",
|
||||
"level": "0",
|
||||
"name": "The Coffee Club",
|
||||
"takeaway": "yes",
|
||||
"website": "https://www.coffeeclub.com.au/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 5742771761,
|
||||
"lat": -12.4643302,
|
||||
"lon": 130.8431412,
|
||||
"tags": {
|
||||
"addr:housenumber": "28",
|
||||
"addr:street": "Smith Street Mall",
|
||||
"air_conditioning": "yes",
|
||||
"amenity": "cafe",
|
||||
"check_date:opening_hours": "2025-10-18",
|
||||
"cuisine": "coffee;breakfast;waffle;lunch;sandwich",
|
||||
"name": "Caf\u00e9 Twenty One",
|
||||
"opening_hours:signed": "no",
|
||||
"phone": "+61 8 7917 8703",
|
||||
"smoking": "no",
|
||||
"website": "https://www.cafe21.com.au/",
|
||||
"wheelchair": "yes"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 5744907175,
|
||||
"lat": -12.4621411,
|
||||
"lon": 130.8423045,
|
||||
"tags": {
|
||||
"addr:housenumber": "37",
|
||||
"addr:street": "Knuckey Street",
|
||||
"air_conditioning": "yes",
|
||||
"amenity": "cafe",
|
||||
"changing_table": "yes",
|
||||
"email": "gertrude@moorishcafe.com.au",
|
||||
"indoor_seating": "yes",
|
||||
"name": "Moorish Cafe",
|
||||
"opening_hours": "Tu-Fr 11:00-14:30,18:00-22:00; Sa 11:00-22:00",
|
||||
"outdoor_seating": "no",
|
||||
"payment:credit_cards": "yes",
|
||||
"payment:debit_cards": "yes",
|
||||
"phone": "+61 8 8981 0010",
|
||||
"website": "https://www.moorishcafe.com.au/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 7081158586,
|
||||
"lat": -12.4618269,
|
||||
"lon": 130.8416055,
|
||||
"tags": {
|
||||
"amenity": "cafe",
|
||||
"cuisine": "bagel",
|
||||
"diet:vegan": "yes",
|
||||
"name": "Mad Snake"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 9553841165,
|
||||
"lat": -12.4631496,
|
||||
"lon": 130.8420973,
|
||||
"tags": {
|
||||
"air_conditioning": "yes",
|
||||
"amenity": "cafe",
|
||||
"cuisine": "coffee_shop",
|
||||
"name": "The Rabbit Hole",
|
||||
"opening_hours": "Mo-Fr 07:00-15:00; Sa,Su 08:00-14:00",
|
||||
"smoking": "no",
|
||||
"wheelchair": "yes"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 10958632095,
|
||||
"lat": -12.4643252,
|
||||
"lon": 130.8426431,
|
||||
"tags": {
|
||||
"amenity": "cafe",
|
||||
"name": "Pokelicious"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 10958836833,
|
||||
"lat": -12.4644685,
|
||||
"lon": 130.8423883,
|
||||
"tags": {
|
||||
"amenity": "cafe",
|
||||
"cuisine": "juice",
|
||||
"name": "Fruillati"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11895994369,
|
||||
"lat": -12.4608319,
|
||||
"lon": 130.8416269,
|
||||
"tags": {
|
||||
"addr:housenumber": "48",
|
||||
"addr:postcode": "0800",
|
||||
"addr:street": "Cavenagh Street",
|
||||
"amenity": "pharmacy",
|
||||
"branch": "GPO",
|
||||
"brand": "Chemist Warehouse",
|
||||
"brand:wikidata": "Q48782120",
|
||||
"healthcare": "pharmacy",
|
||||
"name": "Chemist Warehouse",
|
||||
"name:en": "Chemist Warehouse Darwin GPO",
|
||||
"opening_hours": "Mo-Fr 08:00-20:00; Sa-Su 08:30-17:00",
|
||||
"phone": "+61 8 8981 9202",
|
||||
"shop": "chemist",
|
||||
"website": "https://www.chemistwarehouse.com.au/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11896000469,
|
||||
"lat": -12.4610643,
|
||||
"lon": 130.8418065,
|
||||
"tags": {
|
||||
"addr:housenumber": "48",
|
||||
"addr:postcode": "0800",
|
||||
"addr:street": "Cavenagh Street",
|
||||
"addr:unit": "5",
|
||||
"brand": "Vinnies",
|
||||
"brand:wikidata": "Q120646672",
|
||||
"name": "Vinnies",
|
||||
"opening_hours": "Mo-Fr 09:00-17:00; Sa 09:00-16:00; Su closed",
|
||||
"phone": "+61 8 8941 2849",
|
||||
"shop": "charity",
|
||||
"website": "https://www.vinnies.org.au/nt/shops/vinnies-darwin-city"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11896004570,
|
||||
"lat": -12.461172,
|
||||
"lon": 130.8419583,
|
||||
"tags": {
|
||||
"addr:housenumber": "48",
|
||||
"addr:postcode": "0800",
|
||||
"addr:street": "Cavenagh Street",
|
||||
"addr:unit": "9",
|
||||
"amenity": "cafe",
|
||||
"check_date:opening_hours": "2025-06-18",
|
||||
"name": "Alberto's",
|
||||
"opening_hours": "Mo-Fr 07:00-15:00; Sa 08:00-14:00",
|
||||
"phone": "+61 426 241 971"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11910908823,
|
||||
"lat": -12.4681147,
|
||||
"lon": 130.846395,
|
||||
"tags": {
|
||||
"addr:housenumber": "7",
|
||||
"addr:street": "Kitchener Drive",
|
||||
"addr:unit": "E4",
|
||||
"name": "Surf Dive 'n Ski - Darwin Waterfront",
|
||||
"opening_hours": "Mo-Fr 09:00-17:30; Sa 09:00-17:00; Su 10:00-17:00",
|
||||
"phone": "+61 8 8941 0425",
|
||||
"shop": "clothes",
|
||||
"website": "https://sds.com.au/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 12069251200,
|
||||
"lat": -12.4629941,
|
||||
"lon": 130.8413635,
|
||||
"tags": {
|
||||
"name": "Bobar Darwin",
|
||||
"opening_hours": "Mo-Fr 11:00-18:00; Sa 11:00-17:30; Su 12:00-15:30",
|
||||
"phone": "+61 2 7978 8669",
|
||||
"shop": "tea",
|
||||
"wheelchair": "no"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 12608531707,
|
||||
"lat": -12.4607079,
|
||||
"lon": 130.8405799,
|
||||
"tags": {
|
||||
"air_conditioning": "yes",
|
||||
"amenity": "cafe",
|
||||
"changing_table": "no",
|
||||
"check_date:opening_hours": "2025-02-22",
|
||||
"indoor_seating": "yes",
|
||||
"name": "Passengers Cafe",
|
||||
"opening_hours:signed": "no",
|
||||
"outdoor_seating": "no",
|
||||
"smoking": "no",
|
||||
"wheelchair": "yes"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 12608563677,
|
||||
"lat": -12.4608522,
|
||||
"lon": 130.8403856,
|
||||
"tags": {
|
||||
"name": "Blooms",
|
||||
"opening_hours": "Mo-Fr 08:00-19:00; Sa,Su 08:00-14:00",
|
||||
"shop": "chemist",
|
||||
"wheelchair": "yes"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 13390374346,
|
||||
"lat": -12.4635109,
|
||||
"lon": 130.8408988,
|
||||
"tags": {
|
||||
"addr:housenumber": "15",
|
||||
"addr:street": "Knuckey Street",
|
||||
"amenity": "cafe",
|
||||
"indoor_seating": "no",
|
||||
"name": "Bustard Town",
|
||||
"outdoor_seating": "yes",
|
||||
"website": "https://bustardtown.com/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 13903181001,
|
||||
"lat": -12.4639071,
|
||||
"lon": 130.8429999,
|
||||
"tags": {
|
||||
"addr:housenumber": "2",
|
||||
"addr:street": "Star Village",
|
||||
"amenity": "cafe",
|
||||
"check_date": "2026-06-02",
|
||||
"check_date:opening_hours": "2026-06-02",
|
||||
"cuisine": "breakfast;cake;coffee_shop",
|
||||
"name": "Four Birds",
|
||||
"opening_hours": "Mo-Fr 07:00-14:00; Sa 08:00-13:00"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
62513
web/assets/towns/_raw/darwin_real.roads.overpass.json
Normal file
596
web/assets/towns/_raw/daylesford_real.overpass.json
Normal file
@ -0,0 +1,596 @@
|
||||
{
|
||||
"fetchedAt": "2026-07-16",
|
||||
"endpoint": "https://overpass-api.de/api/interpreter",
|
||||
"query": "[out:json][timeout:180];(nwr[\"shop\"~\"^(antiques|bag|bakery|books|bookshop|boutique|butcher|charity|cheese|chemist|chocolate|clothes|coffee|confectionery|convenience|cosmetics|dairy|deli|delicatessen|department_store|doityourself|electronics|farm|fashion|florist|furniture|games|garden_centre|general|greengrocer|hardware|homeware|houseware|jewellery|jewelry|kiosk|mobile_phone|music|newsagent|pastry|pawnbroker|perfumery|pharmacy|second_hand|shoes|stationery|supermarket|tea|toys|trade|variety_store|video|video_games|vintage)$\"][\"name\"](-37.35201,144.13167,-37.33399,144.15433);nwr[\"amenity\"~\"^(cafe)$\"][\"name\"](-37.35201,144.13167,-37.33399,144.15433););out center;",
|
||||
"bbox": {
|
||||
"minLat": -37.35200900900901,
|
||||
"minLon": 144.13166817643216,
|
||||
"maxLat": -37.333990990991,
|
||||
"maxLon": 144.15433182356784
|
||||
},
|
||||
"elements": [
|
||||
{
|
||||
"type": "node",
|
||||
"id": 1013037027,
|
||||
"lat": -37.3412366,
|
||||
"lon": 144.1418112,
|
||||
"tags": {
|
||||
"amenity": "cafe",
|
||||
"name": "Harvest Cafe"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 1240758570,
|
||||
"lat": -37.3410037,
|
||||
"lon": 144.1431033,
|
||||
"tags": {
|
||||
"addr:housenumber": "1",
|
||||
"addr:street": "Burke Square",
|
||||
"brand": "Mitre 10",
|
||||
"brand:wikidata": "Q6882393",
|
||||
"brand:wikipedia": "en:Mitre 10",
|
||||
"name": "Crameri's Mitre 10",
|
||||
"opening_hours": "Mo-Fr 07:30-17:30; Sa 08:30-15:30; Su 09:00-12:00",
|
||||
"phone": "+61 3 5348 2316",
|
||||
"shop": "doityourself",
|
||||
"website": "https://www.mitre10.com.au/stores/vic/daylesford-crameri-s-mitre-10-853648"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 1240783219,
|
||||
"lat": -37.3478628,
|
||||
"lon": 144.1371111,
|
||||
"tags": {
|
||||
"amenity": "cafe",
|
||||
"check_date": "2025-12-23",
|
||||
"cuisine": "coffee_shop",
|
||||
"name": "The Boathouse Cafe",
|
||||
"website": "http://www.urbanspoon.com/r/346/1493625/restaurant/Victoria/The-Boathouse-Daylesford"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 1704817248,
|
||||
"lat": -37.3426634,
|
||||
"lon": 144.1422838,
|
||||
"tags": {
|
||||
"addr:housename": "That Bargain Place",
|
||||
"addr:housenumber": "68",
|
||||
"addr:street": "Vincent Street",
|
||||
"contact:facebook": "https://www.facebook.com/ThatBargainPlaceDaylesford/",
|
||||
"name": "That Bargain Place",
|
||||
"shop": "variety_store"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 4403814794,
|
||||
"lat": -37.342778,
|
||||
"lon": 144.1426706,
|
||||
"tags": {
|
||||
"amenity": "cafe",
|
||||
"name": "Larder",
|
||||
"website": "https://www.larderdaylesford.com.au/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 4658624090,
|
||||
"lat": -37.3417167,
|
||||
"lon": 144.1320928,
|
||||
"tags": {
|
||||
"addr:housenumber": "105",
|
||||
"addr:street": "Central Springs Road",
|
||||
"check_date": "2025-12-23",
|
||||
"name": "The Amazing Mill Markets",
|
||||
"opening_hours": "Mo-Su 10:00-18:00",
|
||||
"shop": "antiques",
|
||||
"tourism": "attraction",
|
||||
"website": "https://www.millmarkets.com.au/daylesford/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 5040169824,
|
||||
"lat": -37.3434833,
|
||||
"lon": 144.1491515,
|
||||
"tags": {
|
||||
"amenity": "cafe",
|
||||
"cuisine": "austrian",
|
||||
"diet:vegan": "yes",
|
||||
"diet:vegetarian": "yes",
|
||||
"name": "Wombat Hill House",
|
||||
"opening_hours": "Fr-Tu 08:00-15:00",
|
||||
"payment:credit_cards": "yes",
|
||||
"payment:debit_cards": "yes",
|
||||
"website": "https://wombathillhouse.com.au"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 5597847478,
|
||||
"lat": -37.343045,
|
||||
"lon": 144.142651,
|
||||
"tags": {
|
||||
"addr:housenumber": "73",
|
||||
"addr:street": "Vincent Street",
|
||||
"amenity": "cafe",
|
||||
"name": "Himalaya",
|
||||
"shop": "bakery",
|
||||
"website": "https://himalayabakery.com/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 6541247183,
|
||||
"lat": -37.3406205,
|
||||
"lon": 144.1511771,
|
||||
"tags": {
|
||||
"addr:housenumber": "28",
|
||||
"addr:street": "Raglan Street",
|
||||
"name": "Wombat Hill Florist",
|
||||
"shop": "florist",
|
||||
"website": "https://wombathillflorist.com.au/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 7278926266,
|
||||
"lat": -37.3420268,
|
||||
"lon": 144.1424152,
|
||||
"tags": {
|
||||
"addr:housenumber": "46",
|
||||
"addr:street": "Vincent Street",
|
||||
"name": "Paradise Bookshop",
|
||||
"opening_hours": "Mo-Su 11:00-17:00",
|
||||
"shop": "books",
|
||||
"website": "https://paradisebookshop.com.au/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 9523030703,
|
||||
"lat": -37.3400211,
|
||||
"lon": 144.1451252,
|
||||
"tags": {
|
||||
"addr:housenumber": "1",
|
||||
"addr:postcode": "3460",
|
||||
"addr:state": "VIC",
|
||||
"addr:street": "Howe Street",
|
||||
"addr:suburb": "Daylesford",
|
||||
"name": "Blake Family Grocers",
|
||||
"opening_hours": "Mo-Su 08:00-19:00",
|
||||
"phone": "+61 3 5318 1128",
|
||||
"shop": "supermarket",
|
||||
"website": "https://www.blakefamilygrocers.com.au/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 10290719910,
|
||||
"lat": -37.3482099,
|
||||
"lon": 144.1377154,
|
||||
"tags": {
|
||||
"amenity": "cafe",
|
||||
"cuisine": "coffee_shop;ice_cream;sandwich",
|
||||
"name": "Walters"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 12416866197,
|
||||
"lat": -37.3431995,
|
||||
"lon": 144.1426217,
|
||||
"tags": {
|
||||
"addr:housenumber": "79",
|
||||
"addr:street": "Vincent Street",
|
||||
"name": "Creswick Natural Fibres",
|
||||
"shop": "clothes",
|
||||
"website": "https://creswickwool.com/pages/daylesford-store"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 12416866444,
|
||||
"lat": -37.3420226,
|
||||
"lon": 144.1428444,
|
||||
"tags": {
|
||||
"addr:housenumber": "33",
|
||||
"addr:street": "Vincent Street",
|
||||
"contact:facebook": "https://www.facebook.com/people/Mee-at-Daylesford/100039378596498/",
|
||||
"name": "Mee at Daylesford",
|
||||
"shop": "jewelry"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 12416866480,
|
||||
"lat": -37.3420722,
|
||||
"lon": 144.142835,
|
||||
"tags": {
|
||||
"addr:housenumber": "35",
|
||||
"addr:street": "Vincent Street",
|
||||
"name": "The Empress & Wolf",
|
||||
"shop": "jewelry",
|
||||
"website": "https://theempressandwolf.com/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 12416866585,
|
||||
"lat": -37.3433921,
|
||||
"lon": 144.1425853,
|
||||
"tags": {
|
||||
"addr:housenumber": "87",
|
||||
"addr:street": "Vincent Street",
|
||||
"contact:facebook": "https://www.facebook.com/SweetDecadenceAtLocantro/",
|
||||
"name": "Sweet Decadence at Locantro",
|
||||
"shop": "chocolate"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 12416866829,
|
||||
"lat": -37.3431372,
|
||||
"lon": 144.1426335,
|
||||
"tags": {
|
||||
"addr:housenumber": "77",
|
||||
"addr:street": "Vincent Street",
|
||||
"contact:facebook": "https://www.facebook.com/Enkiorganics/",
|
||||
"name": "Enki Apothecary & Distillery",
|
||||
"shop": "cosmetics"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 12416866831,
|
||||
"lat": -37.3421224,
|
||||
"lon": 144.1428255,
|
||||
"tags": {
|
||||
"addr:housenumber": "37",
|
||||
"addr:street": "Vincent Street",
|
||||
"name": "The Daylesford Meat Co",
|
||||
"shop": "butcher",
|
||||
"website": "https://daylesfordmeatco.com.au/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 12416866871,
|
||||
"lat": -37.3418082,
|
||||
"lon": 144.1428849,
|
||||
"tags": {
|
||||
"addr:housenumber": "27",
|
||||
"addr:street": "Vincent Street",
|
||||
"addr:unit": "1",
|
||||
"name": "Manteau Noir",
|
||||
"shop": "clothes",
|
||||
"website": "https://manteaunoir.com.au/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 12416866919,
|
||||
"lat": -37.342985,
|
||||
"lon": 144.1426623,
|
||||
"tags": {
|
||||
"addr:housenumber": "69-71",
|
||||
"addr:street": "Vincent Street",
|
||||
"contact:facebook": "https://www.facebook.com/daylesfordbakery/",
|
||||
"name": "Daylesford Bakery",
|
||||
"shop": "bakery"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 12416867014,
|
||||
"lat": -37.3433466,
|
||||
"lon": 144.1425939,
|
||||
"tags": {
|
||||
"addr:housenumber": "83-85",
|
||||
"addr:street": "Vincent Street",
|
||||
"name": "Harry and Me",
|
||||
"shop": "clothes",
|
||||
"website": "https://harryandme.com.au/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 12416867098,
|
||||
"lat": -37.3417293,
|
||||
"lon": 144.1430296,
|
||||
"tags": {
|
||||
"addr:housenumber": "27",
|
||||
"addr:street": "Vincent Street",
|
||||
"addr:unit": "5",
|
||||
"contact:instagram": "https://www.instagram.com/aroundthecorner_daylesford/",
|
||||
"name": "Around the Corner",
|
||||
"shop": "antiques"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 12420835148,
|
||||
"lat": -37.3421321,
|
||||
"lon": 144.1423943,
|
||||
"tags": {
|
||||
"addr:housenumber": "50",
|
||||
"addr:street": "Vincent Street",
|
||||
"name": "The Magic Pudding Homewares",
|
||||
"shop": "houseware"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 12420836576,
|
||||
"lat": -37.3416484,
|
||||
"lon": 144.1424901,
|
||||
"tags": {
|
||||
"addr:housenumber": "28",
|
||||
"addr:street": "Vincent Street",
|
||||
"name": "Frances Pilley",
|
||||
"shop": "cosmetics",
|
||||
"website": "https://www.francespilley.com.au/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 12420836577,
|
||||
"lat": -37.3416115,
|
||||
"lon": 144.1422392,
|
||||
"tags": {
|
||||
"addr:housenumber": "22",
|
||||
"addr:street": "Albert Street",
|
||||
"contact:facebook": "https://www.facebook.com/people/Albert-Street-Butchery-Daylesford/100057539834248/",
|
||||
"name": "Albert Street Butchery",
|
||||
"shop": "butcher"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 12420836612,
|
||||
"lat": -37.3404231,
|
||||
"lon": 144.1452113,
|
||||
"tags": {
|
||||
"addr:housenumber": "10-12",
|
||||
"addr:street": "Howe Street",
|
||||
"name": "Buster McGee",
|
||||
"shop": "clothes",
|
||||
"website": "https://bustermcgee.com.au/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 12420836699,
|
||||
"lat": -37.3424976,
|
||||
"lon": 144.1423191,
|
||||
"tags": {
|
||||
"addr:housenumber": "60",
|
||||
"addr:street": "Vincent Street",
|
||||
"name": "Pastry King",
|
||||
"shop": "bakery"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 12420836714,
|
||||
"lat": -37.3407194,
|
||||
"lon": 144.1516473,
|
||||
"tags": {
|
||||
"addr:housenumber": "22",
|
||||
"addr:street": "Raglan Street",
|
||||
"amenity": "cafe",
|
||||
"name": "The Daylesford Hot Chocolate Company",
|
||||
"website": "https://www.daylesfordhotchocolate.com/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 12420836716,
|
||||
"lat": -37.3410013,
|
||||
"lon": 144.1440776,
|
||||
"tags": {
|
||||
"addr:housenumber": "28",
|
||||
"addr:street": "Howe Street",
|
||||
"name": "Vinnies",
|
||||
"opening_hours": "Mo-Sa 09:00-17:00; Su 10:00-17:00",
|
||||
"phone": "+61 3 5348 3862",
|
||||
"shop": "charity",
|
||||
"website": "https://www.vinnies.org.au/vic/shops/vinnies-daylesford"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 12420836864,
|
||||
"lat": -37.3416903,
|
||||
"lon": 144.1424818,
|
||||
"tags": {
|
||||
"addr:housenumber": "30",
|
||||
"addr:street": "Vincent Street",
|
||||
"name": "Alpaca Passion",
|
||||
"shop": "clothes",
|
||||
"website": "https://www.alpacapassion.com.au/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 12420836982,
|
||||
"lat": -37.342088,
|
||||
"lon": 144.1424031,
|
||||
"tags": {
|
||||
"addr:housenumber": "48",
|
||||
"addr:street": "Vincent Street",
|
||||
"name": "The Sew Good Company",
|
||||
"shop": "clothes",
|
||||
"website": "https://thesewgoodcompany.org/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 12420837016,
|
||||
"lat": -37.3417585,
|
||||
"lon": 144.1424683,
|
||||
"tags": {
|
||||
"addr:housenumber": "32-34",
|
||||
"addr:street": "Vincent Street",
|
||||
"name": "Brick Lane Bazaar",
|
||||
"shop": "second_hand",
|
||||
"website": "https://bricklanebazaar.com/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 13396672125,
|
||||
"lat": -37.3418814,
|
||||
"lon": 144.131898,
|
||||
"tags": {
|
||||
"amenity": "cafe",
|
||||
"name": "The Maze",
|
||||
"opening_hours": "Mo-Fr 10:00-16:00; Sa-Su 10:00-15:00",
|
||||
"outdoor_seating": "yes",
|
||||
"website": "https://www.millmarkets.com.au/cafes/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 13588943580,
|
||||
"lat": -37.3407844,
|
||||
"lon": 144.1445263,
|
||||
"tags": {
|
||||
"addr:housenumber": "22-24",
|
||||
"addr:street": "Howe Street",
|
||||
"addr:unit": "1-2",
|
||||
"name": "Salvos",
|
||||
"opening_hours": "Mo-Sa 09:00-17:00",
|
||||
"phone": "+61 3 5348 2720",
|
||||
"shop": "charity",
|
||||
"website": "https://www.salvosstores.com.au/stores/vic/3460-daylesford-806"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 13588943581,
|
||||
"lat": -37.3409459,
|
||||
"lon": 144.1441921,
|
||||
"tags": {
|
||||
"addr:housenumber": "22-24",
|
||||
"addr:street": "Howe Street",
|
||||
"addr:unit": "7",
|
||||
"name": "Uniting Op Shop",
|
||||
"opening_hours": "Tu-Su 09:30-17:00; Mo closed",
|
||||
"phone": "+61 3 9051 2763",
|
||||
"shop": "charity",
|
||||
"website": "https://www.unitingvictas.org.au/locations/op-shop-daylesford/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 13588956511,
|
||||
"lat": -37.3419604,
|
||||
"lon": 144.1424283,
|
||||
"tags": {
|
||||
"addr:housenumber": "44",
|
||||
"addr:street": "Vincent Street",
|
||||
"contact:facebook": "https://www.facebook.com/dcjdaylesford/",
|
||||
"name": "Double C Jeanery",
|
||||
"shop": "clothes"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 13589251803,
|
||||
"lat": -37.3417169,
|
||||
"lon": 144.1429022,
|
||||
"tags": {
|
||||
"addr:housenumber": "27",
|
||||
"addr:street": "Vincent Street",
|
||||
"addr:unit": "3",
|
||||
"name": "Artedeco",
|
||||
"shop": "antiques",
|
||||
"website": "https://www.artedeco.com.au/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 13589288436,
|
||||
"lat": -37.341318,
|
||||
"lon": 144.1424316,
|
||||
"tags": {
|
||||
"addr:housenumber": "26",
|
||||
"addr:street": "Vincent Street",
|
||||
"name": "The Farmer's Wife",
|
||||
"shop": "clothes",
|
||||
"website": "https://thefarmerswifestore.com.au/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "way",
|
||||
"id": 108219615,
|
||||
"center": {
|
||||
"lat": -37.3396039,
|
||||
"lon": 144.1467323
|
||||
},
|
||||
"nodes": [
|
||||
1241976087,
|
||||
1241976088,
|
||||
1241976090,
|
||||
1241976091,
|
||||
1241976079,
|
||||
1241976078,
|
||||
1241976096,
|
||||
1241976097,
|
||||
1241976098,
|
||||
1241976087
|
||||
],
|
||||
"tags": {
|
||||
"brand": "Reddy Express",
|
||||
"brand:wikidata": "Q5144653",
|
||||
"building": "yes",
|
||||
"name": "Reddy Express",
|
||||
"shop": "convenience",
|
||||
"website": "www.reddyexpress.com.au"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "way",
|
||||
"id": 108219853,
|
||||
"center": {
|
||||
"lat": -37.3422119,
|
||||
"lon": 144.1416651
|
||||
},
|
||||
"nodes": [
|
||||
1241978597,
|
||||
1241978598,
|
||||
13589291160,
|
||||
1241978599,
|
||||
1241978602,
|
||||
1241978603,
|
||||
1241978604,
|
||||
2176313230,
|
||||
1241978594,
|
||||
13589291158,
|
||||
13589291159,
|
||||
1241978589,
|
||||
1241978596,
|
||||
1241978597
|
||||
],
|
||||
"tags": {
|
||||
"addr:housenumber": "32",
|
||||
"addr:street": "Albert Street",
|
||||
"brand": "Coles Local",
|
||||
"brand:wikidata": "Q104850818",
|
||||
"brand:wikipedia": "en:Coles Supermarkets",
|
||||
"building": "yes",
|
||||
"name": "Coles Local",
|
||||
"opening_hours": "Mo-Su 07:00-21:00",
|
||||
"operator": "Coles Group",
|
||||
"operator:wikidata": "Q131937428",
|
||||
"phone": "+61 3 5325 7200",
|
||||
"shop": "supermarket",
|
||||
"website": "https://www.coles.com.au/"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
22642
web/assets/towns/_raw/daylesford_real.roads.overpass.json
Normal file
7912
web/assets/towns/_raw/fitzroy_real.overpass.json
Normal file
225052
web/assets/towns/_raw/fitzroy_real.roads.overpass.json
Normal file
6281
web/assets/towns/_raw/geelong_real.overpass.json
Normal file
194781
web/assets/towns/_raw/geelong_real.roads.overpass.json
Normal file
2654
web/assets/towns/_raw/glebe_real.overpass.json
Normal file
184328
web/assets/towns/_raw/glebe_real.roads.overpass.json
Normal file
3385
web/assets/towns/_raw/hobart_real.overpass.json
Normal file
130918
web/assets/towns/_raw/hobart_real.roads.overpass.json
Normal file
2130
web/assets/towns/_raw/launceston_real.overpass.json
Normal file
154616
web/assets/towns/_raw/launceston_real.roads.overpass.json
Normal file
1897
web/assets/towns/_raw/marrickville_real.overpass.json
Normal file
87707
web/assets/towns/_raw/marrickville_real.roads.overpass.json
Normal file
841
web/assets/towns/_raw/newcastle_real.overpass.json
Normal file
@ -0,0 +1,841 @@
|
||||
{
|
||||
"fetchedAt": "2026-07-16",
|
||||
"endpoint": "https://overpass-api.de/api/interpreter",
|
||||
"query": "[out:json][timeout:180];(nwr[\"shop\"~\"^(antiques|bag|bakery|books|bookshop|boutique|butcher|charity|cheese|chemist|chocolate|clothes|coffee|confectionery|convenience|cosmetics|dairy|deli|delicatessen|department_store|doityourself|electronics|farm|fashion|florist|furniture|games|garden_centre|general|greengrocer|hardware|homeware|houseware|jewellery|jewelry|kiosk|mobile_phone|music|newsagent|pastry|pawnbroker|perfumery|pharmacy|second_hand|shoes|stationery|supermarket|tea|toys|trade|variety_store|video|video_games|vintage)$\"][\"name\"](-32.93431,151.73112,-32.91269,151.75688);nwr[\"amenity\"~\"^(cafe)$\"][\"name\"](-32.93431,151.73112,-32.91269,151.75688););out center;",
|
||||
"bbox": {
|
||||
"minLat": -32.93431081081081,
|
||||
"minLon": 151.73112074181012,
|
||||
"maxLat": -32.91268918918919,
|
||||
"maxLon": 151.75687925818988
|
||||
},
|
||||
"elements": [
|
||||
{
|
||||
"type": "node",
|
||||
"id": 367127925,
|
||||
"lat": -32.9173937,
|
||||
"lon": 151.7314109,
|
||||
"tags": {
|
||||
"brand": "IGA",
|
||||
"brand:wikidata": "Q5970945",
|
||||
"brand:wikipedia": "en:IGA Supermarkets Australia)",
|
||||
"name": "IGA",
|
||||
"shop": "supermarket",
|
||||
"wheelchair": "yes"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 1612632841,
|
||||
"lat": -32.9172505,
|
||||
"lon": 151.7491889,
|
||||
"tags": {
|
||||
"amenity": "cafe",
|
||||
"name": "Suspension Espresso",
|
||||
"website": "https://www.slingtown.com"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 2999805547,
|
||||
"lat": -32.9188202,
|
||||
"lon": 151.7480964,
|
||||
"tags": {
|
||||
"amenity": "cafe",
|
||||
"cuisine": "coffee_shop",
|
||||
"name": "Roladoor Cafe",
|
||||
"opening_hours:signed": "no"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 2999805549,
|
||||
"lat": -32.9175975,
|
||||
"lon": 151.7501325,
|
||||
"tags": {
|
||||
"amenity": "cafe",
|
||||
"name": "Peaberrys Coffee Roasters",
|
||||
"website": "https://www.peaberrys.com.au/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 2999805552,
|
||||
"lat": -32.9243297,
|
||||
"lon": 151.7470182,
|
||||
"tags": {
|
||||
"amenity": "cafe",
|
||||
"name": "Mockingbird Cafe"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 2999805558,
|
||||
"lat": -32.9243567,
|
||||
"lon": 151.7467553,
|
||||
"tags": {
|
||||
"amenity": "cafe",
|
||||
"name": "Blue Door Cafe"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 2999805559,
|
||||
"lat": -32.9239333,
|
||||
"lon": 151.7470158,
|
||||
"tags": {
|
||||
"addr:housenumber": "129",
|
||||
"addr:street": "Beaumont Street",
|
||||
"amenity": "cafe",
|
||||
"brand": "Gloria Jean's",
|
||||
"brand:wikidata": "Q2666365",
|
||||
"cuisine": "coffee_shop",
|
||||
"name": "Gloria Jean's",
|
||||
"official_name": "Gloria Jean's Coffees",
|
||||
"takeaway": "yes"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 3128619076,
|
||||
"lat": -32.9220235,
|
||||
"lon": 151.7471464,
|
||||
"tags": {
|
||||
"amenity": "pharmacy",
|
||||
"brand": "Priceline Pharmacy",
|
||||
"brand:wikidata": "Q7242652",
|
||||
"healthcare": "pharmacy",
|
||||
"name": "Priceline Pharmacy",
|
||||
"shop": "chemist"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 3377399796,
|
||||
"lat": -32.9171649,
|
||||
"lon": 151.7322129,
|
||||
"tags": {
|
||||
"name": "Like-nu Appliances",
|
||||
"shop": "electronics"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 3379211532,
|
||||
"lat": -32.914319,
|
||||
"lon": 151.7568551,
|
||||
"tags": {
|
||||
"name": "Viatek",
|
||||
"shop": "electronics"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 3725992754,
|
||||
"lat": -32.9141506,
|
||||
"lon": 151.7328501,
|
||||
"tags": {
|
||||
"name": "Newy Workwear",
|
||||
"shop": "clothes"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 3727810194,
|
||||
"lat": -32.9189192,
|
||||
"lon": 151.7474545,
|
||||
"tags": {
|
||||
"name": "Home of Stone",
|
||||
"shop": "hardware"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 3727810198,
|
||||
"lat": -32.9194716,
|
||||
"lon": 151.7479665,
|
||||
"tags": {
|
||||
"name": "Samaritans",
|
||||
"shop": "clothes"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 3842345611,
|
||||
"lat": -32.9156254,
|
||||
"lon": 151.7463359,
|
||||
"tags": {
|
||||
"name": "Broadley Signs",
|
||||
"shop": "hardware"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 5172145221,
|
||||
"lat": -32.9161294,
|
||||
"lon": 151.7325481,
|
||||
"tags": {
|
||||
"name": "Newcastle Home Timber And Hardware",
|
||||
"shop": "hardware"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 5298432698,
|
||||
"lat": -32.9246914,
|
||||
"lon": 151.7375446,
|
||||
"tags": {
|
||||
"addr:housenumber": "226",
|
||||
"addr:street": "Denison Street",
|
||||
"name": "Belflora",
|
||||
"shop": "florist"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 5298467054,
|
||||
"lat": -32.9241201,
|
||||
"lon": 151.7395237,
|
||||
"tags": {
|
||||
"addr:housenumber": "54-56",
|
||||
"addr:street": "Belford Street",
|
||||
"amenity": "cafe",
|
||||
"check_date": "2025-05-23",
|
||||
"cuisine": "sandwich;coffee_shop;french;breakfast;cake",
|
||||
"internet_access": "no",
|
||||
"name": "The Little French Cafe",
|
||||
"opening_hours": "Mo-Fr 07:00-14:00; Sa 08:00-12:00",
|
||||
"outdoor_seating": "no",
|
||||
"payment:cash": "yes",
|
||||
"payment:credit_cards": "yes",
|
||||
"payment:debit_cards": "yes",
|
||||
"phone": "+61 455 196 070",
|
||||
"smoking": "no",
|
||||
"wheelchair": "limited"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 5601162021,
|
||||
"lat": -32.9243212,
|
||||
"lon": 151.7479496,
|
||||
"tags": {
|
||||
"name": "Jim's Dairy Delight Bar",
|
||||
"shop": "confectionery"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 6869527385,
|
||||
"lat": -32.9207022,
|
||||
"lon": 151.7464265,
|
||||
"tags": {
|
||||
"addr:housenumber": "67",
|
||||
"addr:street": "Cleary Street",
|
||||
"amenity": "cafe",
|
||||
"cuisine": "breakfast;coffee_shop",
|
||||
"name": "Olive Branch Cafe"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 7580092892,
|
||||
"lat": -32.9213178,
|
||||
"lon": 151.7473098,
|
||||
"tags": {
|
||||
"addr:housenumber": "66",
|
||||
"addr:postcode": "2303",
|
||||
"addr:state": "NSW",
|
||||
"addr:street": "Beaumont Street",
|
||||
"addr:suburb": "Hamilton",
|
||||
"air_conditioning": "yes",
|
||||
"amenity": "cafe",
|
||||
"bar": "yes",
|
||||
"cuisine": "international;coffee_shop",
|
||||
"delivery": "yes",
|
||||
"description": "Newest foodie scene to hit Newcastle, breakfast, lunch, dinner and late night eats. Craft beers, bottomless coffee and awesome food!",
|
||||
"diet:dairy_free": "yes",
|
||||
"diet:gluten_free": "yes",
|
||||
"diet:vegan": "yes",
|
||||
"diet:vegetarian": "yes",
|
||||
"email": "hello@sonofagundiner.com.au",
|
||||
"internet_access": "wlan",
|
||||
"internet_access:fee": "customers",
|
||||
"name": "Son of a Gun",
|
||||
"opening_hours": "24/7",
|
||||
"outdoor_seating": "yes",
|
||||
"payment:cash": "yes",
|
||||
"payment:credit_cards": "yes",
|
||||
"payment:debit_cards": "yes",
|
||||
"payment:mastercard": "yes",
|
||||
"payment:visa": "yes",
|
||||
"phone": "+61 2 4049 1136",
|
||||
"reservation": "yes",
|
||||
"smoking": "no",
|
||||
"takeaway": "yes",
|
||||
"website": "https://www.sonofagundiner.com.au/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 8453102948,
|
||||
"lat": -32.9153262,
|
||||
"lon": 151.7487223,
|
||||
"tags": {
|
||||
"amenity": "cafe",
|
||||
"cuisine": "coffee_shop",
|
||||
"name": "The Fast Lane"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11264117470,
|
||||
"lat": -32.9202347,
|
||||
"lon": 151.7424602,
|
||||
"tags": {
|
||||
"addr:housenumber": "4",
|
||||
"addr:postcode": "2303",
|
||||
"addr:state": "NSW",
|
||||
"addr:street": "Samdon Street",
|
||||
"addr:suburb": "Hamilton",
|
||||
"amenity": "cafe",
|
||||
"name": "Second Fiddle Cafe",
|
||||
"website": "https://www.secondfiddle.com.au"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 11332612308,
|
||||
"lat": -32.9243007,
|
||||
"lon": 151.7501634,
|
||||
"tags": {
|
||||
"brand": "OTR",
|
||||
"brand:wikidata": "Q116394019",
|
||||
"name": "OTR",
|
||||
"shop": "convenience"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 12223202805,
|
||||
"lat": -32.9214595,
|
||||
"lon": 151.7475743,
|
||||
"tags": {
|
||||
"name": "Hamilton IGA",
|
||||
"shop": "supermarket"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 12857192879,
|
||||
"lat": -32.9233659,
|
||||
"lon": 151.7471632,
|
||||
"tags": {
|
||||
"check_date": "2025-05-23",
|
||||
"email": "info@qsbooks.com.au",
|
||||
"name": "Q's Books",
|
||||
"phone": "+61 2 4961 0222",
|
||||
"second_hand": "yes",
|
||||
"shop": "books",
|
||||
"website": "https://qsbooks.com.au",
|
||||
"wheelchair": "limited"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 12857192880,
|
||||
"lat": -32.9223281,
|
||||
"lon": 151.7471114,
|
||||
"tags": {
|
||||
"check_date": "2025-05-23",
|
||||
"name": "Rice's Bookshop",
|
||||
"phone": "+61 2 4929 2752",
|
||||
"shop": "books",
|
||||
"wheelchair": "limited"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"id": 12857192881,
|
||||
"lat": -32.9212777,
|
||||
"lon": 151.7475952,
|
||||
"tags": {
|
||||
"check_date": "2025-05-23",
|
||||
"name": "Australian Red Cross",
|
||||
"opening_hours": "Mo-Fr 09:00-16:45; Sa 09:00-14:45; Su off",
|
||||
"phone": "+61 2 4086 4712",
|
||||
"second_hand": "yes",
|
||||
"shop": "charity",
|
||||
"wheelchair": "yes"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "way",
|
||||
"id": 307525838,
|
||||
"center": {
|
||||
"lat": -32.9209867,
|
||||
"lon": 151.7472423
|
||||
},
|
||||
"nodes": [
|
||||
3127081728,
|
||||
3127082329,
|
||||
3127082331,
|
||||
11323345477,
|
||||
11323345478,
|
||||
3127082330,
|
||||
3127081728
|
||||
],
|
||||
"tags": {
|
||||
"amenity": "pharmacy",
|
||||
"building": "commercial",
|
||||
"healthcare": "pharmacy",
|
||||
"name": "Discount Chemist",
|
||||
"shop": "chemist"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "way",
|
||||
"id": 329987168,
|
||||
"center": {
|
||||
"lat": -32.9187688,
|
||||
"lon": 151.7461999
|
||||
},
|
||||
"nodes": [
|
||||
3369503335,
|
||||
3369503331,
|
||||
3369503332,
|
||||
3369503325,
|
||||
3369503324,
|
||||
3369503318,
|
||||
3369503320,
|
||||
3369503330,
|
||||
3369503329,
|
||||
3369503339,
|
||||
11367128008,
|
||||
11367128007,
|
||||
3369503335
|
||||
],
|
||||
"tags": {
|
||||
"addr:housenumber": "11",
|
||||
"addr:street": "Swan Street",
|
||||
"brand": "ALDI",
|
||||
"brand:wikidata": "Q41171672",
|
||||
"brand:wikipedia": "en:Aldi",
|
||||
"building": "retail",
|
||||
"check_date:opening_hours": "2023-05-05",
|
||||
"full_name": "ALDI Hamilton",
|
||||
"landuse": "retail",
|
||||
"name": "ALDI",
|
||||
"opening_hours": "Mo-We,Fr-Su 08:30-20:00; Th 08:30-21:00",
|
||||
"shop": "supermarket",
|
||||
"website": "https://store.aldi.com.au/nsw/hamilton/11-swan-street",
|
||||
"wheelchair": "yes"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "way",
|
||||
"id": 329988023,
|
||||
"center": {
|
||||
"lat": -32.9182007,
|
||||
"lon": 151.7465886
|
||||
},
|
||||
"nodes": [
|
||||
3369516642,
|
||||
3369516641,
|
||||
11384237318,
|
||||
3369516635,
|
||||
3369516636,
|
||||
3369516642
|
||||
],
|
||||
"tags": {
|
||||
"building": "commercial",
|
||||
"name": "Empire Furniture Newcastle",
|
||||
"shop": "furniture"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "way",
|
||||
"id": 330881040,
|
||||
"center": {
|
||||
"lat": -32.9171257,
|
||||
"lon": 151.7320175
|
||||
},
|
||||
"nodes": [
|
||||
3379178024,
|
||||
3379178025,
|
||||
11206155925,
|
||||
11206155926,
|
||||
3379178028,
|
||||
3379178027,
|
||||
3379178024
|
||||
],
|
||||
"tags": {
|
||||
"amenity": "cafe",
|
||||
"building": "commercial",
|
||||
"name": "Crema Coffee House"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "way",
|
||||
"id": 334998496,
|
||||
"center": {
|
||||
"lat": -32.9256878,
|
||||
"lon": 151.7569896
|
||||
},
|
||||
"nodes": [
|
||||
3421299804,
|
||||
11403848649,
|
||||
3421299802,
|
||||
11403848650,
|
||||
3421299803,
|
||||
3421299805,
|
||||
8229426274,
|
||||
3421299804
|
||||
],
|
||||
"tags": {
|
||||
"branch": "Sofas",
|
||||
"brand": "Plush",
|
||||
"brand:wikidata": "Q111081152",
|
||||
"building": "commercial",
|
||||
"name": "Plush",
|
||||
"shop": "furniture"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "way",
|
||||
"id": 336940406,
|
||||
"center": {
|
||||
"lat": -32.915699,
|
||||
"lon": 151.732786
|
||||
},
|
||||
"nodes": [
|
||||
3440293013,
|
||||
3440292984,
|
||||
3440292983,
|
||||
3440292982,
|
||||
3440293045,
|
||||
3440293024,
|
||||
3440293013
|
||||
],
|
||||
"tags": {
|
||||
"building": "retail",
|
||||
"contact:facebook": "https://www.facebook.com/AstroTileNewcastle/",
|
||||
"name": "Astro Tile Newcastle",
|
||||
"shop": "trade",
|
||||
"trade": "tiles"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "way",
|
||||
"id": 370660542,
|
||||
"center": {
|
||||
"lat": -32.9250385,
|
||||
"lon": 151.7339859
|
||||
},
|
||||
"nodes": [
|
||||
3743565333,
|
||||
3743565335,
|
||||
3743565328,
|
||||
3743565326,
|
||||
3743560016,
|
||||
3743560017,
|
||||
3743565331,
|
||||
3743565330,
|
||||
3743565336,
|
||||
3743565337,
|
||||
3743565350,
|
||||
3743565349,
|
||||
3743565333
|
||||
],
|
||||
"tags": {
|
||||
"brand": "Harvey Norman",
|
||||
"brand:wikidata": "Q4040441",
|
||||
"building": "yes",
|
||||
"name": "Harvey Norman",
|
||||
"shop": "electronics"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "way",
|
||||
"id": 370660545,
|
||||
"center": {
|
||||
"lat": -32.9250096,
|
||||
"lon": 151.7347638
|
||||
},
|
||||
"nodes": [
|
||||
3743569412,
|
||||
3743560021,
|
||||
3743560022,
|
||||
3743565339,
|
||||
3743565338,
|
||||
3743569412
|
||||
],
|
||||
"tags": {
|
||||
"brand": "Cash Converters",
|
||||
"brand:wikidata": "Q5048645",
|
||||
"building": "yes",
|
||||
"name": "Cash Converters",
|
||||
"shop": "pawnbroker"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "way",
|
||||
"id": 377809581,
|
||||
"center": {
|
||||
"lat": -32.9136842,
|
||||
"lon": 151.7470167
|
||||
},
|
||||
"nodes": [
|
||||
3812081538,
|
||||
3812077953,
|
||||
3812081537,
|
||||
3812081541,
|
||||
3812081539,
|
||||
3812081540,
|
||||
3812081538
|
||||
],
|
||||
"tags": {
|
||||
"landuse": "retail",
|
||||
"name": "Purnell Trophie & Giftware",
|
||||
"shop": "variety_store"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "way",
|
||||
"id": 377809743,
|
||||
"center": {
|
||||
"lat": -32.914817,
|
||||
"lon": 151.7477229
|
||||
},
|
||||
"nodes": [
|
||||
3812076824,
|
||||
3812076823,
|
||||
3812076825,
|
||||
3812076826,
|
||||
3812076824
|
||||
],
|
||||
"tags": {
|
||||
"building": "retail",
|
||||
"email": "newcastle@tinks.com.au",
|
||||
"landuse": "industrial",
|
||||
"name": "Tink's - Newcastle (Newcastle Chainsaw & Mowers)",
|
||||
"opening_hours": "Mo-Fr 08:00-17:00, Sa 08:00-14:00",
|
||||
"operator": "Tink's Group",
|
||||
"payment:afterpay": "yes",
|
||||
"payment:apple_pay": "yes",
|
||||
"payment:credit_cards": "yes",
|
||||
"payment:debit_cards": "yes",
|
||||
"payment:google_pay": "yes",
|
||||
"payment:openpay": "yes",
|
||||
"payment:zipmoney": "yes",
|
||||
"phone": "+61 2 4961 1332",
|
||||
"shop": "hardware",
|
||||
"website": "https://www.tinks.com.au"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "way",
|
||||
"id": 377811131,
|
||||
"center": {
|
||||
"lat": -32.9169724,
|
||||
"lon": 151.7491715
|
||||
},
|
||||
"nodes": [
|
||||
3812092799,
|
||||
3410550229,
|
||||
3812092639,
|
||||
3812092800,
|
||||
3812092799
|
||||
],
|
||||
"tags": {
|
||||
"landuse": "retail",
|
||||
"name": "Mei Li Furniture & Collectables",
|
||||
"shop": "antiques"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "way",
|
||||
"id": 377811132,
|
||||
"center": {
|
||||
"lat": -32.9168849,
|
||||
"lon": 151.7491025
|
||||
},
|
||||
"nodes": [
|
||||
3812095511,
|
||||
3812095510,
|
||||
3812095508,
|
||||
3812095509,
|
||||
3812095511
|
||||
],
|
||||
"tags": {
|
||||
"landuse": "retail",
|
||||
"name": "Yardsale",
|
||||
"shop": "antiques"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "way",
|
||||
"id": 377811362,
|
||||
"center": {
|
||||
"lat": -32.9168403,
|
||||
"lon": 151.7490657
|
||||
},
|
||||
"nodes": [
|
||||
3812095510,
|
||||
3812092638,
|
||||
3812092798,
|
||||
3812095508,
|
||||
3812095510
|
||||
],
|
||||
"tags": {
|
||||
"landuse": "retail",
|
||||
"name": "Holahan Antiques",
|
||||
"shop": "antiques"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "way",
|
||||
"id": 377811366,
|
||||
"center": {
|
||||
"lat": -32.9169139,
|
||||
"lon": 151.7491261
|
||||
},
|
||||
"nodes": [
|
||||
3812095509,
|
||||
3812092799,
|
||||
3812092800,
|
||||
3812095511,
|
||||
3812095509
|
||||
],
|
||||
"tags": {
|
||||
"landuse": "retail",
|
||||
"name": "Earths Garden Organics",
|
||||
"shop": "convenience"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "way",
|
||||
"id": 377811647,
|
||||
"center": {
|
||||
"lat": -32.9175394,
|
||||
"lon": 151.7493128
|
||||
},
|
||||
"nodes": [
|
||||
3812097488,
|
||||
3812135409,
|
||||
3812135411,
|
||||
3812097491,
|
||||
3812097488
|
||||
],
|
||||
"tags": {
|
||||
"building": "yes",
|
||||
"name": "Regent Renovators",
|
||||
"shop": "hardware"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "way",
|
||||
"id": 377812394,
|
||||
"center": {
|
||||
"lat": -32.9181308,
|
||||
"lon": 151.7496607
|
||||
},
|
||||
"nodes": [
|
||||
3812104221,
|
||||
3727826406,
|
||||
3727826407,
|
||||
8195806812,
|
||||
3812104224,
|
||||
3812104225,
|
||||
3812104223,
|
||||
8195806809,
|
||||
3812104221
|
||||
],
|
||||
"tags": {
|
||||
"landuse": "commercial",
|
||||
"name": "Reece",
|
||||
"shop": "hardware"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "way",
|
||||
"id": 377814336,
|
||||
"center": {
|
||||
"lat": -32.9174539,
|
||||
"lon": 151.748783
|
||||
},
|
||||
"nodes": [
|
||||
3812127767,
|
||||
3812127762,
|
||||
3812127761,
|
||||
3812127765,
|
||||
3812127767
|
||||
],
|
||||
"tags": {
|
||||
"building": "retail",
|
||||
"name": "It\u2019s Just Because By Heather",
|
||||
"shop": "variety_store"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "way",
|
||||
"id": 377817467,
|
||||
"center": {
|
||||
"lat": -32.9169012,
|
||||
"lon": 151.74975
|
||||
},
|
||||
"nodes": [
|
||||
3812170975,
|
||||
3812129989,
|
||||
3812170974,
|
||||
3812170976,
|
||||
3812170975
|
||||
],
|
||||
"tags": {
|
||||
"landuse": "retail",
|
||||
"name": "Islington Antiques",
|
||||
"shop": "antiques"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "way",
|
||||
"id": 377817468,
|
||||
"center": {
|
||||
"lat": -32.9164537,
|
||||
"lon": 151.7494586
|
||||
},
|
||||
"nodes": [
|
||||
3812188098,
|
||||
3812188097,
|
||||
3812188099,
|
||||
3812188100,
|
||||
3812188098
|
||||
],
|
||||
"tags": {
|
||||
"landuse": "retail",
|
||||
"name": "Combined Collectables",
|
||||
"shop": "antiques"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "way",
|
||||
"id": 377818801,
|
||||
"center": {
|
||||
"lat": -32.9162029,
|
||||
"lon": 151.7492962
|
||||
},
|
||||
"nodes": [
|
||||
3812188095,
|
||||
7935349537,
|
||||
3812188093,
|
||||
3812188094,
|
||||
3812188096,
|
||||
3812188095
|
||||
],
|
||||
"tags": {
|
||||
"landuse": "retail",
|
||||
"name": "Saint Vincents de Paul",
|
||||
"shop": "clothes"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
106549
web/assets/towns/_raw/newcastle_real.roads.overpass.json
Normal file
5534
web/assets/towns/_raw/northbridge_real.overpass.json
Normal file
178722
web/assets/towns/_raw/northbridge_real.roads.overpass.json
Normal file
2869
web/assets/towns/_raw/westend_real.overpass.json
Normal file
150591
web/assets/towns/_raw/westend_real.roads.overpass.json
Normal file
1169
web/assets/towns/_raw/wollongong_real.overpass.json
Normal file
116373
web/assets/towns/_raw/wollongong_real.roads.overpass.json
Normal file
41509
web/assets/towns/adelaide_real.json
Normal file
43036
web/assets/towns/ballarat_real.json
Normal file
@ -18,11 +18,22 @@
|
||||
"maxLon": 144.30011618044566
|
||||
},
|
||||
"counts": {
|
||||
"raw": 9,
|
||||
"shops": 9,
|
||||
"raw": 120,
|
||||
"shops": 36,
|
||||
"heroes": 9,
|
||||
"texture": 27,
|
||||
"textureDropped": 81,
|
||||
"roads": 1552
|
||||
},
|
||||
"shops": [
|
||||
{
|
||||
"id": 51469190,
|
||||
"name": "Woolworths",
|
||||
"type": "dept",
|
||||
"lat": -36.766914,
|
||||
"lon": 144.265197,
|
||||
"suburb": "Ironbark"
|
||||
},
|
||||
{
|
||||
"id": 329659785,
|
||||
"name": "Slave-ohs",
|
||||
@ -31,6 +42,22 @@
|
||||
"lon": 144.262936,
|
||||
"suburb": "Ironbark"
|
||||
},
|
||||
{
|
||||
"id": 470420143,
|
||||
"name": "Cafe 307",
|
||||
"type": "milkbar",
|
||||
"lat": -36.771517,
|
||||
"lon": 144.262027,
|
||||
"suburb": "Ironbark"
|
||||
},
|
||||
{
|
||||
"id": 826091288,
|
||||
"name": "The Old Green Bean",
|
||||
"type": "milkbar",
|
||||
"lat": -36.75875,
|
||||
"lon": 144.284821,
|
||||
"suburb": "Bendigo"
|
||||
},
|
||||
{
|
||||
"id": 827550052,
|
||||
"name": "Sinnies",
|
||||
@ -39,6 +66,70 @@
|
||||
"lon": 144.286017,
|
||||
"suburb": "Bendigo"
|
||||
},
|
||||
{
|
||||
"id": 829687117,
|
||||
"name": "Asian Foods Emporium Grocery",
|
||||
"type": "dept",
|
||||
"lat": -36.760588,
|
||||
"lon": 144.283087,
|
||||
"suburb": "Bendigo"
|
||||
},
|
||||
{
|
||||
"id": 835692264,
|
||||
"name": "The Subtle Eye",
|
||||
"type": "milkbar",
|
||||
"lat": -36.757863,
|
||||
"lon": 144.28179,
|
||||
"suburb": "Bendigo"
|
||||
},
|
||||
{
|
||||
"id": 837682665,
|
||||
"name": "Everbean Caf\u00e9",
|
||||
"type": "milkbar",
|
||||
"lat": -36.76049,
|
||||
"lon": 144.281758,
|
||||
"suburb": "Bendigo"
|
||||
},
|
||||
{
|
||||
"id": 837704574,
|
||||
"name": "Noone Imagewear",
|
||||
"type": "dept",
|
||||
"lat": -36.761008,
|
||||
"lon": 144.281254,
|
||||
"suburb": "Bendigo"
|
||||
},
|
||||
{
|
||||
"id": 838398536,
|
||||
"name": "JM Leech Jewellers",
|
||||
"type": "dept",
|
||||
"lat": -36.759482,
|
||||
"lon": 144.280567,
|
||||
"suburb": "Bendigo"
|
||||
},
|
||||
{
|
||||
"id": 838398546,
|
||||
"name": "shop 12 boutique",
|
||||
"type": "dept",
|
||||
"lat": -36.75916,
|
||||
"lon": 144.280448,
|
||||
"suburb": "Bendigo"
|
||||
},
|
||||
{
|
||||
"id": 838844008,
|
||||
"name": "Virtu",
|
||||
"type": "dept",
|
||||
"lat": -36.75983,
|
||||
"lon": 144.281265,
|
||||
"suburb": "Bendigo"
|
||||
},
|
||||
{
|
||||
"id": 1289247489,
|
||||
"name": "Bendigo Original Pie Shop",
|
||||
"type": "milkbar",
|
||||
"lat": -36.759358,
|
||||
"lon": 144.280841,
|
||||
"suburb": "Bendigo"
|
||||
},
|
||||
{
|
||||
"id": 1443367904,
|
||||
"name": "Book Now",
|
||||
@ -47,6 +138,38 @@
|
||||
"lon": 144.282307,
|
||||
"suburb": "Bendigo"
|
||||
},
|
||||
{
|
||||
"id": 4952033541,
|
||||
"name": "Favourite Flavours",
|
||||
"type": "milkbar",
|
||||
"lat": -36.749749,
|
||||
"lon": 144.290565,
|
||||
"suburb": "Bendigo"
|
||||
},
|
||||
{
|
||||
"id": 7756585874,
|
||||
"name": "APM employment services",
|
||||
"type": "dept",
|
||||
"lat": -36.759976,
|
||||
"lon": 144.282313,
|
||||
"suburb": "Bendigo"
|
||||
},
|
||||
{
|
||||
"id": 7816837806,
|
||||
"name": "Lollies2Go",
|
||||
"type": "milkbar",
|
||||
"lat": -36.76132,
|
||||
"lon": 144.282124,
|
||||
"suburb": "Bendigo"
|
||||
},
|
||||
{
|
||||
"id": 7816868914,
|
||||
"name": "The Erindale Farm Butchery",
|
||||
"type": "milkbar",
|
||||
"lat": -36.761341,
|
||||
"lon": 144.282266,
|
||||
"suburb": "Bendigo"
|
||||
},
|
||||
{
|
||||
"id": 7816868915,
|
||||
"name": "Red Floss Shop",
|
||||
@ -55,6 +178,54 @@
|
||||
"lon": 144.282046,
|
||||
"suburb": "Bendigo"
|
||||
},
|
||||
{
|
||||
"id": 7816868916,
|
||||
"name": "Bakers Delight",
|
||||
"type": "milkbar",
|
||||
"lat": -36.761241,
|
||||
"lon": 144.282231,
|
||||
"suburb": "Bendigo"
|
||||
},
|
||||
{
|
||||
"id": 7821369727,
|
||||
"name": "The Flower Spot Bendigo",
|
||||
"type": "stall",
|
||||
"lat": -36.760911,
|
||||
"lon": 144.281603,
|
||||
"suburb": "Bendigo"
|
||||
},
|
||||
{
|
||||
"id": 7828645967,
|
||||
"name": "Bakery Express Caffe",
|
||||
"type": "milkbar",
|
||||
"lat": -36.760079,
|
||||
"lon": 144.281709,
|
||||
"suburb": "Bendigo"
|
||||
},
|
||||
{
|
||||
"id": 7828645972,
|
||||
"name": "Stearns Showcase Jewellers",
|
||||
"type": "dept",
|
||||
"lat": -36.760227,
|
||||
"lon": 144.280907,
|
||||
"suburb": "Bendigo"
|
||||
},
|
||||
{
|
||||
"id": 7828645974,
|
||||
"name": "Gaijin Eatery",
|
||||
"type": "milkbar",
|
||||
"lat": -36.760424,
|
||||
"lon": 144.281128,
|
||||
"suburb": "Bendigo"
|
||||
},
|
||||
{
|
||||
"id": 8040964387,
|
||||
"name": "The Plaza Lady Salon",
|
||||
"type": "dept",
|
||||
"lat": -36.76003,
|
||||
"lon": 144.278691,
|
||||
"suburb": "Bendigo"
|
||||
},
|
||||
{
|
||||
"id": 8040967386,
|
||||
"name": "Dymocks",
|
||||
@ -71,6 +242,30 @@
|
||||
"lon": 144.276186,
|
||||
"suburb": "Bendigo"
|
||||
},
|
||||
{
|
||||
"id": 8089526650,
|
||||
"name": "Magnolia Lane",
|
||||
"type": "milkbar",
|
||||
"lat": -36.75956,
|
||||
"lon": 144.278963,
|
||||
"suburb": "Bendigo"
|
||||
},
|
||||
{
|
||||
"id": 8090307856,
|
||||
"name": "Bendigo Hat Shop",
|
||||
"type": "dept",
|
||||
"lat": -36.760607,
|
||||
"lon": 144.276984,
|
||||
"suburb": "Bendigo"
|
||||
},
|
||||
{
|
||||
"id": 8090344402,
|
||||
"name": "Zenbu",
|
||||
"type": "dept",
|
||||
"lat": -36.759938,
|
||||
"lon": 144.27843,
|
||||
"suburb": "Bendigo"
|
||||
},
|
||||
{
|
||||
"id": 8225415317,
|
||||
"name": "Bat City Music",
|
||||
@ -79,6 +274,14 @@
|
||||
"lon": 144.277469,
|
||||
"suburb": "Bendigo"
|
||||
},
|
||||
{
|
||||
"id": 8649203078,
|
||||
"name": "Jay Jays",
|
||||
"type": "dept",
|
||||
"lat": -36.765669,
|
||||
"lon": 144.280466,
|
||||
"suburb": "Bendigo"
|
||||
},
|
||||
{
|
||||
"id": 8649203082,
|
||||
"name": "EB Games",
|
||||
@ -87,6 +290,14 @@
|
||||
"lon": 144.280806,
|
||||
"suburb": "Bendigo"
|
||||
},
|
||||
{
|
||||
"id": 8649203084,
|
||||
"name": "The Body Shop",
|
||||
"type": "milkbar",
|
||||
"lat": -36.76569,
|
||||
"lon": 144.280177,
|
||||
"suburb": "Bendigo"
|
||||
},
|
||||
{
|
||||
"id": 12421060314,
|
||||
"name": "Cashtheists",
|
||||
@ -94,6 +305,14 @@
|
||||
"lat": -36.759374,
|
||||
"lon": 144.27927,
|
||||
"suburb": "Bendigo"
|
||||
},
|
||||
{
|
||||
"id": 13646608627,
|
||||
"name": "Reddy Express",
|
||||
"type": "milkbar",
|
||||
"lat": -36.756946,
|
||||
"lon": 144.292295,
|
||||
"suburb": "Bendigo"
|
||||
}
|
||||
],
|
||||
"roads": [
|
||||
|
||||
9816
web/assets/towns/bowral_real.json
Normal file
31090
web/assets/towns/braddon_real.json
Normal file
31816
web/assets/towns/brunswick_real.json
Normal file
@ -18,18 +18,77 @@
|
||||
"maxLon": 144.23393501737684
|
||||
},
|
||||
"counts": {
|
||||
"raw": 6,
|
||||
"shops": 6,
|
||||
"raw": 67,
|
||||
"shops": 24,
|
||||
"heroes": 6,
|
||||
"texture": 18,
|
||||
"textureDropped": 43,
|
||||
"roads": 397
|
||||
},
|
||||
"shops": [
|
||||
{
|
||||
"id": 26808662,
|
||||
"name": "Maxi IGA",
|
||||
"type": "dept",
|
||||
"lat": -37.066448,
|
||||
"lon": 144.218622,
|
||||
"suburb": "Castlemaine"
|
||||
},
|
||||
{
|
||||
"id": 41889132,
|
||||
"name": "IGA",
|
||||
"type": "dept",
|
||||
"lat": -37.062981,
|
||||
"lon": 144.216933,
|
||||
"suburb": "Castlemaine"
|
||||
},
|
||||
{
|
||||
"id": 315282080,
|
||||
"name": "Stonemans Bookroom",
|
||||
"type": "book",
|
||||
"lat": -37.065704,
|
||||
"lon": 144.219673,
|
||||
"suburb": ""
|
||||
"suburb": "Castlemaine"
|
||||
},
|
||||
{
|
||||
"id": 315282082,
|
||||
"name": "Castlemaine Floristry",
|
||||
"type": "stall",
|
||||
"lat": -37.065727,
|
||||
"lon": 144.2199,
|
||||
"suburb": "Castlemaine"
|
||||
},
|
||||
{
|
||||
"id": 446991894,
|
||||
"name": "BP Shop",
|
||||
"type": "milkbar",
|
||||
"lat": -37.062781,
|
||||
"lon": 144.217691,
|
||||
"suburb": "Castlemaine"
|
||||
},
|
||||
{
|
||||
"id": 834631308,
|
||||
"name": "K Hub",
|
||||
"type": "dept",
|
||||
"lat": -37.065327,
|
||||
"lon": 144.217675,
|
||||
"suburb": "Castlemaine"
|
||||
},
|
||||
{
|
||||
"id": 4714218689,
|
||||
"name": "Hot & Crusty Bakery",
|
||||
"type": "milkbar",
|
||||
"lat": -37.064863,
|
||||
"lon": 144.217151,
|
||||
"suburb": "Castlemaine"
|
||||
},
|
||||
{
|
||||
"id": 8909393346,
|
||||
"name": "Long Paddock Cheese",
|
||||
"type": "milkbar",
|
||||
"lat": -37.056072,
|
||||
"lon": 144.2137,
|
||||
"suburb": "Castlemaine"
|
||||
},
|
||||
{
|
||||
"id": 9231704552,
|
||||
@ -37,7 +96,7 @@
|
||||
"type": "opshop",
|
||||
"lat": -37.062363,
|
||||
"lon": 144.217526,
|
||||
"suburb": ""
|
||||
"suburb": "Castlemaine"
|
||||
},
|
||||
{
|
||||
"id": 11699200337,
|
||||
@ -45,7 +104,15 @@
|
||||
"type": "opshop",
|
||||
"lat": -37.063201,
|
||||
"lon": 144.218163,
|
||||
"suburb": ""
|
||||
"suburb": "Castlemaine"
|
||||
},
|
||||
{
|
||||
"id": 11699208635,
|
||||
"name": "Just Because She Said So",
|
||||
"type": "dept",
|
||||
"lat": -37.05557,
|
||||
"lon": 144.21891,
|
||||
"suburb": "Castlemaine"
|
||||
},
|
||||
{
|
||||
"id": 11699208641,
|
||||
@ -53,7 +120,31 @@
|
||||
"type": "book",
|
||||
"lat": -37.061647,
|
||||
"lon": 144.217247,
|
||||
"suburb": ""
|
||||
"suburb": "Castlemaine"
|
||||
},
|
||||
{
|
||||
"id": 11699225335,
|
||||
"name": "Mcshanag\u2019s",
|
||||
"type": "milkbar",
|
||||
"lat": -37.064127,
|
||||
"lon": 144.216813,
|
||||
"suburb": "Castlemaine"
|
||||
},
|
||||
{
|
||||
"id": 11699230554,
|
||||
"name": "Castlemaine Fresh",
|
||||
"type": "stall",
|
||||
"lat": -37.065636,
|
||||
"lon": 144.218453,
|
||||
"suburb": "Castlemaine"
|
||||
},
|
||||
{
|
||||
"id": 11699233860,
|
||||
"name": "The Dove Country Eats",
|
||||
"type": "milkbar",
|
||||
"lat": -37.060987,
|
||||
"lon": 144.220007,
|
||||
"suburb": "Castlemaine"
|
||||
},
|
||||
{
|
||||
"id": 11699237531,
|
||||
@ -61,7 +152,39 @@
|
||||
"type": "opshop",
|
||||
"lat": -37.072912,
|
||||
"lon": 144.206493,
|
||||
"suburb": ""
|
||||
"suburb": "Castlemaine"
|
||||
},
|
||||
{
|
||||
"id": 11699277526,
|
||||
"name": "Oakwood Smallgoods Co.",
|
||||
"type": "milkbar",
|
||||
"lat": -37.0563,
|
||||
"lon": 144.214,
|
||||
"suburb": "Castlemaine"
|
||||
},
|
||||
{
|
||||
"id": 11735383822,
|
||||
"name": "Mutual Muse",
|
||||
"type": "dept",
|
||||
"lat": -37.066927,
|
||||
"lon": 144.219421,
|
||||
"suburb": "Castlemaine"
|
||||
},
|
||||
{
|
||||
"id": 11735383825,
|
||||
"name": "Little General Kids",
|
||||
"type": "dept",
|
||||
"lat": -37.066771,
|
||||
"lon": 144.219427,
|
||||
"suburb": "Castlemaine"
|
||||
},
|
||||
{
|
||||
"id": 11735383839,
|
||||
"name": "SAFFS Cafe",
|
||||
"type": "milkbar",
|
||||
"lat": -37.066167,
|
||||
"lon": 144.220226,
|
||||
"suburb": "Castlemaine"
|
||||
},
|
||||
{
|
||||
"id": 11760538035,
|
||||
@ -69,7 +192,31 @@
|
||||
"type": "opshop",
|
||||
"lat": -37.067129,
|
||||
"lon": 144.217625,
|
||||
"suburb": ""
|
||||
"suburb": "Castlemaine"
|
||||
},
|
||||
{
|
||||
"id": 11934078464,
|
||||
"name": "Lazy Bones",
|
||||
"type": "milkbar",
|
||||
"lat": -37.062982,
|
||||
"lon": 144.216481,
|
||||
"suburb": "Castlemaine"
|
||||
},
|
||||
{
|
||||
"id": 12740901493,
|
||||
"name": "Frankie Espresso",
|
||||
"type": "milkbar",
|
||||
"lat": -37.056662,
|
||||
"lon": 144.21462,
|
||||
"suburb": "Castlemaine"
|
||||
},
|
||||
{
|
||||
"id": 12740975418,
|
||||
"name": "Hand Picked Floristry",
|
||||
"type": "stall",
|
||||
"lat": -37.056857,
|
||||
"lon": 144.214144,
|
||||
"suburb": "Castlemaine"
|
||||
}
|
||||
],
|
||||
"roads": [
|
||||
|
||||
14134
web/assets/towns/darwin_real.json
Normal file
7751
web/assets/towns/daylesford_real.json
Normal file
48447
web/assets/towns/fitzroy_real.json
Normal file
@ -18,8 +18,11 @@
|
||||
"maxLon": 115.76181856816406
|
||||
},
|
||||
"counts": {
|
||||
"raw": 21,
|
||||
"shops": 21,
|
||||
"raw": 163,
|
||||
"shops": 80,
|
||||
"heroes": 20,
|
||||
"texture": 60,
|
||||
"textureDropped": 81,
|
||||
"roads": 622
|
||||
},
|
||||
"shops": [
|
||||
@ -31,6 +34,14 @@
|
||||
"lon": 115.747749,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 140484887,
|
||||
"name": "Lawley's Cafe & Bakery",
|
||||
"type": "milkbar",
|
||||
"lat": -32.059502,
|
||||
"lon": 115.751724,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 310008675,
|
||||
"name": "Slave-ohs",
|
||||
@ -39,6 +50,22 @@
|
||||
"lon": 115.74845,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 322574866,
|
||||
"name": "Gallatis'",
|
||||
"type": "milkbar",
|
||||
"lat": -32.060257,
|
||||
"lon": 115.752796,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 341104307,
|
||||
"name": "Officeworks",
|
||||
"type": "milkbar",
|
||||
"lat": -32.045158,
|
||||
"lon": 115.752556,
|
||||
"suburb": "East Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 592578660,
|
||||
"name": "Elizabeth's Warehouse",
|
||||
@ -47,6 +74,30 @@
|
||||
"lon": 115.750901,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 600753458,
|
||||
"name": "kazoomies",
|
||||
"type": "milkbar",
|
||||
"lat": -32.053388,
|
||||
"lon": 115.742238,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 600826931,
|
||||
"name": "San Churro",
|
||||
"type": "milkbar",
|
||||
"lat": -32.055751,
|
||||
"lon": 115.747406,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 662689900,
|
||||
"name": "Gino's",
|
||||
"type": "milkbar",
|
||||
"lat": -32.055597,
|
||||
"lon": 115.747122,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 976912099,
|
||||
"name": "Toyworld",
|
||||
@ -55,6 +106,46 @@
|
||||
"lon": 115.749569,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 1029226779,
|
||||
"name": "D'Angelo Cafe",
|
||||
"type": "milkbar",
|
||||
"lat": -32.053672,
|
||||
"lon": 115.749625,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 1112796032,
|
||||
"name": "Plated",
|
||||
"type": "milkbar",
|
||||
"lat": -32.047705,
|
||||
"lon": 115.754197,
|
||||
"suburb": "East Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 1176518483,
|
||||
"name": "Whistle Stop",
|
||||
"type": "milkbar",
|
||||
"lat": -32.052132,
|
||||
"lon": 115.745259,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 2535026143,
|
||||
"name": "Ethos Deli & Dining Room",
|
||||
"type": "milkbar",
|
||||
"lat": -32.043992,
|
||||
"lon": 115.761648,
|
||||
"suburb": "East Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 2774072502,
|
||||
"name": "Freo Merchant",
|
||||
"type": "milkbar",
|
||||
"lat": -32.05319,
|
||||
"lon": 115.741799,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 3144618368,
|
||||
"name": "Elizabeth's",
|
||||
@ -63,6 +154,30 @@
|
||||
"lon": 115.747192,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 3329420423,
|
||||
"name": "Leisure Cafe",
|
||||
"type": "milkbar",
|
||||
"lat": -32.047955,
|
||||
"lon": 115.75238,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 4364304694,
|
||||
"name": "Two Fat Wombats",
|
||||
"type": "milkbar",
|
||||
"lat": -32.053674,
|
||||
"lon": 115.74582,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 4601037690,
|
||||
"name": "Blue Lotus",
|
||||
"type": "milkbar",
|
||||
"lat": -32.062758,
|
||||
"lon": 115.757131,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 4601037692,
|
||||
"name": "New Edition Bookshop",
|
||||
@ -71,6 +186,30 @@
|
||||
"lon": 115.744283,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 4601038092,
|
||||
"name": "Cafe on Hampton",
|
||||
"type": "milkbar",
|
||||
"lat": -32.06219,
|
||||
"lon": 115.756841,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 4601049291,
|
||||
"name": "Denim/Store",
|
||||
"type": "dept",
|
||||
"lat": -32.054757,
|
||||
"lon": 115.745966,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 4601057690,
|
||||
"name": "Hush",
|
||||
"type": "milkbar",
|
||||
"lat": -32.054802,
|
||||
"lon": 115.745114,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 4601057991,
|
||||
"name": "Bill Campbell Books",
|
||||
@ -79,6 +218,14 @@
|
||||
"lon": 115.744405,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 4637114093,
|
||||
"name": "The Prestige Italian Caf\u00e8",
|
||||
"type": "milkbar",
|
||||
"lat": -32.055089,
|
||||
"lon": 115.742432,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 5026468221,
|
||||
"name": "Mothballs Vintage",
|
||||
@ -87,6 +234,62 @@
|
||||
"lon": 115.747843,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 5179250521,
|
||||
"name": "Fremantle Convenience Store",
|
||||
"type": "milkbar",
|
||||
"lat": -32.053175,
|
||||
"lon": 115.747879,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 5219523076,
|
||||
"name": "Abhi's Bread",
|
||||
"type": "milkbar",
|
||||
"lat": -32.065622,
|
||||
"lon": 115.752583,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 5321919673,
|
||||
"name": "Gorman",
|
||||
"type": "dept",
|
||||
"lat": -32.054756,
|
||||
"lon": 115.746702,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 5321920048,
|
||||
"name": "Terrace Men",
|
||||
"type": "dept",
|
||||
"lat": -32.054894,
|
||||
"lon": 115.746462,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 5321921316,
|
||||
"name": "Culley's",
|
||||
"type": "milkbar",
|
||||
"lat": -32.054222,
|
||||
"lon": 115.746884,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 5321921457,
|
||||
"name": "Breglia Piccolo's Lunch Bar",
|
||||
"type": "milkbar",
|
||||
"lat": -32.055217,
|
||||
"lon": 115.743815,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 5883083585,
|
||||
"name": "Best Wishes",
|
||||
"type": "milkbar",
|
||||
"lat": -32.053391,
|
||||
"lon": 115.746519,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 5885985088,
|
||||
"name": "Cashtheists",
|
||||
@ -95,6 +298,30 @@
|
||||
"lon": 115.748787,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 6933734247,
|
||||
"name": "Kerfuffle",
|
||||
"type": "milkbar",
|
||||
"lat": -32.064294,
|
||||
"lon": 115.752551,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 6944071310,
|
||||
"name": "Vanilla Bean",
|
||||
"type": "milkbar",
|
||||
"lat": -32.059234,
|
||||
"lon": 115.750989,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 7196223177,
|
||||
"name": "Broody Hen",
|
||||
"type": "milkbar",
|
||||
"lat": -32.046988,
|
||||
"lon": 115.752531,
|
||||
"suburb": "East Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 8153813968,
|
||||
"name": "The Record Finder",
|
||||
@ -103,6 +330,38 @@
|
||||
"lon": 115.744369,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 8299402082,
|
||||
"name": "True Brew",
|
||||
"type": "milkbar",
|
||||
"lat": -32.056482,
|
||||
"lon": 115.749276,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 8808000382,
|
||||
"name": "No Escape Cafe",
|
||||
"type": "milkbar",
|
||||
"lat": -32.054938,
|
||||
"lon": 115.752908,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 8889079808,
|
||||
"name": "Little Sister",
|
||||
"type": "milkbar",
|
||||
"lat": -32.053654,
|
||||
"lon": 115.745975,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 9039407645,
|
||||
"name": "Freo Mart",
|
||||
"type": "milkbar",
|
||||
"lat": -32.056461,
|
||||
"lon": 115.749232,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 9156620320,
|
||||
"name": "Shave the Children",
|
||||
@ -111,6 +370,30 @@
|
||||
"lon": 115.752589,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 9156728321,
|
||||
"name": "Bodkin's Bootery",
|
||||
"type": "dept",
|
||||
"lat": -32.054769,
|
||||
"lon": 115.745231,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 9717067495,
|
||||
"name": "IGA",
|
||||
"type": "dept",
|
||||
"lat": -32.054498,
|
||||
"lon": 115.749252,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 9793980117,
|
||||
"name": "Little French Nest",
|
||||
"type": "milkbar",
|
||||
"lat": -32.052161,
|
||||
"lon": 115.749173,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 9794077228,
|
||||
"name": "Adorn Vintage",
|
||||
@ -127,6 +410,70 @@
|
||||
"lon": 115.744727,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 9797581350,
|
||||
"name": "Caffissimo",
|
||||
"type": "milkbar",
|
||||
"lat": -32.054604,
|
||||
"lon": 115.739298,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 9843343165,
|
||||
"name": "Unwind on Wray",
|
||||
"type": "milkbar",
|
||||
"lat": -32.059985,
|
||||
"lon": 115.75219,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 9843343167,
|
||||
"name": "Kartique Corner",
|
||||
"type": "dept",
|
||||
"lat": -32.059934,
|
||||
"lon": 115.752075,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 9843343170,
|
||||
"name": "Bukesh-la",
|
||||
"type": "dept",
|
||||
"lat": -32.059682,
|
||||
"lon": 115.752112,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 9878259117,
|
||||
"name": "Somes Newsageny",
|
||||
"type": "milkbar",
|
||||
"lat": -32.055098,
|
||||
"lon": 115.744827,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 9878259217,
|
||||
"name": "High on 55",
|
||||
"type": "milkbar",
|
||||
"lat": -32.055118,
|
||||
"lon": 115.744766,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 9947458058,
|
||||
"name": "Elk Fish Robotics",
|
||||
"type": "dept",
|
||||
"lat": -32.059517,
|
||||
"lon": 115.748414,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 10585254715,
|
||||
"name": "Red Floss Shop",
|
||||
"type": "dept",
|
||||
"lat": -32.053363,
|
||||
"lon": 115.749563,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 10585254716,
|
||||
"name": "Sinnies",
|
||||
@ -135,6 +482,30 @@
|
||||
"lon": 115.749669,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 10660056921,
|
||||
"name": "Mi-Life",
|
||||
"type": "dept",
|
||||
"lat": -32.053343,
|
||||
"lon": 115.74655,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 10833576345,
|
||||
"name": "Utopia Fremantle",
|
||||
"type": "milkbar",
|
||||
"lat": -32.055981,
|
||||
"lon": 115.747808,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 11323014781,
|
||||
"name": "Wear2",
|
||||
"type": "dept",
|
||||
"lat": -32.054542,
|
||||
"lon": 115.748308,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 11323015046,
|
||||
"name": "The Picker Concept",
|
||||
@ -143,6 +514,22 @@
|
||||
"lon": 115.748306,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 11323035617,
|
||||
"name": "Surf Dive 'n Ski",
|
||||
"type": "dept",
|
||||
"lat": -32.054122,
|
||||
"lon": 115.74721,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 11337000968,
|
||||
"name": "Telstra",
|
||||
"type": "dept",
|
||||
"lat": -32.054429,
|
||||
"lon": 115.747001,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 11337031591,
|
||||
"name": "Nifty in Freo",
|
||||
@ -152,11 +539,59 @@
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 11337040744,
|
||||
"name": "Red Floss Shop",
|
||||
"type": "opshop",
|
||||
"lat": -32.053937,
|
||||
"lon": 115.74609,
|
||||
"id": 11596694746,
|
||||
"name": "Festal Cafe",
|
||||
"type": "milkbar",
|
||||
"lat": -32.054923,
|
||||
"lon": 115.748535,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 11730692453,
|
||||
"name": "Floral Army",
|
||||
"type": "stall",
|
||||
"lat": -32.044197,
|
||||
"lon": 115.761144,
|
||||
"suburb": "East Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 11831943936,
|
||||
"name": "Amante & Co Coffee",
|
||||
"type": "milkbar",
|
||||
"lat": -32.062452,
|
||||
"lon": 115.752073,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 11890633269,
|
||||
"name": "T.O.P. Coffee and Tea",
|
||||
"type": "milkbar",
|
||||
"lat": -32.055047,
|
||||
"lon": 115.748329,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 12096135462,
|
||||
"name": "Fremantle Pharmacy",
|
||||
"type": "milkbar",
|
||||
"lat": -32.054664,
|
||||
"lon": 115.745559,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 12096277829,
|
||||
"name": "Three Stories",
|
||||
"type": "dept",
|
||||
"lat": -32.054661,
|
||||
"lon": 115.746332,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 12307062505,
|
||||
"name": "Cassia",
|
||||
"type": "milkbar",
|
||||
"lat": -32.055207,
|
||||
"lon": 115.749886,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
@ -167,14 +602,30 @@
|
||||
"lon": 115.749467,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 12389475293,
|
||||
"name": "Rustic Spoon",
|
||||
"type": "milkbar",
|
||||
"lat": -32.056402,
|
||||
"lon": 115.746922,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 12490289378,
|
||||
"name": "Warhammer",
|
||||
"type": "toy",
|
||||
"type": "video",
|
||||
"lat": -32.052216,
|
||||
"lon": 115.749274,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 12522399956,
|
||||
"name": "Fremantle Opals",
|
||||
"type": "dept",
|
||||
"lat": -32.054823,
|
||||
"lon": 115.746447,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 12787392414,
|
||||
"name": "Vintage On High",
|
||||
@ -190,6 +641,30 @@
|
||||
"lat": -32.053366,
|
||||
"lon": 115.747261,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 13064860607,
|
||||
"name": "Il Brutto",
|
||||
"type": "milkbar",
|
||||
"lat": -32.056303,
|
||||
"lon": 115.746115,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 13130252840,
|
||||
"name": "Ladies Auxilliary Kiosk",
|
||||
"type": "milkbar",
|
||||
"lat": -32.058524,
|
||||
"lon": 115.751161,
|
||||
"suburb": "Fremantle"
|
||||
},
|
||||
{
|
||||
"id": 13810284696,
|
||||
"name": "Perth Surf Vintage",
|
||||
"type": "dept",
|
||||
"lat": -32.055218,
|
||||
"lon": 115.748222,
|
||||
"suburb": "Fremantle"
|
||||
}
|
||||
],
|
||||
"roads": [
|
||||
|
||||
40401
web/assets/towns/geelong_real.json
Normal file
30958
web/assets/towns/glebe_real.json
Normal file
28677
web/assets/towns/hobart_real.json
Normal file
184
web/assets/towns/index.json
Normal file
@ -0,0 +1,184 @@
|
||||
{
|
||||
"schema": "procity-towns-index/1",
|
||||
"generated": "2026-07-16",
|
||||
"license": "ODbL 1.0",
|
||||
"attribution": "\u00a9 OpenStreetMap contributors",
|
||||
"towns": [
|
||||
{
|
||||
"key": "adelaide_real",
|
||||
"town": "Adelaide",
|
||||
"state": "SA",
|
||||
"shops": 116,
|
||||
"roads": true,
|
||||
"source": "osm"
|
||||
},
|
||||
{
|
||||
"key": "ballarat_real",
|
||||
"town": "Ballarat",
|
||||
"state": "VIC",
|
||||
"shops": 20,
|
||||
"roads": true,
|
||||
"source": "osm"
|
||||
},
|
||||
{
|
||||
"key": "bendigo_real",
|
||||
"town": "Bendigo",
|
||||
"state": "VIC",
|
||||
"shops": 36,
|
||||
"roads": true,
|
||||
"source": "osm"
|
||||
},
|
||||
{
|
||||
"key": "bowral_real",
|
||||
"town": "Bowral",
|
||||
"state": "NSW",
|
||||
"shops": 32,
|
||||
"roads": true,
|
||||
"source": "osm"
|
||||
},
|
||||
{
|
||||
"key": "braddon_real",
|
||||
"town": "Braddon",
|
||||
"state": "ACT",
|
||||
"shops": 20,
|
||||
"roads": true,
|
||||
"source": "osm"
|
||||
},
|
||||
{
|
||||
"key": "brunswick_real",
|
||||
"town": "Brunswick",
|
||||
"state": "VIC",
|
||||
"shops": 96,
|
||||
"roads": true,
|
||||
"source": "osm"
|
||||
},
|
||||
{
|
||||
"key": "castlemaine_real",
|
||||
"town": "Castlemaine",
|
||||
"state": "VIC",
|
||||
"shops": 24,
|
||||
"roads": true,
|
||||
"source": "osm"
|
||||
},
|
||||
{
|
||||
"key": "darwin_real",
|
||||
"town": "Darwin",
|
||||
"state": "NT",
|
||||
"shops": 12,
|
||||
"roads": true,
|
||||
"source": "osm"
|
||||
},
|
||||
{
|
||||
"key": "daylesford_real",
|
||||
"town": "Daylesford",
|
||||
"state": "VIC",
|
||||
"shops": 32,
|
||||
"roads": true,
|
||||
"source": "osm"
|
||||
},
|
||||
{
|
||||
"key": "fitzroy_real",
|
||||
"town": "Fitzroy",
|
||||
"state": "VIC",
|
||||
"shops": 160,
|
||||
"roads": true,
|
||||
"source": "osm"
|
||||
},
|
||||
{
|
||||
"key": "fremantle_real",
|
||||
"town": "Fremantle",
|
||||
"state": "WA",
|
||||
"shops": 80,
|
||||
"roads": true,
|
||||
"source": "osm"
|
||||
},
|
||||
{
|
||||
"key": "geelong_real",
|
||||
"town": "Geelong",
|
||||
"state": "VIC",
|
||||
"shops": 68,
|
||||
"roads": true,
|
||||
"source": "osm"
|
||||
},
|
||||
{
|
||||
"key": "glebe_real",
|
||||
"town": "Glebe",
|
||||
"state": "NSW",
|
||||
"shops": 52,
|
||||
"roads": true,
|
||||
"source": "osm"
|
||||
},
|
||||
{
|
||||
"key": "hobart_real",
|
||||
"town": "Hobart",
|
||||
"state": "TAS",
|
||||
"shops": 80,
|
||||
"roads": true,
|
||||
"source": "osm"
|
||||
},
|
||||
{
|
||||
"key": "katoomba_real",
|
||||
"town": "Katoomba",
|
||||
"state": "NSW",
|
||||
"shops": 80,
|
||||
"roads": true,
|
||||
"source": "osm"
|
||||
},
|
||||
{
|
||||
"key": "launceston_real",
|
||||
"town": "Launceston",
|
||||
"state": "TAS",
|
||||
"shops": 32,
|
||||
"roads": true,
|
||||
"source": "osm"
|
||||
},
|
||||
{
|
||||
"key": "marrickville_real",
|
||||
"town": "Marrickville",
|
||||
"state": "NSW",
|
||||
"shops": 28,
|
||||
"roads": true,
|
||||
"source": "osm"
|
||||
},
|
||||
{
|
||||
"key": "newcastle_real",
|
||||
"town": "Newcastle",
|
||||
"state": "NSW",
|
||||
"shops": 36,
|
||||
"roads": true,
|
||||
"source": "osm"
|
||||
},
|
||||
{
|
||||
"key": "newtown_godverse",
|
||||
"town": "Newtown",
|
||||
"state": "",
|
||||
"shops": 18,
|
||||
"roads": true,
|
||||
"source": "godverse+osm"
|
||||
},
|
||||
{
|
||||
"key": "newtown_real",
|
||||
"town": "Newtown",
|
||||
"state": "NSW",
|
||||
"shops": 72,
|
||||
"roads": true,
|
||||
"source": "osm"
|
||||
},
|
||||
{
|
||||
"key": "northbridge_real",
|
||||
"town": "Northbridge",
|
||||
"state": "WA",
|
||||
"shops": 60,
|
||||
"roads": true,
|
||||
"source": "osm"
|
||||
},
|
||||
{
|
||||
"key": "westend_real",
|
||||
"town": "West End",
|
||||
"state": "QLD",
|
||||
"shops": 44,
|
||||
"roads": true,
|
||||
"source": "osm"
|
||||
}
|
||||
]
|
||||
}
|
||||