# Put It Together: 128 Bodies

*Task 5 of 5 · [N-Body Gravity](https://gpu.rocks/learn/n-body-gravity-5de47751.md) · GPU.js Learn*

Everything from this module, running as one machine. The three kernels below are
your last three tasks — softened O(n²) acceleration, the velocity tick, the position tick.
What's missing is the **conductor**: a JavaScript loop that runs ten full
ticks, feeding each kernel's output into the next and carrying the new state into the next
tick.

Notice who does what: JavaScript never touches a single interaction — it just passes
arrays around. The GPU grinds through 128 × 128 = 16,384 interactions per tick, 163,840
across the run. Swap 128 for 100,000 and this exact structure is a galaxy simulator; the
loop you're about to write wouldn't change by a character.

## Goal

**Goal:** write the simulation loop — ten ticks of
`accel → stepVel → stepPos`, carrying the new arrays forward each time.

## Requirements

- Each tick: accelerations first — `await accel(px, py, mass, SOFT)`
- Unpack the pairs, then `await stepVel(vx, vy, ax, ay, DT)`, then `await stepPos` with the *new* velocities — awaited one after another, because each reads the one before it
- Reassign `px, py, vx, vy` so the next tick starts from the new state
- Run exactly `STEPS` ticks, then log body 0's final position

## Hint 1 — the shape of one tick

Inside the loop: `await accel`, unpack its pairs into
`ax, ay` arrays (the `unpack` helper is right there),
`await stepVel`, unpack, `await stepPos`, unpack. One at a
time — the next call needs the previous one's numbers.

## Hint 2 — carrying the state

End every tick by overwriting the state:

```js
vx = newVx;
vy = newVy;
px = newPx;
py = newPy;
```

— next tick's
`accel` must see the moved bodies, or time never advances.

## Hint 3 — the whole loop

```js
for (let step = 0; step < STEPS; step++) {
  const [ax, ay] = unpack(await accel(px, py, mass, SOFT));
  const [nvx, nvy] = unpack(await stepVel(vx, vy, ax, ay, DT));
  const [npx, npy] = unpack(await stepPos(px, py, nvx, nvy, DT));
  px = npx; py = npy; vx = nvx; vy = nvy;
}
```

## Same idea elsewhere

A host loop launching device kernels in sequence is the universal skeleton of GPU
simulation: CUDA streams queueing kernel after kernel per timestep, WebGPU building one
command encoder per frame, Metal committing a command buffer per tick. Production codes
differ mainly in never reading the arrays back between passes — that's what the textures in
**Pipelines & Textures** are for.

## Starter code

```js
// Three kernels from the last three tasks — and a conductor's podium.
const gpu = new GPU({ mode });
const N = 128;
const DT = 0.01;
const SOFT = 0.1;
const STEPS = 10;

const accel = gpu.createKernel(function (posX, posY, mass, soft) {
  const myX = posX[this.thread.x];
  const myY = posY[this.thread.x];
  let ax = 0;
  let ay = 0;
  for (let j = 0; j < this.constants.n; j++) {
    const dx = posX[j] - myX;
    const dy = posY[j] - myY;
    const r2 = dx * dx + dy * dy + soft * soft;
    const w = mass[j] / (r2 * Math.sqrt(r2));
    ax += dx * w;
    ay += dy * w;
  }
  return [ax, ay];
}, { output: [N], constants: { n: N } });

const stepVel = gpu.createKernel(function (velX, velY, accX, accY, dt) {
  return [velX[this.thread.x] + accX[this.thread.x] * dt,
          velY[this.thread.x] + accY[this.thread.x] * dt];
}, { output: [N] });

const stepPos = gpu.createKernel(function (posX, posY, velX, velY, dt) {
  return [posX[this.thread.x] + velX[this.thread.x] * dt,
          posY[this.thread.x] + velY[this.thread.x] * dt];
}, { output: [N] });

// [x, y] pairs → two plain arrays
function unpack(pairs) {
  const xs = [];
  const ys = [];
  for (let i = 0; i < pairs.length; i++) {
    xs.push(pairs[i][0]);
    ys.push(pairs[i][1]);
  }
  return [xs, ys];
}

let px = posX;
let py = posY;
let vx = velX;
let vy = velY;

for (let step = 0; step < STEPS; step++) {
  // TODO — one full tick. Every kernel call is awaited, and in this
  // order: each stage reads the stage before it.
  //   1. pairs = await accel(px, py, mass, SOFT), unpack into ax, ay
  //   2. await stepVel with DT → unpack into the NEW vx, vy
  //   3. await stepPos with the NEW velocities → unpack into the new px, py
  //   4. reassign px, py, vx, vy for the next tick
}

console.log('after', STEPS, 'ticks, body 0 is at', px[0], py[0]);
```

---

Interactive version: https://gpu.rocks/learn/n-body-gravity-5de47751/5

[Previous task](https://gpu.rocks/learn/n-body-gravity-5de47751/4.md)
