A C++20 simulator and autonomy stack for the IMechE Formula Student AI (FS-AI) ADS-DV car, built by the driverless sub-team of Manchester Stinger Motorsport at the University of Manchester. The repository name dates from the first version, written in C in March 2025. The code moved to C++ from September 2025 and is now C++20 with a few C files.
The simulator runs the team's driverless code against a virtual car and track, over the same CAN interface as the real vehicle.
fsai_run loads or generates a cone track, integrates the vehicle dynamics, renders a synthetic stereo camera for the cone detector, runs the path planner and controller, and sends its commands out over CAN.
A second process, svcu_run, stands in for the car's vehicle control unit (VCU): it decodes the AI's CAN frames, runs the VCU state machine and comms watchdog, drives the simulated car and answers with the ADS-DV feedback messages.
On Linux the two talk over SocketCAN, and --mode car moves the AI side from vcan0 to can0 for use on the car.
Elsewhere the same frames go over UDP on localhost.
Work on it stopped in December 2025, so treat it as a finished team project rather than a maintained tool.
I led Manchester Stinger's driverless (FS-AI) sub-team for three years and wrote about 29k of the 34k lines here (by git blame over the C and C++ sources outside third_party/).
That covers the vehicle dynamics (velox, my C++20 port of the CommonRoad vehicle models, also published as CommonRoad-CXX-Port), the main loop and GUI in sim/app/, track generation, the OpenGL stereo camera in io/camera/sim_stereo/ and the timing and telemetry code in common/.
I'm the sole author of the autonomy-to-VCU CAN layer in control/runtime_cpp/ and sim/svcu/:
- a bit-level codec for the 14 ADS-DV CAN messages, sent over SocketCAN (
adsdv_dbc.cpp); - a packed 13-byte CAN-over-UDP frame (big-endian 4-byte ID, DLC, 8 data bytes) for machines without SocketCAN (
can_link.cpp); - message-rate and watchdog tests against the simulated VCU (
SvcuIntegrationTests.cpp).
The cone-detection pipeline in vision/ and most of the centreline planner in control/centerline.cpp are teammates' work.
| Path | Contents |
|---|---|
sim/app/ |
fsai_run: main loop, mission selection, ImGui panels |
sim/src/, sim/include/ |
world, missions, track generation, collisions and resets, vehicle models |
sim/svcu/ |
simulated VCU (svcu_run), ADS-DV codec, SocketCAN and UDP links, integration tests |
control/ |
centreline planner and beam search, controller, AI-to-VCU CAN interface in runtime_cpp/ |
vision/, vision_test/ |
ONNX cone detector and the vision runtime library |
io/camera/sim_stereo/ |
OpenGL stereo camera (FBO render, PBO readback) |
common/ |
shared types, clock, timing budgets, telemetry, maths |
velox/ |
velox library source; the build compiles its models from a copy under sim/ and reads velox/parameters/ |
configs/ |
vehicle, world, sensor and camera YAML, plus track CSVs |
Install the build toolchain and runtime libraries before configuring the project:
- Common: CMake (3.20+), a C/C++ compiler with C++20 support,
pkg-config. - Maths/geometry: Eigen3, CGAL, Boost.
- Graphics: SDL2, OpenGL development headers.
- Computer vision: ONNX Runtime, OpenCV.
- Config/data: yaml-cpp.
Ubuntu/Debian example:
sudo apt-get update
sudo apt-get install build-essential cmake pkg-config libsdl2-dev libyaml-cpp-dev \
libeigen3-dev libopengl-dev libcgal-dev libboost-dev libopencv-devmacOS (Homebrew) example:
brew install cmake pkg-config sdl2 yaml-cpp eigen cgal boost opencvONNX Runtime is found separately.
CMake checks -Donnxruntime_DIR, then -DONNXRUNTIME_ROOT (or the ONNXRUNTIME_ROOT environment variable), then an unpacked onnxruntime-osx-arm64-* release in the repo root, then /opt/homebrew/onnxruntime/<version> on macOS.
If none of those match, configuration stops and the error message lists the options.
From the root directory of the project:
mkdir -p build
cd build
cmake ..
make -jbuild.sh (first build) and rebuild.sh (run inside build/) wrap the same steps, with a make clean first.
On macOS, CMakeLists.txt picks arm64 or x86_64 from the host and sets the matching Homebrew OpenCV path.
The default target builds fsai_run, svcu_run, fsai_plan (a standalone planner and triangulation viewer), svcu_integration_tests and the Test* programs.
Headers are included by bare file name: CMake adds every directory that holds a header to the include path, so header names must be unique across the repo.
Run everything from build/.
The ONNX model (../vision/models/cone_model.onnx) and svcu_run's vehicle config are loaded relative to the working directory; configs and track CSVs are found through the source path compiled into the binary.
./fsai_runFlags: --mission, --mode sim|car, --can-if, --dt, --cmd-port, --state-port, --sensor-config (sensor noise and latency, default configs/sim/sensors.yaml) and the preview toggles below. --help prints a usage line.
With no --mission flag and an interactive terminal, fsai_run prints a numbered menu, and pressing Enter accepts the default, Autocross (option 3).
Without a terminal it goes straight to Autocross.
--mission <value> skips the menu and takes the same numbers or names (for example accel, skid, auto or track); an unrecognised value exits with an error.
| Mission | Track | Laps |
|---|---|---|
| Acceleration | configs/tracks/acceleration.csv |
one; brakes to a stop once past the end of the straight |
| Skidpad | configs/tracks/skidpad.csv |
four: warm-up, two timed, exit |
| Autocross | randomly generated | one timed |
| Trackdrive | randomly generated | ten timed |
When the last lap is done the car gets zero throttle and full brake.
A cone or boundary collision resets the car.
On Autocross and Trackdrive that reset also generates a new track, as long as regenerate_track is on in configs/sim/world.yaml (it is by default).
The CSV format for custom layouts is in configs/tracks/README.md.
fsai_run opens an SDL window with Dear ImGui panels:
Simulation Stats: pose, kinematics, axle torques, brakes, wheel RPM, accelerations, lap stats and trends, each under a collapsing header.Control PanelandControl Pipeline: controller health, the AI request, the applied command, the last CAN command, and how old each stage's data is.CAN Telemetry: VCU status and steering, drive, brake, wheel-speed and dynamics feedback, with the heartbeat age; stale values change colour.Simulation Log: the log console, withClearandAuto-scroll.Mission, plus edge and cone-detection preview windows, both on by default (--no-edge-preview,--no-detection-preview).
The racing controller always drives; there is no manual keyboard control.
Esc sends an emergency stop, which shuts the simulator down, and closing the window quits.
Start svcu_run in a second terminal, also from build/.
It applies its steering, drive and brake outputs to the simulated car over a separate UDP pair (--cmd-port and --state-port, default 47001 and 47002), which needs no setup.
On Linux both processes default to the virtual SocketCAN interface vcan0:
sudo modprobe vcan
sudo ip link add dev vcan0 type vcan
sudo ip link set up vcan0
./svcu_run # terminal 1
./fsai_run # terminal 2On other systems CAN frames go over UDP. The endpoint format is udp:<listen port>@<peer port>, so give the two processes mirrored ports:
./svcu_run --can-if udp:47010@47011
./fsai_run --can-if udp:47011@47010ctest in build/ runs svcu_integration_tests, which checks mission-status packing, message rates and saturation, and watchdog transitions against the simulated VCU.
It uses vcan0 when it can create it and a UDP pair otherwise.
On macOS the message-rate check currently fails over that UDP fallback: the simulated VCU latches an emergency stop and never enables drive.
The Test* programs build alongside it but aren't registered with CTest; run them directly.
- ADS-DV software interface specification and the ADS-DV DBC, the interface the CAN layer implements.
- Portfolio write-up on the design of the physics, stereo camera, CAN link and timing code.
MIT, see LICENSE.