OzU Racing FS-AI — Autonomous Stack with IPG CarMaker

Autonomous Stack
with IPG CarMaker

FS-AI 2026 Dynamic Driving Task -ADS-DV Platform

02 / 20

What We'll Cover


01IPG CarMaker
02CMRosIF Bridge
03Pipeline Boot
04Perception
05EKF-SLAM
06Path Planning
07Control
08Full Pipeline
09Next Steps
03 / 20

What is IPG Formula CarMaker?


  • Full-vehicle simulation platform by IPG Automotive
  • Physics-based tire, suspension & powertrain models
  • Sensor simulation: camera, LiDAR, radar, object sensors
  • MovieNX 3D rendering with road surface models
  • Formula Student vehicle library (ADS-DV ready)

Key Features

ScriptControl CMRosIF MovieNX RSDS Camera

Our Setup

Version14.1.1
OSUbuntu 22.04
VehicleFS_Autonomous (ADS-DV)
04 / 20

Why CarMaker for FS-AI?


Aspect Real ADS-DV CarMaker Simulation
Cost per test$$$ -track rental, team, logisticsFree -unlimited runs
Sensor dataZED2i stereo + IMU + wheel encodersSynthetic camera + object sensor + ground truth
Crash riskReal damage to vehicleReset in seconds
Track varietyLimited to available venuesAny track layout via Tcl scripts
RepeatabilityWeather, tire temp, battery SOCDeterministic conditions
ROS2 integrationNative (same stack runs on car)Same ROS2 stack via CMRosIF

Same ROS2 nodes run in both environments -simulation is not a separate codebase

Preferred over Gazebo, LGSVL, CARLA -CarMaker provides validated vehicle dynamics, official FS-AI vehicle models, and deterministic sensor simulation out of the box

05 / 20

What is CMRosIF?


The CarMaker ROS Interface -a bridge that embeds ROS2 publishers and subscribers inside the CarMaker process itself.

CarMaker
Physics Engine
⟵⟶
CMRosIF
libCMNode_ROS2.so
⟵⟶
ROS 2 DDS
Topics / Services

How it works

  • CMNode is a shared library loaded by CarMaker at runtime
  • CMJob scheduler syncs ROS publish/subscribe with sim clock
  • No standalone process -lives inside CarMaker's main loop

What it publishes

/carmaker/odom /carmaker/imu /carmaker/wheel_speeds /carmaker/cm2ext
06 / 20

How CMRosIF Attaches Our Bridge Node

IPG CarMaker Physics Engine + MovieNX + Sensors Vehicle Dynamics Sensor Models ScriptControl MovieNX Rendering Tire/Suspension CarMaker Process (single binary) CMRosIF libCMNode_ROS2.so Our CMNode CMNodeHelloCM class CMJob publishers CMJob subscribers Publishes ROS2 topics → Shared library (loaded at runtime) Perception YOLO + Depth EKF-SLAM Pose + Landmarks Planning ft-fsd Path Control Pure Pursuit /carmaker/vehicle_control → actuate /camera /odom /imu /wheels /perception/detections_3d /slam/global_map /planning/path
Perception subscribes to /front_camera_rgb + /front_camera_depth, runs YOLO12, publishes 3D cone positions.
EKF-SLAM fuses /perception/detections_3d + /localization/wheel_odom to track car pose + cone landmarks.
Planning reads /slam/global_map + /slam/odom, computes spline path with curvature.
Control tracks /planning/path via Pure Pursuit, outputs steering + pedals to /carmaker/vehicle_control.
07 / 20

How the Full Pipeline Starts


CarMaker GUI CMRosIF Launch ozu_bringup
# simulation.launch.py conditionally includes:
perception → YOLO cone detector
slam → EKF-SLAM + wheel odometry + TF
planning → ft-fsd path planner
control → Pure Pursuit controller
rviz2 → visualization
StartSim CMNode publishes /clock All nodes sync to sim time

Config: Data/Config/CMRosIFParameters -launch args: perception, slam, planning, control, rviz2

08 / 20

YOLO Cone Detection


ZED2i stereo camera gives us RGB + depth. YOLO12 finds cones in the image, then we look up each cone's depth to get its 3D position in the world.

  • Input: 1920×1080 RGB + aligned depth map
  • Model: Custom YOLO12 trained on FSOCO dataset
  • Confidence threshold: 0.55, max range: 25 m

5 Detection Classes

blue_cone yellow_cone orange_cone large_orange_cone unknown_cone

sample_depth() from our code

# Center 40% strip for large bboxes
if bw > 15:
  sx1 = x1 + int(bw * 0.3)
  sx2 = x2 - int(bw * 0.3)

roi = depth_image[y1:y2+1, sx1:sx2+1]
depths = roi[roi > 0].flatten() / 100.0

# IQR outlier removal
q1, q3 = np.percentile(depths, [25, 75])
iqr = q3 - q1
lo, hi = q1 - 1.5*iqr, q3 + 1.5*iqr
inliers = depths[(depths >= lo)
              & (depths <= hi)]

return float(np.percentile(inliers, 10))
09 / 20

Perception Data Flow


MovieNX 3D Rendering Engine ozu_carmaker_rsds Channel: RGB ozu_carmaker_rsds Channel: Depth 2 instances, 1 TCP stream each, filtered by channel /front_camera_rgb/image_raw /front_camera_depth/image_raw synchronized callback cone_detector node YOLO12 detect → IQR depth sample → 3D in CARS00 → hardcoded transform to Fr1A conf > 0.55  |  max_range 25m  |  640px inference on CUDA /perception/detections_3d Camera 1920x1080 110° f = 672.2 px Fr1A → CARS00
10 / 20

What is EKF-SLAM?


Breaking Down the Name

SLAM = Simultaneous Localization And Mapping. The car figures out "where am I?" and "where are the cones?" at the same time.

EKF = Extended Kalman Filter. A math recipe that combines noisy guesses into one better answer.

The Analogy

Imagine walking blindfolded, counting your steps (wheel odometry). You drift over time. Now you can occasionally remove the blindfold and see the landmarks around you. A Kalman Filter blends your step-counting with those landmark sightings, correcting your drift every time you see something familiar.

How the Kalman Filter Works Inside

Every value we track has an uncertainty (a number saying "how sure are we?"). In the predict step, we use physics to guess the next state, but uncertainty grows. In the update step, we compute a Kalman gain K between 0 and 1. K = 0 means "trust prediction", K = 1 means "trust the sensor". The filter automatically picks K based on which source has less noise right now.

State Vector (what the filter tracks)

// Car pose: "where am I, which way?"
vehicle = [x, y, θ] // 3 values

// Every cone's position on the map
cones = [c1x, c1y, c2x, c2y, ...]
// 2 values per cone (x, y)

State = [vehicle | cones]
// Total: 3 + 2N values
// 50 cones = 103 numbers tracked

Covariance Matrix P

Alongside the state, we keep a covariance matrix P (size (3+2N) x (3+2N)) that stores the uncertainty of every value AND how they relate to each other. When we correct the car's position, the cone positions get corrected too because P links them together.

11 / 20

How SLAM Builds the Map


The 5-Step Loop (every frame)

1Predict: "I moved 0.1m forward" -use wheel speeds to guess new position. Uncertainty cloud grows.
2Observe: Camera spots cones. Perception sends their 3D positions.
3Associate: "Is this the same cone I saw 2 seconds ago?" Match new observations to known landmarks via Mahalanobis distance (a "how surprising is this?" score).
4Update: Compute Kalman gain K. Blend prediction with observation. Both car position and cone positions get corrected. Uncertainty shrinks.
5New landmark? If no match found, initialize a brand new cone in the state vector. Needs 3 sightings before "confirmed".

Data Association (from our code)

# For each detected cone, find best match
for detection in observations:
  costs = []
  for lm in landmarks:
    dist = norm(lm.pos - detection)
    if dist > 5.0: # too far
      costs.append(INF)
    else:
      costs.append(mahalanobis)
  # Hungarian algorithm solves assignment

Subscribes

/perception/detections_3d
/localization/wheel_odom

Publishes

/slam/odom
/slam/global_map
/slam/tracked_cones

Params

range: 4-25 m
min_hits: 3
σxy: 0.3 m
12 / 20

Path Planning Fundamentals


Given sorted blue (left) and yellow (right) cones, find the driveable centerline.

Blue (left) Yellow (right) midpoints -> cubic spline path Cone matching + midpoint extraction + spline fit car
1. Sort cones by side 2. Match blue-yellow pairs 3. Midpoints -> cubic spline 4. Curvature κ at each point
13 / 20

FT-FSD Path Planner


We use ft-fsd-path-planning (MIT license). No Delaunay triangulation - it uses trace-based sorting and distance-angle cone matching.

3-Stage Pipeline (Trackdrive)

1TraceSorter: For each cone, find up to 5 neighbors within 6.5m. Grow connected chains forward/backward using angle constraints (40/65 deg). Separates into left vs. right traces.
2Cone Matching: For each left cone, search within 5m and 50 deg for a right-side partner. If no match found, add a "virtual cone" to fill the gap.
3Path Calc: Average each matched pair to get centerline points. Fit a cubic spline (degree 3) through them. Sample 40 points over 20m with curvature at each.

Skidpad Special Algorithm

Skidpad is a known figure-8 layout. Instead of computing a new path, the library:

Circle Fitting: Fit circles to detected cones using powerset combos. DBSCAN clusters circle centers into inner/outer rings.
Validate: Check inner/outer distance is ~18.25m, radius ~7.6m, spacing ~2.4m.
Reference Path: Transform detected layout to a pre-computed 500-point reference. Slide a 25m window along it for the current segment.

Acceleration mission uses a similar relocalizer for straight-line paths.

14 / 20

Our Custom Path Planner (Design Goals)


ft-fsd works but has limitations. We want to build our own planner with these goals and considerations:

Design Goals

  • Work with as few cones as possible (even 1 per side)
  • Graceful degradation: partial path is better than no path
  • Use rear axle position (Fr1A + 0.5637m) as reference
  • Multi-lap path stitching: connect end of lap back to start
  • Curvature output for speed profiling by the controller
  • Replanning at 10+ Hz without jitter or oscillation

Edge Cases to Handle

  • Only cones on one side visible (tight corners)
  • False positive cones from perception (outliers in map)
  • Orange cones at start/finish line (not part of boundary)
  • Sharp hairpin turns (>90 deg) where sorting breaks
  • Lap closure: detecting we've completed a full loop
  • Path continuity across replanning cycles (no sudden jumps)

Open Questions

Should we use curvature-aware splines? Can we encode track width as a safety margin? How do we handle dynamic replanning without path oscillation?

15 / 20

Pure Pursuit Controller


Think of a dog chasing a ball -it always runs toward the ball. Pure Pursuit works the same way: pick a "lookahead" point on the path ahead, then steer toward it. The faster you go, the further ahead you look.

Steering Law

# Adaptive lookahead distance
ld = 0.8 + 0.3 * speed # [0.8, 3.0] m

# Transform target to vehicle frame
local_x = dx*cos(yaw) + dy*sin(yaw)
local_y = -dx*sin(yaw) + dy*cos(yaw)

# Pure pursuit formula
steer = atan2(
  2 * L * local_y,
  local_x² + local_y²)
# L = 1.53m (wheelbase)

Speed Profiler (forward-backward)

# 1. Max speed from curvature
v = sqrt(a_lat_max / |κ|)

# 2. Forward pass (accel limit)
v[i] = min(v[i],
  sqrt(v[i-1]² + 2*a_max*ds))

# 3. Backward pass (brake limit)
v[i] = min(v[i],
  sqrt(v[i+1]² + 2*a_brake*ds))
max_steer
±0.3665 rad (21°)
PID gains
kp=1.0 ki=0.15 kd=0.05
16 / 20

Control Mode Switching


Two modes: Autonomous (Pure Pursuit follows the planned path) and Manual (human drives via Tkinter GUI). Switch between them via a ROS2 service call at runtime.

pure_pursuit_node Autonomous mode
manual_control_node Manual (Tkinter)
↓ ↓ (only one active at a time)
/carmaker/vehicle_control
CMNode → CarMaker

VehicleControl Message

float64 steer_ang
float64 gas
float64 brake
# steer_ang = road wheel angle (1:1)
# gas/brake = [0.0, 1.0] normalized

Pedal Mapping (from code)

f_need = mass * accel + f_drag
torque = f_need * wheel_radius
gas = torque / (120 * 3.5)
17 / 20

Future: Safe-Set Trajectory Optimization


After completing lap 1 with Pure Pursuit, we have a proven safe trajectory. Now we can use it as a safety net to push faster on subsequent laps.

The Concept

1Lap 1: Pure Pursuit completes the track safely at conservative speed. This entire trajectory (position + velocity at every point) becomes the "safe set".
2Lap 2+: Divide the track into sections. For each section, try an optimized (faster) trajectory. But the exit state of each section MUST land inside the safe set -matching position AND speed.
3Guarantee: If any section's optimizer fails, we fall back to the safe trajectory. The car can always complete the lap -it just might be slower on that section.

Top-Down Track View

S1 S2 S3 fallback Safe (Lap 1) Optimized Entry/Exit Fallback
18 / 20

Full Pipeline at a Glance


IPG CarMaker -Physics + MovieNX Rendering
↓ CMRosIF Bridge (CMNode) ↓
/front_camera_rgb /front_camera_depth /carmaker/odom /carmaker/imu /carmaker/wheel_speeds
↓↓↓
Perception
YOLO + Depth
Wheel Odom
IMU + Encoders
↓ /perception/detections_3d   ↓ /localization/wheel_odom
EKF-SLAM -Vehicle Pose + Cone Landmarks
↓ /slam/odom + /slam/global_map
Path Planning - ft-fsd Trace Sort + Spline
↓ /planning/path
Control -Pure Pursuit → δ, gas, brake
↓ /carmaker/vehicle_control ↓
CarMaker Vehicle Model -Actuate
19 / 20

What's Next & Open Problems


👁 Perception

  • Real ZED2i stereo depth vs. simulated
  • Cone classification accuracy in rain/glare
  • Latency optimization on AGX Orin
  • Training data augmentation pipeline

🗺 Planning

  • Sharp corner handling (>90° turns)
  • Lap closure detection reliability
  • Multi-lap path optimization
  • Dynamic re-planning at higher speeds

🎯 Control

  • Safe-set trajectory optimization
  • Tire model integration (dynamic bicycle)
  • Sim-to-real transfer gap
  • Emergency braking logic
20 / 20

Thank You

Ozyegin University • Istanbul, Turkey • FS-AI 2026 DDT

ROS 2 Humble IPG CarMaker 14.1 EKF-SLAM Pure Pursuit

References: IPG CarMaker User Guide • ft-fsd-path-planning (MIT) • FS-AI Rules 2026