MPC trajectory tracking, MPCC racing line control, and dynamic rollout planning running live on a real Ackermann platform. Not a simulation. Not a bag file replay. The real thing.
I built a full autonomy stack for the RoboRacer F1TENTH platform and ran it on the actual car. The pipeline separates planning from control deliberately — a fast rollout planner scores trajectory candidates using obstacle distance transforms, then hands the best one off to MPC or MPCC to execute cleanly.
This separation matters because it keeps each layer debuggable. When something breaks on track, you know exactly where to look.
The planner runs every control tick and produces a scored set of candidate trajectories. It is fast enough to run online because each rollout is just a forward simulation of the bicycle model under a fixed steering command. RK4 keeps the integration accurate even at higher speeds where Euler diverges.
The obstacle avoidance is done with a Euclidean distance transform on the occupancy map. I precompute it once per map update and then scoring each rollout is just a lookup — not a collision check loop.
Standard MPC tracking but with a bicycle model that includes a slip angle. This matters when you push the car faster — a pure kinematic model starts lying to you. The slip angle gives the controller a better sense of where the car is actually heading versus where the wheels are pointing.
I solve it with CasADi and IPOPT, use warm starting from the previous solution, and publish the first action each tick. The robot pose comes straight from TF — no separate localisation node.
The difference between MPCC and standard MPC comes down to what you are optimising. Standard MPC wants to be at the next waypoint. MPCC wants to move forward along the track as fast as possible while staying near the line. It sounds subtle but in practice it changes how the car corners completely.
I augment the state with an arc length progress variable and add a reward term for advancing it. The contouring error keeps the car on line. The lag error stops it from falling behind the reference. The result is a controller that cuts corners when it helps and stays smooth when it does not.