Autonomous · Choreo · A Rookie Field Guide

Fifteen seconds, alone

For fifteen seconds at the start of a match, nobody is driving. The robot does exactly what you told it to, at speed, with no chance to correct you. This guide builds up to a working Choreo autonomous — and to understanding why it works.

Nine partsEleven live diagramsGlossaryJava · swerve · command-based
t 0.00 s speed 0.0 m/s heading 0° segment
Part 01

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.

Fig 1 Fifteen seconds, spent. Driving is only part of it — the robot also has to raise, aim, release, and be somewhere useful when the buzzer hands control back. Time is the resource everything competes for.

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.

What goes wrong

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.


Part 02

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.

Fig 2 The same curve, twice. Above: the path, coloured by the speed the robot will be travelling at each point. Below: that speed as a graph against time. Slow into the corners, fast down the straights — and the shape alone never told you any of it.

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.

The one-sentence version

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.


Part 03

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.

Fig 3 Drift, and the cure. The solid robot is the truth; the outline is what the code believes. With odometry alone the gap only ever grows. Vision measurements pull the belief back toward reality — the small corrections you can see are the estimator being told where it really is.

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.

Where this connects

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.

What goes wrong

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.


Part 04

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 asksWhat it meansWhat it limits
MassThe whole robot — with battery and bumpers on.How hard it is to speed up and slow down.
Moment of inertiaResistance 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 radiusOf the drive wheel, in metres — measured, not from the catalogue.Converts motor rotations into travel.
GearingMotor rotations per wheel rotation.Trades top speed against pushing force.
Motor max speedIn RPM. Roughly 80% of the motor's free speed is a sane figure.Top speed on the straights.
Max torqueThe turning force per motor, set by how much current you'll allow.Acceleration, and whether the wheels break traction.
Bumper size & module positionsThe 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.

Fig 4 The same path, four different robots. Total time is what changes — and the colour shows you where the time went. A robot with grip but no acceleration loses it on the straights; one with acceleration but no grip loses it in the corners.
What goes wrong

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.

A decent first pass

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.


Part 05

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.

WaypointConstrainsUse it when
PosePosition and headingStart and end points, scoring positions, anywhere the robot must face a particular way.
TranslationPosition only"Go through here, I don't care which way you're facing." Lets the solver pick the fastest heading.
EmptyNeitherNudging 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.

Fig 5 Waypoints and what they cost. Click the middle waypoint to change its type, and watch both the shape and the time change. Pinning the heading in the middle of a corner is expensive; pinning it where the robot was going to face that way anyway is free.

Constraints: rules over a region

Where waypoints pin down single spots, constraints apply to a waypoint or across a whole stretch of path:

What goes wrong

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.


Part 06

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:

  1. Top speed — the motors are spinning as fast as they can.
  2. Available force — you're using all the torque you have to speed up or slow down.
  3. Grip — cornering hard enough that any more would slide the wheels.
Fig 6 The speed the robot is allowed, along the length of the path. The ceiling is the tightest of the three limits at every point, and the actual speed is the tallest curve that fits underneath while still being reachable — you cannot arrive at a corner slowly without having braked for it beforehand.

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.

Why "time-optimal" is worth caring about

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.


Part 07

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.

Fig 7 Push the robot off its trajectory and watch what happens. With feedback, the error is measured and driven back to zero. Without it, the robot keeps executing the plan perfectly from the wrong place — every velocity correct, every position wrong.
What goes wrong

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.


Part 08

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 = 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.

Fig 8 The same four actions, sequenced two ways. Overlapping them isn't a micro-optimisation — it is usually the difference between finishing the routine and running out of clock with the robot mid-movement.

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 scheduleWhen
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 realityWhen
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.

Fig 9 The same marker, two triggers. 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.

Name your markers, don't count seconds

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:

TriggerMeaning
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.

Why 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.


Part 09

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.

  1. 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.
  2. On blocks. Wheels off the ground, robot restrained. Confirms the modules turn and spin the way the plan says, without going anywhere.
  3. 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.
  4. 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.

Fig 10 Four bugs, four shapes. Planned in blue, actual in red. Learning to recognise these from a plot is worth more than any amount of guessing, because each one sends you to a different part of the robot.
The habit worth building

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.

What goes wrong

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.


Appendix

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() and atTime().
Alliance flipping
Mirroring a trajectory for the other side of the field so one path serves both alliances.