time for a snapshot

This commit is contained in:
Schluffe
2026-07-11 17:48:28 +02:00
parent 135b7c3042
commit 7262752e16
47 changed files with 1712 additions and 61 deletions
+35
View File
@@ -0,0 +1,35 @@
# Step 03 — Integration (moving over time)
## Concept
"Integration" sounds like calculus, but the version we need is one line. An
object has a **position** and a **velocity**. Each frame we advance the position
by the velocity, scaled by how much time passed:
```
newPosition = position + velocity * delta
```
That's `add(pos, scale(vel, delta))`. This single step is the beating heart of
every game's update loop.
### Why `delta`?
`delta` is the number of **milliseconds since the last frame**. Frames are not
evenly spaced — a busy frame takes longer. If you moved a fixed amount *per
frame* instead of *per millisecond*, your game would run faster on a fast
computer and slower on a slow one.
By storing velocity as **units-per-millisecond** and multiplying by `delta`, the
distance travelled over a given stretch of real time is the same no matter how
the frames are chopped up. That property is called **framerate independence**,
and one of the tests below proves it: moving once with `delta = 16` lands in the
same place as moving sixteen times with `delta = 1`.
## Task
Implement `integrate(pos, vel, delta)` in `motion.ts`.
```sh
bun test workshop/steps/03-integration
```
+5
View File
@@ -0,0 +1,5 @@
// Finished in earlier steps.
export type Vec = { x: number; y: number };
export const vec = (x: number, y: number): Vec => ({ x, y });
export const add = (a: Vec, b: Vec): Vec => ({ x: a.x + b.x, y: a.y + b.y });
export const scale = (a: Vec, s: number): Vec => ({ x: a.x * s, y: a.y * s });
@@ -0,0 +1,27 @@
import { expect, test } from "bun:test";
import { vec } from "./given.ts";
import { integrate } from "./motion.ts";
test("integrate advances position by velocity * delta", () => {
// velocity 0.1 units/ms, 100ms => move 10 units
expect(integrate(vec(0, 0), vec(0.1, 0), 100)).toEqual({ x: 10, y: 0 });
});
test("zero delta does not move", () => {
expect(integrate(vec(5, 5), vec(0.1, 0.1), 0)).toEqual({ x: 5, y: 5 });
});
test("framerate independence: one big step == many small steps", () => {
const start = vec(0, 0);
const vel = vec(0.1, -0.05);
const oneBigStep = integrate(start, vel, 16);
let manySmall = start;
for (let i = 0; i < 16; i++) {
manySmall = integrate(manySmall, vel, 1);
}
expect(manySmall.x).toBeCloseTo(oneBigStep.x);
expect(manySmall.y).toBeCloseTo(oneBigStep.y);
});
+9
View File
@@ -0,0 +1,9 @@
import { add, scale, type Vec } from "./given.ts";
/**
* Advance a position by a velocity over `delta` milliseconds.
* newPosition = position + velocity * delta
*/
export function integrate(pos: Vec, vel: Vec, delta: number): Vec {
return add(pos, scale(vel, delta));
}