Skip to content

fix: catch a monotonic frame, closing the last gap in the structure check (#235) - #251

Merged
BernardJen merged 1 commit into
mainfrom
fix/structure-check-monotonicity
Aug 18, 2026
Merged

fix: catch a monotonic frame, closing the last gap in the structure check (#235)#251
BernardJen merged 1 commit into
mainfrom
fix/structure-check-monotonicity

Conversation

@BernardJen

Copy link
Copy Markdown
Contributor

Closes #235.

The gap

A flat gradient passed for Plasma. Edge density could not see it — Plasma's baseline is
0.0001, so a drop to zero is smaller than the absolute margin — and #229's spread check
catches a uniform frame, not a smooth ramp. To both of those statistics, a gradient and a
healthy Plasma frame are the same picture: near-zero edges, wide luminance spread.

What separates them

A gradient is monotonic. Its signed slope along a row never reverses; a real image, however
smooth, reverses constantly. Measured across all 30 savers:

undulation
healthy Plasma 0.5948
radial gradient 0.0586 (one reversal per row)
linear ramp 0.0000 (none, by construction)

UNDULATION_MIN = 0.12 — a 5.0× margin under the healthy value and 2× above the radial case.

Two strides, and the max of them. Falling Sand reads 0.5833 at stride 4 and 0.0000 at
stride 16 — its grains are finer than the coarse step — so one stride alone would call a sparse
saver monotonic.

Gated to sub-margin savers, which is Plasma alone

Not applied set-wide, and that is a deliberate limit. The lowest undulation among savers with a
non-zero baseline is Julia Set at 0.1544, which clears 0.12 by only 1.3×. A set-wide floor
would sit within a third of a healthy saver — the same thin margin that made the coarse-stride
approach unshippable in #234, and no more acceptable here.

Verified by fault injection, naming the rule each time

Injected into Plasma Result
linear ramp fail=5"frame is monotonic: undulation 0.0000 is below 0.12"
radial gradient fail=5"frame is monotonic: undulation 0.0586 is below 0.12"
uniform grey fail=5 — caught by the #229 spread rule, as intended
clean ok=150 fail=0, Plasma undulation 0.5948

A self-inflicted error worth recording

My first measurement read 0.0000 for all thirty savers. That looked like a finding and was a
bug: the block sat after lums.sort(), and a sorted array is monotonic by definition. The file
already warned about exactly this for edge density. It is now computed before the sort, with a
comment saying why it must be.

And a harness bug found while verifying

The failure reason never reached CI output. scripts/shader-check.mjs extracts console lines
with a per-line regex against Chrome's INFO:CONSOLE(n] "..." format, so a multi-line message
is dropped entirely — its first line has no closing quote and the rest have no prefix.

The FAIL log was multi-line. So every failed run has printed FAILING SAVERS: <name> with no
reason attached
, and the reason could only be recovered by opening the page. Now emitted on one
line.

That bug is why this PR can state which rule caught which fault instead of inferring it by
elimination.

Verification

  • Branched from main (bf83e38).
  • npm run lint clean.
  • npm test416 passed (412 + 4 new).
  • npm run shadercheckok=150 fail=0 clean; three injected faults each caught by the
    intended rule.
  • Threshold and both strides chosen from a measurement across all 30 savers, not picked.

Not verified / honest limits

  • Applies to one saver today. If another drops below the absolute margin, its undulation must
    be measured before this is trusted — Julia Set would clear the threshold by only 1.3×. The
    constant says so, and a test asserts the gated set is what it is.
  • A gradient that undulates would still pass. Nothing here catches a broken display pass that
    happens to produce reversals — say banded noise. This closes the monotonic case, which was the
    measured gap, not every conceivable smooth failure.
  • The false-positive half of shadercheck cannot tell a smooth-correct frame from a smooth-broken one (Plasma only) #235 — "a legitimately smooth redesign does not fail merely for being
    smooth" — is now argued rather than merely assumed: undulation is a property of variation, not
    of sharpness, and every one of the 30 savers scores above 0.15 while gradients score under 0.06.
    It still has not been tested against a deliberate new smooth redesign, because there isn't one.

…heck (#235)

A flat gradient passed for Plasma. Edge density could not see it -- Plasma's
baseline is 0.0001, so a drop to zero is smaller than the absolute margin -- and
#229's spread check catches a UNIFORM frame, not a smooth ramp. To both of those
statistics a gradient and a healthy Plasma frame are the same picture: near-zero
edges, wide luminance spread.

WHAT SEPARATES THEM

A gradient is MONOTONIC. Its signed slope along a row never reverses; a real image,
however smooth, reverses constantly. Measured across all 30 savers:

  healthy Plasma        0.5948
  radial gradient       0.0586   (one reversal per row)
  linear ramp           0.0000   (none, by construction)

Threshold 0.12: a 5.0x margin under the healthy value and 2x above the radial case.

TWO STRIDES, AND THE MAX

Falling Sand reads 0.5833 at stride 4 and 0.0000 at stride 16 -- its grains are
finer than the coarse step, so one stride alone would call a sparse saver monotonic.
Taking the max of strides 4 and 16 fixes that.

GATED TO SUB-MARGIN SAVERS, WHICH IS PLASMA ALONE

Not applied set-wide, and that is a deliberate limit rather than laziness. The
lowest undulation among savers with a non-zero baseline is Julia Set at 0.1544,
which clears 0.12 by only 1.3x. A set-wide floor would therefore sit within a third
of a healthy saver -- the same thin margin that made the coarse-stride approach
unshippable in #234, and no more acceptable here.

VERIFIED BY FAULT INJECTION, NOT BY ELIMINATION

Each case names the rule that fired:

  linear ramp      fail=5  "frame is monotonic: undulation 0.0000 is below 0.12"
  radial gradient  fail=5  "frame is monotonic: undulation 0.0586 is below 0.12"
  uniform grey     fail=5  caught by the #229 spread rule, as intended
  clean            ok=150 fail=0, Plasma undulation 0.5948

A SELF-INFLICTED ERROR WORTH RECORDING

The first measurement read 0.0000 for all thirty savers, which looked like a
finding and was a bug: the block sat AFTER lums.sort(), and a sorted array is
monotonic by definition. The file already warned about this for edge density. The
computation is now before the sort, with a comment saying why it must be.

AND A HARNESS BUG FOUND WHILE VERIFYING

The failure reason never reached CI output. scripts/shader-check.mjs extracts
console lines with a per-line regex against Chrome's INFO:CONSOLE(n] "..." format,
so a MULTI-LINE message is dropped entirely -- its first line has no closing quote
and the rest have no prefix. The FAIL log was multi-line, so every failed run has
been printing "FAILING SAVERS: <name>" with no reason attached, and the reason could
only be recovered by reading the page. Now emitted on one line.

That bug is why this commit can say which rule caught which fault instead of
inferring it.
@BernardJen
BernardJen merged commit 926c4ef into main Aug 18, 2026
1 check passed
@BernardJen
BernardJen deleted the fix/structure-check-monotonicity branch August 18, 2026 14:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

shadercheck cannot tell a smooth-correct frame from a smooth-broken one (Plasma only)

1 participant