#!/usr/bin/env python3 """Measure how much STRUCTURE an Atlas capture carries, per rung. "Flat" is the word T-1213/D-258 use for the defect the descent ladder exposes, but a word cannot be an acceptance gate and an eyeball cannot be a regression test. This turns the ladder into numbers. WHY NOT JUST COUNT COLOURS -------------------------- Because the count goes the wrong way. Measured on the 2026-08-16 cold ladder: rung distinct lum p1-p99 Global 1581 145.69 Region 2923 33.59 District 53 13.72 Quarter 46 11.01 Region carries almost TWICE Global's distinct-colour count while holding a quarter of its structure — that is the dither/stipple pass (T-1194) adding colour noise, not information. A metric that rewards speckle would have called the flattest rung the richest. So the headline number here is the 1st-99th percentile luminance spread, which ignores per-pixel noise and measures the large-scale variation a map is actually read for; distinct-count is reported alongside precisely so the two can be seen disagreeing. Usage: tooling/atlas-flatness .cache/screenshots/atlas_GJ820Bc_land_Region.png [...] tooling/atlas-flatness --ladder # the standard descent ladder """ import sys from pathlib import Path from tooling.core import config, console from tooling.core.errors import ReachError try: from PIL import Image except ImportError: # pragma: no cover - environment guard console.event("atlas-flatness: Pillow not installed", level="error") sys.exit(2) REPO_ROOT = config.repo_root() SHOTS = REPO_ROOT / ".cache" / "screenshots" # The standard descent ladder: one body, one world point, once per rung. LADDER = [ ("Global", "atlas_GJ820Bc_Global.png"), ("Region", "atlas_GJ820Bc_land_Region.png"), ("District", "atlas_GJ820Bc_land_District.png"), ("Quarter", "atlas_GJ820Bc_land_Quarter.png"), ] # Terrain-only crop. The header/legend panels sit top-left and the overlay chips # top-right; both are flat UI fills that would drag every statistic toward # whatever the panel colour happens to be, and they do not vary with the rung. CROP_LEFT = 700 CROP_TOP = 120 def luminance(px) -> float: r, g, b = px[:3] return 0.2126 * r + 0.7152 * g + 0.0722 * b def measure(path: Path) -> dict: img = Image.open(path).convert("RGB") w, h = img.size if w <= CROP_LEFT or h <= CROP_TOP: raise ValueError(f"{path.name} is {w}x{h}, smaller than the UI crop") img = img.crop((CROP_LEFT, CROP_TOP, w, h)) # get_flattened_data() is the Pillow 12+ name; getdata() is deprecated there # and removed in 14, but is all that older Pillows have. reader = getattr(img, "get_flattened_data", None) or img.getdata pixels = list(reader()) stats = {} for name, ch in zip("RGB", zip(*pixels)): n = len(ch) mean = sum(ch) / n stats[name] = (sum((v - mean) ** 2 for v in ch) / n) ** 0.5 lums = sorted(luminance(p) for p in pixels) n = len(lums) return { "distinct": len(set(pixels)), "std": stats, "spread": lums[int(n * 0.99)] - lums[int(n * 0.01)], } def report(images: list[Path], ladder: bool = False) -> int: """Measure capture structure per rung. Returns 0, or 1 if any file is missing.""" targets = [] if ladder: targets = [(label, SHOTS / name) for label, name in LADDER] targets += [(p.stem, p) for p in images] if not targets: raise ReachError( "no captures given", fix="pass image paths, or --ladder to measure the standard descent", exit_code=2, ) console.out( f"{'rung':22} {'distinct':>9} {'R std':>7} {'G std':>7} {'B std':>7} {'lum p1-p99':>11}" ) missing = 0 for label, path in targets: if not path.exists(): console.out(f"{label:22} MISSING {path}") missing += 1 continue m = measure(path) r, g, b = (m["std"][c] for c in "RGB") console.out( f"{label:22} {m['distinct']:>9} {r:>7.2f} {g:>7.2f} {b:>7.2f} {m['spread']:>11.2f}" ) return 1 if missing else 0