From 98bf6dfa95fce73691a53e84aba6207a9b13f4bd Mon Sep 17 00:00:00 2001 From: Ralph Kuepper Date: Thu, 16 Jul 2026 18:21:09 +0200 Subject: [PATCH] fix(taa): motion-aware disocclusion edge - first measured slice of the rest-sparkle The temporal-variance rig (shooter tools/f12-live.ps1 + high-pass stddev over 5 live captures) showed TAA ADDING flicker on static detailed surfaces vs TAA-off (bank 3.99 vs 1.94, grass 3.46 vs 1.03). Two mechanisms found: 1. FIXED HERE: the disocclusion reject's 0.25-sigma lower edge - at rest, sub-pixel jitter walks pebble/grass detail across it every few frames, oscillating alpha between converged history and raw frame. Rest pixels now need 0.6 sigma (motion keeps 0.25; the chroma-poison flush still fires at ~1 sigma). Measured: bank 3.99 -> 3.83, grass 3.46 -> 3.27. Real, small. 2. THE DOMINANT TERM, identified and left for its own change: sample_catmull_rom(in.uv) samples the CURRENT frame at its jittered position - the resolve never unjitters, so alpha x jitter-difference shimmers into every output frame by construction (~0.1 x edge contrast at rest). Fix = pass the frame's jitter through TaaParams (padding fields free) and offset the current sample - with care at rs < 1 where TSR uses jitter as its super-resolution source. --- native/shared/src/renderer/shaders/post.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/native/shared/src/renderer/shaders/post.rs b/native/shared/src/renderer/shaders/post.rs index 8497677b..367221a2 100644 --- a/native/shared/src/renderer/shaders/post.rs +++ b/native/shared/src/renderer/shaders/post.rs @@ -899,7 +899,18 @@ fn fs_main(in: VsOut) -> @location(0) vec4 { // flushes chroma-poisoned history on static pixels (weakening it // green-tints the whole frame within seconds). let history_dist = abs(history_y_clamped - mean.x); - let disocclusion = smoothstep(stddev.x * 0.25, stddev.x * 1.0, history_dist); + // The reject's lower edge is MOTION-AWARE (2026-07-16 sparkle hunt). + // At rest, sub-pixel jitter walks high-frequency detail (pebble bank, + // grass tips) across the 0.25-sigma edge every few frames, so alpha + // oscillated between converged history and raw current - measured as + // 2x MORE temporal flicker with TAA on than off on static surfaces + // (bank 3.99 vs 1.94 high-pass stddev). Rest pixels now need 0.6 sigma + // before rejecting; under motion the tight 0.25 returns (the + // dark-column-in-history case lives there). The chroma-poison flush + // this reject exists for still fires at rest: poisoned history sits + // at ~1 sigma+, well past either edge. + let dis_lo = stddev.x * mix(0.6, 0.25, motion_alpha); + let disocclusion = smoothstep(dis_lo, stddev.x * 1.0, history_dist); let motion_ramped = mix(u.params.x, 0.85, motion_alpha); let alpha = max(motion_ramped, disocclusion);