# One Thread, One Whole Trajectory

*Task 1 of 6 · [ODE Integrators](https://gpu.rocks/learn/ode-integrators-62f4a3ff.md) · GPU.js Learn*

A mass on a spring, in one line: the acceleration always points back at the
origin, `a = −x`. That is the entire physics of this module. It is
deliberately the dullest force there is, because the subject here is the
**clock**, not the force — N-Body Gravity owns interesting forces.

What the spring buys is that we already know the answer, exactly and forever:

```js
x(t) = x₀·cos t + v₀·sin t
E    = (x² + v²) / 2, constant
```

So every number a solver produces can be subtracted from the truth, and "how wrong
is this?" stops being a feeling. And the GPU shape flips. N-Body's tick loop runs in
JavaScript, one launch per step, because every body needs every other body's
*new* position before the next tick — that is a barrier. These trajectories
never speak to each other, so the loop moves **inside** the kernel: one
launch, one thread, one entire trajectory, a thousand at a time.

## Figures

- **the clock moved inside the kernel — one launch, one thread, one whole trajectory**

## Goal

**Goal:** fill in one explicit-Euler step, so each thread integrates
its own oscillator for 200 steps and returns the final `x`.

## Requirements

- The time loop stays *inside* the kernel — `this.constants.steps` iterations, one thread per trajectory
- Explicit Euler commits to the start of the step: take `a = −x` first
- Advance `x` by the step-start `v · dt`, and `v` by `a · dt`
- Return this thread's final `x`

## Hint 1 — what one step is

Three lines. The acceleration first, because `x` is about to
change under it:

```js
const a = -x;
x = x + v * dt;
v = v + a * dt;
```

## Hint 2 — why that order

Explicit Euler uses *only* step-start values. Writing
`v` first and then moving `x` with the **new**
`v` is a different method — semi-implicit Euler, the one N-Body Gravity
steps with. It is better, and it is task 5's business. Here we want the naive one,
because its badness is the lesson.

## Hint 3 — no arrays inside the loop

`x` and `v` are plain `let` locals. The
arrays are read once, before the loop, to start this thread off — after that the
whole trajectory lives in two registers.

## Same idea elsewhere

One thread per independent problem is how GPUs are pointed at ODEs everywhere:
CUDA and HIP ensemble solvers give each thread one particle's trajectory, WebGPU compute
does the same for particle systems, and the "batched" families in cuBLAS and cuSOLVER
exist because thousands of small independent problems are the shape this hardware likes
best. The kernel is the solver; the launch is the ensemble.

## Starter code

```js
// One thread owns one WHOLE trajectory: the time loop is inside the kernel.
const gpu = new GPU({ mode });
const STEPS = 200;
const DT = 0.025;                 // 200 × 0.025 = t = 5
const T_END = STEPS * DT;

const trajectory = gpu.createKernel(function (startX, startV, dt) {
  let x = startX[this.thread.x];
  let v = startV[this.thread.x];
  for (let s = 0; s < this.constants.steps; s++) {
    // TODO: one explicit-Euler step.
    //   a = -x first, then x moves by v * dt and v changes by a * dt —
    //   both using the values from the START of the step.
  }
  return x;
}, {
  output: [1024],
  constants: { steps: STEPS },
});

const finalX = await trajectory(startX, startV, DT);

console.log('trajectory 0 ended at', finalX[0]);
console.log('the exact answer is  ', startX[0] * Math.cos(T_END) + startV[0] * Math.sin(T_END));
```

---

Interactive version: https://gpu.rocks/learn/ode-integrators-62f4a3ff/1

[Next task](https://gpu.rocks/learn/ode-integrators-62f4a3ff/2.md)
