# Capacity: What the Water Can Carry

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

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:

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

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

## Figures

- **one delta, computed twice, spent in opposite directions**

## Goal

**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

- `capacity = carry * Math.sqrt(drop[y][x]) * water[y][x]` in both kernels
- Under-loaded water cuts `Math.min(pickUp * (capacity − load), maxCut)`; over-loaded water settles `−settle * (load − capacity)`
- `erode` returns `height − delta`; `moveSediment` returns the routed sediment `+ delta`

## Hint 1 — the exchange, spelled out

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

## Starter code

```js
// The rock pays for what the water carries.
const gpu = new GPU({ mode });

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];

  // Sediment rides the water: same shares, same gather, one letter different.
  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);

  // TODO: capacity, load, delta — then add delta to the routed sediment.
  return sediment[y][x] + this.constants.move * (taken - given);
}, {
  output: [64, 64],
  constants: { size: 64, move: 0.6, soft: 0.0002, carry: 60, pickUp: 0.3, settle: 0.05, maxCut: 0.004 },
});

const erode = gpu.createKernel(function (height, water, sediment, drop) {
  const x = this.thread.x;
  const y = this.thread.y;
  // TODO: the SAME capacity / load / delta as above — then take it out of the rock.
  return height[y][x];
}, {
  output: [64, 64],
  constants: { carry: 60, pickUp: 0.3, settle: 0.05, maxCut: 0.004 },
});

const nextSediment = await moveSediment(terrain, water, sediment, drop);
const nextHeight = await erode(terrain, water, sediment, drop);

let before = 0;
let after = 0;
for (let y = 0; y < 64; y++) {
  for (let x = 0; x < 64; x++) {
    before += terrain[y][x] + sediment[y][x];
    after += nextHeight[y][x] + nextSediment[y][x];
  }
}
console.log('rock + sediment before:', before, 'after:', after);
console.log('deepest cut this step:', Math.min(...nextHeight.map((row, y) => Math.min(...row.map((v, x) => v - terrain[y][x])))));
```

---

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

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