Task 3 of 6

Everybody Downhill At Once

Now move the water. On a CPU you would walk each cell and push its water into the neighbours below it. You cannot do that here: two cells pushing into the same neighbour is a write collision, and a kernel writes exactly one output cell — the whole point Thinking in Parallel makes. So invert it. Each cell works out what its uphill neighbours would have sent it, and adds that up.

Neighbour n hands this cell the share d² / drop[n] of its water. And there is the catch that shapes the whole module: drop[n] is an aggregate over n's neighbours, which are two rings away from us. A gather cannot ask a neighbour to add something up on demand — so the previous task exists purely to have that total already sitting in a grid we can read. When a gather needs a neighbour's aggregate, the aggregate gets its own pass.

The cell also hands its own water on, move of it per step, and keeps 1 − move·drop/(drop + soft). That keep-weight is the centre weight of an explicit diffusion step wearing a different hat, and it obeys the same rule The Heat Equation & Stability derives: let it go negative — here, take move past 1 — and the grid detonates. The + soft keeps the division safe where a cell has nowhere to go, and quiets flat water, which would otherwise flip back and forth between neighbours forever.

the total lives two rings away, so it gets a pass of its own
Goal: complete moveWater — gather each uphill neighbour's share, subtract what this cell hands on, and add the rain.

Requirements

Hint 1 — whose total is it?

The share is the neighbour's: it is dividing up the neighbour's water, so the denominator has to be the neighbour's drop total.

d = height[y][xl] + water[y][xl] - here;
if (d > 0) taken += (d * d * water[y][xl]) / (drop[y][xl] + this.constants.soft);

Divide by drop[y][x] instead and the shares stop summing to one — water appears and vanishes.

Hint 2 — the outgoing half

Sum the shares this cell gives away and you get drop[y][x] back on top, so the whole outgoing side is one line:

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;
Hint 3 — the test that catches everything

Water is conserved: whatever leaves one cell arrives in another, so after one step the grid holds exactly what it held plus one step of rain. If your total drifts, the two halves are not using the same shares.

Same idea elsewhere

Gather-instead-of-scatter with a pre-computed normaliser is the shape of sparse matrix–vector multiply, of graph message passing, and of every particle-to-grid transfer written for a GPU: nobody writes to a neighbour, everybody reads from one, and any per-source total the readers need is materialised by an earlier pass.

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.