Lane E R23 (v5.0-alpha): the atlas validator + toowoomba retired (ledger #4)
1. validate_atlas (the G2a format gate). pipeline/validate_atlas.py, wired into validate_manifest so it
runs in qa gate 3. Judges Lane G's tier-1 atlases from the COMMITTED FILES ALONE -- no dealgod DB, no
network. No atlases yet => clean pass, so it's in place before G's lands. Enforces: provenance
{license, attribution, generator, snapshot} (charter law #3 + determinism); NO private-individual item
fields (law #3); 1 atlas/shop hard cap 2 (C §7.1); <=1024², 2048² only for a 60+ item shop (C §7.2);
declared atlas_px == the atlas's REAL pixels + every listed atlas exists; schema/UV/bands/dup-ids
(C §7.3). The licence string is PRINTED, not pattern-matched -- clearing photos for public release is
a human call, so the gate surfaces it. Dependency-free PNG/WebP header parser. Tested against a
10-case matrix (real 2048² WebP + 1024² PNG): good passes clean, all 9 violations caught.
2. toowoomba RETIRED (my call, counted). Tried the re-bbox FIRST: 3.0km CBD-wide -> 1.6km on the Ruthven
St heart. It fixed the named metric (median NN 395.7 -> 89.4m raw, 'passes') but made the real thing
WORSE: hub density 3/12 -> 2/12 within 160m, the pack's worst (D's alive-darwin is 7/12). Its shops
are PAIRS strung along a road, not a high street; densest 300m cluster is 3 shops (< MIN_TOWN_SHOPS),
so no tighter box exists. Shipping it would have gamed the number while leaving a town nobody shops in
(D: 0 finds / 1417 checks). Config entry kept commented with the rationale. Pack: 21 real + 1 godverse
= 22 rostered, 1180 shops, all 8 states/territories (QLD keeps westend). selfcheck ALL GREEN 152015.
-> Lane A, before you freeze the spacing warn (#5): (a) your validator sees the CACHE (raw lat/lon) but
D's numbers are SEATED (post-lift) -- darwin is 27m seated vs 68.3m RAW, so a threshold picked off D's
table would FALSE-WARN darwin; calibrate on the raw spread (daylesford 11.6 ... braddon 119.2,
ballarat 254.6 -- a ~200m line has daylight). (b) median NN alone is GAMEABLE: my re-bbox improved it
4.4x while the town got worse -- the hub fraction is the discriminator, and my raw hub metric
reproduces D's darwin exactly (7/12), so it's directly encodable (alive ~38-75%, dead <=20%).
(c) ballarat is a SECOND spread town D's two-town comparison missed (NN 254.6m, hub 4/20 = 20%) -- it
will trip your warn, correctly. Not retired: the ledger named toowoomba only.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
47f05ecaa8
commit
84e0a1b764
@ -1,5 +1,59 @@
|
||||
# LANE E — cross-lane notes
|
||||
|
||||
## Round 23 — v5.0-alpha: the atlas validator + the toowoomba call ⟨v5.0-alpha⟩
|
||||
|
||||
**1. `validate_atlas` — the G2a format gate (ledger #4).** `pipeline/validate_atlas.py`, wired into
|
||||
`validate_manifest` so it runs in qa gate 3. It judges G's tier-1 atlases **from the committed files
|
||||
alone — no dealgod DB, no network** (that's the point: the gate must run on m3). No atlases yet ⇒ clean
|
||||
pass, so it's in place *before* G's lands. **→ Lane G: this is the format you build to.** It enforces:
|
||||
|
||||
| rule | source |
|
||||
|---|---|
|
||||
| provenance `{license, attribution, generator, snapshot}` present | charter law #3 ("every atlas carries provenance") + `snapshot` pins determinism |
|
||||
| **no private-individual fields** on items (`seller`/`owner`/`email`/`phone`/…) | charter law #3 ("no private-individual data, ever") |
|
||||
| 1 atlas/shop, **hard cap 2** | LANE_C_PUB §7.1 |
|
||||
| **≤1024²**; 2048² only for a **60+ item** shop | LANE_C_PUB §7.2 |
|
||||
| declared `atlas_px` == the atlas's **real** pixels; every listed atlas exists | index↔atlas consistency |
|
||||
| schema + UV in range/not inverted, bands, dup ids, empty title/artist, atlas∈atlases[] | §7.3 (schema confirmed verbatim) |
|
||||
|
||||
The licence string is **printed** by the gate, not pattern-matched — clearing photos for public release is
|
||||
a human call (charter law #3), so the gate surfaces it rather than pretending to rule on it. **Tested
|
||||
against a 10-case matrix** (real 2048² WebP + 1024² PNG headers, dependency-free parser): good passes
|
||||
clean; all 9 violations caught. A gate that's never caught anything isn't a gate.
|
||||
|
||||
**2. Toowoomba — RETIRED (my call, counted). I tried the re-bbox first; the data said retire.**
|
||||
D's finding: DEAD — 0 patronage finds in 1417 checks. I re-bboxed 3.0 km CBD-wide → 1.6 km onto the
|
||||
Ruthven St heart. Result:
|
||||
|
||||
| | median NN (the named metric) | hub ≤160 m (D's real test) |
|
||||
|---|---|---|
|
||||
| toowoomba @3.0 km | 395.7 m ❌ | 3/12 |
|
||||
| **toowoomba @1.6 km (re-bbox)** | **89.4 m ✅ "passes"** | **2/12 (17%) — worst in the pack** |
|
||||
| darwin (D: ALIVE) | 68.3 m | 7/12 (58%) |
|
||||
|
||||
**The re-bbox fixed the metric and made the town worse.** Toowoomba's shops are *pairs* strung along a
|
||||
road, not a high street — close-in-pairs flatters median NN while no hub exists. Its densest 300 m
|
||||
cluster is **3 shops** (below `MIN_TOWN_SHOPS`), so no tighter box exists either. Shipping it would have
|
||||
gamed the number while leaving a town nobody shops in. **Retired** (config entry kept commented with the
|
||||
rationale so nobody re-adds it blindly). Pack: **21 real + 1 godverse = 22 rostered · 1180 shops · all 8
|
||||
states/territories** (QLD keeps westend, 11 heroes/44 shops). selfcheck **ALL GREEN 152015/152015**.
|
||||
|
||||
### ⚠️ → Lane A: two things before you freeze the spacing warn (#5)
|
||||
1. **Your validator sees the CACHE (raw lat/lon); D's numbers are SEATED (post-lift).** They differ:
|
||||
**darwin — D 27 m seated vs 68.3 m raw**; toowoomba 388 seated vs 395.7 raw (spread towns barely move,
|
||||
clustered ones move a lot). **Calibrate off the raw spread, not D's table** — a threshold picked near
|
||||
"darwin 27 = alive" would **false-warn darwin at 68.3**. Raw spread now: daylesford 11.6 · katoomba
|
||||
22.4 · bowral 27.9 · … · launceston 76.8 · braddon 119.2 · **ballarat 254.6**. A ~200 m line has
|
||||
daylight on both sides.
|
||||
2. **Median NN alone is gameable — the hub fraction is the discriminator.** My re-bbox is the proof: NN
|
||||
improved 4.4× while the town got worse. Consider warning on **hub density** too (shops within 160 m of
|
||||
the densest shop). **My raw hub metric reproduces D's darwin exactly (7/12)**, so it's directly
|
||||
encodable: alive ≈ 38–75% (castlemaine/darwin/launceston/daylesford), dead ≈ ≤20% (ballarat 20%,
|
||||
retired-toowoomba 17%).
|
||||
3. **ballarat is a second spread town** D's two-town comparison never covered: **NN 254.6 m, hub 4/20
|
||||
(20%)** — it *will* trip your warn (correctly). Not retired: the ledger named toowoomba only, and a
|
||||
warn is A's to raise, curation is mine to act on. Flagging for Fable's next-round call.
|
||||
|
||||
## Round 22 — v4.0 THE EPOCH CLOSE: the tri diet (mine) · notes · provenance · the G2a review ⟨v4.0⟩
|
||||
|
||||
### ⚠️ Ledger #1b — the `venue_night` tri diet: **MY four furniture GLBs. Fixed. → F: RE-MEASURE.**
|
||||
|
||||
@ -54,7 +54,16 @@ TOWNS = {
|
||||
"daylesford_real": {"town": "Daylesford", "state": "VIC", "center": (-37.3430, 144.1430), "span_km": 2.0},
|
||||
"geelong_real": {"town": "Geelong", "state": "VIC", "center": (-38.1490, 144.3600), "span_km": 3.0},
|
||||
"westend_real": {"town": "West End", "state": "QLD", "center": (-27.4830, 153.0100), "span_km": 2.2},
|
||||
"toowoomba_real": {"town": "Toowoomba", "state": "QLD", "center": (-27.5600, 151.9540), "span_km": 3.0},
|
||||
# ── RETIRED R23 (E's curation call, counted) — toowoomba_real ────────────────────────────────
|
||||
# D's R22 finding: DEAD — median NN 388 m, 3/12 shops near the hub, **0 patronage finds in 1417
|
||||
# checks**. I tried the re-bbox first (3.0 km CBD-wide → 1.6 km on the Ruthven St heart, centre
|
||||
# -27.5610,151.9535): it FIXED the named metric (median NN 395.7 → 89.4 m raw — normal for the
|
||||
# pack) but made the real thing WORSE — hub density 3/12 → **2/12 within 160 m, the worst in the
|
||||
# pack** (D's alive-darwin is 7/12). Toowoomba's shops are PAIRS strung along a road, not a high
|
||||
# street; its densest 300 m cluster is only 3 shops, below MIN_TOWN_SHOPS, so no tighter box
|
||||
# exists. Shipping it would have gamed the spacing metric while leaving a town nobody shops in.
|
||||
# Retired rather than padded. (QLD keeps westend_real, 11 heroes / 44 shops.)
|
||||
# "toowoomba_real": {"town": "Toowoomba", "state": "QLD", "center": (-27.5600, 151.9540), "span_km": 3.0},
|
||||
"northbridge_real": {"town": "Northbridge", "state": "WA", "center": (-31.9490, 115.8570), "span_km": 2.4},
|
||||
"adelaide_real": {"town": "Adelaide", "state": "SA", "center": (-34.9240, 138.6050), "span_km": 2.8},
|
||||
"hobart_real": {"town": "Hobart", "state": "TAS", "center": (-42.8820, 147.3270), "span_km": 2.8},
|
||||
|
||||
172
pipeline/validate_atlas.py
Normal file
172
pipeline/validate_atlas.py
Normal file
@ -0,0 +1,172 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Validate per-shop real-stock atlases — the G2a format gate (v5.0-alpha, ROUND23 ledger #4).
|
||||
|
||||
Lane G bakes one atlas + index per real shop from dealgod inventory (tier 1 of the charter's stock
|
||||
ladder). This gate is the FORMAT side: it proves G's output is well-formed, inside C's ceilings, and
|
||||
carries its provenance — **on m3, from the files alone, with no dealgod DB and no network**. That's the
|
||||
point: the gate must run anywhere, on the committed artefacts.
|
||||
|
||||
python3 pipeline/validate_atlas.py # exit 0 = every atlas valid (or none yet), 1 = a bad one
|
||||
|
||||
Contract (published R22 → LANE_E_NOTES E's half · LANE_C_PUB §7 C's ceilings · charter law #3 licence):
|
||||
index `web/assets/stock_godverse/**/stock_shop_<godverseShopId>_index.json`
|
||||
{version, atlas_px, cell, atlases[], items:[{id,title,artist,price,price_band,atlas,uv}]}
|
||||
+ provenance: {license, attribution, generator, snapshot}
|
||||
C's ceilings — 1 atlas/shop (hard cap 2); ≤1024² typical, 2048² only for a 60+ item shop.
|
||||
charter law #3 — every atlas carries provenance; no private-individual data, ever.
|
||||
determinism — `snapshot` pins the inventory query result (G snapshots it like E snapshots Overpass raw).
|
||||
|
||||
Fail-soft is NOT this gate's business: a missing atlas is legal at runtime (parody canvas, R7 law). This
|
||||
gate only judges atlases that EXIST — a malformed one must fail loudly rather than render garbled sleeves.
|
||||
"""
|
||||
import json, os, sys, glob, struct
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
STOCK_G = os.path.join(ROOT, "web", "assets", "stock_godverse") # Lane G's namespace (charter law #5)
|
||||
BANDS = {"bargain", "standard", "collector", "grail"}
|
||||
REQUIRED_ITEM = ("id", "title", "artist", "price", "price_band", "atlas", "uv")
|
||||
REQUIRED_PROV = ("license", "attribution", "generator", "snapshot") # charter law #3 + determinism
|
||||
MAX_ATLASES = 2 # C: 1 atlas/shop, hard cap 2 (each extra = +1 draw)
|
||||
PX_TYPICAL, PX_MAX = 1024, 2048
|
||||
BIG_SHOP_ITEMS = 60 # C: 2048² only for a 60+ item shop (else spill to the type-pack)
|
||||
# charter law #3: "No private-individual data, ever" — a real shop's stock row must never carry a person.
|
||||
FORBIDDEN_ITEM_KEYS = {"seller", "seller_name", "owner", "owner_name", "customer", "user", "username",
|
||||
"email", "phone", "mobile", "address", "member", "account", "consignor"}
|
||||
|
||||
|
||||
def image_size(path):
|
||||
"""(w, h) for PNG/WebP from the header alone — dependency-free, house style (cf. glb_stat.py)."""
|
||||
with open(path, "rb") as f:
|
||||
head = f.read(32)
|
||||
if head[:8] == b"\x89PNG\r\n\x1a\n":
|
||||
w, h = struct.unpack(">II", head[16:24])
|
||||
return w, h
|
||||
if head[:4] == b"RIFF" and head[8:12] == b"WEBP":
|
||||
fmt = head[12:16]
|
||||
if fmt == b"VP8X": # extended: 24-bit minus-one
|
||||
b = head[24:30]
|
||||
return (b[0] | b[1] << 8 | b[2] << 16) + 1, (b[3] | b[4] << 8 | b[5] << 16) + 1
|
||||
if fmt == b"VP8 ": # lossy: 14-bit each
|
||||
w, h = struct.unpack("<HH", head[26:30])
|
||||
return w & 0x3FFF, h & 0x3FFF
|
||||
if fmt == b"VP8L": # lossless: packed 14-bit
|
||||
b = struct.unpack("<I", head[21:25])[0]
|
||||
return (b & 0x3FFF) + 1, ((b >> 14) & 0x3FFF) + 1
|
||||
raise ValueError("unrecognised image header (want PNG or WebP)")
|
||||
|
||||
|
||||
def validate_index(path):
|
||||
errs, warns = [], []
|
||||
name = os.path.basename(path)
|
||||
try:
|
||||
idx = json.load(open(path))
|
||||
except Exception as e:
|
||||
return [f"{name}: does not parse: {e}"], []
|
||||
d = os.path.dirname(path)
|
||||
|
||||
# ── provenance (charter law #3) — every atlas carries it, and the gate PRINTS the licence ──
|
||||
miss = [f for f in REQUIRED_PROV if not str(idx.get(f, "")).strip()]
|
||||
if miss:
|
||||
errs.append(f"{name}: missing/empty provenance {miss} (charter law #3 — every atlas carries provenance)")
|
||||
|
||||
# ── C's draw ceiling (LANE_C_PUB §7.1) ──
|
||||
atlases = idx.get("atlases", [])
|
||||
if not isinstance(atlases, list) or not atlases:
|
||||
errs.append(f"{name}: no atlases[] listed")
|
||||
atlases = []
|
||||
if len(atlases) > MAX_ATLASES:
|
||||
errs.append(f"{name}: {len(atlases)} atlases — C's hard cap is {MAX_ATLASES} "
|
||||
f"(1/shop; a 60+ item shop spills to the shared type-pack instead of fragmenting)")
|
||||
elif len(atlases) == 2:
|
||||
warns.append(f"{name}: 2 atlases (+1 draw) — C's rule is 1/shop; 2 is the cap, not the target")
|
||||
|
||||
items = idx.get("items", [])
|
||||
if not isinstance(items, list) or not items:
|
||||
errs.append(f"{name}: no items")
|
||||
items = []
|
||||
|
||||
# ── C's texture/VRAM ceiling (§7.2) + index↔atlas consistency (the declared size must be REAL) ──
|
||||
declared = idx.get("atlas_px")
|
||||
for a in atlases:
|
||||
ap = os.path.join(d, a)
|
||||
if not os.path.isfile(ap):
|
||||
errs.append(f"{name}: atlas {a!r} listed but missing on disk")
|
||||
continue
|
||||
try:
|
||||
w, h = image_size(ap)
|
||||
except Exception as e:
|
||||
errs.append(f"{name}: atlas {a!r} unreadable: {e}")
|
||||
continue
|
||||
if declared and (w != declared or h != declared):
|
||||
errs.append(f"{name}: atlas {a!r} is {w}×{h} but index declares atlas_px={declared}")
|
||||
big = len(items) >= BIG_SHOP_ITEMS
|
||||
cap = PX_MAX if big else PX_TYPICAL
|
||||
if max(w, h) > cap:
|
||||
errs.append(f"{name}: atlas {a!r} {w}×{h} exceeds C's ceiling {cap}² "
|
||||
f"({'60+ item shop' if big else f'{len(items)}-item shop → ≤{PX_TYPICAL}²'})")
|
||||
if max(w, h) > PX_TYPICAL and not big:
|
||||
warns.append(f"{name}: atlas {a!r} is {w}×{h} for {len(items)} items — prefer ≤{PX_TYPICAL}²")
|
||||
|
||||
# ── the index itself (schema confirmed verbatim in LANE_C_PUB §7.3) ──
|
||||
ids = set()
|
||||
for i, it in enumerate(items):
|
||||
if not isinstance(it, dict):
|
||||
errs.append(f"{name}[{i}]: item is not an object"); continue
|
||||
m = [f for f in REQUIRED_ITEM if f not in it]
|
||||
if m:
|
||||
errs.append(f"{name}[{i}]: missing {m}"); continue
|
||||
priv = sorted(set(k.lower() for k in it) & FORBIDDEN_ITEM_KEYS)
|
||||
if priv:
|
||||
errs.append(f"{name}[{it['id']}]: private-individual field(s) {priv} — charter law #3 forbids it")
|
||||
if not str(it["title"]).strip() or not str(it["artist"]).strip():
|
||||
errs.append(f"{name}[{it['id']}]: empty title/artist")
|
||||
if it["id"] in ids:
|
||||
errs.append(f"{name}: duplicate id {it['id']!r}")
|
||||
ids.add(it["id"])
|
||||
if it["price_band"] not in BANDS:
|
||||
errs.append(f"{name}[{it['id']}]: bad price_band {it['price_band']!r}")
|
||||
if not isinstance(it["price"], (int, float)) or it["price"] < 0:
|
||||
errs.append(f"{name}[{it['id']}]: bad price {it['price']!r}")
|
||||
if it["atlas"] not in atlases:
|
||||
errs.append(f"{name}[{it['id']}]: atlas {it['atlas']!r} not in atlases[]")
|
||||
uv = it["uv"]
|
||||
if not (isinstance(uv, list) and len(uv) == 4 and all(isinstance(v, (int, float)) for v in uv)):
|
||||
errs.append(f"{name}[{it['id']}]: uv must be [u0,v0,u1,v1] numbers"); continue
|
||||
u0, v0, u1, v1 = uv
|
||||
if not (0 <= u0 < u1 <= 1 and 0 <= v0 < v1 <= 1): # UV origin top-left; C flips V (R7 law)
|
||||
errs.append(f"{name}[{it['id']}]: uv out of range / inverted: {uv}")
|
||||
return errs, warns
|
||||
|
||||
|
||||
def main():
|
||||
idxs = sorted(glob.glob(os.path.join(STOCK_G, "**", "stock_shop_*_index.json"), recursive=True))
|
||||
if not idxs:
|
||||
print("atlas-QA: no per-shop atlases yet (Lane G's G2a) — nothing to validate")
|
||||
return 0
|
||||
all_errs, all_warns = [], []
|
||||
for p in idxs:
|
||||
e, w = validate_index(p)
|
||||
all_errs += e; all_warns += w
|
||||
idx = {}
|
||||
try:
|
||||
idx = json.load(open(p))
|
||||
except Exception:
|
||||
pass
|
||||
print(f" {os.path.basename(p):40} items={len(idx.get('items', [])):>3} "
|
||||
f"atlases={len(idx.get('atlases', []))} px={idx.get('atlas_px')} "
|
||||
f"snapshot={str(idx.get('snapshot', '—'))[:24]} {'OK' if not e else 'ERR(' + str(len(e)) + ')'}")
|
||||
if idx.get("license"): # licence law: the gate SHOWS it, a human clears it
|
||||
print(f" licence: {idx['license']}")
|
||||
for w in all_warns:
|
||||
print(f" WARN {w}")
|
||||
for e in all_errs:
|
||||
print(f" ERR {e}")
|
||||
if all_errs:
|
||||
print(f"atlas-QA FAIL — {len(all_errs)} error(s), {len(all_warns)} warning(s)")
|
||||
return 1
|
||||
print(f"atlas-QA OK — {len(idxs)} per-shop atlas(es) valid, {len(all_warns)} warning(s)")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@ -152,6 +152,15 @@ def main():
|
||||
except Exception as e:
|
||||
warn(f"pack-QA skipped: {e}")
|
||||
|
||||
# per-shop atlas QA (v5 G2a, ROUND23 E #4): Lane G's tier-1 atlases are gate-checked from the
|
||||
# committed files alone — no dealgod DB, no network. No atlases yet ⇒ clean pass.
|
||||
try:
|
||||
import validate_atlas
|
||||
if validate_atlas.main() != 0:
|
||||
err("per-shop atlas validation failed (see atlas-QA errors above)")
|
||||
except Exception as e:
|
||||
warn(f"atlas-QA skipped: {e}")
|
||||
|
||||
print(f"manifest v{m.get('version')} — facades {len(facade)}, "
|
||||
f"skins {sum(len(v) if isinstance(v, list) else (len(v) if isinstance(v, dict) else 0) for v in sk.values())} groups, "
|
||||
f"GLBs {n_glb}, audio {n_audio}")
|
||||
|
||||
@ -20,20 +20,26 @@ the "© OpenStreetMap contributors (ODbL)" credit visible.
|
||||
**`index.json` is the machine-readable roster** (key/town/state/shops/roads) — read it rather than
|
||||
hardcoding a list. Every cache carries the same terms; per-town numbers live in each cache's `counts`.
|
||||
|
||||
**The v4.0 pack (R21): 22 towns · 1192 shops · 298 heroes**, every one fetched from OSM Overpass with a
|
||||
bounded per-town bbox, ODbL 1.0, raw snapshots in `_raw/` (shops + roads per town), fetched 2026-07-16.
|
||||
**The pack: 21 real towns · 1180 shops · 295 heroes** (+ Lane G's `newtown_godverse` = 22 rostered in
|
||||
`index.json`), every one fetched from OSM Overpass with a bounded per-town bbox, ODbL 1.0, raw snapshots
|
||||
in `_raw/` (shops + roads per town), fetched 2026-07-16.
|
||||
|
||||
| state | towns |
|
||||
|---|---|
|
||||
| NSW (6) | katoomba · newtown · glebe · marrickville · newcastle (Hamilton strip) · bowral |
|
||||
| VIC (7) | fitzroy · brunswick · geelong · bendigo · ballarat · castlemaine · daylesford |
|
||||
| WA (2) | fremantle · northbridge |
|
||||
| QLD (2) | westend · toowoomba |
|
||||
| QLD (1) | westend |
|
||||
| TAS (2) | hobart · launceston |
|
||||
| SA (1) | adelaide |
|
||||
| ACT (1) | braddon |
|
||||
| NT (1) | darwin |
|
||||
|
||||
**Retired (R23, counted):** `toowoomba_real` — D's clustering audit measured it DEAD (0 patronage finds
|
||||
in 1417 checks); a re-bbox onto the Ruthven St heart fixed the spacing metric (median NN 396 → 89 m) but
|
||||
made the hub worse (2/12 within 160 m, the pack's worst), and its densest 300 m cluster is only 3 shops.
|
||||
Cache + raw snapshots removed; the config entry stays commented with the rationale. See LANE_E_NOTES R23.
|
||||
|
||||
## The density widening (v4.0-beta, ROUND20 ledger #2)
|
||||
|
||||
The fetch covers the **secondhand heroes** plus the everyday main-street **texture** (bakery/cafe/deli/
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -172,14 +172,6 @@
|
||||
"roads": true,
|
||||
"source": "osm"
|
||||
},
|
||||
{
|
||||
"key": "toowoomba_real",
|
||||
"town": "Toowoomba",
|
||||
"state": "QLD",
|
||||
"shops": 12,
|
||||
"roads": true,
|
||||
"source": "osm"
|
||||
},
|
||||
{
|
||||
"key": "westend_real",
|
||||
"town": "West End",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user