From DNF to Sub-2:00 — How I Used IBM Bob to Fix My AI Racing Car A developer used IBM's Bob assistant to iteratively rewrite the baseline Python driver script from the IBM Bob TORCS University Challenge, turning an AI racing car nicknamed "Max Verstoppin'" from a zero-lap, 1,863-damage run into a clean 02:04.90 lap on the Corkscrew track. The key fix split the forward sensor cone into separate straight-line (max) and corner-guard (min) signals, letting the car hold pace on open straights while braking only when the road ahead genuinely closed in. I spent 1.5 days getting TORCS installed, named my AI driver Max Verstoppin' , and watched him immediately drive full speed into a wall. This is the story of how IBM Bob and I turned him into Max Verstappen. The IBM Bob TORCS University Challenge https://skills.yourlearning.ibm.com/activity/PLAN-6FA860C53B3A gives you a base Python script — snakeoil3 gym.py — that controls an AI car in TORCS https://ibm.box.com/v/TORCSdownloadzip , an open-source racing simulator. The goal: use IBM Bob as your crew, improve the script, and post the fastest clean lap on the Corkscrew track from a standing start with zero damage. Simple brief. Brutal execution. The baseline drive example function looked like this: target speed = 300 R 'steer' = S 'angle' 15 / PI R 'steer' -= S 'trackPos' .10 if S 'speedX' < target speed - R 'steer' 50 : R 'accel' += .01 else: R 'accel' -= .01 No brakes. No corner awareness. A steering multiplier of 15 that caused the wheel to thrash violently. A target speed of 300 km/h regardless of what's ahead. Max hit 237 km/h , missed the first corner entirely, and sat pinned against the barrier wall until the server hit the 100,000 step limit. Laps: 0. Damage: 1,863. Top speed: 237 km/h. I pointed IBM Bob at the script and asked it to analyse what was wrong. It broke down drive example and flagged three core issues immediately: brake key in the action dictionary was never touched 15 caused severe oscillation Once I understood the problems, the approach was straightforward: fix one or two things at a time, run a lap, read the telemetry, feed it back to Bob, identify the next bottleneck. Eight iterations. Alpha through Iota. The first set of changes: 15 to 10 track 8 , track 9 , track 10 the forward-facing sensors to calculate a corner-aware target speed instead of a hardcoded 300 forward dist = min track 8 , track 9 , track 10 target speed = max 60, min 220, forward dist 1.1 speed excess = S 'speedX' - target speed if speed excess 20: R 'brake' = min 1.0, speed excess - 20 / 80.0 R 'accel' = 0.0 Max finished his first ever clean lap. 02:24.17. Zero damage. He was slow — the 1.1 multiplier meant a 100m straight-line sensor reading only set a target of 110 km/h — but he was clean. Foundation laid. Over the next three runs, the multiplier was progressively tuned upward: | Run | Multiplier | Lap Time | Top Speed | |---|---|---|---| | Gamma | × 2.0 | 02:15.40 | 217 km/h | | Epsilon | × 2.5 | 02:06.71 | 225 km/h | Each step brought more straight-line speed while the braking system kept damage at zero. The brake ramp divisor was also tightened to make the car scrub speed faster when approaching corners at higher speeds. This was the most important change of the whole project. The sensor logic was using min across a five-sensor forward cone to set target speed. The problem: when Max drifted toward the track edge, the diagonal sensors in that cone were pointing at the boundary wall and returning short readings — sometimes 20–30m — on a completely open straight. The car thought a corner was coming and backed off the throttle for no reason. The fix was to split the sensor reading into two separate signals: MAX of the cone → straight-line pace one clear sensor is enough straight dist = max track 7 , track 8 , track 9 , track 10 , track 11 MIN of the tightest 3 → corner guard only fires when road ahead closes in corner dist = min track 8 , track 9 , track 10 target speed = max 50, min 280, straight dist 2.8 if corner dist < 55: corner cap = max 50, corner dist 2.8 target speed = min target speed, corner cap As long as any forward sensor sees clear road, Max pushes. Corner braking only triggers when the road directly ahead is genuinely closing in. 02:04.90. 228 km/h. 0 damage. Not every run went forward. Lowering the corner detection threshold to < 45m from < 60m delayed braking too long into the final tight turn. Max ran wide onto the grass, lost grip, and the lap time went up by 6 seconds despite zero damage. The off-track excursion didn't register as damage in TORCS — grass just kills speed. A good reminder that the metrics don't always tell the full story. Theta split the difference: threshold < 55m , multiplier 2.8 . New PB at 02:02.42 . Iota fixed the last remaining issue — Max was loitering along the track edge after corner exits, not punching the throttle onto the straight. Two causes: straight dist . Fix: fall back to dead-ahead sensor only when |trackPos| 0.5 |steer| < 0.1 — too tight. The shallow correction Max held while returning to centre never qualified. Relaxed to 0.2 . 0.15 → 0.30 so he snapped back to the racing line faster. track pos = S.get 'trackPos', 0 if abs track pos 0.5: straight dist = track 9 dead-ahead only — ignore lying diagonal sensors else: straight dist = max track 7 , track 8 , track 9 , track 10 , track 11 centering gain = 0.3 if abs track pos 0.5 else 0.15 R 'steer' -= track pos centering gain 01:58.15. 235 km/h. 0 damage. Sub-2:00. | Run | Lap Time | Top Speed | Damage | Key Change | |---|---|---|---|---| | Alpha | DNF | 237 km/h | 1,863 | Baseline — blind throttle, no brakes | | Beta | 02:24.17 | 196 km/h | 0 | Braking, corner-aware speed | | Gamma | 02:15.40 | 217 km/h | 0 | dist × 2.0 multiplier | | Epsilon | 02:06.71 | 225 km/h | 0 | dist × 2.5 , tighter brake ramp | | Zeta | 02:04.90 | 228 km/h | 0 | Dual max / min sensor logic | | Eta | 02:11.21 | 228 km/h | 0 | ⚠️ Regression — threshold < 45 too aggressive | | Theta | 02:02.42 | 229 km/h | 0 | Threshold < 55 , multiplier 2.8 | | Iota | 01:58.15 | 235 km/h | 0 | Edge sensor fix, adaptive centering | The most useful thing IBM Bob did wasn't writing code — it was helping me understand what the code was actually doing before I changed anything. Once I could see exactly why Max was crashing no brakes, blind speed or slowing down sensor reading the wall on a straight , each fix became obvious. The iterative loop — run, observe, diagnose, fix — is also just good engineering. Every regression Eta taught something. Every metric mattered: lap time, damage, and top speed together told a more complete story than any one number alone. I also completed the IBM Granite Models for Software Development course on IBM SkillsBuild during this project and earned the official badge, which gave me solid context for how these models reason about code — useful when you're having technical back-and-forth with Bob about sensor arrays and braking logic.