Closes the automated half of slice VM6 at754d59d9and records how it was proven. The first pixel method (clock pin 0 s vs 3 s, wind-off pair as the control) is recorded as confounded: the treeline silhouette carries a bimodal 0-or-~280 px rasterisation churn between runs of the same binary, and two conclusions drawn from it were retracted. The replacement apparatus (same pin, wind on vs off, two captures per arm, robust mask = AND of the four cross pairs minus both repeat pairs, shadows off so only geometry can move) found the round-4 defect (wind welded to the shadow gate: 1 m wind = 49-65 px vs a 22 px floor while the CPU state was correct) and, after round 5, gives 14,993 robust wind pixels vs a 65 px floor, every one on a treeline tree, hillside tree or shoreline bush, with the wind-off frame matching the pre-round-4 plain pipeline at mean |d| 0.007 (round 4'seec95535: 1.77, the overhead-sun regression the narrow review caught). tools/vm6/wind-pixel-proof.py is the analysis command the closeout names; evidence overlays under docs/research/evidence/vm6. Plan: VM6 ledger row CODE-COMPLETE 2026-08-23, owner visual gate owed (section VM6 Acceptance). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
59 lines
2.1 KiB
Python
59 lines
2.1 KiB
Python
"""Campaign VM VM6 — robust foliage-wind pixel proof.
|
|
|
|
Usage: py tools/vm6/wind-pixel-proof.py <capture-root>
|
|
|
|
<capture-root> 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")
|