Race Deployment   Real Hardware   2025

Full
Throttle
Autonomy

MPC trajectory tracking, MPCC racing line control, and dynamic rollout planning running live on a real Ackermann platform. This runs on the actual car, not in simulation and not from a bag file replay.

MPC + MPCC
Control stack
IPOPT
NLP solver
RK4
Rollout integrator
Live
Deployed on robot
RoboRacer F1TENTH   Ackermann Steering   ROS 2   CasADi Sector 1: Planner   |   Sector 2: MPC   |   Sector 3: MPCC
MPC
Nonlinear trajectory tracking
CasADi + IPOPT
MPCC
Racing line control
Progress maximisation
RK4
Rollout integration
Online trajectory scoring
Real
Deployed on hardware
RoboRacer Ackermann platform
Race footage
Live deployment run on the RoboRacer F1TENTH platform
What I built

Built to
Race

I built a full autonomy stack for the RoboRacer F1TENTH platform and ran it on the actual car. I kept planning and control separate on purpose. A fast rollout planner scores trajectory candidates using obstacle distance transforms, then it hands the best one to MPC or MPCC to execute.

I did this because it keeps each layer easy to debug. When something breaks on track, I know where to look.

The three layers

Three
Sectors

01
Dynamic Rollout Planner
I sample a range of steering commands and forward simulate each one with RK4 integration over a fixed horizon. If a candidate gets too close to an obstacle, I drop it. I score the rest on clearance, smoothness, and how close they get to the goal. The best one becomes the reference trajectory.
RK4   Distance Transform   Online Scoring
02
Nonlinear MPC
At each control cycle I solve a constrained nonlinear optimisation problem over a horizon of N=10 steps. The bicycle model includes a slip angle approximation, so the controller handles corners more accurately at higher speeds. CasADi builds the symbolic problem, IPOPT solves it, and I apply only the first action.
CasADi   IPOPT   Ackermann Model
03
MPCC Racing Controller
MPCC adds a progress state to the optimisation. This is an arc length variable along the centerline, and the controller gets a reward for advancing it. It also penalises contouring and lag errors separately. So the controller tries to go fast, and it does more than minimise reference tracking error.
Contouring Error   Lag Error   Progress
Sector 01 in detail

Rollout
Planning

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 only a forward simulation of the bicycle model under a fixed steering command. RK4 keeps the integration accurate even at higher speeds where Euler diverges.

I do the obstacle avoidance with a Euclidean distance transform on the occupancy map. I precompute it once per map update. After that, scoring each rollout is a simple lookup instead of a collision check loop.

Rollout scoring
# For each candidate steering angle δ forward_simulate(bicycle_model, δ, N, dt) → candidate path {x_k, y_k, θ_k} # Reject if too close to any obstacle if min(d(x_k, y_k)) < d_safe: discard # Score surviving candidates J = Σ clearance(x_k, y_k) - α * Σ |Δθ_k| # penalise sharp turns - β * dist(p_N, goal) # pull toward target best_path = argmax(J) publish → /dynamic_trajectory
  • RK4 integration keeps rollouts accurate at speed
  • Distance transform precomputed from occupancy grid
  • Best path published as Nav reference for MPC
  • Clean interface, planner and controller are fully decoupled
Sector 02 in detail

Nonlinear
Tracking

This is standard MPC tracking, but with a bicycle model that includes a slip angle. It matters when I push the car faster. A pure kinematic model starts to give wrong predictions. 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, warm start from the previous solution, and publish the first action each tick. The robot pose comes straight from TF, so there is no separate localisation node.

MPC formulation
# State: [x, y, ψ, v] Control: [v_c, δ] # Slip angle gives better cornering accuracy β = atan2(Lr * tan(δ), L) # Dynamics propagation over horizon N x_{k+1} = x_k + v_c * cos(ψ_k + β) * dt y_{k+1} = y_k + v_c * sin(ψ_k + β) * dt ψ_{k+1} = ψ_k + (v_c * cos(β) / L) * tan(δ) * dt # Minimise tracking cost + control effort min Σ ||x_k - x_ref_k||²_Q + ||u_k||²_R # Subject to actuator bounds v_min ≤ v_c ≤ v_max δ_min ≤ δ ≤ δ_max # Apply first action only (receding horizon) publish u_0 → /drive
Sector 03 in detail

Racing
Line Control

The difference between MPCC and standard MPC is what you optimise. 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 like a small change, but in practice it changes how the car corners.

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 controller then cuts corners when it helps and stays smooth when it does not.

MPCC formulation
# Augmented state includes arc length θ θ_{k+1} = θ_k + v_k * dt # Nearest centerline point gives reference heading φ_cl dx = x - x_cl dy = y - y_cl # Contouring error: how far off line laterally ε_c = sin(φ_cl) * dx - cos(φ_cl) * dy # Lag error: how far behind the reference arc ε_l = -cos(φ_cl) * dx - sin(φ_cl) * dy # Objective: stay on line, keep moving forward min Σ q_c*ε_c² + q_l*ε_l² - q_θ*(θ_k - s_cl_k) # progress reward + ||u_k||²_R
  • Progress reward means the controller actively drives forward
  • Stable at speed because it avoids chasing individual waypoints
  • Arc length state makes corner cutting a natural outcome
  • Published predicted horizon as MarkerArray for visualisation