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.