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. Not a simulation. Not a bag file replay. The real thing.

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. 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 three layers

Three
Sectors

01
Dynamic Rollout Planner
I sample a range of steering commands and forward-simulate each one using RK4 integration over a fixed horizon. Any candidate that gets too close to an obstacle gets thrown out immediately. The survivors are scored on clearance, smoothness, and how close they get to the goal. The winner 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 just the first action.
CasADi   IPOPT   Ackermann Model
03
MPCC Racing Controller
MPCC adds a progress state to the optimisation — an arc length variable along the centerline that the controller is rewarded for advancing. It also penalises contouring and lag errors separately. The result is a controller that actually wants to go fast instead of just minimising 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 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.

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

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.

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

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