Files
settled-reach/tooling/atlas-flatness
T
jpmschweitzerandClaude Opus 5 869837f728 test(simulation): the conservation gate's monoculture check was a tautology (T-1213)
D-258 invariant 2 says descending the ladder must reveal COMPOSITION — a cell
reading forest must be able to contain the clearings and rock the vote
suppressed. One assertion stood behind that, and it read:

    assert!(tally.len() > 1 || share == 1.0, ...)

A single-class tally has a 100% share by definition, so both branches are always
satisfiable: the check could never fail, including in the exact case its own
message names, "or nothing was composed". The invariant had a test and no gate.

Split into the two bounds the invariant actually has, because it is two-sided:
conservation caps how much may be invented (majority > 50%, already asserted) and
composition sets a floor on how little (minority >= 0.1%). Verified by raising
the floor to 2% and watching it fail on the measured 1.07%, then restoring it —
the floor is a tripwire for "did anything happen", deliberately far below the
measurement rather than tuned to it.

Measured at the descent ladder's own anchor on Ferrath:
  conservation: majority class 3 at 98.9% across 2 classes {1: 175, 3: 16209}

So composition IS working in the data and conservation holds. The map is flat
anyway, and tooling/atlas-flatness (added here) says why the eye was not enough:

    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 — the dither pass adds colour noise, not information, so
a colour-count metric would have called the flattest rung the richest. Structure
falls ~92% from Global to Quarter.

The cause is a channel mismatch rather than a missing generator: composition
perturbs moisture_q/slope_q, and the base map draws morphology hue x elev_q
lightness. The ladder scenarios pass no overlays deliberately, so the composed
fields are never rendered in the very shots that judge this work. Recorded on
T-1213 with the three ways forward; the choice touches D-258 and is Jeroen's.

The gate is still #[ignore]d — noted on the ticket as worth moving into a harness
that runs, since believability and window-derivation already load real bodies in
the normal cargo test path.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-16 12:35:19 +02:00

126 lines
4.2 KiB
Python
Executable File

#!/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 argparse
import sys
from pathlib import Path
try:
from PIL import Image
except ImportError: # pragma: no cover - environment guard
print("atlas-flatness: Pillow not installed", file=sys.stderr)
sys.exit(2)
REPO_ROOT = Path(__file__).resolve().parent.parent
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 main() -> int:
parser = argparse.ArgumentParser(description="Measure Atlas capture structure")
parser.add_argument("images", nargs="*", type=Path)
parser.add_argument(
"--ladder",
action="store_true",
help="measure the standard descent ladder in .cache/screenshots/",
)
args = parser.parse_args()
targets = []
if args.ladder:
targets = [(label, SHOTS / name) for label, name in LADDER]
targets += [(p.stem, p) for p in args.images]
if not targets:
parser.print_help()
return 2
print(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():
print(f"{label:22} MISSING {path}")
missing += 1
continue
m = measure(path)
r, g, b = (m["std"][c] for c in "RGB")
print(
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
if __name__ == "__main__":
sys.exit(main())