Mario Kart 64
Nintendo's 1996 kart racer for the Nintendo 64, read through the n64decomp decompilation.
On this page
The atlas reads Mario Kart 64 through the n64decomp team’s decompilation, C that builds the US and European ROMs. Names and comments quoted here are theirs, and the claims cite the code the US build compiles. Nothing has been measured in the game yet.
Each place has its own row of 100 slots1, an item box reads one slot at random, and Grand Prix keeps separate tables for human and CPU drivers1. The engine class sets top speeds of 290, 310, and 320 (4 more for light drivers)2,2. Counter-steering during a drift charges a stage after 18 counts, then 8 more3, and the second stage gives a mini-turbo when the drift ends3. The start boost takes a press of A within 8 race frame counts (20 in Time Trials)4 of its window opening4. CPU drivers carry a flag the decompilers call rubber-banding, which adds to their forward force5.
Mechanics
| Mechanic | Kind | How |
|---|---|---|
| Item distribution | By place | Grand Prix and Versus read a 100-slot row for the driver's place, with separate tables for human and CPU drivers in Grand Prix |
| Item distribution | Flat | Battle reads one 100-slot row whatever the place |
| Drift boost | Stick stages | counter-steering during a drift charges a stage after 18 counts and 8 more for each after; two stages give a 31-update mini-turbo |
| Rocket start | Press window | a fresh press of A within 8 race frame counts of the window opening gives a Mushroom boost; revving to 90% of top speed without it spins the kart out |
| Rubber-band AI | Power boost | a flag the decompilers call rubber-banding pulls a CPU kart's added force toward 380 while a pacing check passes |
| Engine class | Top speed | 50cc, 100cc, and 150cc set top speeds of 290, 310, and 320, with 4 more for the three light drivers |
Every claim about this game, by concept (5)
References
- ^ abIn the US version of Mario Kart 64,
gen_random_itempicks an item as one byte from a 100-byte row of a table, the row for the driver's current place. In Grand Prix, human players readcommon_grand_prix_human_item_curveand CPU driverscommon_grand_prix_cpu_item_curve, and that branch reads no player count. Versus picks one of three tables by the number of players (two, three, or four), and Battle reads one row whatever the place. The slot is a random number from 0 to 100 (random_int(100)) plus the previous slot, a counter that held A, B, and R buttons advance, and the race frame counter, taken mod 100.Coden64decomp/mk64revisedHow we know
3665u8 gen_random_item(s16 rank, s16 isCpu) { 3666 u16 rand = random_int(100); ⋮ 3671 sRandomItemIndex = ((u32) rand + (sRandomItemIndex + gControllerRandom) + gRaceFrameCounter) % 100U;
src/update_objects.c lines 3665–3671 at 58cfcb0 · upstream ↗ 3673 if (gModeSelection == VERSUS) { 3674 switch (gPlayerCountSelection1) { 3675 case TWO_PLAYERS_SELECTED: ⋮ 3678 case THREE_PLAYERS_SELECTED: ⋮ 3681 case FOUR_PLAYERS_SELECTED: ⋮ 3685 randomItem = *((rank * 100) + curve + sRandomItemIndex);
src/update_objects.c lines 3673–3685 at 58cfcb0 · upstream ↗ 3687 } else if (gModeSelection == BATTLE) { 3688 curve = segmented_to_virtual((void*) common_battle_item_curve); 3689 randomItem = curve[sRandomItemIndex]; 3690 } else { // GP Mode 3691 if (isCpu == 0) { 3692 curve = segmented_to_virtual((void*) common_grand_prix_human_item_curve); 3693 } else { 3694 curve = segmented_to_virtual((void*) common_grand_prix_cpu_item_curve); ⋮ 3696 randomItem = *((rank * 100) + curve + sRandomItemIndex);
src/update_objects.c lines 3687–3696 at 58cfcb0 · upstream ↗ 3702u8 gen_random_item_human(UNUSED s16 arg0, s16 rank) { 3703 return gen_random_item(rank, false); ⋮ 3706u8 cpu_gen_random_item(UNUSED s32 arg0, s16 rank) { 3707 return gen_random_item(rank, true); ⋮ 3714 randomItem = gen_random_item_human(gLapCountByPlayerId[arg1], gGPCurrentRaceRankByPlayerId[arg1]);
src/update_objects.c lines 3702–3714 at 58cfcb0 · upstream ↗ 3918 cpu_decisions_branch_item(playerId, &cpuStrategy->branch, 3919 cpu_gen_random_item((s16) gLapCountByPlayerId[playerId], 3920 gGPCurrentRaceRankByPlayerId[playerId]));
src/cpu_vehicles_camera_path.c lines 3918–3920 at 58cfcb0 · upstream ↗ 1205void randomize_seed_from_controller(s32 arg0) { ⋮ 1208 if ((controller->button & A_BUTTON) != 0) { 1209 gControllerRandom++; ⋮ 1211 if ((controller->button & B_BUTTON) != 0) { 1212 gControllerRandom++; ⋮ 1214 if ((controller->button & R_TRIG) != 0) { 1215 gControllerRandom++;
src/code_80057C60.c lines 1205–1215 at 58cfcb0 · upstream ↗ 1269u16 random_int(u16 arg0) { 1270 return arg0 * (((f32) random_u16()) / 65535.0);
src/racing/math_util.c lines 1269–1270 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.
- ^ abIn the US version of Mario Kart 64, in Grand Prix and Versus the engine class sets each kart's top speed from
gTopSpeedTablewhen it spawns: 290 at 50cc, 310 at 100cc, and 320 at 150cc for Mario, Luigi, Donkey Kong, Wario, and Bowser, and 4 more for Yoshi, Toad, and Peach. Extra uses the 100cc numbers, Time Trials always reads the 100cc row, and Battle gives every driver 245.Coden64decomp/mk64How we know
63// Mario Luigi Yoshi Toad D.K. Wario Peach Bowser 64f32 gTopSpeed50cc[] = { 65 // 50CC 66 290.0, 290.0, 294.0, 294.0, 290.0, 290.0, 294.0, 290.0, ⋮ 68f32 gTopSpeed100cc[] = { 69 // 100CC 70 310.0, 310.0, 314.0, 314.0, 310.0, 310.0, 314.0, 310.0, ⋮ 72f32 gTopSpeed150cc[] = { 73 // 150CC 74 320.0, 320.0, 324.0, 324.0, 320.0, 320.0, 324.0, 320.0, ⋮ 76f32 gTopSpeedExtra[] = { 77 // Extra ⋮
src/data/kart_attributes.c lines 63–86 at 58cfcb0 · upstream ↗ 89 switch (gModeSelection) { 90 case GRAND_PRIX: 91 case VERSUS: ⋮ 95 player->topSpeed = gTopSpeedTable[gCCSelection][player->characterId]; ⋮ 98 // Uses 100CC values 99 case TIME_TRIALS: ⋮ 103 player->topSpeed = gTopSpeedTable[CC_100][player->characterId]; ⋮ 106 case BATTLE: ⋮ 110 player->topSpeed = gTopSpeedTable[CC_BATTLE][player->characterId];
src/spawn_players.c lines 89–110 at 58cfcb0 · upstream ↗ 160#define CC_50 0 161#define CC_100 1 162#define CC_150 2 163#define CC_EXTRA 3 164#define CC_BATTLE 4
include/defines.h lines 160–164 at 58cfcb0 · upstream ↗ 187#define MARIO 0 188#define LUIGI 1 189#define YOSHI 2 190#define TOAD 3 191#define DK 4 192#define WARIO 5 193#define PEACH 6 194#define BOWSER 7
include/defines.h lines 187–194 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.
- ^ abIn the US version of Mario Kart 64, while a kart drifts on the ground, holding the stick at least 10 of its 53 steps toward the outside of the turn counts up
driftStateCounter. Letting the stick back after 18 to 99 counts raisesdriftStateby one, up to 3, and sets the count to 10, so each later step takes 8 more counts (computed); letting it back before 10 counts, or at 100 or more, resets both to 0. When a human player's drift ends withdriftStateat 2 or more, the kart getsMINI_TURBO_EFFECTfor 31 player updates (computed), during which a term in its forward force moves toward 580 by a fifth of the gap each update.Coden64decomp/mk64How we know
1148void update_drift_state_counter(Player* player, s8 playerIndex) { 1149 if (((s16) player->unk_0C0 / DEGREES(1)) > 0) { 1150 if (((s32) player->steerPosition >> 16) <= -10) { 1151 if (player->driftStateCounter <= 100) { 1152 player->driftStateCounter++; ⋮ 1157 } else { 1158 if ((player->driftStateCounter >= 18) && (player->driftStateCounter < 100)) { 1159 if (player->driftState < 3) { 1160 player->driftState++; ⋮ 1163 if ((player->driftStateCounter >= 10) && (player->driftStateCounter < 100)) { 1164 player->driftStateCounter = 10; 1165 } else { ⋮
src/player_controller.c lines 1148–1170 at 58cfcb0 · upstream ↗ 4166 if (((player->effects & HOP_EFFECT) != HOP_EFFECT) && ((player->effects & DRIFTING_EFFECT) != DRIFTING_EFFECT)) {src/player_controller.c line 4166 at 58cfcb0 · upstream ↗ 4194 } else if (((player->effects & MIDAIR_EFFECT) != MIDAIR_EFFECT) && ((player->effects & HOP_EFFECT) != HOP_EFFECT)) { 4195 if ((((s16) player->unk_0C0) / DEGREES(1)) > 0) { ⋮ 4198 if ((player->steerPosition >> 16) <= -40) { 4199 player->effects |= DRIFT_OUTSIDE_EFFECT; ⋮ 4201 player->effects |= DRIFT_OUTSIDE_EFFECT; ⋮ 4204 update_drift_state_counter(player, playerIndex); ⋮ 4208 if ((player->steerPosition >> 16) >= 40) { 4209 player->effects |= DRIFT_OUTSIDE_EFFECT; ⋮ 4211 player->effects |= DRIFT_OUTSIDE_EFFECT; ⋮ 4214 update_drift_state_counter(player, playerIndex);
src/player_controller.c lines 4194–4214 at 58cfcb0 · upstream ↗ 487#define DRIFT_OUTSIDE_EFFECT 0x20000000 // drifting toward the outside of your turninclude/defines.h line 487 at 58cfcb0 · upstream ↗ 278 /* 0x007C */ s32 steerPosition; // Where the kart is steering, related to joystick position. Basically, in [-53 to 53], but shifted 16 bits leftinclude/common_structs.h line 278 at 58cfcb0 · upstream ↗ 3956 clamped_x = get_clamped_stickX_with_deadzone(controller); ⋮ 3960 player->steerPosition = (clamped_x << 16) & 0xFFFF0000;
src/player_controller.c lines 3956–3960 at 58cfcb0 · upstream ↗ 4862 if (temp_stickX > 0x35) { 4863 temp_stickY = (temp_stickY * 0x0035) / temp_stickX; 4864 temp_stickX = 0x0035; ⋮ 4866 if (temp_stickX < -0x35) { 4867 temp_stickY = (temp_stickY * 0x0035) / -temp_stickX; 4868 temp_stickX = -0x0035;
src/player_controller.c lines 4862–4869 at 58cfcb0 · upstream ↗ 1123void func_8002A79C(Player* player, s8 playerIndex) { 1124 if (((player->effects & MINI_TURBO_EFFECT) != MINI_TURBO_EFFECT) && 1125 ((player->effects & DRIFTING_EFFECT) != DRIFTING_EFFECT) && (player->driftState >= 2)) { 1126 player->effects |= MINI_TURBO_EFFECT; 1127 player->unk_23A = 0; ⋮ 1137 } else if ((player->effects & MINI_TURBO_EFFECT) == MINI_TURBO_EFFECT) { 1138 player->unk_23A += 1; 1139 if (player->unk_23A >= 0x1F) { 1140 player->unk_23A = 0; 1141 player->effects &= ~MINI_TURBO_EFFECT;
src/player_controller.c lines 1123–1143 at 58cfcb0 · upstream ↗ 2139 if ((player->type & PLAYER_HUMAN) == PLAYER_HUMAN) { 2140 func_8002A79C(player, playerId);
src/player_controller.c lines 2139–2140 at 58cfcb0 · upstream ↗ 3093 if ((player->type & PLAYER_HUMAN) == PLAYER_HUMAN) { 3094 if (((player->effects & MINI_TURBO_EFFECT) == MINI_TURBO_EFFECT) && 3095 ((player->effects & DRIFTING_EFFECT) != DRIFTING_EFFECT)) { 3096 move_f32_towards(&player->unk_0E8, 580.0f, 0.2f); 3097 } else { 3098 move_f32_towards(&player->unk_0E8, 0.0f, 0.01f); ⋮ 3102 } else { ⋮ 3107 var_f2 = (player->unk_08C + player->unk_0E8 + player->boostPower + player->unk_0E4) - player->unk_0A0;
src/player_controller.c lines 3093–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 ↗ 2742 spF4[2] = func_80030150(player, playerId); 2743 mtxf_transform_vec3f_mat3(spF4, player->orientationMatrix); ⋮ 2748 newVelocity[2] += (((spF4[2] + sp7C) + spD0[2]) - (newVelocity[2] * (0.12 * player->kartFriction))) / 6000.0;
src/player_controller.c lines 2742–2748 at 58cfcb0 · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-25.
Checked by a second AI model, 2026-10-22.
Checked by a second AI model, 2026-10-25.
- ^ abIn the US version of Mario Kart 64, the start boost's window opens when Lakitu's countdown finishes the animation after its second light sound, before the green-light sound. A press of A that begins within 8 counts of the race frame counter from the first update the window is open (20 in Time Trials) sets
START_BOOST_TRIGGER, and letting go of A before the start clears it. The trigger gives the kart the Mushroom's effect and the same boost timer, 80. Once the window is open, a kart that revs to 90% of its top speed without the trigger getsSTART_SPINOUT_TRIGGER, the spin-out for holding the gas.Coden64decomp/mk64revisedHow we know
3037 case 7: 3038 if (set_and_run_timer_object(objectIndex, 0x00000014) != 0) { ⋮ 3041 play_sound2(SOUND_ACTION_COUNTDOWN_LIGHT); ⋮ 3048 case 9: 3049 if ((set_and_run_timer_object(objectIndex, 8) != 0) && (arg1 == 0)) { 3050 play_sound2(SOUND_ACTION_COUNTDOWN_LIGHT); ⋮ 3053 case 10: 3054 if ((func_80072E54(objectIndex, 0x00000010, 0x00000017, 1, 6, 0) != 0) && (arg1 == 0)) { 3055 D_801656F0 = 1; ⋮ 3058 case 11: 3059 if (set_and_run_timer_object(objectIndex, 8) != 0) { ⋮ 3062 play_sound2(SOUND_ACTION_GREEN_LIGHT);
src/update_objects.c lines 3037–3063 at 58cfcb0 · upstream ↗ 4749 } else if (D_8018D168 == 1) { 4750 if (D_801656F0 == 1) { 4751 if (D_801652E0[playerIndex] == 0) { 4752 D_801652E0[playerIndex] = gRaceFrameCounter; ⋮ 4755 if (controller->button & A_BUTTON) { 4756 player_accelerate_during_start_sequence(player); 4757 } else { 4758 player_decelerate_during_start_sequence(player, 5.0f);
src/player_controller.c lines 4749–4759 at 58cfcb0 · upstream ↗ 3669 if (D_801656F0 == 1) { 3670 time_delta = gRaceFrameCounter - D_801652E0[playerIndex]; 3671 if (gModeSelection == TIME_TRIALS) { 3672 var_v0 = 0x14; 3673 } else { 3674 var_v0 = 8; ⋮ 3676 if ((time_delta < var_v0) && ((player->kartProps & THROTTLE) != THROTTLE)) { 3677 player->triggers |= START_BOOST_TRIGGER; 3678 } else if ((player->topSpeed * 0.9f) <= player->currentSpeed) { 3679 if ((player->triggers & START_BOOST_TRIGGER) != START_BOOST_TRIGGER) { 3680 player->triggers |= START_SPINOUT_TRIGGER; ⋮ 3685 player->kartProps |= THROTTLE;
src/player_controller.c lines 3669–3685 at 58cfcb0 · upstream ↗ 3703 player->triggers &= ~START_BOOST_TRIGGER; 3704 player->kartProps &= ~THROTTLE;
src/player_controller.c lines 3703–3704 at 58cfcb0 · upstream ↗ 1392 if ((player->triggers & START_BOOST_TRIGGER) == START_BOOST_TRIGGER) { 1393 func_8002A704(player, playerId);
src/player_controller.c lines 1392–1393 at 58cfcb0 · upstream ↗ 1112void func_8002A704(Player* player, s8 playerIndex) { 1113 player->effects |= MUSHROOM_EFFECT; 1114 player->triggers &= ~START_BOOST_TRIGGER; ⋮ 1120 player->boostTimer = 0x0050;
src/player_controller.c lines 1112–1120 at 58cfcb0 · upstream ↗ 522void trigger_shroom(Player* player, s8 playerIndex) { ⋮ 526 player->effects |= MUSHROOM_EFFECT; ⋮ 544 player->boostTimer = 0x50;
src/effects.c lines 522–544 at 58cfcb0 · upstream ↗ 441#define START_SPINOUT_TRIGGER 0x10000000 // Spinning out by holding gas at start of raceinclude/defines.h line 441 at 58cfcb0 · upstream ↗ 1254 if (gIsGamePaused == false) { 1255 func_8008C1D8(&D_80165678); 1256 gRaceFrameCounter++;
src/code_80057C60.c lines 1253–1256 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 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.
Sources
Code
- n64decomp. 2026. “mk64: a decompilation of Mario Kart 64 (n64decomp/mk64).” Commit 58cfcb0. n64decomp/mk64 @ 58cfcb0 · licence, all uses