Task 6 of 6

Two Hundred Steps, and a River Appears

The payoff. 96×96, two hundred steps, and the only thing that has changed is scale — the physics is the four kernels you already wrote, and the loop is the one from last task, wrapped in a step() helper. (It calls kernels, so it is async, and every caller has to await it.)

What is new is that you render inside the loop. The console collapses consecutive render() calls into a frame scrubber, so painting every tenth step costs one line and buys the whole story: drag the handle under the picture and watch a plausible-but-lifeless fractal grow a drainage network. Frame 1 is ten steps in — the water has barely spread and the map is still raw noise. Frame 20 has rivers.

The painter is task 1's hillshade with the water laid over it in blue, so the channels are visible while they form. And the erosion dial is worth dragging for what it doesn't do: sixteen times the pick-up rate — 0.05 to 0.8 — moves about half as much rock again (44.6 units of height against 66.8 over the run, deepest cut 0.062 against 0.076), and the drainage pattern is essentially the same picture. Two reasons, and both are the module: where the water goes was settled by the routing long before any rock shifted, and a stream that bites faster also loads up faster and starts settling, so the model throttles itself. That is the whole of temporal accumulation: a cheap step, run often, producing structure no single pass could have computed.

Goal: run STEPS steps, painting and rendering every EVERYth one, so the console shows a scrubber from bare noise to a river network.

Requirements

Hint 1 — the shape of it
for (let i = 1; i <= STEPS; i++) {
  await step();
  if (i % EVERY === 0) {
    await paint(height, water);
    render(paint.canvas);
  }
}

Twenty frames out of two hundred steps, and no bookkeeping at all.

Hint 2 — why step() needs its await

step() awaits four kernels, so it returns a promise like any other async function. Call it without await and the loop fires two hundred overlapping steps at grids that are still being written — the picture will be nonsense and the console will tell you a promise turned up where a grid should be.

Hint 3 — reading the scrubber

The first frames look like nothing is happening: the water is still spreading out. Around frame 3 thin blue threads appear on the slopes, and from there they thicken, merge, and cut visible notches into the ridges. The two hollows fill up and become lakes.

Same idea elsewhere

This is the shape of every offline GPU simulation that ends in a picture: a compute loop that never leaves the device, a render pass tapping it every N steps, and a result that exists only because the cheap step ran thousands of times. Landscape tools (Houdini, World Machine, Gaea) run exactly this loop — just at 4096² and with a few more terms in the capacity.

All tasks in Hydraulic Erosion: Carving Terrain by Accumulation

  1. Seeing the Ground
  2. Which Way Is Down
  3. Everybody Downhill At Once
  4. Capacity: What the Water Can Carry
  5. Ping-Pong the Whole Landscape
  6. Two Hundred Steps, and a River Appears

This page is an interactive exercise — the editor, the GPU runner and your saved progress need JavaScript. The text above is the full brief.