Files
settled-reach/tooling/domains/visual/blank_check.py
T
jpmschweitzerandClaude Opus 5.5 c597ec9131 docs(tooling): T-1253 — sweep the live references to retired tool paths
A script scanned every tracked doc, rule, skill, agent, hook and source file
for tooling/ paths that no longer exist, skipping historical records (sprints,
discussions, workshops, governance, generated wiki pages). It found 62. The
ones that tell a reader what to RUN now name the reach verb:

- The atlas skill still sent agents to tooling/atlas, atlas-verify,
  atlas-update-field and atlas-commit-and-sync — about forty lines, all
  retired in T-1285. They now name the `reach atlas` verbs, and the skill
  records that commit-and-sync STAGES by default (--commit to commit) and
  takes --corridor as an option.
- The clerk agent named tooling/clerk-review (now `reach dev clerk`). The Si
  and clerk briefings sent those agents to the retired tooling/db/decision
  and sqlite-query CLIs and to decisions/*.md paths that moved to
  governance/ in the pql migration. They now name pql.
- The ticket-cli rule documented `pql decisions read`, which does not exist;
  `show` already includes the body.
- The culture authoring guide and the RON sources name
  `reach validate ron`, with the same arguments as before.
- The 41 Blender payloads' usage lines ran the retired tooling/blender
  wrapper, and the docstrings still cited pre-carve-out paths. They now read
  `reach blender run <payload>`.
- Doc comments in server/, client/, wiki TOMLs and the domain modules.

What is left is deliberate: "Formerly …" provenance, dated plans and findings
docs, the retired-pipeline doc, and a build-artefact path.

project.yaml 0.4.14 (mirrored to the client). Comment-only, but four touched
files are in the canvas-version registry (trait_catalog_reader.rs, since
T-1289, canvas_sources.py itself, and two client files). The gate is
path-based and has no override. The previous push was rejected on exactly
this.

Three of the edits are stamped ledger sources, so systems.db is regenerated
and the stamp is fresh.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 20:13:58 +02:00

92 lines
3.6 KiB
Python
Executable File

#!/usr/bin/env python3
"""Reject a screen capture in which the renderer drew nothing.
WHY THIS EXISTS (2026-08-06). `tests/run-visual` verified only that the
captured PNG was non-empty *as a file*. A capture of a blank screen is a
perfectly valid ~19 KB PNG, so it passed — and then, because the golden had
been recorded from an equally blank capture, `visual-diff` reported 0.0%
difference and the scenario PASSED. Two Atlas scenarios
(`atlas_GJ338Bd_Block`, `atlas_GJ445c-m1_Chunk`) sat green for months against
blank goldens. This is the same failure `e024cfb3f` recorded once already
("the Atlas Global goldens have been measuring nothing"); it kept recurring
because nothing checked the property, only the file.
A green comparison between two blank images is the worst kind of test result:
it is indistinguishable from success and it is load-bearing for exactly the
work it fails to cover.
THE METRIC. Fraction of the frame occupied by the single most common colour.
Measured on this project's real captures:
Global (real world map) 38.7% modal
Region (flat colour wash) 7.2% modal <- dither; least uniform of all
District 45.4% modal
Block / Chunk / Quarter 92.9-94.6% modal <- nothing drawn
Nothing falls between 45% and 93%, so the default 0.85 threshold sits in open
space rather than being tuned to a boundary case.
Note what this deliberately does NOT do: it does not judge whether a render is
GOOD. The Region wash is a real product gap (T-1213), and it scores 7.2% —
comfortably "content". The question here is only "did the renderer put a world
on the screen", never "is the world any good". Keep it that way; a check that
creeps toward aesthetics will start failing legitimate frames and get muted.
Usage:
reach visual blank-check <PNG> [--max-modal 0.85] [--quiet]
Exit: 0 = has content, 1 = blank, 2 = error (unreadable/missing).
"""
from pathlib import Path
from tooling.core import console
DEFAULT_MAX_MODAL = 0.85
def modal_fraction(path: Path) -> tuple[float, int]:
"""Return (modal colour's share of the frame, distinct colour count)."""
try:
from PIL import Image
except ImportError:
console.event("visual-blank-check: Pillow not available", level="error")
raise SystemExit(2)
with Image.open(path) as im:
rgb = im.convert("RGB")
total = rgb.size[0] * rgb.size[1]
if total == 0:
return 1.0, 0
# maxcolors high enough that a real frame is never bucketed away;
# getcolors returns None if it overflows, which itself means the frame
# is richly coloured and therefore certainly not blank.
colors = rgb.getcolors(maxcolors=1 << 24)
if colors is None:
return 0.0, 1 << 24
top = max(colors)[0]
return top / total, len(colors)
def run(path, max_modal: float = DEFAULT_MAX_MODAL, quiet: bool = False) -> int:
"""Fail if a capture is overwhelmingly one colour. Returns 0, 1 or 2."""
if not path.is_file():
console.event(f"visual-blank-check: no such file: {path}", level="error")
return 2
modal, distinct = modal_fraction(path)
if modal > max_modal:
console.event(
f"BLANK: {path.name} is {modal:.1%} a single colour "
f"({distinct} distinct) — the renderer drew nothing but chrome. "
f"A golden recorded from this would pass against any other blank "
f"capture forever.",
level="error",
)
return 1
if not quiet:
console.event(f"content: {path.name} modal={modal:.1%} distinct={distinct}")
return 0