Compare commits
9 Commits
06fe64875a
...
d678138ec1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d678138ec1 | ||
|
|
0d992803c5 | ||
|
|
5d29863b78 | ||
|
|
94e56ed5fa | ||
|
|
62cbb955b1 | ||
|
|
078245f7bd | ||
|
|
85745d2017 | ||
|
|
c7ca39b4f7 | ||
|
|
955ec3497b |
@ -3,7 +3,40 @@
|
||||
*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 20) · owner: PROCITY-C · reviewer: Fable
|
||||
|
||||
---
|
||||
|
||||
## 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.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -1,11 +1,40 @@
|
||||
# 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 (51866/51866); rounds 2–20 closed** (see below).
|
||||
v4.0-beta: spine-poster cap scaled by shop count (katoomba 438→31, closes F's venue_night edge) + E's
|
||||
density widening absorbed (5 towns at 3–6× shops, all place 3 venues, drops 5.5% counted by cause); all 10
|
||||
real-town goldens (base + gig) pinned. classic/gig/marched-fixtures frozen.*
|
||||
*Date: 2026-07-16 · owner: PROCITY-A (Opus 4.8)*
|
||||
|
||||
## 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
|
||||
|
||||
@ -311,7 +311,11 @@ camera are all `+Z`; position `lot + (sin ry, cos ry)·(d/2+ε)`), turned to rea
|
||||
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
|
||||
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".
|
||||
|
||||
@ -2,6 +2,42 @@
|
||||
|
||||
Durable answers to questions other lanes raised against Lane A. Full status in `A-progress.md`.
|
||||
|
||||
## 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,70 @@
|
||||
# LANE B — NOTES (measured)
|
||||
|
||||
## 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,10 @@
|
||||
# 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.
|
||||
|
||||
*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 +137,84 @@ 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.)
|
||||
|
||||
@ -6,6 +6,137 @@ _rotOnly/head-bone normalize/upgradeStreetPeople). Measurements on the M3 Ultra
|
||||
|
||||
---
|
||||
|
||||
## 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,54 @@
|
||||
# LANE E — cross-lane notes
|
||||
|
||||
## 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.
|
||||
|
||||
91
docs/LANES/ROUND21_INSTRUCTIONS.md
Normal file
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.
|
||||
@ -17,7 +17,7 @@ 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")
|
||||
@ -28,20 +28,55 @@ UA = "PROCITY-townscout/0.1 (real-map scout; OpenStreetMap ODbL data)"
|
||||
# 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},
|
||||
"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},
|
||||
}
|
||||
|
||||
# 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,18 +149,27 @@ 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},
|
||||
@ -178,7 +222,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 +250,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",
|
||||
@ -257,16 +316,30 @@ 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])
|
||||
towns.append({"key": k, "town": c.get("town", k), "state": TOWNS.get(k, {}).get("state", ""),
|
||||
"shops": len(c.get("shops", [])), "roads": bool(c.get("roads"))})
|
||||
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__":
|
||||
|
||||
@ -17,13 +17,30 @@ the "© OpenStreetMap contributors (ODbL)" credit visible.
|
||||
|
||||
## Provenance table
|
||||
|
||||
| cache | town | source | licence | fetched | shops | raw response |
|
||||
Shop counts are post-widening (v4.0-beta); `index.json` is the machine-readable roster.
|
||||
|
||||
| cache | town | source | licence | fetched | shops (heroes+texture) | 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` |
|
||||
| `katoomba_real.json` | Katoomba, NSW | OSM Overpass (bounded) | ODbL 1.0 | 2026-07-16 | 80 (20+60) | `_raw/katoomba_real.overpass.json` |
|
||||
| `newtown_real.json` | Newtown, Sydney NSW | OSM Overpass (bounded) | ODbL 1.0 | 2026-07-16 | 72 (18+54) | `_raw/newtown_real.overpass.json` |
|
||||
| `fremantle_real.json` | Fremantle, WA | OSM Overpass (bounded) | ODbL 1.0 | 2026-07-16 | 80 (20+60) | `_raw/fremantle_real.overpass.json` |
|
||||
| `bendigo_real.json` | Bendigo, VIC | OSM Overpass (bounded) | ODbL 1.0 | 2026-07-16 | 36 (9+27) | `_raw/bendigo_real.overpass.json` |
|
||||
| `castlemaine_real.json` | Castlemaine, VIC | OSM Overpass (bounded) | ODbL 1.0 | 2026-07-16 | 24 (6+18) | `_raw/castlemaine_real.overpass.json` |
|
||||
|
||||
## 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/`)
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -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": [
|
||||
|
||||
@ -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": [
|
||||
|
||||
@ -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": [
|
||||
|
||||
43
web/assets/towns/index.json
Normal file
43
web/assets/towns/index.json
Normal file
@ -0,0 +1,43 @@
|
||||
{
|
||||
"schema": "procity-towns-index/1",
|
||||
"generated": "2026-07-16",
|
||||
"license": "ODbL 1.0",
|
||||
"attribution": "\u00a9 OpenStreetMap contributors",
|
||||
"towns": [
|
||||
{
|
||||
"key": "bendigo_real",
|
||||
"town": "Bendigo",
|
||||
"state": "VIC",
|
||||
"shops": 36,
|
||||
"roads": true
|
||||
},
|
||||
{
|
||||
"key": "castlemaine_real",
|
||||
"town": "Castlemaine",
|
||||
"state": "VIC",
|
||||
"shops": 24,
|
||||
"roads": true
|
||||
},
|
||||
{
|
||||
"key": "fremantle_real",
|
||||
"town": "Fremantle",
|
||||
"state": "WA",
|
||||
"shops": 80,
|
||||
"roads": true
|
||||
},
|
||||
{
|
||||
"key": "katoomba_real",
|
||||
"town": "Katoomba",
|
||||
"state": "NSW",
|
||||
"shops": 80,
|
||||
"roads": true
|
||||
},
|
||||
{
|
||||
"key": "newtown_real",
|
||||
"town": "Newtown",
|
||||
"state": "NSW",
|
||||
"shops": 72,
|
||||
"roads": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -18,11 +18,54 @@
|
||||
"maxLon": 150.3282458418835
|
||||
},
|
||||
"counts": {
|
||||
"raw": 20,
|
||||
"shops": 20,
|
||||
"raw": 85,
|
||||
"shops": 80,
|
||||
"heroes": 20,
|
||||
"texture": 60,
|
||||
"textureDropped": 4,
|
||||
"roads": 484
|
||||
},
|
||||
"shops": [
|
||||
{
|
||||
"id": 20973534,
|
||||
"name": "The Hatters Cafe",
|
||||
"type": "milkbar",
|
||||
"lat": -33.71685,
|
||||
"lon": 150.310897,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 20973744,
|
||||
"name": "Blue Mountains Chocolate Company",
|
||||
"type": "milkbar",
|
||||
"lat": -33.727787,
|
||||
"lon": 150.310686,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 220588486,
|
||||
"name": "K Hub",
|
||||
"type": "dept",
|
||||
"lat": -33.715253,
|
||||
"lon": 150.310046,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 397769505,
|
||||
"name": "EG Australia",
|
||||
"type": "milkbar",
|
||||
"lat": -33.706992,
|
||||
"lon": 150.306813,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 615402710,
|
||||
"name": "Bunnings Warehouse",
|
||||
"type": "dept",
|
||||
"lat": -33.712464,
|
||||
"lon": 150.320412,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 753467867,
|
||||
"name": "Blue Mountains Cancer Help Op Shop",
|
||||
@ -31,6 +74,78 @@
|
||||
"lon": 150.310861,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 761089046,
|
||||
"name": "Bunnings Garden Center",
|
||||
"type": "stall",
|
||||
"lat": -33.712763,
|
||||
"lon": 150.320644,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 802666966,
|
||||
"name": "Savoy Cafe",
|
||||
"type": "milkbar",
|
||||
"lat": -33.712834,
|
||||
"lon": 150.311983,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 1369162051,
|
||||
"name": "Coles",
|
||||
"type": "dept",
|
||||
"lat": -33.713861,
|
||||
"lon": 150.310503,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 1468118824,
|
||||
"name": "Aldi",
|
||||
"type": "dept",
|
||||
"lat": -33.717427,
|
||||
"lon": 150.310496,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 2381632365,
|
||||
"name": "Blue Mountains Food Co-op",
|
||||
"type": "dept",
|
||||
"lat": -33.714245,
|
||||
"lon": 150.310943,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 2459170313,
|
||||
"name": "Mitre 10",
|
||||
"type": "dept",
|
||||
"lat": -33.710384,
|
||||
"lon": 150.319933,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 2763942902,
|
||||
"name": "Woolworths",
|
||||
"type": "dept",
|
||||
"lat": -33.716617,
|
||||
"lon": 150.309785,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 2763958073,
|
||||
"name": "The Handy Store",
|
||||
"type": "milkbar",
|
||||
"lat": -33.705791,
|
||||
"lon": 150.312268,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 3525171098,
|
||||
"name": "The Yellow Deli",
|
||||
"type": "milkbar",
|
||||
"lat": -33.717377,
|
||||
"lon": 150.31108,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 4047416904,
|
||||
"name": "Tanglecare",
|
||||
@ -63,6 +178,14 @@
|
||||
"lon": 150.309931,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 4047416912,
|
||||
"name": "Todarello's",
|
||||
"type": "stall",
|
||||
"lat": -33.710196,
|
||||
"lon": 150.309242,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 4047416913,
|
||||
"name": "Sinnies",
|
||||
@ -71,6 +194,22 @@
|
||||
"lon": 150.311782,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 4861436783,
|
||||
"name": "Big W",
|
||||
"type": "dept",
|
||||
"lat": -33.715823,
|
||||
"lon": 150.309926,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 4957656365,
|
||||
"name": "Jaycar",
|
||||
"type": "dept",
|
||||
"lat": -33.717449,
|
||||
"lon": 150.310738,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 4958417278,
|
||||
"name": "Tanglecare Op Shops",
|
||||
@ -95,6 +234,62 @@
|
||||
"lon": 150.311403,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 5237464916,
|
||||
"name": "Xpresso",
|
||||
"type": "milkbar",
|
||||
"lat": -33.712201,
|
||||
"lon": 150.31176,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 5237464921,
|
||||
"name": "Clarendon Restaurant",
|
||||
"type": "milkbar",
|
||||
"lat": -33.718026,
|
||||
"lon": 150.312594,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 5237464923,
|
||||
"name": "Smokey Bundits",
|
||||
"type": "milkbar",
|
||||
"lat": -33.711901,
|
||||
"lon": 150.310436,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 6259524189,
|
||||
"name": "Cafe Lurline",
|
||||
"type": "milkbar",
|
||||
"lat": -33.724917,
|
||||
"lon": 150.310787,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 6340856985,
|
||||
"name": "Made with Love Cafe",
|
||||
"type": "milkbar",
|
||||
"lat": -33.713029,
|
||||
"lon": 150.311952,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 6417269322,
|
||||
"name": "Pomegranate Cafe",
|
||||
"type": "milkbar",
|
||||
"lat": -33.713511,
|
||||
"lon": 150.311538,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 6417269860,
|
||||
"name": "Hominy Bakery",
|
||||
"type": "milkbar",
|
||||
"lat": -33.716472,
|
||||
"lon": 150.310974,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 6938379064,
|
||||
"name": "Katoomba Antique Centre",
|
||||
@ -103,6 +298,14 @@
|
||||
"lon": 150.310654,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 6938379066,
|
||||
"name": "Pearce Furniture & Bedding",
|
||||
"type": "dept",
|
||||
"lat": -33.712288,
|
||||
"lon": 150.310753,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 6938414604,
|
||||
"name": "Slave-ohs",
|
||||
@ -119,6 +322,110 @@
|
||||
"lon": 150.31127,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 6938593142,
|
||||
"name": "True to the Bean",
|
||||
"type": "milkbar",
|
||||
"lat": -33.715282,
|
||||
"lon": 150.311217,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 6938593144,
|
||||
"name": "Can Blue",
|
||||
"type": "milkbar",
|
||||
"lat": -33.71536,
|
||||
"lon": 150.311202,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 6938593151,
|
||||
"name": "8 Things",
|
||||
"type": "milkbar",
|
||||
"lat": -33.715508,
|
||||
"lon": 150.311171,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 6938593155,
|
||||
"name": "Katoomba French Hot Bread",
|
||||
"type": "milkbar",
|
||||
"lat": -33.715557,
|
||||
"lon": 150.311159,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 6938593159,
|
||||
"name": "Katoomba Fine Flowers",
|
||||
"type": "stall",
|
||||
"lat": -33.715469,
|
||||
"lon": 150.31118,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 6938593162,
|
||||
"name": "Blue Mountains Buttery",
|
||||
"type": "milkbar",
|
||||
"lat": -33.71583,
|
||||
"lon": 150.311109,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 6938593166,
|
||||
"name": "The Elephant Bean",
|
||||
"type": "milkbar",
|
||||
"lat": -33.715925,
|
||||
"lon": 150.311082,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 6938593174,
|
||||
"name": "Kent + Lane",
|
||||
"type": "dept",
|
||||
"lat": -33.71653,
|
||||
"lon": 150.31096,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 6938677873,
|
||||
"name": "Fish & Fowl",
|
||||
"type": "milkbar",
|
||||
"lat": -33.715274,
|
||||
"lon": 150.310735,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 6938921422,
|
||||
"name": "LuJo By Design",
|
||||
"type": "dept",
|
||||
"lat": -33.716657,
|
||||
"lon": 150.311224,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 6938921424,
|
||||
"name": "Katoomba Workwear",
|
||||
"type": "dept",
|
||||
"lat": -33.716397,
|
||||
"lon": 150.311281,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 6938921480,
|
||||
"name": "Black Cockatoo",
|
||||
"type": "milkbar",
|
||||
"lat": -33.715974,
|
||||
"lon": 150.311074,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 6938921482,
|
||||
"name": "aBrasKadaBras",
|
||||
"type": "dept",
|
||||
"lat": -33.714468,
|
||||
"lon": 150.311655,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 6938921533,
|
||||
"name": "MacArthur's Arcade",
|
||||
@ -135,6 +442,14 @@
|
||||
"lon": 150.311761,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 7012045885,
|
||||
"name": "Mountain Ama Cafe",
|
||||
"type": "milkbar",
|
||||
"lat": -33.716568,
|
||||
"lon": 150.310968,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 7040012373,
|
||||
"name": "Black Antiques",
|
||||
@ -143,6 +458,22 @@
|
||||
"lon": 150.309278,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 7059841233,
|
||||
"name": "Tunk's Landscapes",
|
||||
"type": "dept",
|
||||
"lat": -33.712966,
|
||||
"lon": 150.31851,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 7110813636,
|
||||
"name": "Aquip Supplies",
|
||||
"type": "dept",
|
||||
"lat": -33.712766,
|
||||
"lon": 150.320997,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 7110813637,
|
||||
"name": "Steven Walter Children's Cancer Foundation Warehouse",
|
||||
@ -151,6 +482,78 @@
|
||||
"lon": 150.321286,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 7882541494,
|
||||
"name": "BP Shop",
|
||||
"type": "milkbar",
|
||||
"lat": -33.706897,
|
||||
"lon": 150.316949,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 8175995312,
|
||||
"name": "On The Soul Side",
|
||||
"type": "milkbar",
|
||||
"lat": -33.711672,
|
||||
"lon": 150.3122,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 8303103752,
|
||||
"name": "Mountain Herbs",
|
||||
"type": "stall",
|
||||
"lat": -33.701038,
|
||||
"lon": 150.306761,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 8558905196,
|
||||
"name": "Heroy Asian Grocery",
|
||||
"type": "dept",
|
||||
"lat": -33.717511,
|
||||
"lon": 150.310724,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 8791558114,
|
||||
"name": "Cafe Del Ra",
|
||||
"type": "milkbar",
|
||||
"lat": -33.712238,
|
||||
"lon": 150.310646,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 8964690649,
|
||||
"name": "Metromix Concrete",
|
||||
"type": "dept",
|
||||
"lat": -33.702945,
|
||||
"lon": 150.31863,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 8995679112,
|
||||
"name": "Mantra Uniforms",
|
||||
"type": "dept",
|
||||
"lat": -33.707815,
|
||||
"lon": 150.310875,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 9222666517,
|
||||
"name": "Unique Patisserie",
|
||||
"type": "milkbar",
|
||||
"lat": -33.716093,
|
||||
"lon": 150.31104,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 9225814624,
|
||||
"name": "Bakers Delight",
|
||||
"type": "milkbar",
|
||||
"lat": -33.716238,
|
||||
"lon": 150.310619,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 9231634025,
|
||||
"name": "Little Lost Bookshop",
|
||||
@ -159,6 +562,22 @@
|
||||
"lon": 150.310835,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 9287300149,
|
||||
"name": "High Q Trades",
|
||||
"type": "dept",
|
||||
"lat": -33.712312,
|
||||
"lon": 150.318904,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 9413746790,
|
||||
"name": "Little Paris Cafe",
|
||||
"type": "milkbar",
|
||||
"lat": -33.71444,
|
||||
"lon": 150.311091,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 9577859712,
|
||||
"name": "B'COS Agora Second Hand Bookshop",
|
||||
@ -167,6 +586,22 @@
|
||||
"lon": 150.311173,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 9584302786,
|
||||
"name": "Sip Sip Drink Bar",
|
||||
"type": "milkbar",
|
||||
"lat": -33.712736,
|
||||
"lon": 150.311999,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 9584302788,
|
||||
"name": "Bring the Jungle",
|
||||
"type": "stall",
|
||||
"lat": -33.712986,
|
||||
"lon": 150.311957,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 9791871398,
|
||||
"name": "The Velvet Fog",
|
||||
@ -175,6 +610,22 @@
|
||||
"lon": 150.311566,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 11242865086,
|
||||
"name": "Odd Mountains Oddities",
|
||||
"type": "dept",
|
||||
"lat": -33.7167,
|
||||
"lon": 150.310928,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 11242941181,
|
||||
"name": "Tibetan Three Sisters",
|
||||
"type": "dept",
|
||||
"lat": -33.715893,
|
||||
"lon": 150.311092,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 11242974232,
|
||||
"name": "Sanctus Catholic Books and Gift Shop",
|
||||
@ -182,6 +633,38 @@
|
||||
"lat": -33.715718,
|
||||
"lon": 150.31095,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 12443820960,
|
||||
"name": "Katoomba Falls Kiosk",
|
||||
"type": "milkbar",
|
||||
"lat": -33.726555,
|
||||
"lon": 150.30399,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 12475599270,
|
||||
"name": "Catalina Cafe",
|
||||
"type": "milkbar",
|
||||
"lat": -33.715151,
|
||||
"lon": 150.303325,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 12500043180,
|
||||
"name": "Fidelity",
|
||||
"type": "milkbar",
|
||||
"lat": -33.716268,
|
||||
"lon": 150.310835,
|
||||
"suburb": "Katoomba"
|
||||
},
|
||||
{
|
||||
"id": 12642112599,
|
||||
"name": "Good Fat Pastry",
|
||||
"type": "milkbar",
|
||||
"lat": -33.716468,
|
||||
"lon": 150.310982,
|
||||
"suburb": "Katoomba"
|
||||
}
|
||||
],
|
||||
"roads": [
|
||||
|
||||
@ -18,11 +18,30 @@
|
||||
"maxLon": 151.1931100389657
|
||||
},
|
||||
"counts": {
|
||||
"raw": 19,
|
||||
"shops": 18,
|
||||
"raw": 233,
|
||||
"shops": 72,
|
||||
"heroes": 18,
|
||||
"texture": 54,
|
||||
"textureDropped": 156,
|
||||
"roads": 1086
|
||||
},
|
||||
"shops": [
|
||||
{
|
||||
"id": 62203508,
|
||||
"name": "Bunnings Warehouse",
|
||||
"type": "dept",
|
||||
"lat": -33.905746,
|
||||
"lon": 151.19369,
|
||||
"suburb": "Alexandria"
|
||||
},
|
||||
{
|
||||
"id": 279681996,
|
||||
"name": "Woolworths Metro",
|
||||
"type": "dept",
|
||||
"lat": -33.89859,
|
||||
"lon": 151.181704,
|
||||
"suburb": "Newtown"
|
||||
},
|
||||
{
|
||||
"id": 291725581,
|
||||
"name": "Kidstuff",
|
||||
@ -31,13 +50,93 @@
|
||||
"lon": 151.176459,
|
||||
"suburb": "Camperdown"
|
||||
},
|
||||
{
|
||||
"id": 292127221,
|
||||
"name": "The Hospitality Store",
|
||||
"type": "dept",
|
||||
"lat": -33.88782,
|
||||
"lon": 151.17241,
|
||||
"suburb": "Camperdown"
|
||||
},
|
||||
{
|
||||
"id": 305516372,
|
||||
"name": "IGA",
|
||||
"type": "dept",
|
||||
"lat": -33.89597,
|
||||
"lon": 151.180575,
|
||||
"suburb": "Newtown"
|
||||
},
|
||||
{
|
||||
"id": 423218371,
|
||||
"name": "Foodcraft",
|
||||
"type": "milkbar",
|
||||
"lat": -33.90308,
|
||||
"lon": 151.185224,
|
||||
"suburb": "Erskineville"
|
||||
},
|
||||
{
|
||||
"id": 605984545,
|
||||
"name": "EB Games",
|
||||
"type": "video",
|
||||
"lat": -33.90641,
|
||||
"lon": 151.171739,
|
||||
"suburb": "Newtown"
|
||||
"suburb": "Marrickville"
|
||||
},
|
||||
{
|
||||
"id": 605984546,
|
||||
"name": "Nextra",
|
||||
"type": "milkbar",
|
||||
"lat": -33.906418,
|
||||
"lon": 151.171836,
|
||||
"suburb": "Marrickville"
|
||||
},
|
||||
{
|
||||
"id": 605984562,
|
||||
"name": "Lowes",
|
||||
"type": "dept",
|
||||
"lat": -33.906876,
|
||||
"lon": 151.171753,
|
||||
"suburb": "Marrickville"
|
||||
},
|
||||
{
|
||||
"id": 605984563,
|
||||
"name": "Kimmy Jewellery",
|
||||
"type": "dept",
|
||||
"lat": -33.906791,
|
||||
"lon": 151.171765,
|
||||
"suburb": "Marrickville"
|
||||
},
|
||||
{
|
||||
"id": 605984565,
|
||||
"name": "Forcast",
|
||||
"type": "dept",
|
||||
"lat": -33.906889,
|
||||
"lon": 151.17189,
|
||||
"suburb": "Marrickville"
|
||||
},
|
||||
{
|
||||
"id": 605984566,
|
||||
"name": "Bras N Things",
|
||||
"type": "dept",
|
||||
"lat": -33.907022,
|
||||
"lon": 151.171843,
|
||||
"suburb": "Marrickville"
|
||||
},
|
||||
{
|
||||
"id": 605984567,
|
||||
"name": "Sussan",
|
||||
"type": "dept",
|
||||
"lat": -33.907073,
|
||||
"lon": 151.171834,
|
||||
"suburb": "Marrickville"
|
||||
},
|
||||
{
|
||||
"id": 605984571,
|
||||
"name": "Sunset Flowers",
|
||||
"type": "stall",
|
||||
"lat": -33.907283,
|
||||
"lon": 151.171871,
|
||||
"suburb": "Marrickville"
|
||||
},
|
||||
{
|
||||
"id": 605984580,
|
||||
@ -45,6 +144,118 @@
|
||||
"type": "toy",
|
||||
"lat": -33.907371,
|
||||
"lon": 151.172438,
|
||||
"suburb": "Marrickville"
|
||||
},
|
||||
{
|
||||
"id": 605984595,
|
||||
"name": "Sergio's Cake Shop",
|
||||
"type": "milkbar",
|
||||
"lat": -33.906821,
|
||||
"lon": 151.172217,
|
||||
"suburb": "Marrickville"
|
||||
},
|
||||
{
|
||||
"id": 605984610,
|
||||
"name": "Espresso Brothers",
|
||||
"type": "milkbar",
|
||||
"lat": -33.906382,
|
||||
"lon": 151.172126,
|
||||
"suburb": "Marrickville"
|
||||
},
|
||||
{
|
||||
"id": 1039467648,
|
||||
"name": "Storeroom Vintage",
|
||||
"type": "dept",
|
||||
"lat": -33.898406,
|
||||
"lon": 151.176285,
|
||||
"suburb": "Newtown"
|
||||
},
|
||||
{
|
||||
"id": 1039467672,
|
||||
"name": "Natural Fit Footwear",
|
||||
"type": "dept",
|
||||
"lat": -33.90725,
|
||||
"lon": 151.172143,
|
||||
"suburb": "Marrickville"
|
||||
},
|
||||
{
|
||||
"id": 1039555349,
|
||||
"name": "The Athlete's Foot",
|
||||
"type": "dept",
|
||||
"lat": -33.906646,
|
||||
"lon": 151.17215,
|
||||
"suburb": "Marrickville"
|
||||
},
|
||||
{
|
||||
"id": 1079380693,
|
||||
"name": "Coffee @ Fisher",
|
||||
"type": "milkbar",
|
||||
"lat": -33.886833,
|
||||
"lon": 151.190043,
|
||||
"suburb": "Darlington"
|
||||
},
|
||||
{
|
||||
"id": 1732566722,
|
||||
"name": "Welcome Mart",
|
||||
"type": "dept",
|
||||
"lat": -33.89993,
|
||||
"lon": 151.184575,
|
||||
"suburb": "Erskineville"
|
||||
},
|
||||
{
|
||||
"id": 1735908953,
|
||||
"name": "Boh\u00e4us Cafe",
|
||||
"type": "milkbar",
|
||||
"lat": -33.905708,
|
||||
"lon": 151.191348,
|
||||
"suburb": "Alexandria"
|
||||
},
|
||||
{
|
||||
"id": 2125897845,
|
||||
"name": "Pastizzi Cafe",
|
||||
"type": "milkbar",
|
||||
"lat": -33.903197,
|
||||
"lon": 151.179532,
|
||||
"suburb": "Newtown"
|
||||
},
|
||||
{
|
||||
"id": 2136793910,
|
||||
"name": "Store Espresso",
|
||||
"type": "milkbar",
|
||||
"lat": -33.890334,
|
||||
"lon": 151.176325,
|
||||
"suburb": "Camperdown"
|
||||
},
|
||||
{
|
||||
"id": 2136793911,
|
||||
"name": "Gather on the Green",
|
||||
"type": "milkbar",
|
||||
"lat": -33.890355,
|
||||
"lon": 151.176263,
|
||||
"suburb": "Camperdown"
|
||||
},
|
||||
{
|
||||
"id": 2163387594,
|
||||
"name": "Grounded 2050",
|
||||
"type": "milkbar",
|
||||
"lat": -33.887901,
|
||||
"lon": 151.180526,
|
||||
"suburb": "Camperdown"
|
||||
},
|
||||
{
|
||||
"id": 2170430692,
|
||||
"name": "Carslaw Kitchen",
|
||||
"type": "milkbar",
|
||||
"lat": -33.888238,
|
||||
"lon": 151.190481,
|
||||
"suburb": "Darlington"
|
||||
},
|
||||
{
|
||||
"id": 2365225259,
|
||||
"name": "Ken Neale Overflow",
|
||||
"type": "dept",
|
||||
"lat": -33.90187,
|
||||
"lon": 151.178671,
|
||||
"suburb": "Newtown"
|
||||
},
|
||||
{
|
||||
@ -55,6 +266,30 @@
|
||||
"lon": 151.179525,
|
||||
"suburb": "Newtown"
|
||||
},
|
||||
{
|
||||
"id": 2787084005,
|
||||
"name": "Amazing Paints",
|
||||
"type": "dept",
|
||||
"lat": -33.891631,
|
||||
"lon": 151.176271,
|
||||
"suburb": "Camperdown"
|
||||
},
|
||||
{
|
||||
"id": 2965437480,
|
||||
"name": "Cafe Abercrombie",
|
||||
"type": "milkbar",
|
||||
"lat": -33.892142,
|
||||
"lon": 151.193024,
|
||||
"suburb": "Darlington"
|
||||
},
|
||||
{
|
||||
"id": 2968343588,
|
||||
"name": "Bakerman",
|
||||
"type": "milkbar",
|
||||
"lat": -33.899893,
|
||||
"lon": 151.183961,
|
||||
"suburb": "Erskineville"
|
||||
},
|
||||
{
|
||||
"id": 3278485277,
|
||||
"name": "The Vintage Record",
|
||||
@ -71,6 +306,110 @@
|
||||
"lon": 151.171395,
|
||||
"suburb": "Stanmore"
|
||||
},
|
||||
{
|
||||
"id": 3278493016,
|
||||
"name": "Wildwood Designs",
|
||||
"type": "dept",
|
||||
"lat": -33.887904,
|
||||
"lon": 151.169934,
|
||||
"suburb": "Stanmore"
|
||||
},
|
||||
{
|
||||
"id": 3278493018,
|
||||
"name": "Inspired Design Furniture",
|
||||
"type": "dept",
|
||||
"lat": -33.887902,
|
||||
"lon": 151.170079,
|
||||
"suburb": "Stanmore"
|
||||
},
|
||||
{
|
||||
"id": 3278509468,
|
||||
"name": "Recharging",
|
||||
"type": "milkbar",
|
||||
"lat": -33.887515,
|
||||
"lon": 151.167686,
|
||||
"suburb": "Annandale"
|
||||
},
|
||||
{
|
||||
"id": 3951219159,
|
||||
"name": "Miracle",
|
||||
"type": "dept",
|
||||
"lat": -33.892823,
|
||||
"lon": 151.186108,
|
||||
"suburb": "Newtown"
|
||||
},
|
||||
{
|
||||
"id": 4860774437,
|
||||
"name": "Newtown Garden Market",
|
||||
"type": "stall",
|
||||
"lat": -33.903009,
|
||||
"lon": 151.179679,
|
||||
"suburb": "Newtown"
|
||||
},
|
||||
{
|
||||
"id": 5203491923,
|
||||
"name": "Cynthia's Caf\u00e9",
|
||||
"type": "milkbar",
|
||||
"lat": -33.908747,
|
||||
"lon": 151.191977,
|
||||
"suburb": "Alexandria"
|
||||
},
|
||||
{
|
||||
"id": 5205362421,
|
||||
"name": "Little Vietnam Roll & Juice",
|
||||
"type": "milkbar",
|
||||
"lat": -33.906344,
|
||||
"lon": 151.181132,
|
||||
"suburb": "Newtown"
|
||||
},
|
||||
{
|
||||
"id": 5299091858,
|
||||
"name": "Hui Bro\u2019s Cafe",
|
||||
"type": "milkbar",
|
||||
"lat": -33.898095,
|
||||
"lon": 151.179152,
|
||||
"suburb": "Newtown"
|
||||
},
|
||||
{
|
||||
"id": 5300089045,
|
||||
"name": "Handcraft Specialty Coffee",
|
||||
"type": "milkbar",
|
||||
"lat": -33.892575,
|
||||
"lon": 151.185688,
|
||||
"suburb": "Newtown"
|
||||
},
|
||||
{
|
||||
"id": 5599458217,
|
||||
"name": "The Floral Decorator",
|
||||
"type": "stall",
|
||||
"lat": -33.899566,
|
||||
"lon": 151.184195,
|
||||
"suburb": "Erskineville"
|
||||
},
|
||||
{
|
||||
"id": 5839239307,
|
||||
"name": "Luke Avenue",
|
||||
"type": "milkbar",
|
||||
"lat": -33.897984,
|
||||
"lon": 151.175225,
|
||||
"suburb": "Newtown"
|
||||
},
|
||||
{
|
||||
"id": 5905712358,
|
||||
"name": "The Coffee Nook on Swanson",
|
||||
"type": "milkbar",
|
||||
"lat": -33.900301,
|
||||
"lon": 151.189403,
|
||||
"suburb": "Erskineville"
|
||||
},
|
||||
{
|
||||
"id": 5931284416,
|
||||
"name": "Patisserie Petunia",
|
||||
"type": "milkbar",
|
||||
"lat": -33.90509,
|
||||
"lon": 151.18052,
|
||||
"suburb": "Newtown"
|
||||
},
|
||||
{
|
||||
"id": 5931284422,
|
||||
"name": "Towers Antiques",
|
||||
@ -95,13 +434,29 @@
|
||||
"lon": 151.179587,
|
||||
"suburb": "Newtown"
|
||||
},
|
||||
{
|
||||
"id": 6283737899,
|
||||
"name": "Dangerfield",
|
||||
"type": "dept",
|
||||
"lat": -33.895217,
|
||||
"lon": 151.181947,
|
||||
"suburb": "Newtown"
|
||||
},
|
||||
{
|
||||
"id": 6564462487,
|
||||
"name": "Marani Deli",
|
||||
"type": "milkbar",
|
||||
"lat": -33.895992,
|
||||
"lon": 151.182698,
|
||||
"suburb": "Newtown"
|
||||
},
|
||||
{
|
||||
"id": 6901192228,
|
||||
"name": "Co-op",
|
||||
"type": "book",
|
||||
"lat": -33.890884,
|
||||
"lon": 151.192028,
|
||||
"suburb": "Newtown"
|
||||
"suburb": "Darlington"
|
||||
},
|
||||
{
|
||||
"id": 9518337417,
|
||||
@ -111,6 +466,22 @@
|
||||
"lon": 151.166497,
|
||||
"suburb": "Annandale"
|
||||
},
|
||||
{
|
||||
"id": 9572458384,
|
||||
"name": "Budha Bowl",
|
||||
"type": "milkbar",
|
||||
"lat": -33.898699,
|
||||
"lon": 151.176397,
|
||||
"suburb": "Newtown"
|
||||
},
|
||||
{
|
||||
"id": 9776428741,
|
||||
"name": "K\u00fcrt\u0151sh",
|
||||
"type": "milkbar",
|
||||
"lat": -33.907897,
|
||||
"lon": 151.171833,
|
||||
"suburb": "Marrickville"
|
||||
},
|
||||
{
|
||||
"id": 9888616491,
|
||||
"name": "Haze Guitars",
|
||||
@ -127,13 +498,29 @@
|
||||
"lon": 151.178408,
|
||||
"suburb": "Newtown"
|
||||
},
|
||||
{
|
||||
"id": 11984912425,
|
||||
"name": "Provocator",
|
||||
"type": "dept",
|
||||
"lat": -33.901201,
|
||||
"lon": 151.178441,
|
||||
"suburb": "Newtown"
|
||||
},
|
||||
{
|
||||
"id": 12200892725,
|
||||
"name": "Feather & Bone",
|
||||
"type": "milkbar",
|
||||
"lat": -33.909398,
|
||||
"lon": 151.168471,
|
||||
"suburb": "Marrickville"
|
||||
},
|
||||
{
|
||||
"id": 12958032466,
|
||||
"name": "St Luke\u2019s Op Shop",
|
||||
"type": "opshop",
|
||||
"lat": -33.898898,
|
||||
"lon": 151.170272,
|
||||
"suburb": "Newtown"
|
||||
"suburb": "Enmore"
|
||||
},
|
||||
{
|
||||
"id": 13122105023,
|
||||
@ -143,6 +530,22 @@
|
||||
"lon": 151.176713,
|
||||
"suburb": "Camperdown"
|
||||
},
|
||||
{
|
||||
"id": 13341304001,
|
||||
"name": "AS Colour",
|
||||
"type": "dept",
|
||||
"lat": -33.895051,
|
||||
"lon": 151.181701,
|
||||
"suburb": "Newtown"
|
||||
},
|
||||
{
|
||||
"id": 13585970805,
|
||||
"name": "Numero Uno Coffee Roasters",
|
||||
"type": "milkbar",
|
||||
"lat": -33.908693,
|
||||
"lon": 151.18052,
|
||||
"suburb": "St Peters"
|
||||
},
|
||||
{
|
||||
"id": 13686752819,
|
||||
"name": "Better Read Than Dead",
|
||||
@ -165,7 +568,39 @@
|
||||
"type": "record",
|
||||
"lat": -33.898666,
|
||||
"lon": 151.174207,
|
||||
"suburb": "Enmore"
|
||||
},
|
||||
{
|
||||
"id": 13719824202,
|
||||
"name": "A.P Town",
|
||||
"type": "milkbar",
|
||||
"lat": -33.893929,
|
||||
"lon": 151.183827,
|
||||
"suburb": "Newtown"
|
||||
},
|
||||
{
|
||||
"id": 13785846501,
|
||||
"name": "The Happy Herb Shop Newtown",
|
||||
"type": "milkbar",
|
||||
"lat": -33.898379,
|
||||
"lon": 151.178005,
|
||||
"suburb": "Newtown"
|
||||
},
|
||||
{
|
||||
"id": 13919280070,
|
||||
"name": "Tonton Bread",
|
||||
"type": "milkbar",
|
||||
"lat": -33.90484,
|
||||
"lon": 151.17338,
|
||||
"suburb": "Marrickville"
|
||||
},
|
||||
{
|
||||
"id": 13919369194,
|
||||
"name": "Mamuki",
|
||||
"type": "milkbar",
|
||||
"lat": -33.898789,
|
||||
"lon": 151.173092,
|
||||
"suburb": "Enmore"
|
||||
}
|
||||
],
|
||||
"roads": [
|
||||
|
||||
@ -23,6 +23,10 @@ const r4 = v => Math.round(v * 10000) / 10000;
|
||||
// selfcheck asserts the same floor — import POSTER_CLEAR there, don't re-guess it.
|
||||
export const POSTER_CLEAR = 0.5;
|
||||
const FACADE_PROUD = 0.06;
|
||||
// ROUND20 poster cap: the spine pole run is "2 per main edge", which on a sparse REAL town (few shops, big
|
||||
// edge graph) reads busy (katoomba: 438). Cap it SCALED BY SHOP COUNT — a seeded subsample of the run. On
|
||||
// the synthetic town the shop count is in the hundreds, so the cap never binds and the gig golden is frozen.
|
||||
const POSTER_PER_SHOP = 1.5, POSTER_SPINE_FLOOR = 12;
|
||||
// nearest point on segment AB to P (same helper the selfcheck uses; kept local so gigs.js stays pure).
|
||||
const nearestSeg = (px, pz, ax, az, bx, bz) => {
|
||||
const dx = bx - ax, dz = bz - az, L2 = dx * dx + dz * dz || 1;
|
||||
@ -121,12 +125,13 @@ function pickVenues(plan, citySeed) {
|
||||
if (far.length) cand = far;
|
||||
}
|
||||
}
|
||||
// ROUND16 ledger #6: keep the off-spine kinds off a back-street's bitumen. Prefer candidates whose
|
||||
// +Z facade clears every carriageway by ≥ POSTER_CLEAR; if the warehouse fringe is uniformly narrow,
|
||||
// broaden to ANY off-spine clear lot before falling back. (pub is spine-fronted — its +Z is open —
|
||||
// so it keeps its placement untouched; the hero shot doesn't move.) Always a clear lot exists in the
|
||||
// synthetic town; the fallback only ever fires on a degenerate/osm plan.
|
||||
if (kind === 'band_room' || kind === 'rsl') {
|
||||
// ROUND16 ledger #6 (widened ROUND20): keep a venue's frontage poster off a crossing street's bitumen.
|
||||
// Prefer candidates whose +Z facade clears every carriageway by ≥ POSTER_CLEAR; if the primary set has
|
||||
// none, broaden to any off-spine clear lot before falling back. R16 exempted `pub` because on the
|
||||
// synthetic/marched towns its +Z is always open — but on a REAL road graph at density the pub can land
|
||||
// where its facade overhangs a cross street (newtown, R20). The filter now covers every venue kind: on
|
||||
// the synthetic town every pub/rsl candidate already clears, so it stays a no-op there (golden frozen).
|
||||
{
|
||||
let clear = cand.filter(s => facadeClear(s) >= POSTER_CLEAR);
|
||||
if (!clear.length) clear = pool.filter(s => !onMain(s) && facadeClear(s) >= POSTER_CLEAR);
|
||||
if (clear.length) cand = clear;
|
||||
@ -275,7 +280,7 @@ function buildPosters(plan, citySeed, venues, gigs) {
|
||||
};
|
||||
const pr = rng(citySeed, 'posters', 0);
|
||||
const PER_EDGE = 2;
|
||||
let k = 0;
|
||||
const spineCands = [];
|
||||
for (const e of runs) {
|
||||
const a = nodeById.get(e.a), b = nodeById.get(e.b);
|
||||
if (!a || !b) continue;
|
||||
@ -291,9 +296,15 @@ function buildPosters(plan, citySeed, venues, gigs) {
|
||||
const nx = side > 0 ? -uz : uz, nz = side > 0 ? ux : -ux;
|
||||
const x = a.x + ux * len * t + nx * off, z = a.z + uz * len * t + nz * off;
|
||||
if (onCarriageway(r2(x), r2(z))) continue; // check the r2-rounded position `add` stores — overhangs a crossing → skip (E's R18 finding)
|
||||
add(x, z, Math.atan2(nx, nz), bill[k++ % bill.length].gigId);
|
||||
spineCands.push({ x, z, ry: Math.atan2(nx, nz) });
|
||||
}
|
||||
}
|
||||
// ROUND20 poster cap: thin the pole run to ~POSTER_PER_SHOP × shops (a seeded subsample), so a sparse
|
||||
// real town reads like a main street, not a poster wall. cap ≫ candidates on synthetic ⇒ no-op ⇒ frozen.
|
||||
const cap = Math.max(POSTER_SPINE_FLOOR, Math.round(POSTER_PER_SHOP * plan.shops.length));
|
||||
const chosen = spineCands.length > cap ? shuffle(rng(citySeed, 'postercap', 0), spineCands.slice()).slice(0, cap) : spineCands;
|
||||
let k = 0;
|
||||
for (const c of chosen) add(c.x, c.z, c.ry, bill[k++ % bill.length].gigId);
|
||||
return posters;
|
||||
}
|
||||
|
||||
|
||||
@ -238,9 +238,18 @@ export function generatePlanOSM(citySeed, town = DEFAULT_TOWN, opts = {}) {
|
||||
// stringent structuralSuite still holds). Fidelity is the alpha knob (charter risk #4): real edge,
|
||||
// real order, real side, regularised spacing — not pixel positions (real shops overlap).
|
||||
function buildRealRoads() {
|
||||
const EPS = 6, SNAP = 3, KERB = 4, NODE_CLEAR = 6; // fidelity + geometry knobs (metres)
|
||||
// ROUND20 capacity (the density widening load-tested the R18 overflow fence): at 3–6× shops the
|
||||
// dominant drop cause was OVERFLOW — more shops assign to an edge-side than fit. Widened by tightening
|
||||
// the run: NODE_CLEAR 6→3 and a roads-only RGAP 2→0.5 (the shared marched `GAP` is untouched, so the
|
||||
// marched fixtures stay frozen). 0.5 m also reads truer — a real AU main street is continuous terrace
|
||||
// shopfronts, not detached boxes. Drops 7.9% → 5.5% across the five; the residual stays COUNTED, and
|
||||
// dropping a crowded shop beats teleporting it onto a street it isn't on.
|
||||
const EPS = 6, SNAP = 3, KERB = 4, NODE_CLEAR = 3, RGAP = 0.5; // fidelity + geometry knobs (metres)
|
||||
const JOIN_TOL = 12, BRIDGE_MAX = 200, SEAT_MAX = 200; // ROUND19 fragmentation ruling (metres)
|
||||
const WKIND = { main: SPINE_W, side: AV_W, lane: 8, arcade: 6 };
|
||||
// every dropped shop is counted (Fable's ROUND19 ruling) — and counted BY CAUSE, so the density
|
||||
// accounting stays honest as real towns widen (ROUND20).
|
||||
const drop = (why, n = 1) => { norm.dropped = (norm.dropped || 0) + n; norm['dropped_' + why] = (norm['dropped_' + why] || 0) + n; };
|
||||
const skey = p => `${Math.round(p.x / SNAP)},${Math.round(p.z / SNAP)}`;
|
||||
const rawWays = [];
|
||||
for (const rd of roads) {
|
||||
@ -316,7 +325,7 @@ export function generatePlanOSM(citySeed, town = DEFAULT_TOWN, opts = {}) {
|
||||
const perEdge = new Map();
|
||||
for (const s of fx.shops) {
|
||||
const a = nearest(projX(s.lon), projZ(s.lat));
|
||||
if (!a || a.d > SEAT_MAX) { norm.dropped = (norm.dropped || 0) + 1; continue; } // stranded past bridging → drop + COUNT (ruling)
|
||||
if (!a || a.d > SEAT_MAX) { drop('stranded'); continue; } // stranded past bridging → drop + COUNT (ruling)
|
||||
const key = a.e.id * 2 + (a.side > 0 ? 1 : 0);
|
||||
(perEdge.get(key) || perEdge.set(key, { e: a.e, side: a.side, list: [] }).get(key)).list.push({ s, t: a.t });
|
||||
}
|
||||
@ -337,16 +346,16 @@ export function generatePlanOSM(citySeed, town = DEFAULT_TOWN, opts = {}) {
|
||||
let along = NODE_CLEAR;
|
||||
for (let k = 0; k < list.length; k++) {
|
||||
const s = list[k].s, f = footOf(SHOP_TYPES[s.type] ? s.type : 'opshop');
|
||||
if (along + f > len - NODE_CLEAR) { norm.dropped = (norm.dropped || 0) + (list.length - k); break; } // edge full — overflow drops COUNTED (ruling)
|
||||
if (along + f > len - NODE_CLEAR) { drop('overflow', list.length - k); break; } // edge-side full — overflow COUNTED
|
||||
cands.push({ block, x: e.a.x + ux * (along + f / 2) + nx * (off + dep / 2), z: e.a.z + uz * (along + f / 2) + nz * (off + dep / 2), w: f, d: dep, ry, frontEdge: e.id, s });
|
||||
along += f + GAP;
|
||||
along += f + RGAP;
|
||||
}
|
||||
}
|
||||
cands.sort((p, q) => p.s.id - q.s.id); // real streets crowd at intersections → deterministic overlap-resolve
|
||||
const placed = [];
|
||||
for (const c of cands) {
|
||||
const corners = lotCorners(c);
|
||||
if (placed.some(p => obbOverlap(p.corners, corners))) { norm.dropped = (norm.dropped || 0) + 1; continue; }
|
||||
if (placed.some(p => obbOverlap(p.corners, corners))) { drop('overlap'); continue; }
|
||||
placed.push({ corners, c });
|
||||
}
|
||||
for (const { c } of placed) addShop(addLot(c.block, c.x, c.z, c.w, c.d, c.ry, c.frontEdge, 'shop'), c.s);
|
||||
|
||||
@ -449,15 +449,24 @@ section('v4 real-roads graph lift (schema v2)');
|
||||
// pinned. Guarded so the gate is green before E's first cache — A pins goldens as caches arrive.
|
||||
section('real town caches (web/assets/towns/*.json)');
|
||||
const TOWNS_DIR = join(HERE, '..', '..', 'assets', 'towns');
|
||||
// ROUND19: all five real towns are now on REAL ROADS (E's schema-v2 roads[] landed) and pinned on the
|
||||
// ROUND20: re-pinned once for E's DENSITY WIDENING (5 caches at 3-6x shops) + A's poster cap +
|
||||
// capacity widen. All five real towns are on REAL ROADS and pinned on the
|
||||
// real-roads graph output — the A→E→A finalization, katoomba's golden included. Re-pinned this round after
|
||||
// the fragmentation ruling (junction-protected DP + cull/bridge) + E's spine-poster fix. Amendment law:
|
||||
// the alpha changed nothing outside the cache-schema path; synthetic/fixtures/classic/default stay frozen.
|
||||
const REAL_TOWN_GOLDENS = {
|
||||
katoomba_real: 0xb7ffbca2, newtown_real: 0x6f984c81, fremantle_real: 0xfb197000,
|
||||
bendigo_real: 0x21179b3b, castlemaine_real: 0x7c47f639,
|
||||
katoomba_real: 0x420f677d, newtown_real: 0x741c18ec, fremantle_real: 0x73d385df,
|
||||
bendigo_real: 0xc175194e, castlemaine_real: 0x26c128c9,
|
||||
};
|
||||
const townFiles = (existsSync(TOWNS_DIR) ? readdirSync(TOWNS_DIR) : []).filter(f => f.endsWith('.json'));
|
||||
// ROUND20: the GIG-layer golden per real town (district + capped posters) — the base golden above is
|
||||
// gigs-off so it never captured the poster cap. Pinned here so the ROUND20 poster cap (and the density
|
||||
// absorb to come) are regression-guarded byte-exact, not just by districtInvariants.
|
||||
const REAL_TOWN_GIG_GOLDENS = {
|
||||
katoomba_real: 0x2115732a, newtown_real: 0x17ab31bb, fremantle_real: 0x82c27397,
|
||||
bendigo_real: 0xf5ce58c4, castlemaine_real: 0x48272da9,
|
||||
};
|
||||
// `index.json` is E's towns INDEX (key/town/state/shops/roads for B's selector), not a town cache — skip it.
|
||||
const townFiles = (existsSync(TOWNS_DIR) ? readdirSync(TOWNS_DIR) : []).filter(f => f.endsWith('.json') && f !== 'index.json');
|
||||
if (!townFiles.length) console.log(" (none yet — the contract + hardening are live, ready for E's build_towns.py)");
|
||||
for (const f of townFiles) {
|
||||
const key = f.replace(/\.json$/, '');
|
||||
@ -470,8 +479,12 @@ for (const f of townFiles) {
|
||||
for (const s of GIG_SEEDS) districtInvariants(generatePlanFor(s, 'osm', { gigs: true, town: key, cache }), `real/${key} ${s}`);
|
||||
const hash = xmur3(JSON.stringify(generatePlanOSM(20261990, key, { cache })))() >>> 0;
|
||||
const want = REAL_TOWN_GOLDENS[key];
|
||||
if (want === undefined) console.log(` ⚠ real/${key}: UNPINNED — add REAL_TOWN_GOLDENS['${key}'] = 0x${hash.toString(16).padStart(8, '0')}`);
|
||||
else ok(hash === (want >>> 0), `real/${key}: golden 0x${hash.toString(16)} matches pinned 0x${(want >>> 0).toString(16)}`);
|
||||
if (want === undefined) console.log(` ⚠ real/${key}: base UNPINNED — add REAL_TOWN_GOLDENS['${key}'] = 0x${hash.toString(16).padStart(8, '0')}`);
|
||||
else ok(hash === (want >>> 0), `real/${key}: base golden 0x${hash.toString(16)} matches pinned 0x${(want >>> 0).toString(16)}`);
|
||||
const ghash = xmur3(JSON.stringify(generatePlanFor(20261990, 'osm', { gigs: true, town: key, cache })))() >>> 0;
|
||||
const gwant = REAL_TOWN_GIG_GOLDENS[key];
|
||||
if (gwant === undefined) console.log(` ⚠ real/${key}: gig UNPINNED — add REAL_TOWN_GIG_GOLDENS['${key}'] = 0x${ghash.toString(16).padStart(8, '0')}`);
|
||||
else ok(ghash === (gwant >>> 0), `real/${key}: gig golden 0x${ghash.toString(16)} matches pinned 0x${(gwant >>> 0).toString(16)}`);
|
||||
}
|
||||
|
||||
// ── 4. every facade skin referenced by the registry exists on disk ──────────────────
|
||||
|
||||
@ -13,23 +13,66 @@ const SPEED = 9; // m/s cruising
|
||||
const DWELL = 3.5; // s door pause at each stop
|
||||
const LANE = 3.2; // offset from the road centreline (drive on the left, AU)
|
||||
|
||||
// Ordered spine polyline: walk the chain of MAIN edges from a spine end. Returns [[x,z],…] or null.
|
||||
// Ordered spine polyline: the tram runs the HIGH STREET. Returns [[x,z],…] or null.
|
||||
//
|
||||
// ROUND20 ruling (Fable, ledger #3) — route by SHOP ADJACENCY. The alpha walked whatever main chain
|
||||
// happened to contain the first degree-1 node, which on a real graph is arbitrary (Katoomba: a 3.5 km
|
||||
// highway chain = 21% of the mains, not the high street). Now: split the main-edge subgraph into
|
||||
// connected components, score each by the shops fronting its edges (`lot.frontEdge`), and walk the
|
||||
// winner — ties break on total metres (the longer high street).
|
||||
//
|
||||
// SYNTHETIC IS UNMOVED BY CONSTRUCTION: a synthetic town's mains form ONE component, and the walk below
|
||||
// is seeded by iterating the ORIGINAL `mains` order (filtered to the winner), so `compByNode` is
|
||||
// insertion-identical to the old `byNode` ⇒ same start node ⇒ byte-identical route. Goldens/feel hold.
|
||||
function spinePolyline(plan) {
|
||||
const nodes = new Map(plan.streets.nodes.map((n) => [n.id, n]));
|
||||
const mains = plan.streets.edges.filter((e) => e.kind === 'main');
|
||||
if (!mains.length) return null;
|
||||
const byNode = new Map();
|
||||
for (const e of mains) for (const nid of [e.a, e.b]) { if (!byNode.has(nid)) byNode.set(nid, []); byNode.get(nid).push(e); }
|
||||
let start = null;
|
||||
for (const [nid, es] of byNode) if (es.length === 1) { start = nid; break; }
|
||||
if (start == null) start = mains[0].a;
|
||||
const seq = [start]; const used = new Set(); let cur = start;
|
||||
for (;;) {
|
||||
const next = (byNode.get(cur) || []).find((e) => !used.has(e.id));
|
||||
if (!next) break;
|
||||
used.add(next.id); cur = next.a === cur ? next.b : next.a; seq.push(cur);
|
||||
|
||||
// how many shops front each edge (the high-street signal)
|
||||
const lotById = new Map((plan.lots || []).map((l) => [l.id, l]));
|
||||
const shopsOnEdge = new Map();
|
||||
for (const s of (plan.shops || [])) {
|
||||
const l = lotById.get(s.lot);
|
||||
if (l && l.frontEdge != null) shopsOnEdge.set(l.frontEdge, (shopsOnEdge.get(l.frontEdge) || 0) + 1);
|
||||
}
|
||||
return seq.map((id) => { const n = nodes.get(id); return [n.x, n.z]; });
|
||||
const edgeShops = (e) => shopsOnEdge.get(e.id) || 0;
|
||||
|
||||
// Walk from `start`, at each junction taking the unused main edge that fronts the MOST shops — i.e.
|
||||
// follow the retail, which is what "the high street" means. Component logic isn't needed: the walk
|
||||
// can't leave its component. On a synthetic town every main node has ≤2 mains, so there is never a
|
||||
// choice and this reduces to the pre-R20 walk exactly.
|
||||
const walk = (start) => {
|
||||
const seq = [start]; const used = new Set(); let cur = start, shops = 0, metres = 0;
|
||||
for (;;) {
|
||||
const cands = (byNode.get(cur) || []).filter((e) => !used.has(e.id));
|
||||
if (!cands.length) break;
|
||||
let nx = cands[0];
|
||||
for (const c of cands) if (edgeShops(c) > edgeShops(nx)) nx = c; // ties keep `mains` order
|
||||
used.add(nx.id); shops += edgeShops(nx);
|
||||
const A = nodes.get(cur), nid = nx.a === cur ? nx.b : nx.a, B = nodes.get(nid);
|
||||
if (A && B) metres += Math.hypot(B.x - A.x, B.z - A.z);
|
||||
cur = nid; seq.push(cur);
|
||||
}
|
||||
return { seq, shops, metres, edges: used.size };
|
||||
};
|
||||
// Candidate starts: every dead-end of the main subgraph, in `mains` order. Score each walk by shops
|
||||
// fronted (ties → longer). STRICT `>` keeps the FIRST start on a tie, and a synthetic town's two ends
|
||||
// tie exactly (same chain, reversed) ⇒ the pre-R20 start wins ⇒ byte-identical route.
|
||||
const starts = [];
|
||||
for (const [nid, es] of byNode) if (es.length === 1) starts.push(nid);
|
||||
if (!starts.length) starts.push(mains[0].a);
|
||||
let best = null;
|
||||
for (const s of starts) {
|
||||
const r = walk(s);
|
||||
if (!best || r.shops > best.shops || (r.shops === best.shops && r.metres > best.metres)) best = r;
|
||||
}
|
||||
const poly = best.seq.map((id) => { const n = nodes.get(id); return [n.x, n.z]; });
|
||||
poly.info = { mainEdges: mains.length, candidateStarts: starts.length, routeEdges: best.edges,
|
||||
shopsFronted: best.shops, shopsTotal: (plan.shops || []).length, routeMetres: Math.round(best.metres) };
|
||||
return poly;
|
||||
}
|
||||
|
||||
// bus livery texture (canvas — no fetch): cream body, window band, a red stripe + a route blind.
|
||||
@ -47,10 +90,28 @@ function busTexture() {
|
||||
return t;
|
||||
}
|
||||
|
||||
// ROUND20 per-town fence (Fable's ledger-#3 fallback). On a REAL-ROADS town the best shop-adjacent main
|
||||
// chain can still be a near-shopless highway — real high streets come out of OSM classified `side` (filed
|
||||
// for A with measurements), so the tram would be a highway bus, not a town loop. Fence those towns.
|
||||
//
|
||||
// The fence keys off the REAL-ROADS signal (`edges > 200`), never on shop count alone — because the marched
|
||||
// fixtures legitimately front ZERO shops on their mains (the marched spine is bare; the avenues carry the
|
||||
// shops), and they must keep their v2 tram. Synthetic (22 edges) and fixtures (~15) are far under the
|
||||
// threshold, so they can never be fenced: behaviour there is untouched.
|
||||
const REAL_ROADS_EDGES = 200; // a real OSM graph; synthetic/fixtures are an order of magnitude smaller
|
||||
const FENCE_MIN_SHOPS = 5; // fewer than this on the whole line ⇒ not a high street
|
||||
|
||||
export function createTram({ scene, plan, camera, lighting }) {
|
||||
const route = spinePolyline(plan);
|
||||
const group = new THREE.Group(); group.name = 'tram';
|
||||
if (!route || route.length < 2) { scene.add(group); return { group, update() {}, ring() {}, dispose() { scene.remove(group); } }; }
|
||||
const realRoads = (plan.streets?.edges?.length || 0) > REAL_ROADS_EDGES;
|
||||
const shopsFronted = (route && route.info && route.info.shopsFronted) || 0;
|
||||
const fenced = realRoads && shopsFronted < FENCE_MIN_SHOPS;
|
||||
if (!route || route.length < 2 || fenced) {
|
||||
scene.add(group);
|
||||
const info = { ...((route && route.info) || {}), fenced, reason: fenced ? `real-roads town, best main chain fronts ${shopsFronted} shops (< ${FENCE_MIN_SHOPS}) — highway, not a high street` : (route ? 'no usable spine' : 'no main edges') };
|
||||
return { group, update() {}, ring() {}, dispose() { scene.remove(group); }, get stops() { return 0; }, get routeInfo() { return info; } };
|
||||
}
|
||||
|
||||
// ── cumulative arc length + stop positions projected to arc length ──
|
||||
const seg = []; // { x0,z0, dx,dz, len, s0 }
|
||||
@ -66,12 +127,18 @@ export function createTram({ scene, plan, camera, lighting }) {
|
||||
const d = s - g.s0;
|
||||
return { x: g.x0 + g.dx * d, z: g.z0 + g.dz * d, dx: g.dx, dz: g.dz };
|
||||
};
|
||||
// project each shelter stop onto the route → sorted arc-length stop marks
|
||||
// Project each shelter onto the route → sorted arc-length stop marks, keeping only shelters that are
|
||||
// ACTUALLY ON this route. ROUND20 fix: the projection used to accept every shelter in the town, which
|
||||
// is harmless on synthetic (2 shelters, both on the spine) but nonsense on a real graph — Newtown's
|
||||
// ~148 town-wide shelters all projected onto the line, giving 149 phantom stops (~9 min of dwell at
|
||||
// points the tram never passes). A shelter sits `halfRoad + 1.7` off its own centreline (≤ ~16 m even
|
||||
// on the wide synthetic main), so 30 m keeps every genuine stop and drops the far ones.
|
||||
const STOP_NEAR = 30;
|
||||
const stopS = busShelterStops(plan).map((st) => {
|
||||
let best = 0, bd = 1e18;
|
||||
for (let s = 0; s <= total; s += 3) { const p = posAt(s); const dd = (p.x - st.x) ** 2 + (p.z - st.z) ** 2; if (dd < bd) { bd = dd; best = s; } }
|
||||
return best;
|
||||
}).sort((a, b) => a - b);
|
||||
return { s: best, d2: bd };
|
||||
}).filter((x) => x.d2 <= STOP_NEAR * STOP_NEAR).map((x) => x.s).sort((a, b) => a - b);
|
||||
|
||||
// ── tram mesh: textured body (1 draw) + merged emissive headlights (1 draw) ──
|
||||
const bodyMat = new THREE.MeshStandardMaterial({ map: busTexture(), roughness: 0.7, metalness: 0.1 });
|
||||
@ -113,7 +180,9 @@ export function createTram({ scene, plan, camera, lighting }) {
|
||||
heads.geometry.dispose(); headMat.dispose();
|
||||
scene.remove(group);
|
||||
}
|
||||
return { group, update, ring, dispose, get stops() { return stopS.length; } };
|
||||
// routeInfo: the R20 shop-adjacency verdict, for F's smoke + the notes (which chain the tram picked).
|
||||
return { group, update, ring, dispose, get stops() { return stopS.length; },
|
||||
get routeInfo() { return { ...(route.info || {}), stops: stopS.length, fenced: false }; } };
|
||||
}
|
||||
|
||||
function mergeQuads(geos) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user