Mechanic · time & pacing · racing
Rubber-band AI
Computer-controlled racers speed up when behind the player and ease off when far ahead, keeping the pack close.
On this page
Rubber-band AI keeps computer racers near the player. Mario Kart 64 marks a CPU driver with a flag the decompilers name for rubber-banding, and the flag adds to the kart’s forward force1.
- Power boostMario Kart 64 (1996). A flag the decompilers call rubber-banding pulls a CPU kart's added force toward 380 while a pacing check passes.1
- Time scaleExcitebike 64 (2000). Every CPU rider's physics run up to 15% faster or slower by how far the CPU pack leads the humans, around a lead of 30.2
- Speed stepRidge Racer (1993). Behind the player, the car in slot 0 aims at 1.01 times its field 0x10C; well ahead, at 80% of the lower of its usual target and its field 0xF8.3
Games
| Game | Year | How | Note |
|---|---|---|---|
| Mario Kart 64 | 1996 | Power boost | a flag the decompilers call rubber-banding pulls a CPU kart's added force toward 380 while a pacing check passes |
| Excitebike 64 | 2000 | Time scale | every CPU rider's physics run up to 15% faster or slower by how far the CPU pack leads the humans, around a lead of 30 |
| Ridge Racer | 1993 | Speed step | behind the player, the car in slot 0 aims at 1.01 times its field 0x10C; well ahead, at 80% of the lower of its usual target and its field 0xF8 |
Every claim about this mechanic, by game (3)
References
- ^ abIn the US version of Mario Kart 64,
CPU_FAST_EFFECT, which the decompilers comment as the "CPU Rubberbanding flag", raises a CPU driver's forward force: while it's set, a term in that force moves toward 380 by half the gap each update, and once it's cleared the term falls toward 0 by a tenth of the gap.regulate_cpu_speedsets the flag on a CPU driver below its target speed when the pacing checkfunc_800088D8returns true, and clears it otherwise.Coden64decomp/mk64How we know
479#define CPU_FAST_EFFECT 0x200000 // CPU Rubberbanding flaginclude/defines.h line 479 at 58cfcb0 · upstream ↗ 3086 if ((player->type & PLAYER_HUMAN) != PLAYER_HUMAN) { 3087 if ((player->effects & CPU_FAST_EFFECT) == CPU_FAST_EFFECT) { 3088 move_f32_towards(&player->unk_0E8, 380.0f, 0.5f); 3089 } else { 3090 move_f32_towards(&player->unk_0E8, 0.0f, 0.1f);
src/player_controller.c lines 3086–3092 at 58cfcb0 · upstream ↗ 3107 var_f2 = (player->unk_08C + player->unk_0E8 + player->boostPower + player->unk_0E4) - player->unk_0A0;src/player_controller.c line 3107 at 58cfcb0 · upstream ↗ 863void move_f32_towards(f32* startingValue, f32 targetValue, f32 somePercent) { 864 *startingValue -= ((*startingValue - targetValue) * somePercent);
src/render_player.c lines 863–864 at 58cfcb0 · upstream ↗ 203 if (var_a1 != 1) { 204 if (speed < targetSpeed) { ⋮ 206 player_accelerate_alternative(player); ⋮ 209 } else if (func_800088D8(playerId, gLapCountByPlayerId[playerId], 210 gGPCurrentRaceRankByPlayerIdDup[playerId]) == true) { 211 player->effects |= CPU_FAST_EFFECT; 212 player_accelerate_alternative(player); 213 } else { 214 player->effects &= ~CPU_FAST_EFFECT; 215 player_decelerate_alternative(player, 1.0f);
src/cpu_vehicles_camera_path/cpu_speed_control.inc.c lines 203–216 at 58cfcb0 · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-22.
Checked by a second AI model, 2026-10-22.
- ^In Excitebike 64's US Rev 1 build, riders are rubber-banded through
timeScale, which multiplies the length of each of a rider's physics steps (gPhysDeltaTime). After the countdown, outside time trial and with a human racing,race_update_riderssteps each CPU rider's scale by 0.0002 per update toward a target: 1 plus its skill (spread evenly over about ±0.1 byrace_init_ai_skills, and used when there are two or more CPU riders), less 0.1 when it's more than 64 ahead of the CPU riders' averageraceDist, less the CPU pack's lead over the humans' average beyond 30, divided by 256 and held to ±0.15, all times the track'scpuSpeedfor the difficulty (a finished rider targets 1, the Beat This cheat uses the hardest difficulty'scpuSpeedplus 0.05, and a duel 1.15 times the track's). The code's comment says "CPU riders slow down as their pack leads the humans by more than 30 m and speed up as it falls behind". A human's scale steps by 0.001 toward 1 plus their distance behind the humans' average (plus 16 when it's over 2) over 1024, held between 0.95 and 1.05. With one human the target is 1, and since the step has no dead zone the scale alternates between 1 and 1.001. Mario Kart 64 instead adds a force term toward 380 while a pacing flag is set (mk64/rubber-band).Codechasmlol/excitebike64 · n64decomp/mk64revisedHow we know
2529 gPhysTicks = ticks; 2530 gPhysDeltaTime = (f32)gPhysTicks * rider->timeScale / (f32)FRAME_RATE;
src/game/bikenew.c lines 2529–2530 at 9cdb194 · upstream ↗ 541 } else if (gGame.mode != GAME_MODE_TIME_TRIAL && gEvent.state >= 5) { 542 /* after the countdown: CPU riders slow down as their pack leads the humans by more than 30 m and speed up as 543 it falls behind, around the track's speed for the difficulty and their own skill; humans close up */ 544 cpuLead = ((cpuAvg - humanAvg) - 30.0f) * 0.00390625f; 545 if (cpuLead > 0.15f) { 546 cpuLead = 0.15f; 547 } else if (cpuLead < -0.15f) { 548 cpuLead = -0.15f; ⋮ 552 if (rider->isCpu != 0) { 553 scale = 0.0f; 554 if (numCpu >= 2) { 555 scale = rider->aiSkill; ⋮
src/game/unnamed/s0_80062F50.c lines 541–601 at 9cdb194 · upstream ↗ 790/* Spreads the CPU riders' skills evenly from -0.1 to 0.1 in sAiRiderOrder (then +0.01 for exactly 0.1, -0.01 for 791 the others) and sets their time scale to 1.03 - skill. */ ⋮ 800 gRiders[sAiRiderOrder[i]].aiSkill = (i - (count - 1) * 0.5f) / ((count - 1) * 5.0f);
src/game/unnamed/s0_80062F50.c lines 788–807 at 9cdb194 · upstream ↗ 479#define CPU_FAST_EFFECT 0x200000 // CPU Rubberbanding flaginclude/defines.h line 479 at 58cfcb0 · upstream ↗ 3086 if ((player->type & PLAYER_HUMAN) != PLAYER_HUMAN) { 3087 if ((player->effects & CPU_FAST_EFFECT) == CPU_FAST_EFFECT) { 3088 move_f32_towards(&player->unk_0E8, 380.0f, 0.5f); 3089 } else { 3090 move_f32_towards(&player->unk_0E8, 0.0f, 0.1f);
src/player_controller.c lines 3086–3092 at 58cfcb0 · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-22.
Revised after a check by a second AI model, 2026-10-22.
- ^In Ridge Racer's Japanese release (SLPS-00001), the computer car in entity slot 0 takes its target speed from func_80022C40 during the race (when its field 0x60 is 0 and the game state isn't 0x1A). Unless its mode field 0xAA is 2: when it's on an earlier lap than the player, or on the same lap and behind the player by more than a margin, its target is 1.01 times its field 0x10C; when it's on a later lap, or more than 0x600 ahead on the same lap, its target is 80% of the lower of the usual target and its field 0xF8. The margin is 0x800, or 0x300 times the player's lap count when
D_801D35D0is 0. Between the two the target is left as it was. WhenD_801D35D0is 0 and the player is on the last lap, the target is held to field 0xF8.Codekazuyette/rr-decompHow we know
10 if (car->hAA != 2) { 11 lim = 0x800; 12 if (D_801D35D0 == 0) 13 lim = D_8007C32C * 3 * 256; 14 if ((car->lap == D_8007C32C && lim < car->dist) || car->lap < D_8007C32C) { 15 *spd = car->x10C / 100 + car->x10C; 16 *acc = car->FC; 17 } else if ((car->lap == D_8007C32C && car->dist < -0x600) || D_8007C32C < car->lap) { 18 v = *spd; 19 if (car->F8 < v) 20 v = car->F8; 21 *spd = v * 8 / 10; ⋮
src/fn/func_80022C40.c lines 10–27 at 3b42b36 · upstream ↗ 40 if (D_801D35D0 == 0) { 41 n = 3; 42 if (D_8007C210 == 1) 43 n = 2; 44 if (D_8007C32C == n) { 45 if (*spd > car->F8) { 46 if ((*spd = car->F8) < car->x88)
src/fn/func_80022C40.c lines 40–46 at 3b42b36 · upstream ↗ 101 if (D_801D34F8 != 0x1A && car == &D_801E9250[0]) { 102 acc = s->wA4; 103 if (s->w60 == 0) 104 func_80022C40(car, &spd, &acc);
src/fn/func_80022F88.c lines 101–104 at 3b42b36 · upstream ↗ 70 if (D_801D77B8 == 0) { 71 d = car->pos - D_8007C260; 72 if (d >= 0) 73 d = d >= half ? d - len : d; 74 else 75 d = half >= -d ? d : len + d; 76 car->E0 = d;
src/fn/func_800217F4.c lines 70–76 at 3b42b36 · upstream ↗ 47 if (D_801D77B8 == 0) { 48 if ((((D_801E90E0 << 16) + c->w8) % (D_801E90E0 << 8) & ~0xFF) == 0x2700 49 && (((D_801E90E0 << 16) + c->w84) % (D_801E90E0 << 8) & ~0xFF) == 0x2800) c->h9C++;
src/fn/func_80025268.c lines 47–49 at 3b42b36 · upstream ↗ 87 D_801D35A0 = 3; ⋮ 93 if (D_8007C210 == 1) 94 D_801D35A0--;
src/fn/func_8001384C.c lines 87–94 at 3b42b36 · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-22.
Checked by a second AI model, 2026-10-22.
Sources
Code
- n64decomp. 2026. “mk64: a decompilation of Mario Kart 64 (n64decomp/mk64).” Commit 58cfcb0. n64decomp/mk64 @ 58cfcb0 · licence, all uses