Task 4 of 6

Capacity: What the Water Can Carry

Water alone does nothing to rock. What carves is load: fast, deep water can hold sediment, slow shallow water cannot, and the ground pays the difference. That is one number per cell:

capacity = carry * Math.sqrt(drop) * water

√drop is the size of the downhill gradient — the drop total was a sum of squares, so its square root is the slope — and multiplying by depth is the classic stream power product: steep × wet. Compare it with what the cell is already carrying and you get one signed exchange:

delta = load < capacity
  ? Math.min(pickUp * (capacity - load), maxCut)  // cut
  : -settle * (load - capacity)                   // fill

Then height − delta and sediment + delta. That maxCut ceiling is the same kind of guard as move ≤ 1 next door: cut a cell too deep in one step and its neighbours are suddenly steeper, so they cut harder, and the feedback runs away.

Two kernels need the same delta and there is no way to hand a value from one to the other — so both recompute it from the same snapshot. That is not waste. On a GPU, arithmetic is close to free next to a round trip through memory; recomputing beats communicating almost every time.

one delta, computed twice, spent in opposite directions
Goal: write the same exchange in both kernels — moveSediment adds it to the water's load, erode takes it out of the rock. The routing half of moveSediment is already there.

Requirements

Hint 1 — the exchange, spelled out
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);

Paste the identical four lines into both kernels.

Hint 2 — opposite signs

erode ends return height[y][x] - delta; and moveSediment ends return sediment[y][x] + this.constants.move * (taken - given) + delta;. Rock loses exactly what the stream gains, which is what the conservation test checks.

Same idea elsewhere

"Recompute rather than communicate" is a GPU reflex, not a gpu.js workaround: CUDA kernels routinely redo an index calculation in every thread instead of staging it in shared memory, and shader authors re-derive a normal per fragment rather than interpolate one. Arithmetic units idle while memory is the bottleneck.

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.