PROCITY/pipeline/validate_atlas.py
m3ultra 5815dfbd58 Lane E R24 (v5.0-alpha): the crate's real street, the honest gate, and Ballarat retired
THE STREET (ledger #3). redhill_real: Red Hill, Brisbane — 36 shops (9 heroes + 27 texture,
-29 subsampled), 884 roads, median NN 73 m. **Musgrave Road is in roads[] (41 ways, primary +
residential)** — that was the point: Monster Robot Party's real shop now has its real street.
One bounded Overpass fetch under the standing authorization; raw snapshots in _raw/, ODbL, C's
mapping. This is G's donor for godverse_town.py -> redhill_godverse.

TWO HAZARDS HANDED TO G/A, both measured against the real geometry (notes R24 §1):
(a) The shop is NOT in OSM — no shop=music node, no name match anywhere in the bbox (checked the
raw snapshot, not the emitted cache). It cannot arrive via the fetch; G must inject id 3962749
from the census or the town ships without the shop the release is about.
(b) THE SEATING HAZARD: A's lift seats on the NEAREST edge, and from G's geocode three streets
are nearer than the addressed one — Zig Zag Street 38.6 m (residential), Craig Street 46.1 m,
Musgrave Road 51.6 m. The shop would seat on Zig Zag St and the headline would quietly become
false. Measured fix (G's call): the point on the real Musgrave centreline nearest G's geocode is
-27.455169, 153.006902 — 0 m to Musgrave, next candidate 43 m. Snaps the street, does NOT verify
the house number; I have no address data for where #147 sits along the road.

THE HONEST GATE (ledger #4). F's catch was right and there were FOUR bugs: the glob used the
published-contract name the runtime can never load (0 matches -> returned 0: it passed identically
whether atlases were absent, misnamed, or perfect); the code read provenance top-level while my own
docstring said nested (G nested it correctly — the gate would have failed G's real artifact);
licence/license; and a stale ceiling. Rebuilt on the "runtime is the authority" ruling: globs
stock_godverse/<id>/stock_<type>_index.json, type in {record,book,toy}. Vacuous-gate law implemented
literally — 0 matches + stray files FAIL (listing them); 0 matches + empty dir prints
"atlas-QA SKIP: no atlases present (0 files matched glob ...)"; plus an id equality
(shop.godverse_id == dir) so it proves it touched its subject. Proved on four cases: empty -> loud
SKIP, misnamed (F's exact bug) -> FAIL, unparseable -> FAIL, G's real atlas -> PASS (120 items,
2 atlases, licence printed). My own test found a fifth hole — the glob still matched the old
stock_shop_* name, so the misnamed case failed only incidentally; added a STOCK_TYPES name check
so it fails for the RIGHT reason. A gate that fails for the wrong reason will pass for the wrong
reason later.

BALLARAT — RETIRED, counted, and a DIFFERENT illness from Toowoomba (notes R24 §3). Toowoomba was
dead everywhere. Ballarat is alive exactly where the game isn't: 103 POIs, densest 400 m disc = 47
of them (Toowoomba's was 3) — but that disc is cafes/clothes/bakeries while the op-shops sit 854 m,
1042 m and 1709 m out. Heroes and density don't coincide, so no bbox holds both. Real builds at
every span: 1.0 km -> 8 shops/184 m/hub 4-8 but ZERO op-shops (a thrift town with no thrift shop);
2.0 km -> 12/186 m/3-12; 3.0 km as shipped -> 20/255 m/4-20. Every row worse than any shipped town.
THE BINDING CONSTRAINT WAS MINE, NOT OSM'S: with 2 heroes the 3x texture cap keeps 6 of 47 texture
POIs and the subsample is spatially blind, so survivors scatter at any span — part of the 255 m A's
warn caught was my own cap's fingerprint. Seeding texture near heroes fixes the class but moves
every town's golden -> filed as a v5.x proposal, not done in a release round.

I OVERSTATED MY R23 ADVICE TO A: the hub fraction is NOT a standalone discriminator — glebe (shipped,
healthy) reads 13% and redhill 14%; a hub threshold would false-flag both. Median NN is the robust
primary; hub is only comparable at equal shop counts (toowoomba 2/12 vs darwin 7/12 was fair because
both had 12). The raw-vs-seated calibration point stands.

METHODOLOGY, recorded because it nearly shipped: I picked Ballarat's span from an offline simulation
that predicted 68 m / hub 5-8, and had already written those numbers into a config comment as fact.
The real build returned 184 m / 4-8 — same counts, different sample. Never trust a re-implementation
of the pipeline's own selection; run the pipeline.

-> A (batched pins): ADD REAL_TOWN_GOLDENS['redhill_real']=0x6046700f and
REAL_TOWN_GIG_GOLDENS['redhill_real']=0x673785ee (the headline town currently only WARNS as
UNPINNED — ungated until you pin it). REMOVE ballarat_real 0xf0a9e8a1 / 0xf097c331 — dead config by
your own R23 note.
-> F: tools/qa/tour_shots.py still shoots town=toowoomba_real, which I deleted in R23 — the v4 tour
breaks on a missing town. Your file, your call. A retirement isn't done when the cache is deleted;
it's done when the consumers are swept.
-> C: built to the runtime + G's real artifact per the ruling; LANE_C_PUB §7 line 271 still names the
contradictory stock_shop_<id>_index.json. I reconcile when your corrected contract lands.

Determinism receipt: the retirement forced a full 21-town rebuild from cached raw (no refetches) and
every existing cache came back byte-identical — only ballarat, redhill and index.json moved. Pack:
21 real towns · 1196 shops · 299 heroes (22 rostered with G's newtown_godverse). Selfcheck ALL GREEN
149,696, synthetic golden 0x3fa36874 unchanged. qa.sh --strict --matrix: 7 passed / 0 failed, MATRIX
GREEN 10x7.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 01:06:00 +10:00

214 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""Validate per-shop real-stock atlases — the G2a format gate (v5.0-alpha, R23 #4 · REBUILT R24 #4).
Judges Lane G's tier-1 atlases **from the committed files alone** — no dealgod DB, no network — so the
gate runs on m3. Wired into `validate_manifest` (qa gate 3).
R24 REBUILD — F held the v5.0-alpha tag partly on this file, and was right. What was wrong (all four
real, all mine):
1. It globbed `stock_shop_<id>_index.json` — the PUBLISHED contract's name, which **the runtime can
never load**. G shipped what the loader reads. So the gate matched 0 files, returned 0, and passed
whether the atlas was absent, misnamed or perfect. *A gate that returns 0 on an empty set is not a
gate.* → now globs the RUNTIME path, and an empty set SKIPs loudly while a present-but-unmatched
file FAILS (the vacuous-gate law).
2. Provenance: my code read it TOP-LEVEL while my own docstring said NESTED — G nested it. My doc and
my code contradicted each other and G's atlas failed either way. → nested, per the artifact.
3. `licence` vs `license` — G uses British; I demanded American. → British (the emitter's, and it's
the artifact of record).
4. A ceiling stale by one round (I warned on 2 atlases; C's §7.2 allows ≤2 × ≤2048², and G's 120-item
crate lands exactly on the cap). → C's current numbers.
THE AUTHORITY IS THE RUNTIME (R24 ruling). `stockpack.js` states it: a per-shop pack lives under
`assets/stock_godverse/<godverseShopId>/` and carries the SAME `stock_<type>_index.json` name (type picks
the stock slot); cache identity is `(type, base)`. This gate reads that, not a doc that disagrees with it.
python3 pipeline/validate_atlas.py # 0 = valid (or an honest SKIP), 1 = a bad/misnamed atlas
"""
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)
GLOB = os.path.join(STOCK_G, "*", "stock_*_index.json") # the RUNTIME path — what the loader fetches
BANDS = {"bargain", "standard", "collector", "grail"}
# The loader fetches `<base>stock_<type>_index.json` where `type` is the shop's registry type — it picks
# the stock slot (stockpack SLOT_FOR: record→sleeve, book→spine, toy→box). Any other `<type>` is a name
# the runtime can never fetch. The glob alone can't say that (`stock_*_index.json` also matches the old
# contract's `stock_shop_<id>_index.json`), so the name is checked, not just matched — presence is not
# the-right-thing (the vacuous-gate law's actual lesson).
STOCK_TYPES = {"record", "book", "toy"}
REQUIRED_ITEM = ("id", "title", "artist", "price", "price_band", "atlas", "uv")
MAX_ATLASES = 2 # C §7.2: ≤2 atlases per shop
PX_MAX = 2048 # C §7.2: ≤2048² each
PX_TYPICAL = 1024 # C §7.2: ≤1024² whenever the crate fits it (≤64 items at 256²)
FITS_1024 = 64
# charter law #3: no private-individual data, ever — a 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", "staff"}
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":
return struct.unpack(">II", head[16:24])
if head[:4] == b"RIFF" and head[8:12] == b"WEBP":
fmt = head[12:16]
if fmt == b"VP8X":
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 ":
w, h = struct.unpack("<HH", head[26:30])
return w & 0x3FFF, h & 0x3FFF
if fmt == b"VP8L":
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.relpath(path, STOCK_G)
try:
idx = json.load(open(path))
except Exception as e:
return [f"{name}: does not parse: {e}"], [] # present-but-unparseable ⇒ FAIL
d = os.path.dirname(path)
# ── the NAME must be one the runtime can fetch: stock_<type>_index.json, type ∈ STOCK_TYPES ──
fname = os.path.basename(path)
ftype = fname[len("stock_"):-len("_index.json")]
if ftype not in STOCK_TYPES:
errs.append(f"{name}: unloadable name — the runtime fetches `stock_<type>_index.json` for "
f"type ∈ {sorted(STOCK_TYPES)}; {ftype!r} is never fetched (this is the R23 drift)")
elif idx.get("kind") not in (None, ftype):
errs.append(f"{name}: kind {idx.get('kind')!r} != the filename's type {ftype!r} — the filename "
f"picks the stock slot, so they must agree")
# ── identity: the per-shop base IS the godverse shop id (the loader keys on it) ──
shop_dir = os.path.basename(d)
sid = (idx.get("shop") or {}).get("godverse_id")
if sid is None:
errs.append(f"{name}: shop.godverse_id missing — the loader's base is the shop id; the atlas must say who it is")
elif str(sid) != shop_dir:
errs.append(f"{name}: shop.godverse_id {sid!r} != its directory {shop_dir!r} "
f"(base `stock_godverse/{shop_dir}/` is the cache identity — they must agree)")
# ── provenance: NESTED, per the artifact + charter law #3 ("every atlas carries provenance") ──
prov = idx.get("provenance")
if not isinstance(prov, dict):
errs.append(f"{name}: `provenance` object missing (charter law #3 — every atlas carries provenance)")
prov = {}
for f in ("generator", "snapshot"): # snapshot pins determinism (re-bakeable)
if not str(prov.get(f, "")).strip():
errs.append(f"{name}: provenance.{f} missing/empty")
lic = prov.get("licence") or prov.get("license") # British is the artifact's spelling
if not isinstance(lic, dict):
errs.append(f"{name}: provenance.licence object missing (zone/flag/covers)")
else:
for f in ("zone", "flag", "covers"):
if not str(lic.get(f, "")).strip():
errs.append(f"{name}: provenance.licence.{f} missing/empty")
# ── C §7.2 ceilings ──
atlases = idx.get("atlases")
if not isinstance(atlases, list) or not atlases:
errs.append(f"{name}: no atlases[] listed")
atlases = []
items = idx.get("items")
if not isinstance(items, list) or not items:
errs.append(f"{name}: no items")
items = []
if len(atlases) > MAX_ATLASES:
errs.append(f"{name}: {len(atlases)} atlases — C §7.2 caps a shop at {MAX_ATLASES}")
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}")
if max(w, h) > PX_MAX:
errs.append(f"{name}: atlas {a!r} {w}×{h} exceeds C's ceiling {PX_MAX}²")
elif max(w, h) > PX_TYPICAL and len(items) <= FITS_1024:
warns.append(f"{name}: atlas {a!r} is {w}×{h} for {len(items)} items — C: ≤{PX_TYPICAL}² "
f"whenever the crate fits it (≤{FITS_1024} at 256²)")
# ── the index itself (schema per the loader: it reads items[].uv/atlas; `cell`/`cell_w` are informational) ──
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): # origin top-left; stockpack 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(GLOB))
if not idxs:
# THE VACUOUS-GATE LAW (R23, F): absent subject ⇒ SKIP with a printed reason — and prove the set
# really is empty. Files present but unmatched = a misnamed atlas the loader can't fetch ⇒ FAIL.
stray = [p for p in glob.glob(os.path.join(STOCK_G, "**", "*"), recursive=True) if os.path.isfile(p)]
if stray:
print(f"atlas-QA FAIL — {len(stray)} file(s) under stock_godverse/ but 0 matched {GLOB}")
print(f" the runtime fetches `<base>stock_<type>_index.json`; these cannot be loaded:")
for p in stray[:8]:
print(f" unmatched: {os.path.relpath(p, ROOT)}")
return 1
print(f"atlas-QA SKIP: no atlases present (0 files matched glob {GLOB})")
return 0
all_errs, all_warns = [], []
for p in idxs:
e, w = validate_index(p)
all_errs += e; all_warns += w
try:
idx = json.load(open(p))
except Exception:
idx = {}
shop = (idx.get("shop") or {}).get("name", "?")
print(f" {os.path.relpath(p, STOCK_G):44} {shop[:22]:22} items={len(idx.get('items', [])):>3} "
f"atlases={len(idx.get('atlases', []))} px={idx.get('atlas_px')} "
f"{'OK' if not e else 'ERR(' + str(len(e)) + ')'}")
lic = (idx.get("provenance") or {}).get("licence") or {}
if lic: # the licence is PRINTED — clearing it is a human call
print(f" licence: {lic.get('zone')} · {lic.get('flag')}")
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())