Files
benilla-octo/scripts/tests/test_water_baseline.py
T
gromandClaude Opus 5 624ccec808 water: graft deepthought84's Improved Water onto this fork
The whole workspace compiles with his water in place of ours. Nothing is
verified beyond that yet -- no capture, no gates -- so this lands on
`water-port`, not on main.

Method: scripts/water-port-merge.sh stitches the common ancestor our squashed
history hides (both forks descend from samwhosung/benilla) and runs a real
three-way merge. 134 of his 168 files land clean; the 34 that conflict were
resolved by hand, 82 hunks, and the log is in docs/WATER_PORT.md.

What went, per grom's call (full swap, drop the extras): enhanced_water.wgsl,
liquid/{reflect,scene_depth,waves,lod}.rs, water_fx/bob.rs and the Water Quality
dropdown, with our LiquidExt bindings and the WaterDepth prepass they fed.

What was kept and re-applied onto his tree: the Ironforge magma palette
(MagmaTone, now multiplying the body before his stylised grade so a tinted pool's
crust and glow are its own colour) and our shader hooks -- fog, emissive,
monkey_frame, wind, wet, moonlight -- which his liquid.wgsl knew nothing about.

Four things needed real work rather than a side:
  * static_gx now keys its pipelines per VIEW, not per world camera, because his
    mirror and probe faces carry their own samples and format; our torch and
    light groups and the shadow-filter def ride along, and the depth-only twin
    his Hi-Z prepass needs is built beside each colour pipeline.
  * the booth layer ladder moved past the liquid render layers. His seam claims
    30 and 31; our pane block grew from UI_MODELS_LAYER with nothing stopping it
    reaching them, and the const assert fired exactly as the doc predicted.
  * two SystemParam tuples were already at the 16 ceiling (the minimap's blip
    inputs, the capture driver) and had to nest to take his.
  * sky.rs grew `dome_uniforms`, the one law his water march and the dome now
    share, without losing our Enhanced sky inputs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
2026-11-09 10:57:18 -07:00

213 lines
9.4 KiB
Python

"""Fixtures for scripts/water_baseline.py: a synthetic capture run whose table is known.
python3 -m unittest discover -s scripts/tests
"""
import contextlib
import io
import json
import pathlib
import sys
import tempfile
import unittest
import numpy as np
from PIL import Image
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1]))
import water_baseline as wb # noqa: E402
REGION = """scene = "wg-t"
[box]
water = [0, 20, 40, 40]
[[row]]
metric = "luma_sd"
box = "water"
[[row]]
metric = "lum_vs_ref"
box = "water"
[[row]]
metric = "shimmer"
box = "water"
[[row]]
metric = "pop"
box = "water"
gate = false
"""
def save(rgb, path):
Image.fromarray(np.asarray(rgb, dtype=np.uint8)).save(path)
def frame(v, w=40, h=40):
img = np.full((h, w, 3), v, dtype=np.uint8)
img[30:, :20] = v // 2 # structure in the water box: luma_sd > 0
return img
def run(fn, *args):
with contextlib.redirect_stdout(io.StringIO()) as out:
rc = fn(*args)
return rc, out.getvalue()
class Plan(unittest.TestCase):
def test_all_runs_the_stills_views_bursts_and_repeats_in_order(self):
p = wb.plan("all")
self.assertEqual([(s, label) for s, label, _, _ in p], [
("stills", "stills"), ("stills", "view-fresnel"), ("stills", "view-foam"),
("stills", "view-tiers"), ("bursts", "bursts"), ("repeat", "stills"),
("repeat-bursts", "bursts"),
])
views = {e["WATER_VIEW"]: e["WATER_SCENES"].split() for _, _, e, _ in p
if "WATER_VIEW" in e}
self.assertEqual(views["fresnel"], wb.VIEW_SCENES)
self.assertEqual(views["foam"], wb.VIEW_SCENES)
self.assertEqual(views["tiers"], wb.VIEWS["tiers"])
bursts = [e["WATER_SCENES"].split() + a for s, _, e, a in p if s == "bursts"]
self.assertEqual(bursts, [wb.STATIC + wb.PANS + ["60", "2"]])
self.assertEqual(wb.plan("stills"), [("stills", "stills", {}, [])])
def test_capture_runs_each_sweep_through_water_shots_with_its_environment(self):
with tempfile.TemporaryDirectory() as d:
d = pathlib.Path(d)
stub = d / "shots.sh"
calls = d / "calls.txt"
line = '"$1|${2:-}|${3:-}|${WATER_SCENES:-}|${WATER_VIEW:-}"'
stub.write_text(f"#!/bin/sh\necho {line} >> {calls}\n")
stub.chmod(0o755)
saved = wb.MIN_MEM_MB, wb.MIN_DISK_GB
wb.MIN_MEM_MB = wb.MIN_DISK_GB = 0
try:
rc, _ = run(wb.capture, d / "run", "bursts", stub)
finally:
wb.MIN_MEM_MB, wb.MIN_DISK_GB = saved
self.assertEqual(rc, 0)
out, n, every, scenes, view = calls.read_text().strip().split("|")
self.assertEqual((out, n, every, view), (str(d / "run" / "bursts"), "60", "2", ""))
self.assertEqual(scenes.split(), wb.STATIC + wb.PANS)
self.assertTrue((d / "run" / "bursts" / "vram.tsv").exists())
def test_a_sweep_does_not_start_without_room(self):
with tempfile.TemporaryDirectory() as d:
saved = wb.MIN_MEM_MB
wb.MIN_MEM_MB = 10**9
try:
with contextlib.redirect_stderr(io.StringIO()):
rc, _ = run(wb.capture, pathlib.Path(d), "stills", pathlib.Path("/bin/false"))
finally:
wb.MIN_MEM_MB = saved
self.assertEqual(rc, 3)
class Vram(unittest.TestCase):
def test_each_amdgpu_client_counts_once(self):
a = "pos:\t0\ndrm-driver:\tamdgpu\ndrm-client-id:\t7\ndrm-total-vram:\t1000 KiB\n" \
"drm-total-gtt:\t20 KiB\n"
other = "drm-driver:\ti915\ndrm-client-id:\t9\ndrm-total-vram:\t5 KiB\n"
b = a.replace("\t7", "\t8").replace("1000", "24")
self.assertEqual(wb.VramWatch.fdinfo_totals([a, a, b, other, "pos:\t0\n"]), (1024, 40))
class Table(unittest.TestCase):
def test_a_run_becomes_both_lanes_the_burst_rows_and_ins4(self):
with tempfile.TemporaryDirectory() as d:
d = pathlib.Path(d)
regions, stills, bursts = d / "regions", d / "run" / "stills", d / "run" / "bursts"
for p in (regions, stills, bursts):
p.mkdir(parents=True)
(regions / "wg-t.toml").write_text(REGION)
save(frame(200), stills / "wg-t.png")
save(frame(100), stills / "wg-t-ref.png")
for i in range(3):
save(frame(200 + i), bursts / f"wg-t-{i:03d}.png")
journal = ["t,mean_ms,cpu_ms,main_ms,gpu_ms,gpu_water"]
journal += [f"{i},10,20,5,{i},{i / 10}" for i in range(12)]
(stills / "wg-t.fps.csv").write_text("\n".join(journal) + "\n")
(stills / "wg-t.log").write_text(
"\x1b[32m INFO\x1b[0m capture: image settled after 1571 frames, aging 1000\n"
"GPU_PASSES frames=60 water_hiz_seed=9.0\n"
"GPU_PASSES frames=60 water_hiz_seed=1.0 water_snapshot=0.4\n"
)
(stills / "vram.tsv").write_text(f"{stills / 'wg-t.png'}\t2048\t1024\n")
# The burst sweep shoots the same scene under the same stem, with its own peak.
(bursts / "wg-t.fps.csv").write_text("\n".join(journal) + "\n")
(bursts / "vram.tsv").write_text(f"{bursts / 'wg-t.png'}\t4096\t512\n")
rc, _ = run(wb.table, d / "run", d / "out", regions)
self.assertEqual(rc, 0)
base = json.loads((d / "out" / "baseline.json").read_text())
self.assertEqual(sorted(base), ["wg-t", "wg-t-ref"])
self.assertAlmostEqual(base["wg-t"]["lum_vs_ref"]["value"], 2.0, places=2)
self.assertEqual(base["wg-t"]["shimmer"]["source"], "bursts")
self.assertGreater(base["wg-t"]["luma_sd"]["value"], 0)
self.assertNotIn("source", base["wg-t"]["luma_sd"])
perf = json.loads((d / "out" / "ins4.json").read_text())["stills/wg-t"]
self.assertEqual(perf["rows"], 8)
self.assertEqual(perf["gpu_ms"], 7.5) # the median of the last 8 rows, 4-11
self.assertEqual(perf["water_hiz_seed"], 1.0) # the last GPU_PASSES line
self.assertEqual(perf["water_hiz_reduce"], 0.0)
self.assertEqual((perf["settle"], perf["age"], perf["flat"]), (1571, 1000, False))
self.assertEqual((perf["vram_mib"], perf["gtt_mib"]), (2.0, 1.0))
burst = json.loads((d / "out" / "ins4.json").read_text())["bursts/wg-t"]
self.assertEqual((burst["vram_mib"], burst["gtt_mib"]), (4.0, 0.5))
md = (d / "out" / "table.md").read_text()
self.assertIn("| shimmer | shimmer |", md)
self.assertIn("| lum_vs_ref | lum_vs_ref | 2 | min 1 pass |", md)
self.assertRegex(md, r"\| pop \| pop \| \S+ \| info \|")
self.assertIn("(burst) |", md)
self.assertIn("| stills/wg-t | 7.5 |", md)
class Checks(unittest.TestCase):
def test_selfcheck_names_the_pixels_that_differ(self):
with tempfile.TemporaryDirectory() as d:
a, b = pathlib.Path(d) / "a", pathlib.Path(d) / "b"
a.mkdir()
b.mkdir()
for p in (a, b):
save(frame(90), p / "same.png")
save(frame(90), a / "moved.png")
img = frame(90)
img[5:7, 10:13] += 6
save(img, b / "moved.png")
rc, out = run(wb.selfcheck, a, b)
self.assertEqual(rc, 1)
self.assertIn("moved.png: 6 px differ, max 6 codes", out)
self.assertIn("bbox x10-12 y5-6, > 4 codes 6 px", out)
self.assertIn("identical 1, differing 1, unmatched 0", out)
(b / "moved.png").unlink() # a shot only one run took is counted, not failed
rc, out = run(wb.selfcheck, a, b)
self.assertEqual(rc, 0)
self.assertIn("identical 1, differing 0, unmatched 1", out)
def test_diff_lists_moved_relabelled_regated_bounded_informational_and_deferred_rows(self):
old = {"s": {"a": {"metric": "m", "value": 1.0}, "b": {"metric": "view_std", "value": 2},
"c": {"metric": "pop", "value": 3}, "d": {"metric": "m", "value": 4},
"e": {"metric": "m", "value": 5, "gate": {"max": 10.8}},
"f": {"metric": "m", "value": 6}, "g": {"metric": "m", "value": 7,
"defer": "P1-35"},
"h": {"metric": "m", "value": 8}}}
new = {"s": {"a": {"metric": "m", "value": 1.5}, "b2": {"metric": "frame_std", "value": 2},
"c": {"metric": "pop", "value": 3, "gated": False},
"d": {"metric": "m", "value": 4},
"e": {"metric": "m", "value": 5,
"gate": {"max": 10.8, "monotone": {"min": 1.0}}},
"f": {"metric": "m", "value": 6, "defer": "P1-35"},
"g": {"metric": "m", "value": 7},
"h": {"metric": "m", "value": 8, "within": [0.6, 1.0]}}}
self.assertEqual(wb.changes(old, new), [
("s", "a", "value 1 -> 1.5"),
("s", "b", "removed (view_std 2)"),
("s", "c", "informational"),
("s", "e", "gate max 10.8 -> max 10.8 monotone min 1"),
("s", "f", "deferred (P1-35)"),
("s", "g", "no longer deferred"),
("s", "h", "within 0.6-1x"),
("s", "b2", "added (frame_std 2)"),
])
if __name__ == "__main__":
unittest.main()