Simplifying IT, Amplifying Knowledge

Racing AI

Published on: 28 November 2025 Category: programming

So once again, I started programming things that I "always wanted to try," and I also did a bit of modern "vibecoding". I've never really enjoyed the early stages of a project when "nothing works yet," so I often use a method where I let AI write some tragic, but partially functional code that gets things moving, and then I rewrite it about twenty times to make it work the way I want it to. And yes, I admit, it's several times more work, hassle, corrections, etc. But after the first twenty minutes of work, I have something moving, and that fills me with the desire to fix it and finish it the way I want. Don't judge me.

This time, I decided to revive my efforts to create a 2D racing game from a top-down perspective. As I already mentioned, my first attempt, where I controlled a car that drove along a marked route and slowed down when it drove onto grass, was completed quite quickly. I gradually fine-tuned the walls that cannot be driven through and generate collisions, and I was quite satisfied with the basic concept. But then I realized that I needed someone to race against. So I let LLM whip up a simple first draft of AI for my vehicles. Surprisingly, it all looked pretty good at first glance, and after some initial "vibecoding" and tweaking with LLM, I achieved pretty decent results and continued tweaking on my own. I came to the conclusion that a fairly simple "radar" would suffice, one that would send out beams at several angles in front of the car, detect what was in front of it, and select the path with the fewest obstacles, which it would then try to follow.

Paradoxically, this very simple solution worked quite well. Of course, it took some time to fix out all the issues, but in the end, the car raced around the track quite well, and due to the absence of a sophisticated turning and acceleration/deceleration model, it raced through the turns at full speed. The problem arose when more AI was added, with varying degrees of "aggressiveness," and the vehicles caught up with each other. It was therefore necessary to implement an overtaking mode.

The AI began to use "radar" to scan the vehicles it was approaching. If the vehicle in front was close enough, the overtaking mode was activated, and the radar began to focus on the sides of the car, evaluate the width of the road, and attempt to overtake. This was a bit more challenging, but in the end, we managed to implement it and everything started to work.



Racing AI thumbnail

Once I had a fairly decent AI, I decided to add a more sophisticated circuit to the game, one that was more realistic and wider. Suddenly, however, problems with the AI that had been working on the narrow track began to appeared. The problem was that the cars often managed to "get lost" in the corners. In other words, the car still had plenty of road ahead of it until it reached the outer apex of the corner. But then it got stuck because there was no other way forward, as there was not enough space to "turn" the corner.

So I added another layer of radar that tried to predict turns and steer the vehicle in one direction or another so that it would start turning in time. After a lot of tinkering, I finally succeeded, and because I'm lazy, I let LLM do some of the work again to implement recovery logic in case the car really got stuck, so that it would go into rescue mode and start backing up. I didn't want to rewrite the entire decision sequence by hand, so LLM did that for me, and I then started implementing the rescue steps. In the end, I managed to successfully detect that the car was in a dead end and start the rescue by reversing. Unfortunately, it wasn't very good at detecting "stuck" situations, so the cars often ended up in an endless mode of jerking in reverse.

Racing AI thumbnail

So I tried to discuss with LLM that she could try to implement pathfinding that would predict the route far enough ahead, interpolate the curve, and then the vehicle would try to stick to it. After several attempts, which often resulted in the car either driving in endless circles because the width of the track allowed it to turn at full speed, or driving in the opposite direction, or into a strange dead end, where the pathfinding calculated immediately after the start to drive "full speed" into a wall. At that moment, it seemed so hopeless and annoying that the results were worse in all cases than driving according to the "radar," so I continued with the radar method and managed to get it far enough that the cars drove quite nicely around the circuit, and with extended corner prediction, it actually completed 10 laps without a hitch.

But something else started to bother me. The physics model was completely inadequate for making the driving look smooth, and the gameplay wasn't much fun either. All turns could be taken at full speed because the turning radius was fixed and did not depend on speed. So I had to improve the physics engine, and while I was at it, I also implemented the influence of tires and their wear.

So, I used LLM again to design a quick framework and basic implementation, which saved a lot of time, and then I set out to fine-tune the effects of grip, wear, tire temperature, and so on. Writing the basic framework didn't take too long, and the foundation was quickly ready and functional, but in the end, it required a lot of time to fine-tune the details and various constants. In retrospect, it would probably have been better to implement the individual parts gradually, rather than letting LLM throw everything in at once (wear, temperatures, tire types, the effect of speed on radius, etc.), because then fine-tuning the individual effects, their ratios, and the overall effect of individual things on the driving model proved to be too chaotic and time-consuming.

In the end, however, I had a driving model that felt very natural and seemed very close to reality. What proved to be a problem, however, was my AI. Using a relatively simple, albeit two-layer, radar, it was no longer possible to make sufficiently good decisions in this situation. The cars entered combined turns at such angles that it was impossible to navigate the next turns, or they were unable to brake in time, and even after thorough tuning, optimization, and further attempts, the final options were unsatisfactory. Either the AI drove unbearably slowly everywhere in order to take the turns, or it simply crashed everywhere and got stuck in a turn every few moments, or it needed some form of "cheat," which LLM often suggested. He often came to the conclusion that it was necessary to add a hard braking function to the vehicle implementation, which meant that the AI would essentially negate the physical model and simply "stop on the spot."

Racing AI thumbnail

So I was once again in a situation where AI practically always ended up in the first turn. Unfortunately, I came to the conclusion that radar simply wouldn't be enough here, and that without sophisticated pathfinding, it simply couldn't be solved. So I turned to LLM again to fire off the basis for pathfinding. This was probably the longest "vibecoding" session because LLM simply refused to grasp it correctly. It kept generating routes in a constant circle, or suspiciously often thought that it was a classic old "go up" game, and so when instructed to prefer a more forward path, it generated routes towards the top edge of the canvas. In the end, I used the skeleton and started doing it "my way."

After many hours of suffering, I at least got to the point where the AI was trying to drive along the track a little. However, more problems are emerging than solutions are being found.

Racing AI thumbnail

When searching for track optimizations, cars often tend to try to get as close to the walls as possible, trying too aggressively to hit the apex, so that they end up hitting the wall sideways, or, conversely, they drive through a sharp turn almost straight. Any attempts to correct between these two states always ended in completely crazy behavior that was completely unpredictable.

Finally, when I managed to correct the pathfinding more or less to the track, I encountered another problem. It often happened that the pathfinding algorithm found a path right through a solid wall. But that didn't make any sense to me, given the number of different protections, optimizations, and checks. It took me an unreasonably long time to realize that it simply happens that the points on the found route are too far apart, so it can happen that two adjacent points are both on the track, but the line between them intersects a barrier (as can be seen in the screenshot above). So there were two solutions. The first, which checked whether the line segment intersected the wall, proved to be too computationally intensive and caused lag. The second solution, which at first glance seemed more demanding than the first, was simply to increase the density of the points so that the wall could not fit between them. Paradoxically, this turned out to be quite lenient in terms of computation time and solved the problem.

However, this problem helped me with debugging and the final repair of the "recovery" system, because I had more than enough test situations. So now the AI can initiate a recovery plan, back up, and return to pathfinding!

Racing AI thumbnail

I reached a stage where I was able to create curves for the vehicles to follow relatively easily and without any collisions. Unfortunately, it often happened that the route ended up leading into a wall. It was therefore necessary to implement a certain level of recursion, whereby if the generated path was shorter than X points, it was necessary to note that "this way does not lead anywhere," return to the beginning of the path, and try another route. However, as I later discovered, it is necessary to limit recursion sufficiently well so that it searches for a "passable" route, and not the best possible one, because this again leads to a drastic increase in computation time, and in fact it is not necessary at all.

All this optimization led to a significant improvement, and the cars now follow the racing lines quite nicely. Unfortunately, this had the side effect of bringing back the problem I solved at the beginning of my work with complex pathfinding: vehicles often reach the point of recursion where, if they can't go into a wall, it's better to turn around and go back, or just spin in circles. On the other hand, if I try to fix this behavior by preferring a "straighter" path, it often leads to the AI being unable to take a sharper turn, so it has to crash and reverse several times.

In conclusion, this is the current state of affairs, but it seems to be the most reliable way to complete at least two laps without crashing. So, if I don't lose my nerve, you may see a sequel in the future. However, if you were expecting a happy ending with at least one lap completed, I'm afraid I'll have to disappoint you.

Keep your fingers crossed for me with the tuning.


Comments:

×