Spaces:
Sleeping
Sleeping
File size: 3,660 Bytes
6a75a66 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 | """N4 β white-tile washout certification (highlight-preserving shading).
Certifies apply_shade (golden_render.py, mirrored as applyShade in
canvas-engine.ts): brightening (shade > 1) must preserve pale-tile detail
instead of slamming it into the 255 ceiling / soft-clip shoulder.
Checks:
1. dimming path is the exact physical multiply (m <= 1 unchanged behaviour)
2. continuity at m = 1 (no curve seam between dim and brighten)
3. white-tile contrast: tile 245 / grout 200 under m = 1.3 keeps >= 60% of
the unshaded contrast (the old multiply+clip path collapses it)
4. headroom by construction: any texel under any m in (1, 2] stays < 255
5. monotonic in texel value for fixed m (no detail inversions)
6. documents the bug it fixes: the legacy soft_clip(multiply) path keeps
< 25% of the same contrast
"""
import numpy as np
from golden_render import apply_shade, soft_clip
def main():
ok = True
# 1 β dimming is a pure multiply
s = np.linspace(0, 255, 256)
for m in (0.4, 0.7, 1.0):
got = apply_shade(s, np.full_like(s, m))
want = s * m
good = np.allclose(got, want, atol=1e-9)
print(f" [{'PASS' if good else 'FAIL'}] dim path m={m}: exact multiply")
ok &= good
# 2 β continuity at m = 1
below = apply_shade(s, np.full_like(s, 1.0 - 1e-9))
above = apply_shade(s, np.full_like(s, 1.0 + 1e-9))
good = float(np.max(np.abs(above - below))) < 1e-3
print(f" [{'PASS' if good else 'FAIL'}] continuity at m=1: max jump "
f"{float(np.max(np.abs(above - below))):.2e}")
ok &= good
# 3 β white-tile contrast retention under brightening
tile, grout, m = 245.0, 200.0, 1.3
base_contrast = tile - grout
new = apply_shade(np.array([tile, grout]), np.array([m, m]))
new_contrast = float(new[0] - new[1])
retained = new_contrast / base_contrast
good = retained >= 0.60 and float(new.max()) < 255.0
print(f" [{'PASS' if good else 'FAIL'}] white-tile contrast: {base_contrast:.0f} -> "
f"{new_contrast:.1f} ({retained * 100:.0f}% retained), max {new.max():.1f} < 255")
ok &= good
# 4 β headroom by construction: any texel BELOW white stays below white
# (pure 255 in -> 255 out is correct: white can't get whiter).
sub = s[s < 255.0]
worst = 0.0
worst_white = 0.0
for m in np.linspace(1.001, 2.0, 40):
worst = max(worst, float(apply_shade(sub, np.full_like(sub, m)).max()))
worst_white = max(worst_white, float(apply_shade(np.array([255.0]), np.array([m]))[0]))
good = worst < 255.0 and worst_white <= 255.0
print(f" [{'PASS' if good else 'FAIL'}] headroom: max output {worst:.2f} < 255 "
f"for texel<255, white -> {worst_white:.2f} <= 255, m in (1, 2]")
ok &= good
# 5 β monotonic in texel for fixed m
mono_ok = True
for m in (1.1, 1.35, 1.8):
got = apply_shade(s, np.full_like(s, m))
mono_ok &= bool(np.all(np.diff(got) > -1e-9))
print(f" [{'PASS' if mono_ok else 'FAIL'}] monotonic in texel for m=1.1/1.35/1.8")
ok &= mono_ok
# 6 β the legacy path really was the bug
legacy = soft_clip(np.array([tile, grout]) * m)
legacy_contrast = float(legacy[0] - legacy[1])
good = legacy_contrast / base_contrast < 0.25
print(f" [{'PASS' if good else 'FAIL'}] legacy multiply+clip collapses the same "
f"contrast to {legacy_contrast:.1f} ({legacy_contrast / base_contrast * 100:.0f}%)")
ok &= good
print("\n" + ("ALL N4 SIM CHECKS PASSED" if ok else "N4 SIM CHECKS FAILED"))
return 0 if ok else 1
if __name__ == "__main__":
raise SystemExit(main())
|