Additive ink is unbounded: overlapping strokes stack well past 1.0, and a raw write
clips every saturated channel to one flat white. The shoulder compresses overshoot
through an exponential roll-off instead — byte-identical below the knee, slope 1
there, asymptote just under paper-white. Left of the divider, the raw clip. Right,
the shoulder. Paint across the boundary.
raw · clampsoft shoulderdrag to inject ink · divider drags · ←/→ move it
SOFT SHOULDER — UNSUPPORTED
Probing…
transfer · input → outputper channel
accumulation is real: values stack unbounded in a half-float target
The test cell accumulates gaussian splats
additively into an RGBA16F target — the exact failure shape that produced flat-white
plateaus in Dye Bench's dye view. Nothing about the accumulation differs left from
right; only the present pass does. That is the point: this is a display-side fix, and
the solver never knows it happened.
Drop-in · executed live above
An unbounded accumulation target — half-float renderable (WebGL2 + EXT_color_buffer_float here) whose values legitimately exceed 1.0 before display.
To run on the present pass only. Data textures, solver fields and readbacks are untouched; the shoulder lives between simulation and screen.
SHOULDER_GLSL — the chunk, drop-in ready; shoulder(c), a JS reference implementation matching it bit-for-bit in spirit for plotting/tests; createShoulderCell(canvas), the whole accumulate-and-present cell behind the demo.
Byte-identical below the knee (0.72): inputs under it pass through unchanged, so ordinary tones cannot shift.
C¹ continuity at the knee, slope 1: no visible seam where compression begins; the derivative matches on both sides.
Asymptote 0.985 (knee 0.72 + shoulder 0.265): output approaches paper-white but never reports a false 1.0 — dense cores keep gradation and hue because the map is monotone per channel.
The solver stays honest: this fixes presentation, never data. Any HUD quantity is read from the untouched field, not the toned image.
Fails by naming, like every GPU page here: without WebGL2 or float render targets the cell renders a card that says which capability is missing.
Seeded stillness: the opening composition comes from mulberry32(20260822) and nothing animates unattended — screenshots and reduced motion get the same frame.
Dye Bench (designpages.dev/dye-bench), dye-view fragment: the KNEE/SHOULDER constants and the min(col,KNEE) + S·(1−exp(−over/S)) expression, plus the reasoning comment that justified them.