From c44b527fe8f39aa4f8843a2661895b0cc3e2af60 Mon Sep 17 00:00:00 2001 From: Dejvino Date: Thu, 6 Aug 2026 19:41:58 +0200 Subject: [PATCH] Side quest 3: fix the Horizon Lines determinism flake at the cause MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 7's determinism check reported Horizon Lines at 2/255 against a 1/255 tolerance, but only when phase 7 ran in isolation — a full run warmed the GPU first and it passed. A gate whose verdict depends on preceding load will eventually stay green through a real regression, and determinism is the one property this whole project is built on. The cause turned out to be geometry, not the driver. The lines were drawn with `smoothstep(u_thickness * (0.5 + u_sigLine), 0.0, d)` where the width is 0.008 scene units against a 0.028-unit pixel at 720p — a line under a THIRD of a pixel wide, ramping from full brightness to nothing across that third. The parameter range goes down to 0.002, which is a fourteenth of a pixel. That is a near-vertical cliff, and a cliff turns a float wobble of 1e-7 in a cancelling subtraction into a whole byte of colour. Nothing else in the library sums 40 such terms. Replaced with analytic coverage: `sat((w - d) / px + 0.5)`, where px is the exact height of a pixel in scene units. The transition now always spans one pixel, so no pixel sits on a discontinuity — and a line thinner than a pixel comes out DIM instead of being drawn at full brightness wherever a pixel centre happens to land on it. That second part is a real image fix as well as a determinism one: it is the end of the shimmer this scene has always had. Width stays in scene units, so resolution independence is untouched — the 320x180 vs 1280x720 diff still reads 0.00183 against a 0.06 limit. Measured: worst delta over two identical 20-frame captures drops from 2 to at most 1, usually 0. Phase 7 now passes in isolation, repeatedly, which is the condition that was failing. Horizon Lines is no longer even the worst scene on that metric. Also removed the dead `total` accumulator while in there. 104/104 checks pass including the slow set. See SIDE-QUESTS.md §3. Co-Authored-By: Claude Opus 5 --- flow-state/src/scenes/shader/horizon-lines.js | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/flow-state/src/scenes/shader/horizon-lines.js b/flow-state/src/scenes/shader/horizon-lines.js index 209c22a..08ea428 100644 --- a/flow-state/src/scenes/shader/horizon-lines.js +++ b/flow-state/src/scenes/shader/horizon-lines.js @@ -39,7 +39,10 @@ vec4 scene(vec2 uv, vec2 p) { float offset = sin(p.x * 2.2 + t * 2.0) * u_bend * envelope; vec3 col = pal(0) * 0.06; - float total = 0.0; + + // One pixel, in the scene units p is measured in. p spans [-1, 1] vertically + // regardless of output size, so this is the exact height of a pixel. + float px = 2.0 / u_resolution.y; for (int i = 0; i < 40; i++) { if (float(i) >= u_count) break; @@ -49,12 +52,28 @@ vec4 scene(vec2 uv, vec2 p) { float y = slot + offset * (0.4 + fract(fi * 0.37)); float d = abs(p.y - y); - float line = smoothstep(u_thickness * (0.5 + u_sigLine), 0.0, d); + // Analytic coverage rather than a smoothstep to zero. + // + // The width here is genuinely sub-pixel: 0.008 scene units against a + // 0.028-unit pixel at 720p is a line under a third of a pixel wide, and + // the range goes down to a fourteenth. Ramping from full to nothing + // across a third of a pixel is a near-vertical cliff, and a cliff turns + // any float wobble in d into a whole byte of colour — which is what + // made this the one scene in the library that failed the determinism + // gate, intermittently, at 2/255. + // + // Coverage fixes the cause rather than the symptom. The transition + // always spans exactly one pixel, so nothing sits on a discontinuity; + // and a line thinner than a pixel now comes out DIM rather than being + // drawn at full brightness wherever a pixel centre happens to land on + // it, which is both correct and the end of the shimmer it used to have. + // Width stays in scene units, so resolution independence is unchanged. + float w = u_thickness * (0.5 + u_sigLine); + float line = sat((w - d) / px + 0.5); float halo = exp(-d * 26.0) * u_glow; vec3 c = pal(i); col += c * (line + halo * 0.55); - total += line; } // Keep the far edges dark so the lines read as a subject, not wallpaper.