← Back to projects

First Place in CEDC AI Grand Prix 2026

Project Overview

For the CEDC AI Grand Prix 2026 I trained an AWS DeepRacer car: a small convolutional network that sees one grayscale camera frame and chooses a steering angle and speed, fifteen times a second.

The project started with a surprise. A plain linear regression on the track's waypoints drove every track perfectly. The rest of the project was about teaching the car's camera to see what those waypoints already knew. Along the way: an expert driver hidden inside the reward function, a physics-based racing line, and a car that learned to wobble from its own teacher.

1st
on the leaderboard
100%
lap completion
5.55 s
best lap (was 11.09 s)
Reinforcement LearningImitation LearningControl TheoryReservoir ComputingHPC

1. The Surprise: Linear Regression Gets 100%

I didn't start with the competition car. I started with a question about brains: could a reservoir computer, a fixed random recurrent network with a trained linear readout, learn to drive the way a motor cortex might? To test it fast, I wrote a Python simulator on the real DeepRacer track files and gave the controller what the training environment knows: the car's offset from the center line, its heading, and the bearing to six points on the track ahead (0.4 to 3.5 m).

I trained the reservoir online with FORCE learning against a simple expert (pure-pursuit steering) and compared it with the most boring baseline I could think of: a linear readout on the same inputs, with no reservoir at all.

The linear readout won. It completed 100% of its laps on the training track and on five tracks it had never seen. The 600-neuron reservoir did worse on the hard tracks, because its memory learned quirks of the training track.

FORCE reservoir vs linear readout paths on six tracks
Trained on one track, tested on six. Green: linear readout. Blue: FORCE reservoir. Red: offline ridge regression, which never saw its own mistakes and crashed. Red ✕ marks an off-track.

The reason is almost embarrassing. The bearing to a point on the line ahead is nearly the answer: pure-pursuit steering is roughly a scaled copy of it. When you can see the road ahead, steering is close to a linear function of what you see. Try it below. Collect 100 seconds of an expert driving with random wobble, fit seven numbers by least squares, and let those seven numbers drive.

Interactive Demonstration: A Linear Readout Driving

Steering = w · (bearings to six center-line points ahead) + b, fitted by least squares. Speed is fixed by the slider. The blue rays are the six inputs.

Fitted weights

When I ran it, the fit reached R² = 0.999 and the readout drove 22 laps out of 22 without leaving the track. Look at the fitted weights: almost all of the weight lands on the 0.8 m and 1.2 m bearings, either side of the teacher's own lookahead distance at this speed (0.95 m). The regression has rediscovered how its teacher steers.

There's a catch, and it shaped everything that came after. Those bearings come from the track map, and the real car never gets a map.

2. The Real Car Only Has a Camera

In the competition, the car's brain is fixed: the evaluator loads only weights into a standard network. At race time it gets one 160×120 grayscale frame and nothing else: no position, no map, no memory of earlier frames.

Camera
160×120 gray
→
Conv 32 · 8×8
stride 4
→
Conv 64 · 4×4
stride 2
→
Conv 64 · 3×3
→ 11,264 features
→
Dense 512
ReLU
→
Linear readout
→ one of N actions

Purple: the layers that have to learn to see. Blue: the controller.

Look at the last layer: it's a linear readout. Section 1 showed that a linear readout steers well when its inputs encode the geometry of the road ahead. So the plan became clear:

Train the convolutional layers to extract the lookahead geometry from pixels, and the final linear layer can do the rest.

3. Hiding the Expert Inside the Reward

DeepRacer trains with reinforcement learning (PPO). You don't label data; you write a reward_function(params) that scores each step. The trick is that during training, params holds exactly the privileged information the car never sees: its position, its heading, and every waypoint of the track.

So I put the expert inside the reward. Every step, the reward function works out what pure pursuit would do from the car's true position, and pays the network for choosing the same thing:

steer_score = exp(−½ · ((steering − teacher_steering) / 6°)²)
speed_score = exp(−½ · ((speed − teacher_speed) / 0.5 m/s)²)
reward      = steer_score · (0.4 + 0.6 · speed_score)        (0.001 if off track)

To earn reward, the network has to match a teacher that is steering by the map, so its convolutional layers have to learn to see where the road goes. I also built the action space from the teacher itself: I drove it with added steering noise, recorded every command, and clustered them into 15 steering/speed pairs. That way the actions it needs, including recoveries, all exist.

v1 (pure pursuit on the center line) trained on a laptop and scored 100% completion with an 11.09 s best lap. It worked. Next, it needed to be fast.

4. A Faster Teacher: Physics and the Racing Line

A student can only be as fast as its teacher, and the center-line teacher wasn't fast. So I did what racers do.

First, I measured the car. From 17,000 logged simulator steps I compared commanded steering with how sharply the car actually turned. Its effective wheelbase is 0.334 m, twice the nominal value, so the old teacher had been under-steering. Sideways grip holds to about 9 m/s², while the old teacher planned corners at 3.6.

Then I solved for the fastest path. Each center-line point may slide sideways by αi, staying inside the track. I minimize total squared curvature, which is a bounded linear least-squares problem:

p_i = c_i + α_i · n_i          |α_i| ≤ half_width_i − margin
minimize   Σ ‖ p_(i−1) − 2·p_i + p_(i+1) ‖²

Finally, a speed for every point. Corner speed is set by grip, v = √(alat / κ). Then a backward pass adds braking zones and a forward pass limits acceleration. On re:Invent 2018 the sharpest bend drops from 3.76 to 2.03 per meter, and the ideal lap gets about 15% faster. Move the grip slider to see how the speed plan changes:

Interactive Demonstration: Racing Line Explorer

Color = target speed: blue is slow, orange is fast. Watch the braking zones appear before each corner.

The new teacher (pure pursuit on this line, using the measured wheelbase) laps about twice as fast as the old one in my simulator: 5.27 s against 10.73 s.

5. What the Reward Actually Looks Like

From the network's side, each step is a multiple-choice question: which of my 18 actions does the reward like best, given where I am? Drag the car around the track below. The heatmap shows the reward for every possible steering and speed. White dots are the 18 actions the network can choose from, and the orange ring is the best one. Click the heatmap to inspect another action. Turn the car with the heading slider to see the reward pull it back toward the line.

Interactive Demonstration: Reward vs. Position

Drag the car · green dot = the teacher's pure-pursuit target · faint blue = racing line

A few things to notice:

  • On straights the bright region sits at high speed, and entering a corner it shifts to the teacher's steering angle at a lower speed.
  • Steering is the gate. The wrong steering scores near zero at any speed, because the reward multiplies the steering score by everything else.
  • The reward width (σ = 6°) roughly matches the gap between neighboring actions, so the closest available action still scores well.

6. Scaling Up, Then a Wobble

PPO needs a lot of driving, so I moved training to CU Denver's Alderaan cluster. DeepRacer-for-Cloud expects Docker, and the cluster only offers Singularity, so I rebuilt the pipeline:

  • The trainer runs directly instead of through Docker.
  • An S3 gateway stores model files as plain files on the cluster's shared filesystem.
  • 8 Gazebo simulators run in parallel on one 64-core node.

Checkpoint 165 of the racing-line model went to the leaderboard: 100% completion, 5.542 s best lap, first place, half the time of v1.

But in my own 10-lap tests it went off the track in 3 laps out of 10. I recorded video of every failure:

Fastest clean lap at real speed, then every off-track at half speed.

Every failure looked the same: a wobble that grows. The car swings across the line, each swing bigger than the last (±15, then ±30, then ±50 cm), flipping between full lock left and right at 4 m/s until it leaves the track.

Clean laps vs laps with off-tracks
Left: 7 clean laps. Right: the 3 laps with off-tracks; red ✕ marks where the car left the track.

7. Fix the Teacher, Not the Student

My first guess was that the network hadn't learned well enough. The data said otherwise. Replaying the failure laps through the reward, the car's steering was within 3.6° of the teacher's during the wobble, about the same as on clean laps (3.3°). The car was faithfully copying a teacher that wobbles.

Pure pursuit has no damping. The simulator also reacts a step or two late (about 67–133 ms), so each correction lands after the car has already moved, overshoots, and the next correction overshoots further. This is the same delay effect my early brain-inspired experiments ran into. The fix:

  • A longer lookahead: 0.55 + 0.35·v instead of 0.45 + 0.25·v.
  • A recovery rule: the teacher slows down by up to 20% when the car is pointed away from the line or far from it.

I tuned both on a grid of settings in the simulator, with a deliberate delay added. Below, both teachers drive at once with the same delay. At 0 steps of delay they both look fine; move it to 2 and watch the orange car. Over 50 laps at 133 ms of delay, the v2 teacher goes off track about 3 times per lap, while the damped teacher averages about one off-track every four laps.

Interactive Demonstration: Delay vs. Damping

Both cars pick the nearest of the same 18 actions to their teacher's command, like a trained network would.

Damping the teacher wasn't enough on its own, because the network has no memory. So I gave the reward function a little memory instead. Each simulator worker keeps its reward function loaded between steps, so the reward can remember the previous step:

reward = v2_score · (0.6 + 0.2·heading_score + 0.2·line_score)
       · (0.5 + 0.5·exp(−½·(unexplained_jerk / σ(speed))²))      ← jerk the teacher didn't ask for
       · 0.5  if steering flips left↔right above 10° at speed       ← the wobble's signature

The action space stayed the same, so v2.1 continued training from checkpoint 165 instead of starting over. Its best checkpoint, 649, drove 10 clean laps out of 10 in the same test where 165 managed 7, and it scored my best leaderboard result yet.

8. The Phases

PhaseTeacher in the rewardTrained fromLeaderboard
v1: imitatePure pursuit on the center linescratch (laptop)100% · 11.09 s
v2: raceRacing line, measured physics, 6.5 m/s² cornersscratch (HPC, 8 workers)100% · 5.542 s · score 5.615
v2.1: steadyDamped teacher + heading, line, jerk and flip termscheckpoint 165100% · 5.550 s · score 5.610
v2.1 phase 2: paceSame, but paid for lap progress per step + a finishing bonuscheckpoint 649training now

The leaderboard ranks completion first, then a lower time-based score. That's why v2.1 traded a few hundredths of a second of best lap for never leaving the track.

The phases build on each other because the action space never changes, so each phase can start from the previous checkpoint. First, learn to see the road (imitation). Then learn a fast line. Then learn to stay steady on it. Last, learn to push.

Key Findings

Linear is enough, with the right features

A linear readout on lookahead bearings drove every track. The hard part is perception, so I shaped the CNN to produce those features.

Put the expert in the reward

Reward functions see the map; the car doesn't. Scoring actions against a privileged teacher turns reinforcement learning into fast imitation learning.

Fix the teacher, not the student

The wobble wasn't a training failure; the network copied a teacher that oscillates under delay. Damping the teacher fixed the student.

Lessons

  • Test in a cheap simulator first. Every idea here was checked in a Python simulator calibrated to the real one, minutes before it got hours of cluster time.
  • Train in closed loop. Models fitted only on an expert's driving never see their own mistakes. FORCE, DAgger and PPO all learn on the states the learner itself causes.
  • Delay changes everything. A controller that's perfect with instant feedback can oscillate with 100 ms of lag, whether it's a reservoir, a teacher, or a CNN.
  • Look at the failures. One video and one replay of the trace explained more than a day of training curves.

Acknowledgments

This project moved fast because I built it with Claude (Anthropic), working through Claude Code, as a hands-on collaborator. I set the direction (the questions, the ideas to try, and the calls on what to keep), and Claude helped turn them into working experiments, often within the hour:

  • building and calibrating the Python track simulator, the FORCE reservoir and delay experiments, and the racing-line solver;
  • porting DeepRacer-for-Cloud from Docker to Singularity on the Alderaan cluster, and debugging it one failed job at a time;
  • digging through simulator logs and failure videos, which is how the wobble was traced back to the teacher;
  • writing the reward functions, the evaluation scripts, and the interactive demos on this page.

Most of the useful turns came from checking an idea quickly and looking honestly at the result, including the ones that didn't work. Having a collaborator that could build, test and report back that fast is a big part of how the project got from a linear regression to first place in a few days.

Built with DeepRacer-for-Cloud (simapp 6.0.6) on CU Denver's Alderaan cluster. The demos run a JavaScript port of my calibrated simulator on the re:Invent 2018 track.
Gunnar E
© 2026 All Rights Reserved