From a remote-controlled toy to a self-driving car: the four-module autonomy stack and the loop that ties it together. This is the map for the entire series.
A factory welding arm is a marvel. It repeats the same weld, to the same millimeter, ten thousand times a shift, for years. But move the car body two centimeters to the left and the arm welds the air. It has no idea anything changed. It is running a fixed script — a sequence of joint angles recorded once and replayed forever.
Now picture a Waymo at a four-way stop. A cyclist edges into the intersection. A pedestrian hesitates on the curb. A delivery van double-parks ahead. No two seconds of that scene have ever happened before, and no script could enumerate them. Yet the car decides — yields, creeps, reroutes — and keeps deciding, ten times a second, forever.
That is the gap this whole course is about. The welding arm is automated: it executes a pre-planned motion open-loop. The Waymo is autonomous: it senses a world it didn't script, builds an understanding of it, decides what to do, and acts — then senses again to see what its action did. That last clause is the whole secret. Autonomy is a closed loop.
Stanford's AA274A calls this loop the see-think-act cycle, and it organizes the entire field into four jobs a robot must do over and over: perceive the world, figure out where it is, plan and control motion, and coordinate all of that into coherent behavior. Those four jobs are the autonomy stack, and the 15 lessons in this series are a guided tour of each layer.
Before we name the parts, let's watch the loop itself turn — because once you see it, you can never un-see it inside any robot.
A robot must reach the green goal. The world drifts (wind, slippage) every step. Toggle the loop and press Run. Open-loop replays a fixed plan and blindly drifts off; closed-loop re-senses each step and corrects.
Open-loop, the robot commits to a plan and executes it with its eyes shut; the smallest disturbance compounds, step after step, and it arrives nowhere near the goal. Closed-loop, it looks at where it actually ended up after every step and recomputes — disturbances get corrected before they accumulate. Same actuators, same goal. The difference is the loop.
Every autonomous robot runs the same loop, forever, dozens of times a second. It is worth burning into memory because every algorithm you will ever meet in robotics lives at one specific point on this loop.
The loop has three beats — See, Think, Act — wrapped around the real world:
Stanford draws this exactly — the "see-think-act" cycle from Lecture 1. Let's make it turn, and watch the data morph as a packet rides around the ring.
Press Run to let a data packet ride the loop. Watch its label change at every edge — that label is the kind of data being passed. The robot in the center nudges forward each full cycle, because acting changed the world.
Notice that nothing waits for anything. The loop never "finishes." There is no end state where the robot is done thinking and starts acting — it is always doing all three, with fresh data, because the world never stops changing. A robot that pauses to think while moving at 30 mph is a robot that has already crashed.
Here is the payoff of memorizing the loop: it is a coat-rack for the entire field.
| Loop beat | Data in → out | Lessons in this series |
|---|---|---|
| See / extract | pixels, LiDAR → features, detections | 7–10 (sensors, cameras, SfM, learned perception) |
| Think / localize | features + motion → pose, map | 11–14 (SLAM, Kalman/EKF, particle filters) |
| Think / plan | map + goal → trajectory | 3–5 (A*, RRT, trajectory optimization) |
| Act / control | trajectory → actuator commands | 2, 6 (kinematics, PID/LQR control) |
| Coordinate | everything → coherent behavior | 1, 15 (this lesson; advanced behavior) |
"See-think-act" is the rhythm. The autonomy stack is the machinery that produces it. Stanford splits the work into four modules of roughly equal weight — and this series devotes its lessons to each in turn.
1. Perception. Turns raw sensor streams into meaning. In: camera images (a tensor of shape height × width × 3), LiDAR point clouds (sets of 3D points), depth maps. Out: features, detections ("stop sign at pixel 412,88"), segmented regions, extracted lines and corners.
2. Localization & SLAM. Answers "where am I, and what does the world look like?" In: the perception module's features plus the robot's own motion estimate. Out: the robot's pose (position + orientation) and a map.
3. Motion Planning & Control. Decides how to move and makes it happen. Planning: map + goal → a collision-free trajectory. Control: trajectory + current pose → actuator commands that track it despite disturbances.
4. Executive & System Architecture. The conductor. It decides which behavior is active right now (explore? approach? stop? recover?), sequences the other three modules, and handles failures gracefully. Often built as a finite state machine — the subject of the back half of this lesson.
The interfaces — the type of data on each arrow — are the contract that makes a robot buildable by a team. Click each module below to see exactly what it consumes and produces, and which lessons cover it.
Tap any band to expand its input/output contract and the lessons that build it. This same four-band diagram reappears at the top of every lesson in the series, with the relevant band lit up — it's your "you are here."
That outer teal frame is the Executive. Notice it touches everything — it doesn't sit in the data flow, it sits around it, deciding which mode each module runs in. We'll formalize the Executive as a finite state machine in Chapter 5. First, two prerequisites every layer assumes: what "state" means (Chapter 3), and how a robot represents the world as a map (Chapter 4).
Every module on the stack passes around one quantity above all others: the robot's state. Before we can plan, localize, or control, we have to agree on what numbers describe "where the robot is and how it's oriented." Those numbers are the robot's configuration.
Stanford's Lecture 1 defines it precisely. The generalized coordinates of a robot are the smallest set of numbers that completely specify its configuration. We collect them in a vector written ξ (the Greek letter xi):
Here n is the number of generalized coordinates, ℝn means "an ordered list of n real numbers," and the small ⊤ (transpose) just means we treat it as a column. For a wheeled robot rolling on a flat floor, three numbers are enough:
where (x, y) is the position of the robot on the floor and θ (theta) is its heading — the direction it faces, measured as an angle. The pair (position + orientation) has a special name: the robot's pose. A robot that knows its pose knows everything it needs to know about where it is.
Let's make the pose vector concrete. Drag the robot to set (x, y); rotate it to set θ. The readout shows the live configuration vector and the robot's heading vector — the unit vector pointing where it faces:
Drag the robot to move it; use the slider to rotate. Toggle the no-slip constraint to see the direction a wheeled robot cannot move (sideways) — a one-line preview of Lesson 2's kinematics.
With the constraint on, try to drag the robot sideways to its heading — it resists, sliding only along the barred line's perpendicular. That is the no-slip constraint: a rolling wheel cannot translate along its axle. Written as an equation, the sideways velocity must be zero:
Here ξ̇ (xi-dot) means "the rate of change of ξ" — the velocity. Don't worry about deriving this; Lesson 2 builds it from scratch and shows how it produces the famous unicycle and differential-drive models. The point for now: state is three numbers, and the robot's geometry restricts how those numbers can change. That restriction is what makes robot motion planning interesting rather than trivial.
A robot that knows its pose still needs to know what's around it: where the walls are, which rooms connect, where the kitchen is. That knowledge is a map. But "map" is not one thing — the same physical environment can be stored four very different ways, each good at a different job. Choosing the wrong representation makes a problem hard that the right one makes trivial.
Here is one apartment, rendered four ways. Flip between them and notice they describe the same world through different lenses:
Switch representations. Press plan path to ask each map for a route to the goal — watch which maps can answer and which can't. For the occupancy grid, the resolution slider trades memory for precision.
Occupancy grid (metric). Chop the world into a grid of cells; each cell stores the probability it is occupied — free, occupied, or unknown. Great for precise collision-checking and path planning. Costly in memory (cells grow with area / resolution²). This is the workhorse — Lesson 12 builds it in full.
Topological (graph). Forget metric detail; store places as nodes and connections as edges ("kitchen connects to hallway connects to bedroom"). Tiny memory, instant high-level routing ("3 rooms away"), but no notion of exact distance or shape.
Feature / landmark. Store only a sparse set of distinctive points — corners, blobs — each as a coordinate plus a descriptor that lets you recognize it again. This is what visual SLAM (Lesson 11) and structure-from-motion (Lesson 9) actually keep. Compact, perfect for re-localizing, useless for "is this exact spot free?"
Semantic. Label regions by meaning: "wall," "door," "kitchen," "person." This is what learned perception (Lesson 10) produces. Lets a robot reason about what things are ("don't drive through the person"), not just where mass is.
How does a single grid cell decide it's occupied? Not all at once — it accumulates evidence across measurements. The trick (full story in Lesson 12) is to track belief in log-odds ℓ, because then each new measurement just adds a number instead of multiplying probabilities:
Start at total ignorance: P = 0.5, so ℓ0 = log(0.5/0.5) = 0. A LiDAR "hit" on this cell contributes ℓhit = +0.85; a "miss" (beam passed through) contributes ℓmiss = −0.4. Watch evidence pile up:
| Step | Measurement | ℓ (running sum) | P(occupied) = 1/(1+e−ℓ) |
|---|---|---|---|
| 0 | prior (no data) | 0.00 | 0.50 |
| 1 | hit (+0.85) | 0.85 | 0.70 |
| 2 | hit (+0.85) | 1.70 | 0.85 |
| 3 | miss (−0.40) | 1.30 | 0.79 |
Two hits push the cell to 85% sure it's occupied; one stray miss nudges it back to 79%. The belief is robust to single bad readings because evidence accumulates. That is why we store probabilities, not booleans — sensors are noisy, and a probability lets a cell remember how sure it is. (We add in log-odds because logarithms turn Bayes' multiplication into addition — the same trick reappears in every filter in Lessons 13–14.)
We have Perception, Localization, and Planning–Control. What decides which of these the robot should be doing right now? When a TurtleBot bumps a wall, who says "stop driving forward, start backing up"? That is the job of the Executive, and the oldest, clearest tool for the job is the Finite State Machine (FSM).
An FSM is defined by a small, fixed set of pieces. Every symbol here matters, so we define each before using it:
That's the whole model: (S, I, O, n, o, s0). Each tick the executive reads an input, applies n and o, emits a command, and updates its state. Written as a loop, it's almost embarrassingly simple:
We draw FSMs as graphs: a circle for each state, an arrow for each transition. We label each arrow with the input that triggers it and the output it produces, written input / output. The current state glows; when an input fires a transition, that arrow flashes and the glow jumps. That picture is the next-state function n and the output function o, drawn.
This is exactly how your TurtleBot's navigator.py works, and how production drone autopilots like PX4 are structured. In ROS, the SMACH library builds these for you, with visualization. But you don't need a library — a class with a state variable and an if/elif block per state is a complete FSM. We'll build one by hand next chapter.
Stanford's canonical FSM example is a parking-gate arm — small enough to trace by hand, real enough to matter. Let's specify it completely, then trace a car through it, then run it live, then build it in code.
The problem. A gate arm guards a parking lot. It can be in four states. A sensor reports whether a car is waiting; another reports whether a car just passed; position sensors report whether the arm is fully up or fully down. The arm can raise, lower, or hold (nop). Desired behavior: when a car arrives, raise the arm; keep it up until the car drives through; then lower it.
States S: down, raising, up, lowering.
Inputs I: car waiting, no car, gate at top, not at top, car passed, gate at bottom.
Outputs O: raise, lower, hold.
Initial state s0: down.
This table is the next-state function n and output function o. Each row is one tick: given the state and input, here is the commanded output and the next state.
| Tick | State st | Input it | Output o(i,s) | Next state n(i,s) |
|---|---|---|---|---|
| 0 | down | no car | hold | down |
| 1 | down | car waiting | raise | raising |
| 2 | raising | not at top | raise | raising |
| 3 | raising | gate at top | hold | up |
| 4 | up | car not passed | hold | up |
| 5 | up | car passed | lower | lowering |
| 6 | lowering | not at bottom | lower | lowering |
| 7 | lowering | gate at bottom | hold | down |
Trace it in words. We start down, holding (tick 0). A car arrives — tick 1 fires n(car waiting, down) = raising and commands raise. The arm rises (ticks 2–3) until the top sensor trips, landing in up. It waits, holding, until the car passes — tick 5 fires n(car passed, up) = lowering and commands lower. The arm descends (6–7) and returns to down. One clean cycle. Notice that raise and lower each fire exactly once.
Now press the sensor buttons yourself and watch the arm move and the graph light up. Try the fault inject toggle — a stuck sensor — to see the FSM hang in a state, which motivates the robustness ideas in Chapter 7.
Left: the physical gate. Right: the FSM graph (current state glows). Fire sensor inputs, or press Auto-run to play a full car-through cycle. The trace log records every (state, input → next, output).
The FSM is a class with a state variable and one if/elif block per state — the textbook OOP pattern. Verbose first, with every branch matching a table row:
python class ParkingGateFSM: def __init__(self): self.state = 'down' # s0: the initial state def step(self, car, gate): # one tick: read inputs (car, gate sensors) -> (output, next state) out = 'hold' # default output: do nothing if self.state == 'down': if car == 'waiting': out, self.state = 'raise', 'raising' elif self.state == 'raising': out = 'raise' if gate == 'top': out, self.state = 'hold', 'up' elif self.state == 'up': if car == 'passed': out, self.state = 'lower', 'lowering' elif self.state == 'lowering': out = 'lower' if gate == 'bottom': out, self.state = 'hold', 'down' return out
The compact, table-driven form scales better — the transitions live in a dictionary, so adding a state is data, not code:
python # (state, input) -> (output, next_state); missing key = hold + stay TABLE = { ('down', 'waiting'): ('raise', 'raising'), ('raising', 'top'): ('hold', 'up'), ('up', 'passed'): ('lower', 'lowering'), ('lowering','bottom'): ('hold', 'down'), } def step(state, sym): out, nxt = TABLE.get((state, sym), ('hold', state)) return out, nxt
In the Code Lab below, you'll implement the step method and run a scripted car through the gate, animating the arm as the FSM commands it — and your trace must match the table above. This is the executive layer, in 15 lines.
up and the sensor reports car not passed. What output and next state?The parking gate had four states. A real self-driving stack has hundreds — and adding one state can spawn an exponential number of transitions. Stanford warns that designing these "extremely complex state machines... is still one of the most time-consuming, difficult tasks faced by companies." Three tools keep FSMs tractable.
Two states are equivalent if (a) they produce the same output, and (b) for every input they transition to the same-or-equivalent states. Equivalent states can be merged with no change in behavior. There's a polynomial-time algorithm: put all states in one set, partition by output behavior, then repeatedly split subsets whose members disagree on where they transition, until nothing splits further. A classic sequence-detector for "010 or 110" collapses from 7 states to 4 this way — same behavior, fewer boxes.
Even when states aren't equivalent, closely related ones can be grouped into a super-state, with generalized transitions between super-states. Your TurtleBot's "navigate" super-state might contain follow-path, avoid-obstacle, and recover sub-states; from the outside, the rest of the system just sees "navigating." This is how ROS's SMACH library lets you nest machines. Hierarchy turns an unreadable flat graph of 200 states into a handful of nested machines of a dozen each.
Build big machines by wiring small ones together. Three standard patterns:
In SMACH these are smach.Sequence (cascade), smach.Concurrence (parallel), and smach.Iterator (feedback). The takeaway: complex behavior is composed from simple, individually-verifiable machines — exactly like functions in code.
Time to put every piece together. Below is a TurtleBot in a gridworld it has never seen. It must reach the goal flag. It does not know where the obstacles are — it discovers them by sensing, builds an occupancy grid, plans a path, drives along it, and an FSM executive coordinates the whole thing. Every tick is one full see-think-act cycle, and the panel on the right shows what each module of the stack is doing right now.
Press Run. The robot explores, hits unknown obstacles, replans, and reaches the goal. Click a cell to drop a new obstacle mid-run and force a live DETECT → REPLAN. Wall off the goal completely and the executive enters BLOCKED — graceful failure, not a crash.
Watch the discovered grid fill in as the robot moves: grey is unknown, dark is sensed-free, red is sensed-occupied. The dotted line is the current plan; when the robot senses a new obstacle on its path, the Planning card flips to REPLAN, a fresh search floods the grid, and a new path appears. The robot never sees the whole map — it acts on its belief, updates the belief by sensing, and re-plans. That is autonomy. That is the loop from Chapter 1, fully realized.
Now break it on purpose: click cells to build a wall between the robot and the goal. The next REPLAN finds no path, the executive transitions to BLOCKED, and the robot stops cleanly instead of driving into the wall. Handling failure gracefully — "how do I get my TurtleBot to back off after a collision?" — is exactly what the executive layer is for.
You now hold the skeleton the whole course hangs on. Here is where each of the next 14 lessons sits on the autonomy stack — your roadmap.
| Module | Lessons | What you'll build |
|---|---|---|
| Control & kinematics | 2, 6 | SE(2)/SE(3) transforms, unicycle/diff-drive models, PID & LQR controllers |
| Planning | 3, 4, 5 | A* on a C-space grid, RRT/RRT*, trajectory optimization via differential flatness |
| Perception | 7, 8, 9, 10 | LiDAR/IMU/ICP, pinhole cameras & calibration, SfM & RANSAC, learned/semantic perception |
| Localization & SLAM | 11, 12, 13, 14 | factor-graph SLAM, occupancy mapping & exploration, Kalman/EKF/UKF, particle filters & MCL |
| Executive & frontier | 1, 15 | this lesson; then imitation learning, VLAs, 3D Gaussian Splatting, world models |
These existing lessons go deeper on topics we touched: