Interactive study / 01 Simulation running

Emergent motion, explained from the inside

BoidsNo leader. No map. Together? A flock.

Each agent can see only a few neighbors. Three tiny steering decisions turn that limited view into motion that feels coordinated, alive, and larger than any one boid.

  1. 01SeparationMake room
  2. 02AlignmentMatch motion
  3. 03CohesionStay near
01The premiseLocal input → global pattern

A flock is not commanded.
It is negotiated.

In 1986, Craig Reynolds showed that convincing group motion does not require choreography. Give every bird-like agent the same small loop and let the pattern emerge.

A boid has no map of the flock, no destination shared with everyone, and no idea what the overall shape should be. It only reacts to what is nearby, one frame at a time.

  1. 01

    Notice

    Find the neighbors inside a limited radius and field of view.

  2. 02

    Propose

    Turn crowding, headings, and positions into three steering vectors.

  3. 03

    Move

    Add the vectors, limit the turn, move forward, and repeat.

02 / Rule 01Keep space

Separation

Too close? Turn away.

When another boid enters the red safety radius, the tracked boid proposes a turn in the opposite direction. Several nearby boids become one combined push, so the response is strongest where the crowd is densest.

If distance < minimumadd an away vector
Live local viewTracked boid / orange
Safety radiusCombined pushBoid + heading
Functionseparate(boid, neighbors)Code
function separate(boid, neighbors) {
  return neighbors
    .filter((other) => distance(boid, other) < MIN_DISTANCE)
    .reduce((steer, other) => ({
      x: steer.x + boid.x - other.x,
      y: steer.y + boid.y - other.y,
    }), { x: 0, y: 0 });
}
03 / Rule 02Share direction

Alignment

Where are the neighbors heading?

The small green arrows are nearby headings. Their average becomes the larger green proposal on the tracked boid. It does not copy any single neighbor; it continuously compromises with the group it can currently see.

Mean of visible velocitiessteer toward that heading
Live local viewHeadings update every frame
Neighbor headingsAverage headingVisible radius
Functionalign(boid, neighbors)Code
function align(boid, neighbors) {
  if (neighbors.length === 0) return { x: 0, y: 0 };

  const average = mean(neighbors.map((other) => other.velocity));
  return {
    x: average.x - boid.velocity.x,
    y: average.y - boid.velocity.y,
  };
}
04 / Rule 03Stay connected

Cohesion

Where is the local middle?

Average the positions of visible neighbors and a temporary center appears. The blue cross marks that point; the dashed arrow is the boid's proposal to move toward it. As neighbors change, the center moves too.

Mean of visible positionssteer toward that center
Live local viewCenter is local, not global
Local centerCohesion pullVisible radius
Functioncohere(boid, neighbors)Code
function cohere(boid, neighbors) {
  if (neighbors.length === 0) return { x: 0, y: 0 };

  const center = mean(neighbors.map((other) => other.position));
  return {
    x: center.x - boid.position.x,
    y: center.y - boid.position.y,
  };
}
05 / A boid's-eye viewFlockScroll to inspect ↓
01 / 04

The global pattern has a local cause.

From a distance, the flock reads as one organism. Move closer and that unity dissolves into agents with incomplete information.

  1. 01Flock
  2. 02Field
  3. 03Signals
  4. 04Steer
06SynthesisLive rule mixer

Tune your own flock.

There are lots of ways to tune a flock. Change the weight of each proposal step and the same simple agents become a loose cloud, a directional school, or a nervous cluster.

Move through the field to disturb it. The flock will follow you.

velocitynext=velocity+s · separate+a · align+c · cohere
Rule weights / 0–100Drag to tune
Presets
Live synthesisPointer influence / on