Fifteen seconds alone
Autonomous is the opening of a match: the robot runs on its own, with no driver input at all. Everything it does was decided before the match started, by you.
That sounds like ordinary programming until you notice what's missing. There is no human watching the robot drift left and nudging it back. If your robot believes it is somewhere it isn't, it will act on that belief confidently, at full speed, for the whole fifteen seconds. Autonomous doesn't fail gently.
The upside is that it's the most valuable code you write. Every team's driver is good; not every team's robot scores reliably before the driver takes over.
Notice that the robot is doing more than driving. It drives and lifts and shoots, ideally at the same time. A routine that does one thing at a time will run out of clock long before it runs out of tasks, which is why Part 08 spends its energy on doing things during a movement rather than between movements.
The most common rookie autonomous is "drive forward for 2 seconds." It works on a carpet at the shop and does something different on a competition field, because 2 seconds of motor output is not a distance — it depends on battery voltage, carpet, weight, and whether the wheels slipped. Part 02 is about the difference between commanding time and commanding motion.
A path is not a trajectory
This is the one idea to take away. Everything Choreo does, and everything that goes wrong, makes sense once this distinction is solid.
A path is a shape — the curve the robot's centre traces across the field. It answers "where?" and nothing else.
A trajectory is a path plus a schedule. For every instant in time it says where the robot should be, how fast it should be going, and which way it should be facing. It answers "where, and when, and how fast?"
The difference matters because a robot can't be commanded to be somewhere. It can only be commanded to spin its motors. A trajectory is what converts a drawing into motor commands: at t = 1.4 s you should be at this point moving at this velocity, so send these speeds. A path alone gives you nothing to send.
Look at where the robot slows down. Nobody wrote those numbers; they fall out of the geometry. A tight corner cannot be taken quickly, because taking a corner requires sideways force, and the only thing pushing your robot sideways is friction between four wheels and the carpet. Exceed it and you slide.
A path is a drawing. A trajectory is a plan. Choreo's job is to turn the first into the second, in the least time your robot is physically capable of.
Where the robot thinks it is
A trajectory says where the robot should be at each instant. To act on that, the robot needs a belief about where it actually is. That belief is always a little wrong, and knowing how it goes wrong explains most autonomous failures.
Odometry: counting your own footsteps
Odometry estimates position by adding up wheel rotations and gyro readings. Every loop, the robot asks "how far did each wheel turn, and which way am I facing?", works out how far it moved, and adds that to its running total.
It's fast, it never drops out, and it needs no external hardware. It also has one unavoidable property: errors accumulate and never heal. A wheel that slips reports distance that never happened, and that phantom distance stays in the total forever. Nothing later corrects it, because nothing else ever measures the truth.
This is why vision matters for autonomous specifically. A camera that recognises fixed landmarks gives the robot an absolute measurement — one that doesn't depend on any history and therefore can't drift. Fusing the two gives you odometry's smoothness with vision's long-term truth.
The companion guide, Setting up the Jetson, is entirely about producing those absolute measurements — and about the timestamp discipline they need to be usable. This is what that work is for.
Autonomous that works perfectly from one starting position and fails from another is nearly always a pose problem, not a path problem. The robot began the match believing it was somewhere it wasn't, and followed a correct trajectory from the wrong origin. Which is why every routine starts by setting the pose rather than assuming it.
Describing your robot
Choreo generates the fastest trajectory your robot can physically execute. To know what "physically" means, it needs a handful of numbers about the machine. Get them wrong and it will confidently plan something impossible.
This is the step teams rush, and it's the one that quietly ruins everything downstream. The solver has no way to know your robot is heavy or geared slowly except by being told.
| What it asks | What it means | What it limits |
|---|---|---|
| Mass | The whole robot — with battery and bumpers on. | How hard it is to speed up and slow down. |
| Moment of inertia | Resistance to spinning about the vertical axis. Usually 3–8 kg·m² for an FRC drivetrain. | How quickly the robot can change which way it faces. |
| Wheel radius | Of the drive wheel, in metres — measured, not from the catalogue. | Converts motor rotations into travel. |
| Gearing | Motor rotations per wheel rotation. | Trades top speed against pushing force. |
| Motor max speed | In RPM. Roughly 80% of the motor's free speed is a sane figure. | Top speed on the straights. |
| Max torque | The turning force per motor, set by how much current you'll allow. | Acceleration, and whether the wheels break traction. |
| Bumper size & module positions | The robot's footprint and where the wheels sit. | Obstacle clearance and how spinning couples into driving. |
Drag the values below and watch what the solver produces. The path barely changes; the schedule changes completely — which is Part 02's point made concrete.
Optimistic numbers produce a trajectory your robot cannot follow. It will fall behind, the correction terms will wind up trying to catch a schedule that was never achievable, and the robot will arrive late, off-line, or sideways. If your robot consistently lags its trajectory, suspect the configuration before you touch the controller gains.
Weigh the robot on a bathroom scale, competition-ready. Measure a wheel with calipers after it's been driven on — they wear down. Take moment of inertia as 5 kg·m² to begin with, then refine it if rotation looks wrong. Set torque from a current limit you actually apply in code, not the motor's stall rating, which no breaker will ever let you reach.
Drawing the path
In Choreo you place waypoints and add constraints. You are not drawing the curve — you are describing the requirements, and letting the solver find the curve that satisfies them fastest.
Waypoints: how much you're pinning down
There are three kinds, and choosing between them is the main skill. Each one says how much of the robot's state you insist on at that spot.
| Waypoint | Constrains | Use it when |
|---|---|---|
| Pose | Position and heading | Start and end points, scoring positions, anywhere the robot must face a particular way. |
| Translation | Position only | "Go through here, I don't care which way you're facing." Lets the solver pick the fastest heading. |
| Empty | Neither | Nudging the shape of a curve without pinning anything down. Can't be a start or end point. |
The instinct to over-constrain is the enemy. Every pose waypoint you add is a promise the solver must keep, and each one removes freedom it could have spent going faster. If you don't care about the heading halfway down the field, don't specify it.
Constraints: rules over a region
Where waypoints pin down single spots, constraints apply to a waypoint or across a whole stretch of path:
- Max velocity / acceleration / angular velocity — go gently through this bit.
- Stop point — come to a complete halt here, both linear and angular.
- Point at — keep facing a given spot on the field while moving. Useful for a camera or a shooter.
- Keep-in circle / rectangle / lane — stay inside this region.
- Keep-out circle — keep the bumpers out of this region. Obstacles.
Constraints that can't all be satisfied make the solver fail, and the message rarely names the culprit. When generation stops working, the cause is almost always the last constraint you added, or two constraints that quietly contradict — a keep-out circle sitting on top of a pose waypoint, a stop point immediately followed by a demand for speed. Remove them one at a time until it solves, then add back the one you actually needed.
What the solver does
Choreo doesn't draw a spline and then guess a speed for it. It solves a mathematical problem: of all the ways to get through those waypoints without violating physics, which takes the least time?
The important consequence is that the shape and the speed are decided together. A tool that picks a pretty curve first and then asks how fast you can take it will always be slower, because it never considers that a slightly wider corner might let you carry far more speed through it.
At any instant your robot is limited by whichever of three things binds first:
- Top speed — the motors are spinning as fast as they can.
- Available force — you're using all the torque you have to speed up or slow down.
- Grip — cornering hard enough that any more would slide the wheels.
That last clause is the whole reason braking starts early. The solver works backwards from every corner as well as forwards from every straight, so the speed at any point is limited both by how fast you could have got there and by how slow you must already be for what's coming.
Autonomous is fifteen seconds. Half a second saved on each of three movements is a second and a half — often the difference between one more scoring cycle and not. That's the entire argument for using a solver instead of hand-tuned waypoints.
Following it
The trajectory is a file full of samples: at this time, be here, moving this fast. Turning that into motion takes two things working together, and understanding the split is what makes tuning make sense.
Feedforward: doing what the plan says
Every sample carries the velocity the robot should have at that instant. Sending exactly that is feedforward — acting on the plan, without reference to what's actually happening. On a perfect robot on a perfect field, feedforward alone would follow the trajectory exactly.
Feedback: fixing the difference
Robots aren't perfect. The robot ends up slightly off the point it should be at, so you measure that error and add a correction proportional to it. That's feedback — a controller whose only job is to close the gap between where you are and where the plan says you should be.
In ChoreoLib the two live in one short method, and it is worth reading closely because the whole idea is visible in it:
public void followTrajectory(SwerveSample sample) { Pose2d pose = getPose(); // where we actually are ChassisSpeeds speeds = new ChassisSpeeds( sample.vx + xController.calculate(pose.getX(), sample.x), sample.vy + yController.calculate(pose.getY(), sample.y), sample.omega + headingController.calculate( pose.getRotation().getRadians(), sample.heading)); driveFieldRelative(speeds); }
Each line is the same shape: the plan's velocity, plus a correction for being off the plan. Delete the controller terms and the robot drives the trajectory open-loop, drifting further off with every bump. Delete the sample velocities and you get a robot that only ever reacts — always behind, always chasing.
Feedback gains are not where you fix a bad trajectory. If the plan is impossible, error grows no matter how hard the controller pulls, and raising gains turns a lagging robot into an oscillating one. Symptom-sorting: consistently behind means the configuration is too optimistic; wobbling around the line means gains are too high; drifting off and never coming back means feedback isn't working at all, or your pose estimate is wrong.
Building a routine
A real autonomous is several trajectories with actions woven through them. ChoreoLib's job is to let you say "start the intake a second into this movement" without writing a state machine.
The pieces
- AutoFactory — created once, wired to your drive subsystem. It knows how to read your pose, reset it, and follow a sample.
- AutoRoutine — one complete autonomous. Owns the triggers.
- AutoTrajectory — one trajectory inside a routine, which you can attach logic to.
autoFactory = new AutoFactory( driveSubsystem::getPose, // where am I? driveSubsystem::resetOdometry, // I am actually here driveSubsystem::followTrajectory, // the method from Part 07 true, // mirror for the other alliance driveSubsystem);
Triggers: reacting to progress
Instead of sequencing by hand, you attach behaviour to moments in the trajectory. done() fires when a trajectory finishes, active() is true while it runs, and atTime("name") fires at an event marker you placed in the editor.
public AutoRoutine pickupAndScore() { AutoRoutine routine = autoFactory.newRoutine("pickupAndScore"); AutoTrajectory pickup = routine.trajectory("pickupGamepiece"); AutoTrajectory score = routine.trajectory("scoreGamepiece"); // On start: believe the trajectory's start pose, then drive it. routine.active().onTrue( Commands.sequence(pickup.resetOdometry(), pickup.cmd())); // Partway through the drive, not after it. pickup.atTime("intake").onTrue(intake.run()); pickup.done().onTrue(score.cmd()); score.active().whileTrue(shooter.spinUp()); score.done().onTrue(shooter.fire()); return routine; }
Read that as a description rather than a sequence. Nothing says "wait, then do" — each line says "when this becomes true, that happens." The intake starts partway through the drive, the shooter spins up while the robot is still moving, and the fifteen seconds get used properly.
Every way of saying "when"
Those three triggers are the common ones, but the full set is worth knowing, because picking the right one is the difference between an action that fires where you meant and one that fires where the plan said you'd be.
They fall into two families, and the split is the important part.
| Fires on the schedule | When |
|---|---|
| atTime("intake") | The named event marker's time is reached. |
| atTime(2.5) | 2.5 seconds after this trajectory started. |
| atTimeBeforeEnd(0.4) | 0.4 seconds before it finishes. |
| Fires on reality | When |
|---|---|
| atTranslation("piece", 0.15) | The robot is genuinely within 15 cm of that marker's position. |
| atPose("score", 0.1, 0.05) | Within tolerance of the marker's position and its heading. |
Both families also take literal values instead of marker names — atTranslation(new Translation2d(x, y), tol) and atPose(pose, posTol, rotTol) — though naming a marker in the editor is almost always the better habit.
atTime fires when the plan reaches the marker — so if the robot is behind, it fires while the robot is still short of it. atTranslation waits until the robot is actually there. Set the lag to zero and the two fire in the same place, which is why the difference stays hidden until the day it matters.So the rule of thumb: use time for things that should start early and don't care exactly where — dropping an intake, spinning up a shooter. Use position for things that are wrong in the wrong place — releasing a game piece, closing a gripper.
atTime(2.5) is a number that quietly becomes wrong the moment you re-optimise the path or change the robot config, because the trajectory gets faster and 2.5 s now lands somewhere else entirely. atTime("intake") is defined in the file next to the path, so it moves when the path moves. The two look equally reasonable in code and only one of them survives editing.
The rest of the triggers
For completeness, since these are what you reach for when a routine gets more complicated:
| Trigger | Meaning |
|---|---|
| active() / inactive() | True while this trajectory is running, or while it isn't. |
| done() | One pulse the moment it finishes. The usual way to start the next thing. |
| doneDelayed(t) | One pulse, a short delay after it finishes — for letting the robot settle. |
| doneFor(t) | Stays true for a while after finishing, rather than a single pulse. |
| recentlyDone() | This was the last trajectory to run, and it's finished. |
| chain(other) | Shorthand for done().onTrue(other.cmd()). |
And on the routine itself, for logic spanning several trajectories: anyDone(...), anyActive(...), allInactive(...), idle() for when nothing is running, and observe(condition) to hook any boolean of your own — "do we actually have a game piece?" — into the same trigger system.
If a marker name appears more than once along a path, collectEventTimes("name") and collectEventPoses("name") hand you every occurrence so you can attach to a particular one.
resetOdometry comes first
At the start of a match the robot has no idea where it is. The first thing a routine does is declare it: "you are at this trajectory's starting pose." Get that wrong — robot placed a foot off, or the wrong routine selected — and a perfect trajectory drives a perfect shape through the wrong part of the field. This is Part 03's failure, and it is the most common autonomous bug there is.
Testing it safely
A robot executing a wrong autonomous is a fast heavy object heading somewhere you didn't expect. There is a sane order to testing, and skipping to the end is how people get hurt and parts get broken.
- Simulation. No robot involved. Run the routine and watch the pose move on a field view. Catches wrong file names, wrong starting poses, and routines that never finish.
- On blocks. Wheels off the ground, robot restrained. Confirms the modules turn and spin the way the plan says, without going anywhere.
- Slow, on the floor. Turn the constraints down hard and run it in open space with a hand on the disable switch. You are checking the shape, not the speed.
- Full speed, with room. Only once the shape is right.
Throughout, the single most useful habit is logging the planned pose and the actual pose together. One is what you asked for, one is what happened, and the shape of the gap between them names the bug.
Publish the planned pose alongside the measured pose every loop, and watch them on a field view while the robot runs. Two robots on screen, and their relationship tells you which of the last four parts to go and read again.
An autonomous that works on the practice field and fails at competition is usually one of three things: a different starting position, a lower battery, or carpet with different grip. All three are configuration and pose problems rather than code problems — which is why the numbers in Part 04 and the reset in Part 08 deserve more care than they usually get.
Glossary
Every term this guide uses, in plain words. Type to filter or pick a group.
- Path
- The shape a robot traces across the field. Says where, and nothing about when or how fast.
- Trajectory
- A path plus a schedule: for every instant, a position, a velocity, and a heading. This is what a robot can actually follow.
- Sample
- One instant of a trajectory — a row in the file. Contains the pose and velocities the robot should have at that time.
- Pose
- A position and an orientation together: x, y, and an angle. "Where and which way round."
- Field-relative
- Measured from a fixed corner of the field rather than from the robot. Trajectories are field-relative; a robot's own sense of "forward" is not.
- Curvature
- How sharply the path bends at a point. High curvature means a tight corner, which caps how fast you can go through it.
- Velocity profile
- The graph of speed against time or distance along a trajectory. The "schedule" part of the plan.
- Time-optimal
- The fastest trajectory that never violates a constraint. Not the smoothest or the prettiest — the quickest that is actually possible.
- Odometry
- Estimating position by adding up wheel rotations and gyro readings. Smooth and always available, but its errors accumulate and never heal.
- Pose estimator
- The component that blends odometry with absolute measurements like vision into a single best guess of the robot's pose.
- Gyro
- Sensor measuring rotation. Far more trustworthy than inferring heading from wheels, which is why heading is usually taken from it directly.
- Drift
- The slowly growing gap between where the robot thinks it is and where it is. The characteristic failure of odometry.
- Moment of inertia
- How much a robot resists being spun about its vertical axis. Mass resists pushing; moment of inertia resists turning. Typically 3–8 kg·m² for an FRC drivetrain.
- Traction limit
- The most force friction between wheels and carpet can supply before they slide. It caps cornering speed and acceleration alike.
- Gearing
- Motor rotations per wheel rotation. More gearing means more force and less speed.
- Swerve
- A drivetrain where each wheel steers independently, so the robot can move in any direction while facing any direction.
- Feedforward
- Commanding what the plan says, without looking at the result. Does the bulk of the work; can't correct anything.
- Feedback
- Measuring the error between plan and reality and commanding a correction proportional to it. Fixes small deviations; can't rescue an impossible plan.
- PID
- The usual feedback recipe — a correction built from the present error, its accumulation, and its rate of change. For trajectory following, the P term does nearly all the useful work.
- ChassisSpeeds
- WPILib's way of saying how the whole robot should move: forward, sideways, and rotational speed. The drivetrain converts it into individual wheel commands.
- Open loop
- Commanding without measuring the result. "Drive forward for 2 seconds" is open loop, which is why it isn't repeatable.
- Closed loop
- Measuring the result and adjusting. Every reliable autonomous is closed loop somewhere.
- Choreo
- A time-optimal trajectory planner for FRC by SleipnirGroup. You describe waypoints and constraints; it solves for the fastest trajectory your robot can execute.
- ChoreoLib
- The robot-side library that loads generated trajectories and helps you build routines from them.
- Waypoint
- A point the trajectory must pass through. Pose waypoints fix position and heading, translation waypoints fix position only, empty waypoints fix neither.
- Constraint
- A rule the solver must respect — a speed limit, a stop, a region to stay inside or out of, a direction to face.
- Keep-out
- A region the robot's bumpers must not enter. How obstacles are described.
- Point at
- A constraint that keeps the robot facing a chosen spot while it drives. Useful for cameras and shooters.
- Split
- A division of one path into sections that can be loaded and scheduled separately in code.
- Event marker
- A named moment inside a trajectory that robot code can attach behaviour to, so actions happen during a movement rather than after it.
- AutoFactory
- The ChoreoLib object wired to your drive subsystem — it knows how to read your pose, reset it, and follow a sample.
- AutoRoutine
- One complete autonomous routine, owning the triggers that decide what happens when.
- AutoTrajectory
- A single trajectory within a routine, exposing triggers like
done(),active()andatTime(). - Alliance flipping
- Mirroring a trajectory for the other side of the field so one path serves both alliances.