# Two Hundred Steps, and a River Appears

*Task 6 of 6 · [Hydraulic Erosion: Carving Terrain by Accumulation](https://gpu.rocks/learn/hydraulic-erosion-07165ca1.md) · GPU.js Learn*

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

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

## Requirements

- `await step()` `STEPS` times — it is async, so the `await` is not optional
- Every `EVERY` steps, `await paint(height, water)` then `render(paint.canvas)`
- Keep the `render()` calls consecutive — a `console.log` between two of them splits the scrubber

## Hint 1 — the shape of it

```js
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.

## Starter code

```js
// Everything you wrote, at 96×96, for two hundred steps.
const gpu = new GPU({ mode });

const SIZE = 96;
const STEPS = 200;
const EVERY = 10;
const erosion = slider('erosion', { min: 0.05, max: 0.8, value: 0.3, step: 0.05, label: 'erosion rate (pickUp)' });
const SHARED = { size: SIZE, move: 0.6, soft: 0.0002, rain: 0.00002, carry: 60, pickUp: erosion, settle: 0.05, maxCut: 0.004 };

const dropTotal = gpu.createKernel(function (height, water) {
  const x = this.thread.x;
  const y = this.thread.y;
  let xl = x - 1; if (xl < 0) xl = this.constants.size - 1;
  let xr = x + 1; if (xr > this.constants.size - 1) xr = 0;
  let yd = y - 1; if (yd < 0) yd = this.constants.size - 1;
  let yu = y + 1; if (yu > this.constants.size - 1) yu = 0;
  const here = height[y][x] + water[y][x];
  let total = 0;
  let d = here - (height[y][xl] + water[y][xl]); if (d > 0) total += d * d;
  d = here - (height[y][xr] + water[y][xr]); if (d > 0) total += d * d;
  d = here - (height[yd][x] + water[yd][x]); if (d > 0) total += d * d;
  d = here - (height[yu][x] + water[yu][x]); if (d > 0) total += d * d;
  return total;
}, { output: [SIZE, SIZE], constants: SHARED });

const moveWater = gpu.createKernel(function (height, water, drop) {
  const x = this.thread.x;
  const y = this.thread.y;
  let xl = x - 1; if (xl < 0) xl = this.constants.size - 1;
  let xr = x + 1; if (xr > this.constants.size - 1) xr = 0;
  let yd = y - 1; if (yd < 0) yd = this.constants.size - 1;
  let yu = y + 1; if (yu > this.constants.size - 1) yu = 0;
  const here = height[y][x] + water[y][x];
  let taken = 0;
  let d = height[y][xl] + water[y][xl] - here;
  if (d > 0) taken += (d * d * water[y][xl]) / (drop[y][xl] + this.constants.soft);
  d = height[y][xr] + water[y][xr] - here;
  if (d > 0) taken += (d * d * water[y][xr]) / (drop[y][xr] + this.constants.soft);
  d = height[yd][x] + water[yd][x] - here;
  if (d > 0) taken += (d * d * water[yd][x]) / (drop[yd][x] + this.constants.soft);
  d = height[yu][x] + water[yu][x] - here;
  if (d > 0) taken += (d * d * water[yu][x]) / (drop[yu][x] + this.constants.soft);
  const given = (drop[y][x] * water[y][x]) / (drop[y][x] + this.constants.soft);
  return water[y][x] + this.constants.move * (taken - given) + this.constants.rain;
}, { output: [SIZE, SIZE], constants: SHARED });

const moveSediment = gpu.createKernel(function (height, water, sediment, drop) {
  const x = this.thread.x;
  const y = this.thread.y;
  let xl = x - 1; if (xl < 0) xl = this.constants.size - 1;
  let xr = x + 1; if (xr > this.constants.size - 1) xr = 0;
  let yd = y - 1; if (yd < 0) yd = this.constants.size - 1;
  let yu = y + 1; if (yu > this.constants.size - 1) yu = 0;
  const here = height[y][x] + water[y][x];
  let taken = 0;
  let d = height[y][xl] + water[y][xl] - here;
  if (d > 0) taken += (d * d * sediment[y][xl]) / (drop[y][xl] + this.constants.soft);
  d = height[y][xr] + water[y][xr] - here;
  if (d > 0) taken += (d * d * sediment[y][xr]) / (drop[y][xr] + this.constants.soft);
  d = height[yd][x] + water[yd][x] - here;
  if (d > 0) taken += (d * d * sediment[yd][x]) / (drop[yd][x] + this.constants.soft);
  d = height[yu][x] + water[yu][x] - here;
  if (d > 0) taken += (d * d * sediment[yu][x]) / (drop[yu][x] + this.constants.soft);
  const given = (drop[y][x] * sediment[y][x]) / (drop[y][x] + this.constants.soft);
  const capacity = this.constants.carry * Math.sqrt(drop[y][x]) * water[y][x];
  const load = sediment[y][x];
  let delta = -this.constants.settle * (load - capacity);
  if (load < capacity) delta = Math.min(this.constants.pickUp * (capacity - load), this.constants.maxCut);
  return sediment[y][x] + this.constants.move * (taken - given) + delta;
}, { output: [SIZE, SIZE], constants: SHARED });

const erode = gpu.createKernel(function (height, water, sediment, drop) {
  const x = this.thread.x;
  const y = this.thread.y;
  const capacity = this.constants.carry * Math.sqrt(drop[y][x]) * water[y][x];
  const load = sediment[y][x];
  let delta = -this.constants.settle * (load - capacity);
  if (load < capacity) delta = Math.min(this.constants.pickUp * (capacity - load), this.constants.maxCut);
  return height[y][x] - delta;
}, { output: [SIZE, SIZE], constants: SHARED });

// Task 1's hillshade, with the water painted over it in blue.
const paint = gpu.createKernel(function (height, water) {
  const x = this.thread.x;
  const y = this.thread.y;
  let xl = x - 1; if (xl < 0) xl = this.constants.size - 1;
  let xr = x + 1; if (xr > this.constants.size - 1) xr = 0;
  let yd = y - 1; if (yd < 0) yd = this.constants.size - 1;
  let yu = y + 1; if (yu > this.constants.size - 1) yu = 0;
  const gx = (height[y][xr] - height[y][xl]) * 0.5 * this.constants.relief;
  const gy = (height[yu][x] - height[yd][x]) * 0.5 * this.constants.relief;
  const inv = 1 / Math.sqrt(gx * gx + gy * gy + 1);
  let lit = this.constants.light * (1 + gx + gy) * inv;
  if (lit < 0) lit = 0;
  const rock = Math.min(1, height[y][x] * 1.15);
  const wet = Math.min(1, Math.sqrt(water[y][x] * 40));
  this.color(
    lit * (0.3 + 0.62 * rock) * (1 - wet),
    lit * (0.44 + 0.4 * rock) * (1 - wet) + 0.26 * wet,
    lit * (0.54 + 0.2 * rock) * (1 - wet) + 0.85 * wet,
    1
  );
}, { output: [SIZE, SIZE], graphical: true, constants: { size: SIZE, relief: 18, light: 0.5774 } });

function blank() {
  const grid = [];
  for (let y = 0; y < SIZE; y++) grid.push(new Array(SIZE).fill(0));
  return grid;
}

let height = terrain;
let water = blank();
let sediment = blank();

// One step of everything. It awaits kernels, so it is async — and so is every
// call to it.
async function step() {
  const drop = await dropTotal(height, water);
  const nextWater = await moveWater(height, water, drop);
  const nextSediment = await moveSediment(height, water, sediment, drop);
  const nextHeight = await erode(height, water, sediment, drop);
  height = nextHeight;
  water = nextWater;
  sediment = nextSediment;
}

// TODO: run STEPS steps, painting and rendering every EVERY of them.
// Consecutive render() calls collapse into one frame scrubber.
```

---

Interactive version: https://gpu.rocks/learn/hydraulic-erosion-07165ca1/6

[Previous task](https://gpu.rocks/learn/hydraulic-erosion-07165ca1/5.md)
