fix: stop Wave Tank damping the interference out of existence (#184) - #237
Merged
Conversation
A 3000x600 capture showed a soft oil slick -- no ripples, no rings, no fringes. The physics was running; the image did not show it. THE DAMPING WAS THE BUG, AND NOT BY A LITTLE The band was 0.972-0.984 per step. At 240 steps/s that leaves 0.5% of a wave after ONE SECOND. The scheme's CFL number is 0.5, so a disturbance moves sqrt(0.5) ~ 0.707 cells per step and crossing a 2048-cell tank takes ~2900 steps -- after which 0.978 leaves 1e-28 of the wave. Interference is this saver's entire subject and it was being annihilated twenty-eight orders of magnitude before two ripples could meet. Surviving one crossing at 25% needs 0.99952; three crossings at 10% needs 0.99974. Settled at 0.99935-0.99955, the LOW end, because that is a balance rather than a maximum: at 0.99972 waves outlived three crossings and, with events arriving every few seconds, the tank accumulated into exactly the uniform chop this issue also complains about -- fine at 20s, over-energised by 45s. The band is documented in the file with the derivation. A LATENT BUG THAT ONLY EXISTED ON THE WALL MAX_CELLS was clamped per axis independently, so at 6000x1200 the grid came out 1024x300 -- 3.41:1 against a 5:1 canvas, stretching every ripple 1.46x horizontally. It was invisible at 3000x600, which never reaches the cap, so no capture would ever have shown it. Both axes now scale by one factor. THE REST - Sim resolution: CELL_PX 4 -> 2 and MAX_CELLS 1024 -> 2048. The old comment argued a quarter-res grid "looks identical"; true at 1080p, and a 4x upsample at 6000x1200 is visibly soft. - Wall-clock stepping. Two-steps-per-frame made wave speed AND decay depend on the frame rate -- the same tank ran twice as fast on a 120Hz panel. Steps now come from dt at a fixed 240/s, clamped so a stall cannot dump a burst. - Caustics from the Laplacian of the height field. Curvature is what converges refracted rays, so -laplacian is the focusing term, and the four neighbours were already fetched for the gradient. This is the change that makes it read as water. - Refraction: the floor is sampled displaced by the surface gradient, over a tiled bed -- distortion is only visible as distortion OF something. - Fresnel mix into a sky gradient, so a glancing surface mirrors rather than showing the floor. - Choreography: one deliberate event at a time separated by 6-14s of quiet. A single large drop; two sustained in-phase sources for a fringe pattern (fringes are steady-state, so an impulse cannot produce them); or a line source sweeping in y to build a plane wave. - A barrier with a slit on 40% of activations, so diffraction is on show. THE BARRIER WAS BROKEN AND ONLY FORCING IT ON REVEALED IT Three seeds in a row happened not to enable it. Rather than assume, I forced it true -- and nothing rendered. Two bugs: the drawn wall was sized from uTexel, so it was about three pixels wide, and the slit term was inverted, painting the gap dark and leaving the wall transparent. The simulation barrier was correct all along. Both passes now share BARRIER_HALF_W so the wall the physics blocks is the wall you see, verified by measuring the darkest interior column (0.111 -> 0.035). MEASUREMENTS - Dynamic range under 12% washout: 0.020 before, 0.812 after. The old surface had essentially no contrast to wash out. - 120.1 fps at 6000x1200, vsync-capped, against main's 120.0, despite 6.7x the cells -- the stencil is bandwidth-bound and was never the cost. - shadercheck ok=150 fail=0; no baseline change. Wave Tank still measures 0 at frames 5/12 because the first event lands at 0.15s, so it remains one of the two documented zero-baseline savers and is still not covered by the structure check.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #184.
The damping was the bug, and not by a little
The band was
0.972-0.984per step. At 240 steps/s that leaves 0.5% of a wave after onesecond. The scheme's CFL number is 0.5, so a disturbance moves ~0.707 cells/step and
crossing a 2048-cell tank takes ~2900 steps — after which
0.978leaves 1e-28 of thewave.
Interference is this saver's entire subject and it was being annihilated twenty-eight
orders of magnitude before two ripples could meet.
Settled at 0.99935–0.99955, the low end — a balance, not a maximum. At 0.99972 waves
outlived three crossings and the tank accumulated into exactly the uniform chop this issue
also complains about: fine at 20s, over-energised by 45s. The derivation is in the file.
A latent bug that only existed on the wall
MAX_CELLSwas clamped per axis independently, so at 6000×1200 the grid came out1024×300 — 3.41:1 against a 5:1 canvas, stretching every ripple 1.46× horizontally.
It cannot be seen at 3000×600, which never reaches the cap, so no capture would ever have
shown it. Both axes now scale by one factor.
The rest of the issue
CELL_PX4 → 2,MAX_CELLS1024 → 2048. The old comment argued aquarter-res grid "looks identical" — true at 1080p; a 4× upsample at 6000×1200 is soft.
rate — the same tank ran twice as fast on a 120Hz panel. Steps now derive from
dt.rays, and the four neighbours were already fetched for the gradient. This is the single
change that makes it read as water.
because distortion is only visible as distortion of something.
sustained in-phase sources for fringes (fringes are steady-state — an impulse cannot make
them); or a line source sweeping in y to build a plane wave.
The barrier was broken, and only forcing it on revealed it
Three seeds in a row happened not to enable it. Rather than report a feature I hadn't seen,
I forced it true — and nothing rendered. Two bugs:
uTexel, making it ~3 pixels wide;The simulation barrier was correct all along, so the physics worked and was invisible. Both
passes now share
BARRIER_HALF_W. Verified by measuring the darkest interior column:0.111 → 0.035, with the slit and downstream diffraction visible.
Measurements
contrast to wash out, which is the "soft oil slick" quantified.
The stencil is bandwidth-bound and was never the cost.
npm run lintclean,npm test407 passed,npm run shadercheckok=150 fail=0.Acceptance criteria
Not verified / honest limits
tank — which this issue explicitly asks for — waves live 10–25s, so events every 6–14s
necessarily overlap. Genuine calm would need gaps longer than the wave lifetime, leaving
the wall flat most of the time. Individual wavefronts are traceable across the full width,
which is the property that matters; "quiet" is not achievable alongside "crosses the tank".
baseline stays 0, so it remains one of the two documented savers the structure check skips.
This PR does not change that, and deliberately does not force an early event just to make a
number non-zero — a tiny density would sit under the absolute margin anyway.
so the captures show interference rather than a textbook fringe photograph.