Lane G R24 (v5.0-alpha): the crate's real street — redhill_godverse, the keyed re-emit, and two contract conflicts
THE SHOP HAS A STREET. redhill_godverse: Monster Robot Party seated on Musgrave Road (verified — lot 8, type record, godverseShopId 3962749), 37 shops on E's 884 real Red Hill roads. Its coordinate is John-supplied (Google Maps place /g/11g6rlbbc0 -> -27.4553791, 153.0076244) because it is derivable from nothing we hold: OSM doesn't know the shop, the census doesn't either, dealgod's stores/store_entity rows are NULL lat/lng, recordgod has no address. Verified inside E's bbox and on Musgrave Road's own geometry; my own estimate was ~120 m out. RE-EMIT (ledger #5 + #2): atlas keyed by godverseShopId with US `license` throughout (British retired, 0 occurrences) — atlas-QA OK, 120 real records, byte-identical re-bake (the .webp bytes did not move; only the index). newtown_godverse re-emitted with ids (18 keyed). godverse_town.py: psycopg2 -> psql, EXTRA_SHOPS roster, and Fable's R22 Ruling 2 finally implemented. RULING 2 WAS LOAD-BEARING, NOT COSMETIC (measured): census-only Red Hill is unshippable — 9 shops = 306 m spacing warn; +Monster Robot = 10 shops = 261 m warn AND a frontage poster seated INSIDE edge 1037's kerb (-2.95 m); + the donor's texture layer (37) = no warn, poster passes. 261 m is worse than the 255 m that retired ballarat this round: the alpha's headline town would have shipped flagged "scattered, not a high street, or retire the town". 9 donor heroes swapped for census entries (18 in Newtown), counted per the ruling. CONFLICT #1 — C §7.3 vs E's validator. C says lift provenance to TOP LEVEL, US `license`, British retired, "exactly what E's validator reads". Measured: it is not. validate_atlas.py:98 reads NESTED `provenance`; :105 reads `licence` British-first. Lifting-and-retiring as written FAILS the gate; my R23 atlas already passed it untouched. Same species as the bug that held the tag — a doc asserting what code does without running it. Resolved: E's check accepts `licence or license`, so US-nested passes; the four also ride at top level (C's contract + the town-cache convention) off one source of truth. -> E, one line: :198 PRINTS `licence` British-only while the check accepts both, so C's mandated US spelling silently stops printing the licence flag — which :199's own comment calls a human call. Gate passes; evidence line vanished. Fix: .get("licence") or .get("license"). CONFLICT #2 — Ruling 2 vs A's identity gate. NEEDS A RULING; it is why the tree is red (6 of 8). Ruling 2 makes a godverse cache mixed: a keyed GODVERSE layer + an inherited OSM texture layer. A's sweep asserts EVERY shop is keyed -> partial FAILs. Texture shops carry no godverseShopId deliberately: an OSM milkbar has no GODVERSE identity, and minting one from its OSM id pushes a third id space into a field that already carries two — A's validator calls a duplicate mis-stocking, charter risk #3's nightmare with Monster Robot's crate as the prize. And A's own two checks disagree: the validator says "Missing = tier 0 (soft). Duplicate = mis-stocked (hard). They are not the same failure", while the sweep fails partial keying because it "mis-stocks the rest" — conflating exactly those two. Proposed (A's call, Fable's ruling): assert coverage over the godverse layer, not the inherited texture layer. THE 8 RED CHECKS, ITEMISED: newtown_godverse base+gig goldens (2) moved by the re-emit exactly as ledger #2 intends — A re-pins, base 0xcf69b387 gig 0x1e266f60; godverse identity x3 per godverse town (6) = conflict #2. Nothing else. Classic covenant 0x3fa36874 intact. redhill_godverse pins: base 0xb2224939. Verified live: the shop's 120-record pack and the town-wide 350 pack now COEXIST through C's type+base fix. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
5815dfbd58
commit
86e298531e
@ -3,6 +3,84 @@
|
|||||||
*Lane brief: `docs/LANES/LANE_G_GODVERSE.md`. This file follows your progress-file convention —
|
*Lane brief: `docs/LANES/LANE_G_GODVERSE.md`. This file follows your progress-file convention —
|
||||||
newest round first. Fable-on-ultra writing.*
|
newest round first. Fable-on-ultra writing.*
|
||||||
|
|
||||||
|
## Round 24 (2026-07-17) — the re-emit + THE SHOP'S REAL STREET (ledger #5)
|
||||||
|
|
||||||
|
**Landed (branch `lane-G-godverse`):**
|
||||||
|
- **`redhill_godverse`** — the town whose record shop is THE shop. Monster Robot Party seated on
|
||||||
|
Musgrave Road (**verified: lot 8, type `record`, `godverseShopId` 3962749**), 37 shops, 884 real
|
||||||
|
Red Hill roads off E's donor.
|
||||||
|
- **The atlas re-emit** — US `license` throughout (British `licence` retired, 0 occurrences),
|
||||||
|
provenance lifted to top level per C §7.3 **and** kept nested per E's validator (see conflict #1).
|
||||||
|
`atlas-QA OK`, 120 real records, byte-identical re-bake holds.
|
||||||
|
- **`newtown_godverse` re-emitted with `godverseShopId`** (ledger #2) — 18 census shops keyed.
|
||||||
|
- **`godverse_town.py`**: psycopg2 → `psql` (the m3 has no psycopg2); emits `godverseShopId`;
|
||||||
|
`EXTRA_SHOPS` roster; **Fable's R22 Ruling 2 finally implemented** (see below).
|
||||||
|
- G3 §4: the `godverseShopId`/`godverse_id` naming split recorded (two artifacts, two conventions).
|
||||||
|
|
||||||
|
**Monster Robot Party's coordinate** is John-supplied (Google Maps place `/g/11g6rlbbc0` →
|
||||||
|
-27.4553791, 153.0076244). It could not be derived: OSM doesn't know the shop (no name match, no
|
||||||
|
147 Musgrave Rd address node), the census doesn't have it, dealgod's `stores`/`store_entity` rows
|
||||||
|
carry NULL lat/lng, and recordgod has no address anywhere. Verified inside E's bbox **and** on
|
||||||
|
Musgrave Road's own geometry. My own estimate was ~120 m out — asked instead of shipping it.
|
||||||
|
|
||||||
|
**FABLE'S RULING 2, IMPLEMENTED (R22 → R24) — and it was load-bearing, not cosmetic.** "Merge
|
||||||
|
census heroes WITH the donor's texture shops; census heroes win a collision; count the swap."
|
||||||
|
I never built it. Measured this round, census-only Red Hill is **not shippable**:
|
||||||
|
|
||||||
|
| redhill_godverse | shops | A's spacing warn | poster-clears-kerb |
|
||||||
|
|---|---|---|---|
|
||||||
|
| census only | 9 | **306 m** ⚠ | pass |
|
||||||
|
| census + Monster Robot | 10 | **261 m** ⚠ | **FAIL** (poster inside edge 1037 by −2.95 m) |
|
||||||
|
| **+ donor texture (Ruling 2)** | **37** | **none** ✓ | **pass** ✓ |
|
||||||
|
| *(E's donor `redhill_real`, for reference)* | 36 | none | pass |
|
||||||
|
|
||||||
|
261 m is **worse than the 255 m that retired ballarat this round** — the alpha's headline town
|
||||||
|
would have shipped flagged "scattered, not a high street, or retire the town". Ruling 2 fixes both.
|
||||||
|
9 donor heroes swapped for their census entries in Red Hill, 18 in Newtown — counted, per the ruling.
|
||||||
|
|
||||||
|
**CONFLICT #1 — C's contract §7.3 vs E's validator (provenance).** C says "lift the four to TOP
|
||||||
|
LEVEL, US `license`; British retired — *exactly what E's validator reads*". **Measured: it is not.**
|
||||||
|
`validate_atlas.py:98` reads **nested** `provenance`, and `:105` reads `licence` British-first.
|
||||||
|
Lifting-and-retiring as written **fails the gate**. My R23 atlas already passed it untouched. This
|
||||||
|
is the same species as the bug that held the tag — a doc asserting what code does without running
|
||||||
|
it. **Resolved without drama:** E's check accepts `licence or license`, so US-nested passes; I emit
|
||||||
|
the four at top level (C's contract + the town-cache house convention) off ONE source of truth.
|
||||||
|
**→ E, one line:** `validate_atlas.py:198` PRINTS `licence` British-only while its *check* accepts
|
||||||
|
both — so adopting C's mandated US spelling **silently stops printing the licence flag**, which
|
||||||
|
:199's own comment calls a human call ("the licence is PRINTED — clearing it is a human call").
|
||||||
|
The gate still passes; the evidence line vanished. Fix: `.get("licence") or .get("license")`.
|
||||||
|
|
||||||
|
**CONFLICT #2 — Ruling 2 vs A's identity gate. THIS ONE NEEDS A RULING; it is why the tree is red.**
|
||||||
|
Ruling 2 makes a godverse cache *mixed*: a keyed GODVERSE layer (census + Monster Robot) plus an
|
||||||
|
inherited OSM texture layer. A's sweep asserts **every** shop is keyed → partial ⇒ FAIL. The two
|
||||||
|
standing rulings cannot both hold.
|
||||||
|
|
||||||
|
**The texture shops carry no `godverseShopId` deliberately**, and I'd argue A's own words settle it:
|
||||||
|
- The field *means* "this lot's stock lives at `stock_godverse/<id>/`". An OSM milkbar has no
|
||||||
|
GODVERSE identity. Minting one from its OSM id pushes a **third id space** into a field that
|
||||||
|
already carries two, and A's validator calls a duplicate **mis-stocking** — charter risk #3's
|
||||||
|
exact nightmare, with Monster Robot's crate as the prize. (Measured: OSM ids here are ≥53.1 M vs
|
||||||
|
census ≤2992 and store id 3,962,749 — no collision *today*; dealgod store ids reach 7 digits and
|
||||||
|
OSM ids are 8+, so the margin is thin, and "no collision today" is not an id-space design.)
|
||||||
|
- **A's validator and A's sweep disagree with each other.** The validator: *"Missing = tier 0
|
||||||
|
(soft). Duplicate = mis-stocked (hard). **They are not the same failure.**"* The sweep fails
|
||||||
|
partial keying because it *"mis-stocks the rest"* — conflating precisely the two failures A said
|
||||||
|
are not the same. Unkeyed → parody is the **designed ladder**, not a gap.
|
||||||
|
- **Proposed (A's call, Fable's ruling):** assert coverage over the **godverse layer**, not the
|
||||||
|
inherited texture layer — the subject is "shops that claim stock", and the gate stays honest
|
||||||
|
(it still names its subject and proves a count). Zero-keyed still SKIPs; duplicates still error.
|
||||||
|
|
||||||
|
**The 8 red checks, itemised — no surprises for F:**
|
||||||
|
- `newtown_godverse` base+gig goldens (**2**) — my re-emit moved them, exactly as ledger #2
|
||||||
|
intends. **A re-pins** (batched): base `0xcf69b387`, gig `0x1e266f60`.
|
||||||
|
- `godverse identity` ×3 on each godverse town (**6**) — conflict #2, above. Nothing else.
|
||||||
|
- Unpinned (warn, expected): `redhill_godverse` base `0xb2224939` / gig — **A pins**.
|
||||||
|
- **Classic covenant `0x3fa36874` intact.** `atlas-QA OK`.
|
||||||
|
|
||||||
|
**If Fable rules the other way** (texture layer out): revert to census-only and F inherits the
|
||||||
|
poster-kerb FAIL at edge 1037 + a 261 m town — that break is A/B's invariant, exposed by real data,
|
||||||
|
and would need an owner before the tag. I'd rather hand F the conflict than the broken town.
|
||||||
|
|
||||||
## Round 23 (2026-07-16) — G2a: THE FIRST REAL CRATE + the G3 draft
|
## Round 23 (2026-07-16) — G2a: THE FIRST REAL CRATE + the G3 draft
|
||||||
|
|
||||||
**Landed (branch `lane-G-godverse`, G namespace only — ready for your mid-round merge):**
|
**Landed (branch `lane-G-godverse`, G namespace only — ready for your mid-round merge):**
|
||||||
|
|||||||
@ -112,7 +112,12 @@ Design rules:
|
|||||||
says only what changed. A sold record is a `sku` in `gone[]` → `collapseBuyItem()` (already
|
says only what changed. A sold record is a `sku` in `gone[]` → `collapseBuyItem()` (already
|
||||||
built, R9) zero-areas the sleeve. Sold-means-gone is a *diff*, not a re-fetch.
|
built, R9) zero-areas the sleeve. Sold-means-gone is a *diff*, not a re-fetch.
|
||||||
- **`etag`/`since`** so a re-enter is a 304, not a payload.
|
- **`etag`/`since`** so a re-enter is a 304, not a payload.
|
||||||
- Shop id is the **godverse id** (§1a), never a POS internal id.
|
- Shop id is the **godverse id** (§1a), never a POS internal id. **R24 naming, as landed:** the
|
||||||
|
town cache carries `shop.godverseShopId` (A's schema field, camelCase, read by the JS runtime);
|
||||||
|
the atlas index carries `shop.godverse_id` (snake_case, what E's `validate_atlas.py` requires).
|
||||||
|
Same id, two artifacts, two house conventions — the API uses `godverse_id` on the wire to match
|
||||||
|
the Python/JSON side it is served from. Recorded rather than silently reconciled: if the
|
||||||
|
integrators want one spelling, it is a one-line change in each emitter, not a design question.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@ -35,7 +35,7 @@
|
|||||||
"captured": "2026-07-15",
|
"captured": "2026-07-15",
|
||||||
"extraction": "see load_image_map() docstring \u2014 the products projection command"
|
"extraction": "see load_image_map() docstring \u2014 the products projection command"
|
||||||
},
|
},
|
||||||
"licence": {
|
"license": {
|
||||||
"covers": "the shop's own product shots (monsterrobot.party), cached by dealgod",
|
"covers": "the shop's own product shots (monsterrobot.party), cached by dealgod",
|
||||||
"zone": "in-house-green",
|
"zone": "in-house-green",
|
||||||
"flag": "FLAG BEFORE ANY PUBLIC/COMMERCIAL RELEASE",
|
"flag": "FLAG BEFORE ANY PUBLIC/COMMERCIAL RELEASE",
|
||||||
|
|||||||
@ -151,7 +151,9 @@ def snapshot(args):
|
|||||||
"captured": args.captured,
|
"captured": args.captured,
|
||||||
"extraction": "see load_image_map() docstring — the products projection command",
|
"extraction": "see load_image_map() docstring — the products projection command",
|
||||||
},
|
},
|
||||||
"licence": {
|
# US `license` throughout (R24: British `licence` retired per C's contract §7.3; E's
|
||||||
|
# validator reads `provenance.licence or provenance.license`, so US passes the gate).
|
||||||
|
"license": {
|
||||||
"covers": "the shop's own product shots (monsterrobot.party), cached by dealgod",
|
"covers": "the shop's own product shots (monsterrobot.party), cached by dealgod",
|
||||||
"zone": "in-house-green", "flag": "FLAG BEFORE ANY PUBLIC/COMMERCIAL RELEASE",
|
"zone": "in-house-green", "flag": "FLAG BEFORE ANY PUBLIC/COMMERCIAL RELEASE",
|
||||||
"metadata": "real titles/artists — John's R23 amendment to the no-real-trademarks law",
|
"metadata": "real titles/artists — John's R23 amendment to the no-real-trademarks law",
|
||||||
@ -204,16 +206,31 @@ def pack(args):
|
|||||||
|
|
||||||
with open(args.snapshot, "rb") as fh:
|
with open(args.snapshot, "rb") as fh:
|
||||||
snap_sha = hashlib.sha256(fh.read()).hexdigest()
|
snap_sha = hashlib.sha256(fh.read()).hexdigest()
|
||||||
|
lic = snap["license"]
|
||||||
|
generator, snapshot_name = "pipeline/godverse_stock.py", os.path.basename(args.snapshot)
|
||||||
|
attribution = (f"Stock, prices + covers: {shop['name']} ({OWN_PHOTO_HOST}) — the shop's own "
|
||||||
|
f"product shots · cover cache: GODVERSE/dealgod · POS: recordgod")
|
||||||
index = {
|
index = {
|
||||||
"version": 1, "kind": "record", "tier": 1,
|
"version": 1, "kind": "record", "tier": 1,
|
||||||
"atlas_px": ATLAS_PX, "cell_w": CELL_W, "cell_h": CELL_H,
|
"atlas_px": ATLAS_PX, "cell_w": CELL_W, "cell_h": CELL_H,
|
||||||
"count": len(idx_items), "atlases": atlases,
|
"count": len(idx_items), "atlases": atlases,
|
||||||
|
# TOP LEVEL, per C's contract §7.3 + the house convention of the shipped town caches
|
||||||
|
# (`license` is a short US-spelled string there, `attribution` its companion).
|
||||||
|
"license": f"{lic['zone']} — {lic['covers']}; {lic['flag']}",
|
||||||
|
"attribution": attribution,
|
||||||
|
"generator": generator,
|
||||||
|
"snapshot": snapshot_name,
|
||||||
"shop": {k: shop[k] for k in ("godverse_id", "slug", "name", "suburb", "state")},
|
"shop": {k: shop[k] for k in ("godverse_id", "slug", "name", "suburb", "state")},
|
||||||
"crate": snap["crate"],
|
"crate": snap["crate"],
|
||||||
|
# NESTED, per E's validate_atlas.py — which is the authority C's §7.3 defers to for these
|
||||||
|
# fields, and which reads provenance.{generator,snapshot,licence|license{zone,flag,covers}}.
|
||||||
|
# C's doc asserts top-level is "exactly what E's validator reads"; measured, it is not (see
|
||||||
|
# G-progress R24). Emitting both keeps C's contract and E's gate green off ONE source of
|
||||||
|
# truth (`lic`/`generator`/`snapshot` above) — flagged for F/Fable to collapse to one.
|
||||||
"provenance": {
|
"provenance": {
|
||||||
"generator": "pipeline/godverse_stock.py",
|
"generator": generator,
|
||||||
"source": snap["source"], "licence": snap["licence"],
|
"source": snap["source"], "license": lic,
|
||||||
"snapshot": os.path.basename(args.snapshot), "snapshot_sha256": snap_sha,
|
"snapshot": snapshot_name, "snapshot_sha256": snap_sha,
|
||||||
"determinism": "pure bake — same snapshot + same cover bytes => byte-identical atlas; "
|
"determinism": "pure bake — same snapshot + same cover bytes => byte-identical atlas; "
|
||||||
"covers pinned by sha256, no RNG, no clock",
|
"covers pinned by sha256, no RNG, no clock",
|
||||||
},
|
},
|
||||||
|
|||||||
@ -6,12 +6,22 @@ shops[] come from thriftgod's Postgres (the real-business census DealGod scraped
|
|||||||
walk the town's actual streets past its actual secondhand shops.
|
walk the town's actual streets past its actual secondhand shops.
|
||||||
|
|
||||||
python3 pipeline/godverse_town.py newtown_real newtown_godverse # roads-donor key, out key
|
python3 pipeline/godverse_town.py newtown_real newtown_godverse # roads-donor key, out key
|
||||||
|
python3 pipeline/godverse_town.py redhill_real redhill_godverse # THE shop's real street
|
||||||
|
|
||||||
Runs on ultra (needs the local `thriftgod` PG + psycopg2 — the thriftgod server's own deps).
|
Reads thriftgod via the `psql` CLI (R24): psycopg2 isn't installed on the m3, and one less dep
|
||||||
|
between the census and the cache. Works unchanged wherever thriftgod lives.
|
||||||
|
|
||||||
|
`godverseShopId` (R24 ledger #2, with Lane A) — the OPAQUE key that resolves
|
||||||
|
`stock_godverse/<godverseShopId>/`. **It is not `shop.id` and not "the census id"**: census shops
|
||||||
|
carry their thriftgod id (max 2992), while Monster Robot Party isn't in the census at all and
|
||||||
|
carries its dealgod store id (3962749). Two disjoint id spaces, one opaque field — G emits it,
|
||||||
|
everyone else reads it, nobody derives it. A's `validateTownCache` wants a positive integer,
|
||||||
|
unique per cache; **missing is a WARN, not an error** (measured — A's rule is deliberate: absent
|
||||||
|
stock identity must never fail a town's boot, per the charter's determinism boundary). So the
|
||||||
|
GODVERSE layer is keyed and the inherited texture layer is not — see Ruling 2 below for why that
|
||||||
|
is the honest shape rather than a gap.
|
||||||
"""
|
"""
|
||||||
import json, os, sys
|
import json, math, os, subprocess, sys
|
||||||
|
|
||||||
import psycopg2, psycopg2.extras
|
|
||||||
|
|
||||||
DONOR, OUT = sys.argv[1], sys.argv[2]
|
DONOR, OUT = sys.argv[1], sys.argv[2]
|
||||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||||
@ -22,17 +32,98 @@ TYPE_MAP = {'music': 'record', 'charity': 'opshop', 'second_hand': 'opshop', 'an
|
|||||||
'books': 'book', 'toys': 'toy', 'video_games': 'video', 'games': 'toy',
|
'books': 'book', 'toys': 'toy', 'video_games': 'video', 'games': 'toy',
|
||||||
'pawnbroker': 'pawn', 'market': 'dept'}
|
'pawnbroker': 'pawn', 'market': 'dept'}
|
||||||
|
|
||||||
|
# REAL shops that exist in neither OSM nor thriftgod's census, added by explicit, sourced ruling.
|
||||||
|
# This is not a mapping table (the R12 gigKey law) — it's a hand-verified roster of the shops the
|
||||||
|
# epoch is actually about. Every entry states where its identity and its coordinates came from.
|
||||||
|
#
|
||||||
|
# Monster Robot Party: the v5 alpha's shop — its POS *is* `recordgod`, its own site hosts the
|
||||||
|
# covers, and dealgod scrapes it as store 3962749 (which is therefore its godverseShopId). OSM
|
||||||
|
# doesn't know it (no name match, no 147 Musgrave Rd address node) and the census doesn't either,
|
||||||
|
# so the coordinate cannot be derived from any dataset we hold: John supplied the shop's Google
|
||||||
|
# Maps place (place `/g/11g6rlbbc0`), whose point is -27.4553791, 153.0076244 — inside
|
||||||
|
# `redhill_real`'s bbox and on Musgrave Road's own geometry. Verified, not estimated.
|
||||||
|
EXTRA_SHOPS = {
|
||||||
|
'redhill_godverse': [
|
||||||
|
{'id': 3962749, 'godverseShopId': 3962749, 'name': 'Monster Robot Party', 'type': 'record',
|
||||||
|
'lat': -27.4553791, 'lon': 153.0076244, 'suburb': 'Red Hill',
|
||||||
|
'source': 'GODVERSE/dealgod store 3962749 · POS recordgod · coords: Google Maps place '
|
||||||
|
'/g/11g6rlbbc0 (147 Musgrave Road, Red Hill QLD), John-supplied R24'},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def psql(db, sql):
|
||||||
|
out = subprocess.run(['psql', '-d', db, '-tAF\t', '-c', sql],
|
||||||
|
capture_output=True, text=True, check=True).stdout
|
||||||
|
return [ln.split('\t') for ln in out.splitlines() if ln.strip()]
|
||||||
|
|
||||||
|
|
||||||
donor = json.load(open(os.path.join(TOWNS, f'{DONOR}.json')))
|
donor = json.load(open(os.path.join(TOWNS, f'{DONOR}.json')))
|
||||||
bb = donor['bbox']
|
bb = donor['bbox']
|
||||||
db = psycopg2.connect(os.environ.get('THRIFTGOD_DSN', 'dbname=thriftgod'))
|
rows = psql(os.environ.get('THRIFTGOD_DB', 'thriftgod'), f"""
|
||||||
c = db.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
|
SELECT id, COALESCE(name,''), COALESCE(shop_type,''), lat, lng, COALESCE(suburb,'')
|
||||||
c.execute("""SELECT id, name, shop_type, lat, lng, suburb FROM shop
|
FROM shop
|
||||||
WHERE lat BETWEEN %s AND %s AND lng BETWEEN %s AND %s ORDER BY id""",
|
WHERE lat BETWEEN {bb['minLat']} AND {bb['maxLat']}
|
||||||
(bb['minLat'], bb['maxLat'], bb['minLon'], bb['maxLon']))
|
AND lng BETWEEN {bb['minLon']} AND {bb['maxLon']}
|
||||||
shops = [{'id': r['id'], 'name': r['name'],
|
ORDER BY id""")
|
||||||
'type': TYPE_MAP.get(r['shop_type'], 'opshop'),
|
|
||||||
'lat': float(r['lat']), 'lon': float(r['lng']),
|
shops = [{'id': int(r[0]),
|
||||||
'suburb': (r['suburb'] or '').split('|')[0]} for r in c.fetchall()]
|
'godverseShopId': int(r[0]), # census shops: the thriftgod id IS the key (not derived
|
||||||
|
# from `id` by law — they coincide, see the docstring)
|
||||||
|
'name': r[1],
|
||||||
|
'type': TYPE_MAP.get(r[2], 'opshop'),
|
||||||
|
'lat': float(r[3]), 'lon': float(r[4]),
|
||||||
|
'suburb': r[5].split('|')[0]} for r in rows]
|
||||||
|
census_n = len(shops)
|
||||||
|
|
||||||
|
extras = EXTRA_SHOPS.get(OUT, [])
|
||||||
|
known = {s['id'] for s in shops}
|
||||||
|
for e in sorted(extras, key=lambda s: s['id']):
|
||||||
|
if e['id'] in known: # census wins a collision, and we count the swap
|
||||||
|
continue
|
||||||
|
shops.append({k: e[k] for k in ('id', 'godverseShopId', 'name', 'type', 'lat', 'lon', 'suburb')})
|
||||||
|
extra_n = len(shops) - census_n
|
||||||
|
|
||||||
|
|
||||||
|
# ── Fable's Ruling 2 (R22, implemented R24): INHERIT THE DONOR'S TEXTURE LAYER ────────────────
|
||||||
|
# "Merge census heroes WITH the donor's texture shops; census heroes always win a collision
|
||||||
|
# (dedupe by proximity + name), drop the donor hero, keep yours, count the swap. Result: real
|
||||||
|
# census shops on a street that also feels alive."
|
||||||
|
#
|
||||||
|
# Not cosmetic — MEASURED (R24): the census layer alone is 9 shops across Red Hill's 2.4 km bbox →
|
||||||
|
# A's spacing warn at 261 m ("scattered, not a high street"), *worse than the 255 m that retired
|
||||||
|
# ballarat this round*, plus a frontage poster seated inside edge 1037's kerb. With the donor's
|
||||||
|
# texture layer merged (37 shops) both go away and E's donor's own density is preserved.
|
||||||
|
#
|
||||||
|
# THE MERGED SHOPS CARRY NO `godverseShopId`, DELIBERATELY. The field is the GODVERSE stock
|
||||||
|
# identity; an OSM cafe has none, and minting one from its OSM id would push a third id space into
|
||||||
|
# a field that already carries two (thriftgod census ≤2992 · dealgod store ids). OSM ids are the
|
||||||
|
# same order of magnitude as dealgod store ids, so a collision is *possible* — and A's validator
|
||||||
|
# calls a duplicate godverseShopId mis-stocking (charter risk #3: two shops keyed to one atlas,
|
||||||
|
# one of them Monster Robot's crate). Unkeyed is both true and safe: they fall soft to tier 0,
|
||||||
|
# which is exactly what a milkbar with no stock should do. A's coverage warn states this honestly.
|
||||||
|
def norm(n):
|
||||||
|
return ' '.join((n or '').lower().split())
|
||||||
|
|
||||||
|
|
||||||
|
def near(a, b, m=150.0): # metres, flat-earth at suburb scale — plenty at 150 m
|
||||||
|
dy = (a['lat'] - b['lat']) * 111_320.0
|
||||||
|
dx = (a['lon'] - b['lon']) * 111_320.0 * math.cos(math.radians(a['lat']))
|
||||||
|
return math.hypot(dx, dy) <= m
|
||||||
|
|
||||||
|
|
||||||
|
swaps = 0
|
||||||
|
for d in sorted(donor.get('shops', []), key=lambda s: s['id']):
|
||||||
|
hit = next((s for s in shops if norm(s['name']) == norm(d['name']) and near(s, d)), None)
|
||||||
|
if hit: # the donor's own entry for a shop we already hold
|
||||||
|
swaps += 1
|
||||||
|
continue
|
||||||
|
if d['id'] in {s['id'] for s in shops}:
|
||||||
|
continue
|
||||||
|
shops.append({'id': d['id'], 'name': d['name'], 'type': d['type'],
|
||||||
|
'lat': d['lat'], 'lon': d['lon'], 'suburb': d.get('suburb', '')})
|
||||||
|
texture_n = len(shops) - census_n - extra_n
|
||||||
|
shops.sort(key=lambda s: s['id']) # deterministic order, extras + texture included
|
||||||
|
|
||||||
out = dict(donor)
|
out = dict(donor)
|
||||||
out.update({
|
out.update({
|
||||||
@ -42,10 +133,20 @@ out.update({
|
|||||||
'generator': 'pipeline/godverse_town.py',
|
'generator': 'pipeline/godverse_town.py',
|
||||||
'attribution': donor['attribution'] + ' · shop census: GODVERSE/thriftgod',
|
'attribution': donor['attribution'] + ' · shop census: GODVERSE/thriftgod',
|
||||||
'shops': shops,
|
'shops': shops,
|
||||||
'counts': {**donor.get('counts', {}), 'shops': len(shops)},
|
# The donor's shop-side counts (raw/heroes/texture/textureDropped) describe the shop layer we
|
||||||
|
# REPLACE, so carrying them would state a census of shops this cache doesn't contain. Keep the
|
||||||
|
# donor's road count; recount our own shops.
|
||||||
|
'counts': {**{k: v for k, v in donor.get('counts', {}).items() if k == 'roads'},
|
||||||
|
'shops': len(shops), 'census': census_n, 'extra': extra_n,
|
||||||
|
'texture': texture_n, 'donorSwaps': swaps},
|
||||||
})
|
})
|
||||||
dst = os.path.join(TOWNS, f'{OUT}.json')
|
dst = os.path.join(TOWNS, f'{OUT}.json')
|
||||||
json.dump(out, open(dst, 'w'), indent=1)
|
with open(dst, 'w') as fh:
|
||||||
|
json.dump(out, fh, indent=1)
|
||||||
types = {}
|
types = {}
|
||||||
for s in shops: types[s['type']] = types.get(s['type'], 0) + 1
|
for s in shops:
|
||||||
print(f"{OUT}: {len(shops)} REAL shops ({types}) + {len(out.get('roads', []))} OSM roads -> {dst}")
|
types[s['type']] = types.get(s['type'], 0) + 1
|
||||||
|
keyed = sum(1 for s in shops if 'godverseShopId' in s)
|
||||||
|
print(f"{OUT}: {len(shops)} shops = {census_n} census + {extra_n} extra + {texture_n} donor-texture "
|
||||||
|
f"({swaps} donor heroes swapped for census) · {keyed} keyed w/ godverseShopId")
|
||||||
|
print(f" {types} + {len(out.get('roads', []))} OSM roads -> {dst}")
|
||||||
|
|||||||
@ -10,6 +10,10 @@
|
|||||||
"stock_record_atlas_00.webp",
|
"stock_record_atlas_00.webp",
|
||||||
"stock_record_atlas_01.webp"
|
"stock_record_atlas_01.webp"
|
||||||
],
|
],
|
||||||
|
"license": "in-house-green \u2014 the shop's own product shots (monsterrobot.party), cached by dealgod; FLAG BEFORE ANY PUBLIC/COMMERCIAL RELEASE",
|
||||||
|
"attribution": "Stock, prices + covers: Monster Robot Party (monsterrobot.party) \u2014 the shop's own product shots \u00b7 cover cache: GODVERSE/dealgod \u00b7 POS: recordgod",
|
||||||
|
"generator": "pipeline/godverse_stock.py",
|
||||||
|
"snapshot": "monsterrobot_550.snapshot.json",
|
||||||
"shop": {
|
"shop": {
|
||||||
"godverse_id": 3962749,
|
"godverse_id": 3962749,
|
||||||
"slug": "monsterrobot",
|
"slug": "monsterrobot",
|
||||||
@ -32,7 +36,7 @@
|
|||||||
"captured": "2026-07-15",
|
"captured": "2026-07-15",
|
||||||
"extraction": "see load_image_map() docstring \u2014 the products projection command"
|
"extraction": "see load_image_map() docstring \u2014 the products projection command"
|
||||||
},
|
},
|
||||||
"licence": {
|
"license": {
|
||||||
"covers": "the shop's own product shots (monsterrobot.party), cached by dealgod",
|
"covers": "the shop's own product shots (monsterrobot.party), cached by dealgod",
|
||||||
"zone": "in-house-green",
|
"zone": "in-house-green",
|
||||||
"flag": "FLAG BEFORE ANY PUBLIC/COMMERCIAL RELEASE",
|
"flag": "FLAG BEFORE ANY PUBLIC/COMMERCIAL RELEASE",
|
||||||
@ -40,7 +44,7 @@
|
|||||||
"pii": "none \u2014 inventory/crate/disc_cache only; customer/staff/sales never read"
|
"pii": "none \u2014 inventory/crate/disc_cache only; customer/staff/sales never read"
|
||||||
},
|
},
|
||||||
"snapshot": "monsterrobot_550.snapshot.json",
|
"snapshot": "monsterrobot_550.snapshot.json",
|
||||||
"snapshot_sha256": "d6b0e2f7a7474a69354370ade98b1887d8c778a4f07a2ba62a6c366dadd22c2f",
|
"snapshot_sha256": "8b0ff903ce8f746aa80ef25f3eb498ec9a3bc890ec0607c2a5921674e4ef612a",
|
||||||
"determinism": "pure bake \u2014 same snapshot + same cover bytes => byte-identical atlas; covers pinned by sha256, no RNG, no clock"
|
"determinism": "pure bake \u2014 same snapshot + same cover bytes => byte-identical atlas; covers pinned by sha256, no RNG, no clock"
|
||||||
},
|
},
|
||||||
"items": [
|
"items": [
|
||||||
|
|||||||
@ -18,13 +18,17 @@
|
|||||||
"maxLon": 151.1931100389657
|
"maxLon": 151.1931100389657
|
||||||
},
|
},
|
||||||
"counts": {
|
"counts": {
|
||||||
"raw": 19,
|
"roads": 1086,
|
||||||
"shops": 18,
|
"shops": 72,
|
||||||
"roads": 1086
|
"census": 18,
|
||||||
|
"extra": 0,
|
||||||
|
"texture": 54,
|
||||||
|
"donorSwaps": 18
|
||||||
},
|
},
|
||||||
"shops": [
|
"shops": [
|
||||||
{
|
{
|
||||||
"id": 158,
|
"id": 158,
|
||||||
|
"godverseShopId": 158,
|
||||||
"name": "Egg Records",
|
"name": "Egg Records",
|
||||||
"type": "record",
|
"type": "record",
|
||||||
"lat": -33.897238,
|
"lat": -33.897238,
|
||||||
@ -33,6 +37,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 247,
|
"id": 247,
|
||||||
|
"godverseShopId": 247,
|
||||||
"name": "The Vintage Record",
|
"name": "The Vintage Record",
|
||||||
"type": "record",
|
"type": "record",
|
||||||
"lat": -33.8875182,
|
"lat": -33.8875182,
|
||||||
@ -41,6 +46,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 248,
|
"id": 248,
|
||||||
|
"godverseShopId": 248,
|
||||||
"name": "Aalders",
|
"name": "Aalders",
|
||||||
"type": "opshop",
|
"type": "opshop",
|
||||||
"lat": -33.8877879,
|
"lat": -33.8877879,
|
||||||
@ -49,6 +55,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 568,
|
"id": 568,
|
||||||
|
"godverseShopId": 568,
|
||||||
"name": "Towers Antiques",
|
"name": "Towers Antiques",
|
||||||
"type": "opshop",
|
"type": "opshop",
|
||||||
"lat": -33.9035515,
|
"lat": -33.9035515,
|
||||||
@ -57,6 +64,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 569,
|
"id": 569,
|
||||||
|
"godverseShopId": 569,
|
||||||
"name": "Old Wares",
|
"name": "Old Wares",
|
||||||
"type": "opshop",
|
"type": "opshop",
|
||||||
"lat": -33.9032933,
|
"lat": -33.9032933,
|
||||||
@ -65,6 +73,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 608,
|
"id": 608,
|
||||||
|
"godverseShopId": 608,
|
||||||
"name": "Goulds Books",
|
"name": "Goulds Books",
|
||||||
"type": "book",
|
"type": "book",
|
||||||
"lat": -33.9029178,
|
"lat": -33.9029178,
|
||||||
@ -73,6 +82,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 717,
|
"id": 717,
|
||||||
|
"godverseShopId": 717,
|
||||||
"name": "Co-op",
|
"name": "Co-op",
|
||||||
"type": "book",
|
"type": "book",
|
||||||
"lat": -33.890884,
|
"lat": -33.890884,
|
||||||
@ -81,6 +91,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 1082,
|
"id": 1082,
|
||||||
|
"godverseShopId": 1082,
|
||||||
"name": "Downtown Music",
|
"name": "Downtown Music",
|
||||||
"type": "record",
|
"type": "record",
|
||||||
"lat": -33.8878728,
|
"lat": -33.8878728,
|
||||||
@ -89,6 +100,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 1173,
|
"id": 1173,
|
||||||
|
"godverseShopId": 1173,
|
||||||
"name": "Haze Guitars",
|
"name": "Haze Guitars",
|
||||||
"type": "record",
|
"type": "record",
|
||||||
"lat": -33.8925979,
|
"lat": -33.8925979,
|
||||||
@ -97,6 +109,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 1697,
|
"id": 1697,
|
||||||
|
"godverseShopId": 1697,
|
||||||
"name": "Thats So Mi",
|
"name": "Thats So Mi",
|
||||||
"type": "opshop",
|
"type": "opshop",
|
||||||
"lat": -33.9011361,
|
"lat": -33.9011361,
|
||||||
@ -105,6 +118,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 2019,
|
"id": 2019,
|
||||||
|
"godverseShopId": 2019,
|
||||||
"name": "St Luke\u2019s Op Shop",
|
"name": "St Luke\u2019s Op Shop",
|
||||||
"type": "opshop",
|
"type": "opshop",
|
||||||
"lat": -33.898898,
|
"lat": -33.898898,
|
||||||
@ -113,6 +127,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 2048,
|
"id": 2048,
|
||||||
|
"godverseShopId": 2048,
|
||||||
"name": "Savers",
|
"name": "Savers",
|
||||||
"type": "opshop",
|
"type": "opshop",
|
||||||
"lat": -33.8869187,
|
"lat": -33.8869187,
|
||||||
@ -121,6 +136,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 2200,
|
"id": 2200,
|
||||||
|
"godverseShopId": 2200,
|
||||||
"name": "Better Read Than Dead",
|
"name": "Better Read Than Dead",
|
||||||
"type": "book",
|
"type": "book",
|
||||||
"lat": -33.8961303,
|
"lat": -33.8961303,
|
||||||
@ -129,6 +145,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 2201,
|
"id": 2201,
|
||||||
|
"godverseShopId": 2201,
|
||||||
"name": "Elizabeth's Bookshop",
|
"name": "Elizabeth's Bookshop",
|
||||||
"type": "book",
|
"type": "book",
|
||||||
"lat": -33.8959321,
|
"lat": -33.8959321,
|
||||||
@ -137,6 +154,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 2212,
|
"id": 2212,
|
||||||
|
"godverseShopId": 2212,
|
||||||
"name": "Papa Disquo",
|
"name": "Papa Disquo",
|
||||||
"type": "record",
|
"type": "record",
|
||||||
"lat": -33.8986661,
|
"lat": -33.8986661,
|
||||||
@ -145,6 +163,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 2377,
|
"id": 2377,
|
||||||
|
"godverseShopId": 2377,
|
||||||
"name": "Kidstuff",
|
"name": "Kidstuff",
|
||||||
"type": "toy",
|
"type": "toy",
|
||||||
"lat": -33.8870857,
|
"lat": -33.8870857,
|
||||||
@ -153,6 +172,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 2505,
|
"id": 2505,
|
||||||
|
"godverseShopId": 2505,
|
||||||
"name": "EB Games",
|
"name": "EB Games",
|
||||||
"type": "video",
|
"type": "video",
|
||||||
"lat": -33.9064099,
|
"lat": -33.9064099,
|
||||||
@ -161,11 +181,444 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 2506,
|
"id": 2506,
|
||||||
|
"godverseShopId": 2506,
|
||||||
"name": "Toys and Tales",
|
"name": "Toys and Tales",
|
||||||
"type": "toy",
|
"type": "toy",
|
||||||
"lat": -33.9073712,
|
"lat": -33.9073712,
|
||||||
"lon": 151.1724377,
|
"lon": 151.1724377,
|
||||||
"suburb": "Newtown"
|
"suburb": "Newtown"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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": 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": 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": 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"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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": 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": 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": 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": 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": 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": 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": [
|
"roads": [
|
||||||
|
|||||||
38572
web/assets/towns/redhill_godverse.json
Normal file
38572
web/assets/towns/redhill_godverse.json
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user