Shiffman, Chapter 6

Autonomous Agents

Forces from within — vehicles that seek, flee, arrive, wander, and flock. Craig Reynolds's steering behaviors bring our shapes to life.

Prerequisites: Chapters 1-5. You need vectors, forces, and particle systems.
10
Chapters
2
Simulations
10
Quizzes

Chapter 0: Forces from Within

In Chapters 1-5, we built objects that are pushed around by external forces — gravity, wind, springs. They're inanimate. They sit there and wait for something to happen to them.

What if a shape could want something? What if it could perceive its environment and decide to move on its own? This is the leap from physics to artificial life. We're going to design autonomous agents.

What is an autonomous agent? An entity that makes its own choices about how to act in its environment without a leader or global plan. For us, "acting" means moving. The desires and actions are simply forces — but forces that come from inside the object, not from the world around it.

Three key properties of autonomous agents:

PropertyWhat it means
Limited perceptionThe agent can sense its environment, but only partially. An insect knows its immediate surroundings, not the entire world.
Processes and actsThe agent takes in information and calculates an action — a force. "There's a shark coming; I'll flee."
No leaderNo centralized command. Complex group behavior emerges from local interactions.
What makes an autonomous agent different from a particle pushed by external forces?

Chapter 1: Vehicles & Steering

In the late 1980s, computer scientist Craig Reynolds developed algorithmic steering behaviors for animated characters: seek, flee, wander, arrive, pursue, evade, flock. These behaviors let individual shapes navigate their environment in a lifelike manner.

Reynolds was inspired by Valentino Braitenberg's 1986 book Vehicles: Experiments in Synthetic Psychology, which argued that extraordinarily simple mechanical vehicles can manifest behaviors like fear, aggression, and love.

Reynolds describes vehicle motion in three layers:

LayerWhat it doesOur focus
Action SelectionChoose a goal: "I see a zombie, I want to flee."We design these goals.
SteeringCalculate a force to achieve the goal.Reynolds's formula: steering = desired - velocity
LocomotionThe physical animation of movement.Mostly ignored — our shapes just glide.
Why "Vehicle"? Reynolds uses the term "vehicle" for his autonomous agents. We rename our Mover/Particle class one more time. A Vehicle has location, velocity, acceleration — plus maxspeed and maxforce to control its steering ability.
What is Reynolds's steering force formula?

Chapter 2: The Steering Force

A vehicle is moving with some velocity and wants to reach a target. We could use gravitational attraction (Chapter 2), but that ignores the vehicle's current state. Steering is smarter: the vehicle knows where it wants to go and how it's currently moving.

The desired velocity is a vector pointing from the vehicle's location to the target, scaled to maximum speed:

pseudocode
desired = target - location
desired.normalize()
desired.mult(maxspeed)

# Reynolds's steering formula
steer = desired - velocity
steer.limit(maxforce)
applyForce(steer)
Why does this work so well? The steering force isn't just "go toward the target." It's "here's the error between where I want to go and where I'm currently going." If the vehicle is already heading the right way, the steering force is small. If it's heading the wrong way, the force is large. This self-awareness is what makes it feel alive.

Two variables control the feel: maxspeed determines how fast the vehicle can travel. maxforce limits its steering ability. A high maxforce means tight, race-car turns. A low maxforce means slow, lumbering adjustments.

Why does limiting the steering force with maxforce matter?

Chapter 3: Arriving

Seeking works great, but the vehicle flies past the target and has to double back. Why? Because it always wants to go at maximum speed, even when it's inches away.

The arrive behavior adds a slowdown zone. Imagine a circle around the target. Outside the circle: full speed. Inside the circle: speed is proportional to distance.

pseudocode
desired = target - location
d = desired.mag()
desired.normalize()

if d < slowRadius:
  speed = map(d, 0, slowRadius, 0, maxspeed)
else:
  speed = maxspeed

desired.mult(speed)
steer = desired - velocity
steer.limit(maxforce)
The magic of "desired minus velocity": With gravitational attraction, the force always points directly toward the target. But with steering, if you're moving too fast toward the target, the error tells you to slow down. Only living things know their own velocity. A box falling off a table doesn't know it's falling. A cheetah chasing prey does.
How does the "arrive" behavior differ from "seek"?

Chapter 4: Flow Fields

A flow field is a grid where each cell contains a vector. As a vehicle moves, it asks: "What vector is beneath me? That's my desired velocity!"

The flow field is stored as a 2D array of vectors. The vehicle looks up its cell based on its position, then uses that vector as the desired direction.

pseudocode
# Flow field: 2D array of vectors
field[cols][rows]

# Fill with Perlin noise angles
for i in cols:
  for j in rows:
    angle = map(noise(i*0.1, j*0.1), 0, 1, 0, TWO_PI)
    field[i][j] = vectorFromAngle(angle)

# Vehicle lookup
col = vehicle.x / resolution
row = vehicle.y / resolution
desired = field[col][row]
Perlin noise makes it natural. Random vectors produce jittery movement. Perlin noise produces smooth, organic flow — like a gentle river current or a gust of wind winding through trees. The resolution of the grid controls granularity: fewer cells means broader patterns, more cells means finer detail.
In a flow field, how does a vehicle determine its desired velocity?

Chapter 5: Flocking Rules

In 1986, Craig Reynolds created a simulation of flocking behavior with three simple rules. Recreating this brings together everything in this chapter: steering forces, group behaviors, and emergent complexity.

RuleDescriptionDesired velocity
SeparationSteer to avoid crowding neighborsAverage of vectors pointing away from nearby boids
AlignmentSteer in the same direction as neighborsAverage velocity of nearby boids
CohesionSteer toward the center of nearby boidsAverage location of nearby boids (then seek it)
Emergence in action: No single boid knows what the flock is doing. Each one only looks at its immediate neighbors. Yet the result is structured, lifelike group behavior — swirling, splitting, reforming. This is the hallmark of a complex system: intelligence from simple local rules.

Each rule produces a steering force. The forces are weighted and combined:

pseudocode
sep = separate(boids)
ali = align(boids)
coh = cohesion(boids)

sep.mult(1.5)   # Separation matters most
ali.mult(1.0)
coh.mult(1.0)

applyForce(sep)
applyForce(ali)
applyForce(coh)
What are the three rules of flocking?

Chapter 6: Separation

Separation is "flee from anyone who's too close." For each boid, we look at every other boid. If one is within a desired separation distance, we compute a vector pointing away from it. We average all those vectors and use the result as our desired velocity.

pseudocode
function separate(boids):
  sum = (0, 0)
  count = 0
  for other in boids:
    d = dist(me, other)
    if d > 0 and d < desiredSeparation:
      diff = me.location - other.location
      diff.normalize()
      diff /= d          # Weight by distance: closer = stronger
      sum += diff
      count += 1
  if count > 0:
    sum /= count
    sum.setMag(maxspeed)   # Desired velocity
    steer = sum - velocity  # Reynolds's formula
    steer.limit(maxforce)
    return steer
Dividing by distance is a subtle but important detail. A boid that is very close should produce a strong repulsion. A boid that is barely within range should produce a weak one. Dividing the direction vector by the distance achieves this inverse weighting.
In the separation algorithm, why do we divide the difference vector by the distance?

Chapter 7: Alignment & Cohesion

Alignment: A boid's desired velocity is the average velocity of its nearby neighbors. "Everyone around me is heading northeast, so I want to head northeast too."

Cohesion: A boid's desired velocity points toward the average location of its nearby neighbors. "Everyone is over there — I should join them." This is essentially a seek toward the center of mass.

Short-range relationships are key. Each boid only considers neighbors within a certain radius (say 50 pixels). This is what makes the system a complex system. If every boid knew about every other boid globally, the behavior would be flat and uniform. Local awareness creates the organic, swirling patterns.

A crucial implementation detail: both alignment and cohesion must skip the boid itself (distance > 0) and only count neighbors within a threshold distance.

pseudocode
function align(boids):
  sum = (0, 0)
  count = 0
  for other in boids:
    d = dist(me, other)
    if d > 0 and d < neighborDist:
      sum += other.velocity
      count += 1
  if count > 0:
    sum /= count
    sum.setMag(maxspeed)
    steer = sum - velocity
    steer.limit(maxforce)
    return steer
What is the key difference between alignment and cohesion?

Chapter 8: Steering Simulation

Below is a flocking simulation. Triangular boids follow the three rules — separation, alignment, and cohesion — and wrap around the edges. Click or tap to add more boids.

Flocking Boids

Each boid steers based only on its nearby neighbors. Click/tap to add boids. Use the sliders to adjust behavior weights.

Sep: Ali: Coh:
Try the sliders! Turn separation to zero and watch the boids pile on top of each other. Turn cohesion to zero and they scatter. Turn alignment to zero and they lose their coordinated flow. The magic is in the balance of all three.
What happens to the flock if you remove the separation force entirely?

Chapter 9: Summary

TopicKey Takeaway
Autonomous agentsObjects with internal forces, limited perception, no leader
Steering formulasteering = desired velocity - current velocity
Seek & ArriveSeek always goes at max speed; Arrive slows down near the target
Flow fieldsA grid of vectors; the vehicle follows the vector at its location
SeparationAverage of vectors pointing away from close neighbors
AlignmentAverage velocity of nearby neighbors
CohesionSeek the average position of nearby neighbors
FlockingAll three combined → emergent, lifelike group behavior
The lesson: Simple local rules produce sophisticated global behavior. Steering behaviors are building blocks. The real creativity comes from mixing, matching, and weighting multiple behaviors in a single vehicle. These are not formulas to copy — they're a foundation for invention.

What we covered:

• Craig Reynolds's steering behaviors
• Seek, arrive, flow fields
• Separation, alignment, cohesion
• Flocking as emergent complexity
• Weighting and combining forces

What comes next:

Chapter 7: Cellular Automata. We leave motion behind and explore systems of simple cells on a grid. Wolfram's elementary CA and Conway's Game of Life show how complexity emerges from the simplest possible rules.

Which property of complex systems is demonstrated by flocking?