Interactive simulatorIntermediate

Path Following Simulator: Pure Pursuit and Stanley

Drive a course with pure pursuit or Stanley, with real steering lag. Drag the lookahead and the whole run replots — including what a 1 Hz GPS fix costs you.

Category
Autonomous Robots
Time
20–40 min
Platform
Browser · Arduino · Raspberry Pi
Path Following Simulator: Pure Pursuit and Stanley technical schematicLOOKAHEADGOAL POINTCOMMANDED PATH

01 / Start here

Introduction

A planner hands you a path; nothing in it tells the wheels what to do. This lab is that missing rung. Pure pursuit and Stanley steer the same waypoint course with a first-order steering response and sensing delay, because a model without lag makes the wrong tuning look optimal. Drag a waypoint or the lookahead and the entire run recomputes.

Live lab / Path following

Path following simulator

Pure pursuit and Stanley steering the same course, with the lag a real robot has. Drag the lookahead and the whole run replots — too short oscillates, too long cuts corners, and the best value moves with speed.

Browser native
A top-down path-following lab. A robot follows a course of waypoints using the selected controller, and the route it actually drove is drawn against the route it was asked to drive. Use the controls below to run it and the keyboard hint to move the waypoints.
A plot of how far the robot was from the path, against distance along the path. Corners appear as spikes; a lookahead that is too short appears as jitter everywhere.

The dashed line with dots is the course you asked for; the solid line is where the robot went. The faint line is the whole run at the current settings, computed the moment you change one. Below, cross-track error along the path — dotted verticals are corners. Drag a waypoint to rebuild the course.

  • Commanded path
  • Driven
  • Full run preview
  • GPS fix
Simulation

Ready. The faint line is the whole run at these settings — press Start to drive it.

RMS error
Worst error
Corner cut
Steering effort
Progress
0%
Lap time

Keyboard: focus the course, then Space to run/pause, N to step, R to reset, C for a new course, F for full screen. Press 1–8 to pick a waypoint and the arrow keys to move it.

Controls

Controller switches between pure pursuit and Stanley. Lookahead is pure pursuit’s only knob and is disabled for Stanley, which does not use one — that is itself the point.

Speed and lookahead interact, which is the thing worth playing with: set 1 m/s and find the best lookahead, then set 3 m/s and find it again.

Position from swaps perfect odometry for a 1 Hz fix with metre-scale noise, held between updates. Error is always scored against the truth, so the penalty is real rather than hidden.

Drag any waypoint, or press 18 to select one and move it with the arrow keys. The whole run recomputes as you drag.

Theory

Both controllers answer the same question — given where I am and where the path is, what steering angle? — and answer it completely differently.

Pure pursuit is geometry. Find the point on the path a lookahead distance L ahead, and drive the circular arc through it:

κ = 2y / L_d²          δ = atan(2 b sin α / L_d)

with y the lateral offset of the goal point in the robot’s frame and b the wheelbase. One parameter, and it is a distance rather than a gain — so it transfers between robots.

Stanley is control. Reference the front axle and add two terms:

δ = θ_e + atan(k · e / v)

heading error plus an arctangent of cross-track error. Dividing by speed is what lets one gain work across a speed range. k is a true control gain and does not transfer.

The lab adds what textbook derivations leave out: 0.4 s of total lag, as a 0.25 s first-order steering response plus 0.15 s of sensing and compute delay. Without it, pure pursuit gets monotonically better as the lookahead shrinks and the whole tuning question disappears.

Algorithm

Each step, for both controllers:

  1. Project onto the path, searching a window around the arc length last reached rather than the whole path. A global search matches whichever segment is nearest, so a course that doubles back teleports the robot’s progress to a corner it passed a minute ago.
  2. Resolve a tie at a vertex to the outgoing segment. Past a corner, both adjacent segments project to the same point at the same distance; keeping the incoming one hands the controller a heading error and a lateral offset that are both near zero while it drives straight off the path, silently.
  3. Compute the steering angle from the controller’s own rule, and clamp it to the mechanical limit.
  4. Apply lag, then integrate the bicycle model.
  5. Score against the truth, never the estimate.

Step 2 is not an optimisation. It was the difference between Stanley working and Stanley confidently leaving the course at exactly 78% every single run.

Source code

Pure pursuit, complete, in the form it takes on a real robot:

float lastS = 0;   // arc length last reached — state, and it only moves forward

float pursue(float x, float y, float heading, float speed) {
  // The lookahead is scheduled on speed, with a hard floor: at a standstill a
  // proportional schedule asks for zero, which is a division by zero in disguise.
  float lookahead = constrain(0.7f * speed + 0.4f, 0.4f, 3.0f);

  float s = projectOntoPath(x, y, lastS);      // windowed around lastS
  lastS = max(lastS, s);
  Point goal = pointAtArcLength(lastS + lookahead);

  float dx = goal.x - x, dy = goal.y - y;
  float lateral = -sinf(heading) * dx + cosf(heading) * dy;
  float ld = max(0.3f, hypotf(dx, dy));        // clamp, or curvature blows up

  float curvature = 2.0f * lateral / (ld * ld);
  return curvature;                            // omega = v * curvature
}

The three lines that matter are the schedule floor, the lastS monotonicity, and the ld clamp. Everything else is the formula.

Assumptions and hardware differences

The lab gives the robot a perfect map of the path, a perfectly known wheelbase, no wheel slip, and lag that is a clean first-order model. A real robot has none of those.

Wheel slip makes the position estimate wrong in a way no controller can detect. Gearbox backlash adds dead travel at every steering reversal, which behaves like extra lag and pushes the optimal lookahead up. And a differential drive has no steering angle at all — it has a commanded rotation rate, so ω = v κ replaces the atan and the wheelbase only matters for how tightly it can turn.

What survives all of it is the shape: an interior optimum, moving right with speed, and error at that optimum growing with speed regardless.

Circuit diagram

The controller is the cheap part. What it needs from the rest of the robot is the expensive part.

Position. Quadrature encoders on both wheels and differential-drive odometry integrating them, at the loop rate. Indoors that is enough for a few tens of metres.

Heading. A gyro for the short term, a magnetometer for absolute reference, or both. Heading error is the term both controllers weight most heavily, and it is the one odometry loses first.

An absolute correction, outdoors. A GPS module fused into the estimate rather than followed directly — the difference is 0.09 m of RMS error against 1.25.

For the theory in full, see pure pursuit and cross-track error; to build it, the GPS waypoint rover starts indoors on encoders and goes outside only once the follower works.

Hardware checklist

Components

  • A base whose steering or wheel speeds you can command directly
  • A position estimate — encoders at minimum, fused with GPS outdoors
  • A heading reference that does not drift over a mission
  • A loop fast enough that its own lag is smaller than your lookahead

Explore the graph

Where this simulator is used

The projects, learning paths, and tutorials that build on this lab.

Continue building

Download resources

Use these on-page references while working through the project. Downloadable project bundles will be added only after their source and version are published.

Common questions

Frequently asked questions

Why does a shorter lookahead not always track better?

In a model with no lag it does, which is exactly why so many textbook derivations are misleading. Real robots take time to reach a commanded steering angle and act on a pose that is already tens of milliseconds old. This lab models a 0.25 second steering time constant and 0.15 seconds of sensing delay. With that lag present the error curve becomes a U: a short lookahead commands a correction that arrives after the robot has already moved past the error, so the next command over-corrects. A controller faster than its own actuator oscillates.

How do I choose the lookahead for my robot?

Measure your total control lag, multiply by your speed, and start between one and two times that distance. In this lab the lag is 0.4 seconds, and the measured optima are 0.9 metres at 1 m/s rising to 1.4 metres at 3 m/s — between 1.2 and 2.3 times the distance covered during the lag. Notice that the optimum grows with speed but less than proportionally, so a fixed number of seconds of travel is close but not right. Ship it as a small linear schedule on speed with a hard minimum, because at a standstill the schedule wants zero.

Which is better, pure pursuit or Stanley?

They fail differently, which is the useful answer. Over this course at 2 m/s pure pursuit cuts corners and finishes 0.8 percent short of the commanded path length; Stanley refuses to leave the line, swings wide, and travels 2.2 percent long. Stanley holds a tighter line where it matters but spends 18.5 radians of steering against pure pursuit's 4.3 — four times the actuator movement, current and tyre scrub. Use pure pursuit when the path is a suggestion and Stanley when it is a constraint.

Why is the error measured against the true position rather than the estimate?

Because a robot that thinks it is on the path reports zero error by construction. Switch the position source to GPS and the controller starts steering on a noisy, one-second-old fix while the lab keeps scoring against the pose the model actually integrated. That is the only reason the GPS penalty shows up as a number: 0.09 metres of RMS error becomes 1.25, and steering effort more than quadruples.

What does the faint line show?

The complete run at the current settings, computed the moment you change one. Every slider move re-runs the whole course headless in about twenty milliseconds, so dragging the lookahead shows you the trajectory it produces rather than a promise about it. The bold line is the robot animating over that same run when you press Start.

Why does the robot never stop turning at the very sharp corner?

Both controllers have a minimum turning radius set by the wheelbase and maximum steering angle, and a corner sharper than that cannot be followed however you tune. The honest fixes are upstream: soften the path with a fillet or an arc, or slow down for the corner, which is what a motion profile is for. No steering controller can drive a geometry the vehicle does not have.

Further reading

References

Authoritative sources for going deeper than this simulator's bounded educational model.