time for a snapshot
This commit is contained in:
@@ -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
|
||||
```
|
||||
@@ -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);
|
||||
});
|
||||
@@ -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));
|
||||
}
|
||||
Reference in New Issue
Block a user