"""Independent pixel-only inverse arm for the breach-relevel world. Receives the rendered PNG and nothing else: no latent scene, no renderer, no prompt, generator, verifier or analytic-gold code. The lattice, the ground line, the block colours, the red column and the arrow column are all recovered from the raster, the removal is applied to the recovered profile, and the standing-water equilibrium is recomputed from scratch. """ from __future__ import annotations import numpy as np from PIL import Image _PALETTE = { "bg": (255, 255, 255), "rock": (70, 74, 82), "cut": (198, 44, 44), "water": (150, 196, 236), } def _classify(pixel) -> str: best, best_d = "bg", None for name, ref in _PALETTE.items(): d = sum((int(pixel[k]) - ref[k]) ** 2 for k in range(3)) if best_d is None or d < best_d: best, best_d = name, d return best def _ground_top(arr: np.ndarray) -> int: """Row where the ground bar starts: the widest dark horizontal run.""" dark = (arr.astype(np.int16).sum(axis=2) < 600) counts = dark.sum(axis=1) peak = int(counts.max()) rows = np.flatnonzero(counts >= peak - 2) return int(rows.min()) def _column_runs(arr: np.ndarray, ground: int) -> list[tuple[int, int]]: """Contiguous x-runs of block ink above the ground, one per drawn column.""" band = arr[: ground - 4] ink = np.any(np.abs(band.astype(np.int16) - 255) > 12, axis=2) present = ink.any(axis=0) runs = [] x = 0 width = present.shape[0] while x < width: if present[x]: start = x while x < width and present[x]: x += 1 runs.append((start, x - 1)) else: x += 1 return runs def _profile(image: Image.Image): arr = np.asarray(image.convert("RGB")) ground = _ground_top(arr) runs = _column_runs(arr, ground) if not runs: raise ValueError("no columns found") starts = [r[0] for r in runs] pitch = 56 if len(starts) > 1: gaps = sorted({b - a for a, b in zip(starts, starts[1:])}) for gap in gaps: if gap > 0 and all((s - starts[0]) % gap == 0 for s in starts): pitch = gap break origin = starts[0] n_cols = (starts[-1] - origin) // pitch + 1 block = pitch heights = [0] * n_cols water = [0] * n_cols cut = -1 for col in range(n_cols): cx = origin + col * pitch + block // 2 - 3 level = 0 while True: cy = ground - level * block - block // 2 if cy < 0: break kind = _classify(arr[cy, cx]) if kind == "bg": break if kind == "water": water[col] += 1 else: if water[col]: break heights[col] += 1 if kind == "cut": cut = col level += 1 if cut < 0: raise ValueError("no red column found") black = np.all(np.asarray(image.convert("RGB"))[ground + 6:] < 40, axis=2) ys, xs = np.nonzero(black) if xs.size == 0: raise ValueError("no arrow found") arrow_x = float(xs.mean()) query = int(round((arrow_x - (origin + block / 2.0)) / pitch)) query = max(0, min(n_cols - 1, query)) return heights, query, cut def _standing(heights) -> list[int]: n = len(heights) pref, suf = [0] * n, [0] * n run = 0 for i in range(n): run = max(run, heights[i]) pref[i] = run run = 0 for i in range(n - 1, -1, -1): run = max(run, heights[i]) suf[i] = run return [max(0, min(pref[i], suf[i]) - heights[i]) for i in range(n)] def decision_from_image(image: Image.Image) -> str: """Water depth over the arrow column after the red column is taken away.""" heights, query, cut = _profile(image) edited = list(heights) edited[cut] = 0 return f"depth-{_standing(edited)[query]}"