# steerkit > Craig Reynolds' steering behaviors for autonomous characters (seek, flee, arrive, pursue, evade, wander, keepAway…) as small, typed, engine-agnostic functions for JavaScript and TypeScript. They work on the project's own `{ x, y, z }` objects, `THREE.Vector3` included, with no dependency and no allocation per frame. Install with the project's package manager: `npm install steerkit` (ESM only, TypeScript types included). ## How to use it - An agent is any object with `position` and `velocity` (`{ x, y, z }`), `maxSpeed` (units per second), `maxForce` (units per second², low turns like a ship, high like a fly) and an optional `mass` (1 by default). A Three.js object can lend its own `mesh.position`: `step` moves it directly. - Every behavior computes a steering force, writes it into the `out` vector passed last, and returns it. Create these vectors once and reuse them every frame; never allocate per frame. - Each frame, per agent: compute the behaviors, combine their forces, then call `step(agent, force, dt)` once. `dt` is in seconds; clamp it (`Math.min(dt, 1 / 20)`) so a frame after the tab was in the background doesn't teleport agents. - Combine with `blend(out, [force, weight], …)` (a weighted sum) or `prioritize(out, agent.maxForce, …forces)` (in order of priority within a budget, so a lesser force can't cancel an urgent one). For hundreds of agents, use their allocation-free forms: `zero(out)` then `add(out, force, weight)` or `addWithin(out, budget, force)`, with one temporary vector reused for every behavior. In such loops, also hoist options objects (`{ slowingDistance: 1.2 }`) out of the loop as constants. - `wander` needs a state per agent, from `createWanderState()`, kept across frames. - 2D: keep `z` at 0 everywhere; pass `plane: "xy"` to `wander` for a 2D canvas, `plane: "xz"` for characters on the ground in 3D. Without `plane`, `wander` roams on a sphere, in all three axes. - Scale `maxSpeed`, `maxForce` and distances (`slowingDistance`, `radius`) to the project's units: the demos use a world about 10 units tall. - steerkit only moves a point. Turning the model to face its velocity is up to the project (in Three.js: `mesh.lookAt(tmp.copy(mesh.position).add(velocity))`). - When `maxSpeed` drops suddenly (the end of a speed boost), pass `{ overspeedDamping: seconds }` to `step` so the extra speed fades out instead of being cut in one frame. ## Example ```ts import { arrive, blend, keepAway, step } from "steerkit"; const vec = (x = 0, y = 0, z = 0) => ({ x, y, z }); // or THREE.Vector3, as is const fairy = { position: vec(), velocity: vec(), maxSpeed: 2, maxForce: 4 }; const [force, goal, away] = [vec(), vec(), vec()]; // reused every frame // Every frame blend(force, [arrive(fairy, target, { slowingDistance: 1.2 }, goal), 1], [keepAway(fairy, camera, { radius: 1 }, away), 2], ); step(fairy, force, dt); ``` ## API ```ts /** * Any `{ x, y, z }` object: plain literals, `THREE.Vector3`, or your engine's * own vectors all fit, by structural typing. For 2D, keep `z` at 0. */ export type Vec3 = { x: number; y: number; z: number }; /** * A steered character, as Reynolds models it: a point mass with a bounded * force and a bounded speed. Your own objects fit as long as they have these * fields. */ export type Agent = { position: Vec3; velocity: Vec3; /** Top speed, in units per second. */ maxSpeed: number; /** * Bound on the steering force, in units per second²: low turns like a * ship, high like a fly. */ maxForce: number; /** A heavier agent responds slower to the same force. Must be > 0; 1 when not given. */ mass?: number; }; /** Something that moves, for the behaviors that predict where it will be. */ export type Mover = { position: Vec3; velocity: Vec3; }; /** A steering force and its weight, for {@link blend}. */ export type Term = readonly [force: Vec3, weight: number]; /** A plane to keep a behavior in, for 2D canvases (`"xy"`) or ground characters (`"xz"`). */ export type Plane = "xy" | "xz" | "yz"; /** Full speed toward `target`. */ export function seek(agent: Agent, target: Vec3, out: Vec3): Vec3; /** Full speed away from `from`. */ export function flee(agent: Agent, from: Vec3, out: Vec3): Vec3; /** * Seek that slows down within `slowingDistance` of `target`, to stop on it. * A `slowingDistance` ≤ 0 means no slowing down: plain seek. */ export function arrive( agent: Agent, target: Vec3, { slowingDistance }: { slowingDistance: number }, out: Vec3, ): Vec3; /** * Flee `from` only within `radius` of it, harder the closer the agent gets: * the force fades to 0 at the edge, so entering the zone doesn't jolt. A * personal space around a point. Zero force on the point itself, where no * direction is better than another. */ export function keepAway( agent: Agent, from: Vec3, { radius }: { radius: number }, out: Vec3, ): Vec3; /** Come to a stop: the force opposing the velocity. */ export function brake(agent: Agent, out: Vec3): Vec3; export type PredictionOptions = { /** Cap on how far ahead to predict, in seconds. Unbounded when not given. */ maxPrediction?: number; }; /** Seek where `quarry` will be, not where it is: an interception. */ export function pursue( agent: Agent, quarry: Mover, { maxPrediction }: PredictionOptions, out: Vec3, ): Vec3; /** Flee where `threat` will be, not where it is: dodging a pursuer. */ export function evade( agent: Agent, threat: Mover, { maxPrediction }: PredictionOptions, out: Vec3, ): Vec3; /** * The memory of {@link wander}, one per agent: where the wander point sits on * its circle, relative to the heading. Treat it as opaque. */ export type WanderState = { /** Unit direction from the circle's center to the wander point. */ offset: Vec3; /** Unit heading at the last call, to carry `offset` along when the agent turns. */ heading: Vec3; /** Uniform in [0, 1), `Math.random` by default. */ random: () => number; }; /** * Creates the state {@link wander} needs between frames. Pass a seeded * `random` for reproducible wandering (tests, recorded demos). */ export function createWanderState( random: () => number = Math.random, ): WanderState; export type WanderOptions = { /** Radius of the circle the wander point moves on: larger turns sharper. */ radius: number; /** How far ahead of the agent that circle sits: larger turns smoother. */ distance: number; /** * How fast the wander point drifts: about `jitter` units over one second. * The drift is a random walk scaled by √dt, so the wandering looks the same * at any framerate. */ jitter: number; /** * Keeps the wandering in a plane: `"xy"` for a 2D canvas, `"xz"` for a * character on the ground. On a sphere, in all three axes, when not given. */ plane?: Plane; }; /** * Reynolds' wander: seek a point on a circle (a sphere in 3D) ahead of the * agent, which drifts a little at random every frame. The heading thus * changes smoothly, in natural curves, instead of trembling. The point is * kept relative to the heading, so a point on the left keeps the agent * turning left until it drifts away. */ export function wander( agent: Agent, state: WanderState, { radius, distance, jitter, plane }: WanderOptions, dt: number, out: Vec3, ): Vec3; /** Sets `out` to the zero vector, to start a sum. */ export function zero(out: Vec3): Vec3; /** Adds `force × weight` to `out`: the allocation-free form of {@link blend}. */ export function add(out: Vec3, force: Vec3, weight = 1): Vec3; /** * Adds `force × weight` to `out`, but only what fits in what is left of * `budget`, a bound on the length of `out` (typically `agent.maxForce`). * Called in order of priority, the first forces take what they need and the * last get the rest, or nothing: the allocation-free form of * {@link prioritize}. */ export function addWithin( out: Vec3, budget: number, force: Vec3, weight = 1, ): Vec3; /** The weighted sum of the forces of `terms`. */ export function blend(out: Vec3, ...terms: Term[]): Vec3; /** * Sums `forces` in order of priority within `budget` (typically * `agent.maxForce`): each one takes what it needs from what is left, the * last one is cut to the remainder, and the next ones get nothing. Unlike * {@link blend}, a lesser force can't cancel a more urgent one (Buckland's * "truncated running sum with prioritization"). */ export function prioritize(out: Vec3, budget: number, ...forces: Vec3[]): Vec3; export type StepOptions = { /** * Softens the speed limit, in seconds. By default the speed is cut to * `maxSpeed` at once, Reynolds' way: when `maxSpeed` drops (the end of a * speed boost), the agent loses all its extra momentum in one frame. With * this time constant, the extra speed fades out instead, by e^(−dt/τ) per * step, so the same at any framerate. The force can still turn the agent * meanwhile, just not speed it up past the fading limit. */ overspeedDamping?: number; }; /** * Moves the agent under `force` for `dt` seconds: the force is bounded by * `maxForce` and divided by the mass, the velocity follows it and is bounded * by `maxSpeed`, and the position follows the velocity. `force` is left * untouched. Clamp `dt` on your side if frames can be long (a tab in the * background). */ export function step( agent: Agent, force: Vec3, dt: number, options?: StepOptions, ): void; ``` ## Demos - [Seek](https://steerkit.thibaut-lefrancois.com/seek/): Interactive demo of seek, Craig Reynolds' steering behavior: full speed toward a target. Forces drawn, live parameters, and the steerkit code. - [Flee](https://steerkit.thibaut-lefrancois.com/flee/): Interactive demo of flee, Craig Reynolds' steering behavior: full speed away from a point. Forces drawn, live parameters, and the steerkit code. - [Arrive](https://steerkit.thibaut-lefrancois.com/arrive/): Interactive demo of arrive, Craig Reynolds' steering behavior: seek that slows down to stop on its target. Forces drawn and the steerkit code. - [Keep away](https://steerkit.thibaut-lefrancois.com/keep-away/): Interactive demo of keepAway in steerkit: flee within a radius, harder the closer, for a personal space around a point. Forces drawn, live code. - [Pursue](https://steerkit.thibaut-lefrancois.com/pursue/): Interactive demo of pursuit, Craig Reynolds' steering behavior: intercept a moving quarry where it will be. Prediction drawn, steerkit code. - [Evade](https://steerkit.thibaut-lefrancois.com/evade/): Interactive demo of evasion, Craig Reynolds' steering behavior: flee where a moving threat will be, dodging out of its way. Live steerkit code. - [Wander](https://steerkit.thibaut-lefrancois.com/wander/): Interactive demo of wander, Craig Reynolds' steering behavior: a natural random walk in smooth curves, in 2D or 3D. Live parameters and code. - [Combining](https://steerkit.thibaut-lefrancois.com/combine/): Combine steering behaviors with steerkit: blend by weight or prioritize within a force budget. Fairies that wander and keep away, live code. - [Speed limit](https://steerkit.thibaut-lefrancois.com/speed-limit/): Soft speed limit in steerkit: when maxSpeed drops after a boost, fade the extra speed out instead of cutting it in one frame. Chart and code. ## Links - [Repository and README](https://github.com/thibautlfr/steerkit) - [npm package](https://www.npmjs.com/package/steerkit) - [Craig Reynolds, Steering Behaviors For Autonomous Characters (GDC 1999)](https://www.red3d.com/cwr/steer/gdc99/)