#!/usr/bin/env bash
# Phase-5 visual A/B render harness driver (decisions 0008 + 0010).
#
# Captures deterministic benilla screenshots (one process per scenario, server-less + clutter-off +
# pinned camera, game clock AND frame step — see the `capture` module) and diffs them with
# `benilla-visual`. `selfcheck` is the proof that "deterministic" is true. The intended
# loop: capture `baseline` on the current pipeline, make a render change, then `diff` — the diff is the
# machine check that catches a regression before the director's eye (decision 0008).
#
# Captures contain Blizzard-derived imagery, so they live under the gitignored target/visual/ and are
# NEVER committed. macOS/local only: capture opens a real window (no headless GPU path here).
#
# Usage:
# scripts/visual.sh list # print scenario names
# scripts/visual.sh capture
# capture every scenario into /
# scripts/visual.sh baseline # capture into target/visual/baseline/
# scripts/visual.sh diff [--fail ] # capture into target/visual/candidate/, diff vs baseline/
# scripts/visual.sh selfcheck # capture the sweep TWICE from ONE build; demand identical
set -euo pipefail
cd "$(dirname "$0")/.."
VIS=target/visual
build() { cargo build -q -p benilla -p benilla-visual; }
# The scenario list is owned by the binary (capture/mod.rs), printed via WOW_CAPTURE=list — so this driver
# never drifts from the code.
scenarios() { WOW_CAPTURE=list cargo run -q -p benilla; }
capture_into() {
local dir="$1"
mkdir -p "$dir"
local s
for s in $(scenarios); do
echo "capture $s -> $dir/$s.png"
WOW_CAPTURE="$s" WOW_CAPTURE_OUT="$dir/$s.png" cargo run -q -p benilla
done
}
cmd="${1:-}"
case "$cmd" in
list)
build
scenarios
;;
capture)
build
capture_into "${2:?usage: visual.sh capture }"
;;
baseline)
build
capture_into "$VIS/baseline"
echo "baseline written to $VIS/baseline"
;;
diff)
shift
build
capture_into "$VIS/candidate"
echo "--- diff candidate vs baseline ---"
./target/debug/benilla-visual diff-dir "$VIS/baseline" "$VIS/candidate" --out "$VIS/diff" "$@"
;;
selfcheck) # selfcheck [] — the tolerance defaults to 0 (bit-identical)
# The harness's own tripwire (decision 0723). A golden diff is evidence ONLY if the harness is
# deterministic, so measure that directly: capture the whole sweep twice from one unchanged
# build and demand a bit-identical result (--fail 0 passes only at MAE exactly 0). Before 0723
# this failed at MAE 0.009 / max delta 180 — the same band a real render change lands in, which
# is how 0721's flame pixels ended up unreadable and how 0719 read signal out of churn. Run it
# after touching anything the capture clock feeds, and whenever a diff looks like noise.
build
capture_into "$VIS/self-a"
capture_into "$VIS/self-b"
echo "--- selfcheck: self-a vs self-b, one build, two runs ---"
# The bar is 0 — bit-identical — and the four blessed scenarios meet it (0817 set the bar, 1183
# cut the sweep to two director-framed spots x two day times). Scenarios that could not hold it
# left the sweep rather than being carried as slack in the bar (the old 0.001 was cover for two
# overlook cells at 421 px / max delta 43).
#
# What makes a cell flake is a real renderer defect, not harness drift: draw order follows spawn
# order, and spawn order varies because asset loads complete on a thread pool (0723 "Open, named"
# -> 0815 Open -> 1182, which measured it). Both states are perfectly STABLE, so no amount of
# settling or waiting fixes it. **Do not respond to a failure here by widening the bar or the
# waits** — that mistake has cost several sessions. Anything above 0 is either that defect
# surfacing in a blessed cell or a genuine harness regression; pass a number to probe.
#
# 1182 is also the correction to the obvious first guess: the tie is NOT always a coplanar pair.
# In the canal cells (deleted by 1183) it was two surfaces INTERSECTING, 0.62 yd apart, tying at
# one MSAA sample — invisible with WOW_MSAA=off. Run WOW_PICK through the differing pixels and
# read the PERPENDICULAR gap before assuming which geometry you are chasing.
./target/debug/benilla-visual diff-dir "$VIS/self-a" "$VIS/self-b" --out "$VIS/self-diff" \
--fail "${2:-0}"
echo "selfcheck OK — the harness clock is a pure function of the build"
;;
*)
echo "usage: visual.sh {list | capture | baseline | diff [--fail ] | selfcheck}" >&2
exit 1
;;
esac