---
name: water-rock
description: >
  A dark ocean against rocks and a shore in one Three.js file. Non-repeating
  waves, refraction, contact foam, and stone whose wetness remembers the
  wave field. Use when changing the water, the rocks, the shore or the
  bubbles on WebGPU. The reference file is water.html beside this skill.
  If they disagree, the HTML wins.
---

# Water at the rock

One HTML file. No textures, no bundler. Three.js 0.186.1 from a CDN via an import map (`three` and `three/webgpu` both point at `three.webgpu.js`, `three/tsl` at `three.tsl.js`, `three/addons/` at `examples/jsm/`). `WebGPURenderer` only. Set `renderer._getFallback = null` before `init()`. If `navigator.gpu` is missing, or the backend is not WebGPU after `init()`, stop. No WebGL fallback.

`antialias` stays false. Refraction copies the framebuffer through `viewportMipTexture`, and WebGPU will not copy a multisampled texture.

Open it through a server. `file://` will not load the ES modules. `php -S localhost:8000` or `npx serve`.

## Waves

`oceanH` is the afl_ext sum (Shadertoy MdXyzX, public domain): 13 octaves of `exp(sin(x) - 1)`, each octave in a direction from a running angle (`iter += 1232.399963`), derivative drag `0.28`, then normalised, centred at `-0.40`, scaled by `0.72`.

Each `ROCKS` entry adds a lap ring: `sin(d * 2.6 - t * 2.1) * 0.075 * exp(-0.30 * max(d, 0))`, measured from `foamR`. Virtual entries have no mesh. The three at x = -33 line the beach. The array is baked when the graph is built. Adding a rock later means rebuilding the materials.

`jsOceanH` is a line-for-line CPU mirror. Bubbles, the underwater check and the waterline band read it. Edit both functions in the same change. Master height `0.72` lives in both.

`vnoise` uses a quintic fade. `fbmN` rotates each octave by 0.62 rad. Do not put the cubic fade back, and do not drop the rotation. The ripple grid shows through.

The water mesh is a 240 plane, 360 by 360 segments, at the origin so local space is world space. `positionNode` adds `oceanH`. Do not raise the segment count.

## Wetness

`rockColor` samples `oceanH` at ages `[0, 0.3, 0.8, 1.6, 3, 5.5, 9, 14, 20, 27, 35, 44]`. Weight is `exp(-age * 0.035)`. Keep the max. Do not thin the early samples. Infiltration margin is `0.12`. Wet albedo is 13% of dry. Specular is multiplied by wetness, through the noisy micro-normal. Dry stone has none.

The shore uses the same material. `makeShoreGeometry` is the bank (world x from -46 to -12). Wetness, caustics and contact foam apply because the material does, not because the mesh is special.

Rocks are `IcosahedronGeometry` detail 5, displaced with `jsFbm`, Y scaled by `sq`. Do not raise the detail.

## Contact

`EDGE_BLUR` is `0.45`. `waterOpacityNode` is `smoothstep(0, EDGE_BLUR, depthGap())`. That fade is what softens the silhouette. If the line goes hard, check it first.

Refraction: distort `screenUV` by the normal, reject a tap whose scene depth is in front of the water, sample `viewportMipTexture` at `contactSoft * 3.2 + wDepth * 0.22` (clamped 0 to 4). Absorption `exp(wDepth * (-1.15, -0.30, -0.26))`. Clarity `exp(-0.42 * wDepth)`.

Foam masks, in order: surge ring, contact band, crest, shore swash. Amplitudes 0.945, 0.585, 0.35, 0.81. Domain-warp the lace (`pw = p + 2.6 * warp`). Worley at 9 and 24 breaks thin foam into bubbles. Depth-edge foam uses the same gap as the opacity fade.

Bubbles: `CLUSTER_COUNT` 36, 8 to 26 each, plus about 40 stragglers. One `InstancedMesh` of hemispheres. Steepness weight is `steep^4`. No lateral motion. One `jsOceanH` per cluster per frame. `depthWrite` false, `renderOrder` 2.

## Underwater

Water is `DoubleSide`. `frontFacing` selects the Snell underside. Below `jsOceanH`, fog is `0x0a2226`, near 0.5, far 30. Above, `0x556366`, near 48, far 170. The CSS band `#wline` shows when the camera is within 0.55 of the surface. It is a horizontal strip.

`OrbitControls`. `maxPolarAngle` is `Math.PI - 0.12`, so the camera can go under. Drag right turns the scene with the cursor. Do not swap the spherical mapping.

Reflections are the sky only. There is no planar reflection of the rocks. The spray term in the water shader is a leftover haze. Do not replace it with points.
