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())