Tanay Jain
  • Work
  • Index
  • About
  • Resume
© 2026 Tanay Jain. Designed and built in West Lafayette.build 730f164 · Oct 16, 2026
  • GitHub ↗
  • LinkedIn ↗
← Index

Personal

Racing Generations

Neural-network drivers that teach themselves to race — from a 2D sim to full 3D TORCS.

Year
2026
Role
Solo — research & engineering
Stack
Python, NumPy, TORCS, Docker, TypeScript
Links
Source private — ask me
LiveOpens on a race recorded in TORCS and replayed in 3D: the best driver of five generations, from 1 to 912, racing as ghosts on g-track-1. Drag to orbit, or pick a car to follow. Switch to 2D for the real algorithm, ported from Python to TypeScript and evolving live in a Web Worker in your browser. Generation 1 crashes into everything; by generation 4 someone usually finishes a lap.
90%
Held-out tracks finished
46.9s
TORCS lap, gen 157
100
Cars per generation

The question

Can you get a car to drive well without ever telling it how? No reward shaping from a human driver, no labeled data — just a population of tiny neural networks, a fitness score, and evolution.

And the harder question: does the driver actually learn to drive, or does it just memorize one track?

What I built

A 2D top-down simulator where 100 cars, each driven by a 15→16→16→3 network (579 weights), race at once. Each car sees the world through rangefinder sensors and outputs steering, throttle and brake. After every generation the best drivers breed the next one.

The physics is a kinematic bicycle model vectorized across the whole population in a single NumPy call, and the networks run as one batched einsum per layer — so a generation of 100 cars costs about as much as one. I tested the physics against highway-env to 1e-9.

I implemented four algorithms side by side — a classic genetic algorithm, MAP-Elites, NEAT, and a 'layer evolution' mode where networks can add and remove layers. Layer evolution ended up deleting every hidden layer: the best driver is a direct sensors→controls network with just 48 parameters, and it finishes 21 of 24 tracks it has never seen. That's the driver you can switch to in the demo.

Generalization

The interesting experiment: train on one fixed track and the best driver finishes 0% of tracks it has never seen. Train on a freshly generated random track every generation and it finishes 90% of held-out tracks. A POET-style curriculum landed at 80%.

Going 3D

The successor, Evo Racing Sim, plugs the same evolution core into TORCS — a full 3D racing simulator — running in six Docker containers on Apple Silicon and talking to Python over UDP at about 110× real time. A generation of 100 full laps takes ~30 seconds.

Drivers get 19 rangefinders plus rpm and gear, control the gearbox themselves, and are scored on sector times. By generation 157 the evolved AI ran a 46.91s standing-start lap on g-track-1, beating TORCS's built-in berniw2 bot (50.41s) and within a tenth of berniw (46.82s). There's also a mode where you race a ghost of the best AI yourself.

Replaying TORCS in the browser

TORCS can't run in a web page, so the 3D demo is a recording. TORCS is deterministic in lock-step mode, so re-racing a saved driver reproduces its original race exactly. I took the best driver of generations 1, 10, 50, 157 and 912 from a later 989-generation run, raced them together as ghost cars in the same Docker image, and logged every car every 20 ms of race time.

The SCR interface never reports x, y or z, only how far along the track a car is, how far across it, and its angle to the track axis. The replay puts those back on g-track-1 as rebuilt from TORCS's own track file, elevation and banking included, the same way Evo Racing Sim's dashboard draws its 3D map. Lap times in the replay are TORCS's own. They come from a different run than the generation-157 lap above, so the numbers differ.

TORCS race on g-track-1
Evo Racing Sim: TORCS on g-track-1, running headless in Docker on Apple Silicon and driven from Python over UDP.
TORCS race on the Forza track
Forza.
TORCS race on A-Speedway
A-Speedway.
Bar chart of held-out track progress, fixed vs random training
The generalization experiment: same GA, 150 generations. Trained on one fixed track (blue), the best driver finishes none of the held-out tracks. Trained on random tracks (orange), it finishes 9 of 10.
Next project
TanayOS→