test(shader-transitions): add midpoint (p=0.5) regression invariants for all shaders#378
test(shader-transitions): add midpoint (p=0.5) regression invariants for all shaders#378vanceingalls wants to merge 3 commits intovance/srgb-hdr-reference-testsfrom
Conversation
a1d1fdd to
afe3517
Compare
2163cd0 to
ca9c359
Compare
ca9c359 to
d8c43e8
Compare
2af7fa2 to
f675c92
Compare
d8c43e8 to
1733e38
Compare
f675c92 to
b09ef42
Compare
1733e38 to
6881964
Compare
jrusso1020
left a comment
There was a problem hiding this comment.
Midpoint (p=0.5) regression invariants are the right surface to pin for a shader-transitions library. Shaders at p=0 and p=1 have strong invariants (equal to fromScene / toScene respectively), but the middle is where actual transition logic runs — wipe direction, crossfade alpha, shader-specific parameters. Without a p=0.5 pin, a shader rewrite can flip direction, swap source/destination, or change easing curve without being detected by the existing endpoint tests.
"Invariants for all shaders" is the right scope — a single shader-by-shader snapshot would be noisy and brittle, but invariant assertions (e.g. "the midpoint output is a mix of both sources" or "no pixel is fully black") are durable and meaningful.
Approved.
— Rames Jusso
b09ef42 to
4ea3875
Compare
0483635 to
1d14220
Compare
17e374e to
8499b89
Compare
1d14220 to
2f8e87f
Compare
|
Thanks for the approval @jrusso1020. You nailed the rationale — endpoint pins ( The "invariants over snapshots" framing is what kept the harness durable: assertions like "midpoint output is a non-trivial mix of both sources", "no pixel is fully black/saturated", and "midpoint is symmetric for crossfade-class shaders" survive shader refactors that don't change semantics, but reliably trip on direction flips, source/destination swaps, or easing-curve regressions. No outstanding action items from the review. |
2f8e87f to
abe64f1
Compare
8499b89 to
87bce21
Compare
a8a7d7f to
e3ea8d6
Compare
bd45f57 to
b1e97ae
Compare
e3ea8d6 to
230c5df
Compare
b1e97ae to
f07a20c
Compare
230c5df to
aa5be19
Compare
f07a20c to
57d8296
Compare
456f47f to
bb1d031
Compare
4ac7856 to
f1bcae5
Compare
bb1d031 to
c1d8c12
Compare
f1bcae5 to
f152c4e
Compare
…d-transfer caller error PR #370 review feedback (jrusso1020): - chunkEncoder: when codec=h264 and hdr is set, log a warning and strip hdr instead of emitting a half-HDR file (BT.2020 container tags + BT.709 VUI inside the bitstream). libx264 has no HDR support; the only honest output is SDR/BT.709. Caller is told to use codec=h265. - videoFrameExtractor: comment at the convertSdrToHdr call site clarifying that dominantTransfer is majority-wins; mixing PQ and HLG sources in a single composition is caller-error and the minority transfer's videos will be converted with the wrong curve. Render two compositions if you need both transfers. - docs/guides/hdr.mdx: limitations section now documents (a) H.264 + HDR is rejected at the encoder layer, and (b) GPU H.265 (nvenc, videotoolbox, qsv, vaapi) emits BT.2020 + transfer tags but does NOT embed master-display or max-cll SEI, since ffmpeg won't pass x265-params through hardware encoders. Acceptable for previews, not for HDR10 delivery.
Mirrors buildSrgbToHdrLut() in Python so future intentional LUT changes (transfer-function constants, BT.709→BT.2020 matrix, OOTF, SDR-white nit reference) can regenerate the byte-exact SRGB_TO_HDR_REFERENCE table in one shot instead of hand-editing 12 inline literals. Addresses PR #377 review feedback (jrusso1020): "reference values are currently inline literals. If the LUT ever gets regenerated for a legitimate reason, updating 97 lines of literals by hand is noisy. A helper pattern — scripts/generate-lut-reference.py that emits the current LUT values as a test-fixture JSON — is worth ~5 minutes to write and would make future regenerations one-shot." Usage: python3 packages/engine/scripts/generate-lut-reference.py --probes Produces a paste-ready TS snippet matching the existing reference array. Default mode (no flag) emits full 256-entry HLG/PQ LUTs as JSON. The script uses int(math.floor(x + 0.5)) to mirror JS Math.round() for non-negative numbers, which is what guarantees byte-identical output. Verified: --probes regenerates the existing 12 reference rows exactly.
…for all shaders Existing smoke tests cover only the endpoints (p=0 ≈ from, p=1 ≈ to), which miss a class of regressions that surface specifically at the midpoint — where the transition is most visible to viewers — and let shaders silently rot in CI: • A shader becomes a no-op (returns input as-is) • A shader prematurely completes (returns target at midpoint) • A shader doesn't write to the output buffer at all • A shader loses determinism (Math.random / Date.now / leaked state) Add four invariants every shader must satisfy at p=0.5, applied via a describe loop over ALL_SHADERS so any new transition added to the registry automatically picks up the same coverage: 1. output ≠ from catches no-ops 2. output ≠ to catches premature completion 3. output is non-zero catches blank output 4. output is deterministic catches accidental non-determinism Uses two distinct uniform input colors (40000/30000/20000 vs 10000/10000/10000) so equality checks have distinct byte patterns to compare against. Even shaders that warp UVs (which would be no-ops on uniform input alone) produce mix16(from, to, 0.5) at every pixel, distinct from both inputs. 60 new tests (4 invariants × 15 shaders), all passing. Follow-up to plans/hdr-followups.md Chunk 9G.
c1d8c12 to
1f9a361
Compare

Summary
Add four midpoint (
p=0.5) regression invariants applied via adescribeloop overALL_SHADERS, so every existing and future shader transition automatically gets coverage at the most viewer-visible point in the animation.Why
Chunk 9Gofplans/hdr-followups.md. Existing smoke tests cover only the endpoints (p=0 ≈ from,p=1 ≈ to), which miss a class of regressions that surface specifically at the midpoint and let shaders silently rot in CI:Math.random/Date.now/ leaked state)What changed
packages/engine/src/utils/shaderTransitions.test.ts: a singledescribeloop overALL_SHADERSthat asserts atp=0.5:output ≠ from— catches no-opsoutput ≠ to— catches premature completionoutputis non-zero — catches blank outputoutputis deterministic — catches accidental non-determinismUses two distinct uniform input colors (40000/30000/20000 vs 10000/10000/10000) so equality checks have distinct byte patterns to compare against. Even shaders that warp UVs (which would be no-ops on uniform input alone) produce
mix16(from, to, 0.5)at every pixel, distinct from both inputs.Test plan
Stack
Chunk 9G of
plans/hdr-followups.md. Test-only change, independent of all other chunks.