승코딩
Craft

REBUILD / storytelling.noomoagency.com

The surface is solved, not played back.

Two solvers run every frame and their normals are blended: a Navier-Stokes grid for the cursor’s wake, and a wave-equation ripple for the ring that travels out from it. Every constant below was read out of the site’s own bundle and scroll timeline — no value was measured off a screenshot.

Normal blend
Timeline

FluidSimulation — Navier-Stokes

tokenvalueread from
targetPixels2^18 (2^16 mobile)static targetPixels = _E() ? 2**16 : 2**18
dye scale2x sim per axiscreateRTs(e, t, i) with i = 2
pressure iterations2for (let t = 0; t < qne; t++), qne = 2
pressure decay0.99clearProgram.value = B1 = .99
velocity dissipation0.99advection dissipation = Kne = .99
density dissipation0.97advection dissipation = Yne = .97
vorticity curl10vorticityProgram.curl = Jne = 10
splat radius0.0015 → 0.001Fluid_epsSize.position.y
normal epsilon0.1 → 0.005Fluid_epsSize.position.x
pointer force gain1e4dx: (t - prevPointer.x) * 1e4
pointer min travel1e-4distanceTo(prevPointer) > 1e-4

SimpleFluidSim — wave equation

tokenvalueread from
simDamping0.98#define simDamping 0.98
trailSize0.035#define trailSize 0.035
trailPulseWidth0.02#define trailPulseWidth 0.02
trailPulseFrequencypi * 4 (2 Hz)#define trailPulseFrequency pi * 4.
neighbour offset3.6 texelsvec3 e = vec3(3.6 * texelSize, 0.)
injection gain2ripple * 2. + (t + r + b + l) * 0.5 - prev.y
buffer scale0.5constructor(e = .5)
step / max substeps1/60 s · 5simDt = 1/60, const i = 5

Water — composite

tokenvalueread from
type0.5 (fixed)ke(dl.typeProvider, .5) — the .z track is never bound
emissionFactor5 → 1Water_emissionXWhitenessType.position.x
emissionWhiteness0 → 0.5Water_emissionXWhitenessType.position.y
opacity1 (x0.25 mobile)Water_opacity.position.x, opacityMultiplier
color255,255,255 → 255,192,64Water_color.position
blueness0.02@G Water.blueness 0 0.1 0.02
vignettepow(16·uv.x·uv.y, 1.1)uv = vUv(1-vUv)·0.99 + 0.005
refractionuv + normal.xzvec2 uv = vScreenUv + normal.xz;