Bug or feature?

Originally posted to Shawn Hargreaves Blog on MSDN, Tuesday, December 29, 2009

Writing about randomness reminded me of an interesting bug in the first commercial game I ever released. Extreme G was a futuristic racer for the Nintendo 64. Each vehicle had a limited number of turbo boosts, which increased your speed as long as you drove cleanly, but cut out as soon as you clipped the edge of the track. It was impossible to corner cleanly at turbo speed, so the best tactic was to save your boosts for the longest straight section, while trying to knock boosting opponents off the track.

Ash (the lead programmer) wrote some AI code that decided when the computer players should use their turbo. They would boost when they reached a straight piece of track, and also (for variety) at occasional randomly chosen intervals.

Shortly after the game came out, we read a review that went something like:

"We especially loved the aggressive AI, which does everything in its power to stop you overtaking. If you pass a computer player, they will fight back by firing their turbo, even midway through a turn where that is sure to cause a massive pileup. Perhaps not the best winning strategy, but an awesome f-you attitude!"

"Hah!", quoth Ash, "what a foolish reviewer! That's not how it works. I bet the random number generator just happened to trigger its turbo at the same time he was overtaking, and he read too much into that. My actual AI code is nowhere near so subtle."

But then he went back to look at this actual AI code :-)

A universal challenge of racing game design is how to keep the pack of vehicles close together. If they become too widely separated, the player can be left racing on what appears to be an empty track, with the computer players out of sight in front or behind them, which is no fun at all. In Extreme G, this problem was exacerbated by two things:

To help keep the pack together, Ash biased the turbo AI based on race position. When a computer player was in front of the human they would rarely use their turbo, but if they were far behind they would fire it in an attempt to catch up.

What he meant to write was:

    if (computerPlayer is behind human)
    {
        ushort difference = human.TrackPos - computerPlayer.TrackPos;

        if (difference > catchUpThreshold)
            FireTurboBoost();
    }

The TrackPos field held a 16 bit counter of how far around the track each vehicle was, normalized so that 0 represented the starting line, 32768 was halfway around, and 65535 was a full lap nearly back to the start. This format was convenient because the rounding rules of integer arithmetic automatically handled the modulo operations necessary when comparing positions from different laps, or from either side of the start line.

Thanks to an order of processing bug, what actually happened was:

    if (computerPlayer is behind human)
    {
        ushort difference = human.TrackPos - computerPlayer.TrackPosFromPreviousFrame;

        if (difference > catchUpThreshold)
            FireTurboBoost();
    }

This worked as intended, except when you overtook an AI vehicle:

Oops!

But the reviewer was absolutely right. Although accidental, the resulting behavior was good gameplay. We left this bug unchanged in the sequel.

Blog index   -   Back to my homepage