80 AI cars learn F1-style racing from scratch through genetic algorithms — across a 7-level curriculum of tracks, in your browser.
A browser-native simulation in which a population of 80 simple neural-network agents learns to drive F1-style cars on procedurally-generated tracks. No hand-coded driving logic, no reinforcement learning — just mutation and selection.
The system trains across a 7-level curriculum of increasingly demanding tracks. Cars that fail to make meaningful progress on a level get culled; their genomes (network weights) are recombined and mutated to produce the next generation. A plateau-based escalation rule advances the whole population to the next track only once their best-of-generation fitness stops improving.
Two ways to run it:
- Browser —
npm run dev, open the page, watch the cars learn live with Three.js visualization. - Headless trainer —
npm run trainto run thousands of generations overnight, save the best genome, then load it back into the browser to watch it drive.
- 🧬 Pure neuroevolution — no gradient descent, no RL libraries. Genomes are real-valued vectors; selection is tournament-based; mutation is Gaussian.
- 🏁 7-level curriculum with plateau detection — population only advances when fitness flatlines.
- ⚡ Headless trainer — runs without rendering for fast iteration, then loads the saved genome into the visual mode.
- 🎮 Three.js visualization — orbital camera, real-time car colors keyed to fitness, track switching mid-run.
- 🧪 Vitest test suite for the simulation core (collision, ray-cast sensors, fitness, plateau detector).
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ Population of │ │ Run all 80 │ │ Tournament │
│ 80 genomes │───▶│ cars on track │───▶│ selection + │
│ (NN weights) │ │ for N seconds │ │ Gaussian mutation│
└──────────────────┘ └──────────────────┘ └────────┬─────────┘
│
▼
┌──────────────────┐
│ Next generation │
└──────────────────┘
│
┌───── plateau detector ────────┘
│ (best fitness flat for K gens)
▼
┌──────────────────┐
│ Advance to next │
│ track in the │
│ curriculum │
└──────────────────┘
Each car has a small feed-forward network that takes a fixed-length ray-cast sensor array (distances to track walls in N directions) as input and outputs [steering, throttle]. Fitness is a function of distance travelled and average speed, with a stiff penalty for collisions.
The 7 tracks are designed to expose distinct skills: straights (raw speed), banked corners (commit), chicanes (rapid reversal), hairpins (low-speed steering), and combinations.
- Node.js 18+
- A modern browser with WebGL (any Chrome/Firefox/Safari from the last few years)
git clone https://github.com/aifriend/f1-neuroevolution.git
cd f1-neuroevolution
npm installnpm run devOpen the URL it prints. You'll see 80 cars on track 1. Watch them get less terrible.
npm run train -- --generations 500 --population 80 --save best.jsonAfter training, load best.json in the browser:
npm run dev -- --load best.jsonnpm test.
├── src/
│ ├── genome.js # NN weight vector, mutation, crossover
│ ├── network.js # Tiny feed-forward NN evaluator
│ ├── car.js # Physics, ray sensors, fitness
│ ├── tracks/ # 7 procedurally-generated tracks
│ ├── population.js # Selection + reproduction
│ ├── curriculum.js # Plateau detection + level advance
│ └── viz/ # Three.js scene, camera, HUD
├── trainer/
│ ├── headless.js # Non-rendered training loop
│ └── benchmark.js
├── tests/ # Vitest unit tests
└── docs/ # Hero image, screenshots
Tweakable from the CLI or config.js:
| Flag | Default | Meaning |
|---|---|---|
--population |
80 | Cars per generation |
--generations |
500 | Max generations per level |
--plateau-window |
20 | Generations of flat fitness before advancing |
--mutation-sigma |
0.1 | Gaussian noise added to weights |
--elitism |
4 | Top genomes carried unchanged into next gen |
--tournament-size |
5 | Candidates per selection round |
| Track | Generations to clear (median) | Best lap time |
|---|---|---|
| 1 — straight | ~5 | to record |
| 2 — sweepers | ~30 | to record |
| 3 — chicanes | ~80 | to record |
| 4 — hairpins | ~140 | to record |
| 5 — combo A | ~200 | to record |
| 6 — combo B | ~270 | to record |
| 7 — final | ~350 | to record |
- Save and replay best genome alongside the live population
- Compare two genomes side-by-side on the same track
- Add an "NPCs" mode — load a previously-trained genome as a competitor
- Optional NEAT-style structural mutation (currently weights-only)
- WebGPU port for the training loop
I'm interested in gradient-free learning as a primitive — what circuits look like when they're shaped purely by selection pressure. F1-style racing is a clean test case: dense reward signal (distance), continuous control, easy to visualize, and the curriculum lets you watch competence emerge level by level.
MIT — see LICENSE.
Jose Lopez — AI engineer in Madrid, working on the intersection of biological and artificial intelligence.
- GitHub: @aifriend
- LinkedIn: jafdl
- Website: auto-latam.com
- The classic NEAT line of work (Stanley & Miikkulainen) for the genetic-encoding intuitions
- Three.js for making in-browser 3D approachable
- Every "watch AI learn to do X" YouTube video that's made this style of demo a recognizable genre
