"""Campaign VM VM6 — robust foliage-wind pixel proof. Usage: py tools/vm6/wind-pixel-proof.py holds four run-offline-pixel-gate.ps1 output directories, all at the SAME ACDREAM_SKY_PHASE_SECONDS pin and sun-shadow-strength=0: amp-t3, amp-t3b wind on (amplified or default strength) nowind-t3a, nowind-t3b wind-enabled=false Each directory contains screenshots/world-offline.png. Why four captures: the same binary/settings/pin differs run-to-run by 0 or ~280 isolated leaf-edge pixels (rasterisation flips along the treeline), so a single on-vs-off pair cannot be trusted. The repeat pairs measure that floor; the robust mask is the AND of all four cross pairs minus both repeat pairs. See docs/research/2026-08-23-vm6-foliage-wind-pixel-proof.md. """ import sys import numpy as np from PIL import Image root = sys.argv[1] threshold = int(sys.argv[2]) if len(sys.argv) > 2 else 8 def load(name): return np.asarray( Image.open(f"{root}/{name}/screenshots/world-offline.png").convert("RGB") ).astype(int) def strong(a, b): return np.abs(a - b).max(axis=2) >= threshold def bands(mask): ys, _ = np.nonzero(mask) return np.bincount(ys // 60, minlength=12).tolist() if len(ys) else [0] * 12 A, B, N1, N2 = load("amp-t3"), load("amp-t3b"), load("nowind-t3a"), load("nowind-t3b") noise = strong(N1, N2) repeat = strong(A, B) print(f"threshold |d|>={threshold}") print("floor wind-off A vs B :", int(noise.sum()), bands(noise)) print("repeat wind-on A vs B :", int(repeat.sum()), bands(repeat)) for name, (x, y) in { "wind-on A vs off A": (A, N1), "wind-on A vs off B": (A, N2), "wind-on B vs off A": (B, N1), "wind-on B vs off B": (B, N2), }.items(): m = strong(x, y) print(f"{name:20s}: {int(m.sum()):6d} {bands(m)}") robust = strong(A, N1) & strong(A, N2) & strong(B, N1) & strong(B, N2) & ~noise & ~repeat print("ROBUST wind mask :", int(robust.sum()), bands(robust)) out = A.copy() out[robust] = [255, 0, 0] Image.fromarray(out.astype(np.uint8)).save(f"{root}/diff-robust-wind.png") print(f"overlay: {root}/diff-robust-wind.png")