A design pattern for game AI that stays cheap to run and easy to debug.

Building an opponent for a racing game looks like an animation problem and turns out to be an architecture problem. A 2021 kart racing project split the NPC into two parts that never have to negotiate with each other: one drives, the other decides.

Driving runs on waypoints. Each one is a marker on the track and the kart simply aims for the next in line. It is a plain technique, and it means the designer controls the racing line by moving markers rather than by rewriting code.
Decisions sit on top. A decision tree checks conditions such as an obstacle ahead or a rival alongside, then returns an action. Because the tree is separate from the steering, behaviour can change without touching the movement code.

There is no training phase. Nothing has to be recorded, labelled, or tuned overnight, which matters when a small team is on a deadline. Rules stay readable, so a strange move on the track can be traced back to the branch that produced it.
The testing backed the design. Black box and white box checks passed, and the lap times came out ahead of the machine learning opponent in the original template. The cost to the frame rate stayed low enough for several opponents to share the track.
The honest caveat is scale. This is one karting game, and a crowded track may need more than a small tree. Start with waypoints and a decision tree, then add complexity when a specific behaviour actually demands it.
Mas'udi et al. (2021). Development of Non-Player Character for 3D Kart Racing Game Using Decision Tree. Fountain of Informatics Journal.