Excitebike 64
Left Field Productions' 2000 motocross game for the Nintendo 64, read through chasmlol's decompilation.
On this page
The atlas reads Excitebike 64 through chasmlol’s decompilation, C that builds the hotel, US, and European ROMs byte for byte. Its functions and fields carry names and comments from the decompilation; the claims quote them as theirs and read the US Rev 1 cartridge. Nothing has been measured in the game yet.
The turbo is paid for in engine heat. Below 7000 the throttle and the turbo both heat the engine, and above it only the turbo does, divided by the rider’s turbo stat; at 12000 the engine overheats and cools at a fixed rate until it’s back to 7000, about 4.2 seconds1. Overheated, the engine gives 0.32 of its torque whichever button is held, against 0.75 on the throttle and 1.225 on the turbo2. A turbo pressed in the last 10 updates before a jump adds up to 53 to the bike’s speed at takeoff, less above a speed of 10 and none at 20 or overheated3.
The landing is graded by the difference between the two wheels’ distances to the ground when the first touches down. Within ±0.34 the landing is clean, further out it’s rough, and further still the rider crashes, unless the impact is soft4. A rough landing raises the drag to at least 7.05 for about a second5. In the last quarter second of a long jump the game turns the bike’s pitch toward the ground’s slope, at up to 26 radians a second; a rider stat gates this for human riders, but every profile the atlas has decoded passes the test6.
The CPU riders are rubber-banded through time itself: each one’s physics step is scaled, slower as their pack pulls more than 30 ahead of the humans and faster as it falls behind, by up to ±0.157. In a multiplayer race, trailing humans get the same treatment, up to 5% faster7. Mario Kart 64 adds a force term to its CPU karts instead7.
Mechanics
| Mechanic | Kind | How |
|---|---|---|
| Overheat | Power cut | the turbo heats the engine to an overheat at 12000, after which the engine gives 0.3 of its torque whichever button is held, and no takeoff boost, until the heat falls to 7000 |
| Landing angle | Graded | the gap between the wheels' distances to the ground at touchdown sorts a landing into clean, rough (more drag for about a second), or a crash |
| Rubber-band AI | 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 |
Every claim about this game, by concept (7)
References
- ^In Excitebike 64's US Rev 1 build,
engine_update_heatmoves a rider's engine heat each bike update by a rate fromsEngineHeatRates, picked by whether the heat is above 7000 and by the drive: Z (the turbo), else A (the throttle), else neither. At 7000 or below the rates are −20 with neither, +20 on the throttle, and +40 on the turbo; above 7000 they're −12, −8, and +20. A positive rate while Z is held is divided by the rider's turbo stat, which is 1.0 for CPU riders. The heat is held between 0 and 12000. Reaching 12000 setsoverheated, and while it's set the rate is −20 whatever is held, until the heat is back to 7000 or below. At 60 updates a second, a turbo stat of 1.0 takes a cold engine to 12000 in 425 updates, about 7.1 seconds, and an overheat lasts 250 updates, about 4.2 seconds; the throttle alone holds the heat around 7000 (computed). While the rear wheel is on the ground and the event isn't stopped (EVENT_STOPPED), a surface flaggedSURFACE_COOLS_ENGINE(commented "resets the engine heat") sets the heat to 0.Codechasmlol/excitebike64revisedHow we know
56/* The rider's drive: 2 turbo (Z), 1 throttle (A), 0 neither. */ ⋮ 61 if (rider->input & INPUT_Z) { 62 return 2; ⋮ 64 throttle = rider->input & INPUT_A; 65 return throttle != 0; ⋮ 78 drive = drive_input(rider); 79 hot = 0; 80 if (rider->engine.heat > 7000.0f) { 81 hot = 1; ⋮ 83 if (rider->engine.overheated) { 84 hot = drive = 0; ⋮ 86 rate = sEngineHeatRates[hot][drive]; ⋮
src/game/unnamed/s0_800358C0.c lines 56–106 at 9cdb194 · upstream ↗ 3999f32 sEngineHeatRates[0x6] = { 4000 /* 0x000 */ -20.0f, 20.0f, 40.0f, -12.0f, 4001 /* 0x010 */ -8.0f, 20.0f,
src/data/game_800C2280.c lines 3999–4002 at 9cdb194 · upstream ↗ 115 rider = &gRiders[riderIdx]; 116 engine_update_heat(rider);
src/game/unnamed/s0_800358C0.c lines 114–116 at 9cdb194 · upstream ↗ 125 if (rider->rearWheel.airTicks < 1 && (gEvent.flags & EVENT_STOPPED) == 0) {src/game/unnamed/s0_800358C0.c line 125 at 9cdb194 · upstream ↗ 163 if (rider->rearWheel.surface->flags & SURFACE_COOLS_ENGINE) { 164 rider->engine.heat = 0.0f;
src/game/unnamed/s0_800358C0.c lines 163–165 at 9cdb194 · upstream ↗ 16#define SURFACE_COOLS_ENGINE 0x0200 /* resets the engine heat */include/structs/surface.h line 16 at 9cdb194 · upstream ↗ 349 rider->bike.mass = 250.0f; 350 rider->wallFriction = 0.3f; 351 rider->landing = 1.0f; 352 rider->turbo = 1.0f;
src/game/bikenew.c lines 349–352 at 9cdb194 · 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 Excitebike 64's US Rev 1 build,
engine_updatescales the engine's torque bysTurboPowerScale(1.225) while Z, the turbo, is held, bysThrottlePowerScale(0.75) on A, the throttle, alone, and bysOverheatPowerScale(0.3) while the engine is overheated, whichever of the two is held. The scale applies only while the rear wheel is on the ground and the event isn't stopped (EVENT_STOPPED). With neither button held, or with either held while the event is stopped, the torque is −3728.4995 (commented "engine braking") above a speed of 1 and 0 below it. The turbo gives about 1.63 times the throttle's torque, and an overheated engine 0.4 times the throttle's and about 0.24 times the turbo's (computed).Codechasmlol/excitebike64revisedHow we know
190 if ((rider->input & (INPUT_Z | INPUT_A)) != 0 && (gEvent.flags & EVENT_STOPPED) == 0) { 191 if (rider->rearWheel.airTicks < 1) { 192 if (rider->engine.overheated != 0) { 193 tmp = sOverheatPowerScale; 194 } else if (rider->input & INPUT_Z) { 195 tmp = sTurboPowerScale; 196 } else { 197 tmp = sThrottlePowerScale; ⋮ 199 torque = torque * tmp; 200 rider->engine.torque = torque; ⋮ 202 } else if (1.0f < rider->speed) { 203 rider->engine.torque = -3728.4995f; /* engine braking */ ⋮
src/game/unnamed/s0_800358C0.c lines 190–206 at 9cdb194 · upstream ↗ 4003f32 sTurboPowerScale = 1.225f; 4004f32 sThrottlePowerScale = 0.75f; 4005f32 sOverheatPowerScale = 0.3f;
src/data/game_800C2280.c lines 4003–4005 at 9cdb194 · 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 Excitebike 64's US Rev 1 build,
bike_update_jumpboosts a human rider who leaves the ground while riding and holding Z, the turbo, first pressed fewer than 10 updates earlier (turboPressFrame), on a jump whose predicted time in the air (landingTime, twice the vertical speed over gravity) is longer than a minimum: 0.25 seconds up to a takeoff speed of 10, 0.5 above 20, and linear between. Below a speed of 20 and while the engine isn't overheated, the bike's velocity is scaled so its speed gains 5, less half the speed above 10: 5 at 10 or less, 2.5 at 15, and nothing at 20. If the bike's pitch (rot[0]) is −0x17FF or more, its angular velocity also loses 2 times its right axis; the LodgeNet build subtracts 3, and the decompilers comment that "retail pitches the bike less". CPU riders get no takeoff boost.Codechasmlol/excitebike64How we know
2235 if (rider->rearWheel.airTicks > 0 && rider->frontWheel.airTicks > 0) { 2236 if (rider->airFrames <= 0) { ⋮ 2240 rider->landingTime = (rider->bike.vel.y + rider->bike.vel.y) / GRAVITY; ⋮ 2242 takeoffSpeed = rider->speed; 2243 minAirTime = 0.5f; 2244 if (!(takeoffSpeed > 20.0f)) { 2245 minAirTime = 0.25f; 2246 if (takeoffSpeed > 10.0f) { 2247 minAirTime = (takeoffSpeed - 10.0f) / 40.0f + 0.25f; ⋮ 2250 if (minAirTime < rider->landingTime) {
src/game/bikenew.c lines 2235–2250 at 9cdb194 · upstream ↗ 2258 if (rider->isCpu == 0 && rider->crashState == CRASH_STATE_RIDING && gRaceFrame - rider->turboPressFrame < 10 && (rider->input & INPUT_Z)) { 2259 /* turbo at takeoff: a boost (none when overheated) */ 2260 speed = rider->speed; 2261 if (speed < 20.0f && rider->engine.overheated == 0) { 2262 boost = 5.0f; 2263 if (speed > 10.0f) { 2264 boost = 5.0f - (speed - 10.0f) * 0.5f; ⋮ 2266 if (speed != 0.0f) { 2267 speed = boost / speed + 1.0f; 2268 } else { 2269 speed = 0.0f; ⋮ 2271 rider->bike.vel.x *= speed; ⋮
src/game/bikenew.c lines 2258–2285 at 9cdb194 · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-22.
Checked by a second AI model, 2026-10-22.
- ^ abIn Excitebike 64's US Rev 1 build,
bike_handle_landingruns when a wheel touches down. After 41 or more updates in the air (airFrames), it takes the front wheel'sgroundDist, the signed offset along the bike's up axis from the wheel's point on the bike to the ground plus 0.5, which grows as the ground comes nearer, less the rear wheel's, so the difference is negative when the rear wheel is nearer the ground. Below −2.0 the rider crashes at once (ANIM_CRASH_BACK); from −2.0 to under −0.8 that crash is queued (pendingCrash); from −0.8 to under −0.3 the landing is rough (roughLanding); from −0.3 to under 0.3 it's clean, and the bike's turning is stopped; from 0.3 to under 0.6 it's rough; from 0.6 up a crash is queued (ANIM_CRASH_FRONT). A queued crash becomes a rough landing when the impact, the bike's velocity along the two wheels' ground normals added together, is above −10. Whatever the wheels, a rider still in a trick animation after 11 or more updates in the air crashes. By the atlas's reading the wheel difference measures the bike's pitch against the ground it lands on.Codechasmlol/excitebike64How we know
1968/* Lands the bike: cleanly, roughly or in a crash, from the wheels' heights and the impact. */ 1969void bike_handle_landing(Rider *arg) ⋮ 1976 impactVel = vec3_dot(vel, &rider->rearWheel.normal.x) + vec3_dot(vel, &rider->frontWheel.normal.x); 1977 if ((u8)rider->anim < ANIM_TRICK_COUNT && rider->airFrames >= 0xB) 1978 bike_crash_begin(rider, ANIM_CRASH_FRONT); ⋮ 1986 if (rider->airFrames < 0x29) 1987 goto record_impact; 1988 wheelDiff = rider->frontWheel.groundDist - rider->rearWheel.groundDist; ⋮ 1990 if (wheelDiff < -2.0f) { 1991 bike_crash_begin(rider, ANIM_CRASH_BACK); 1992 goto soften; ⋮ 1994 if (wheelDiff < -0.8f) { ⋮
src/game/bikenew.c lines 1968–2017 at 9cdb194 · upstream ↗ 2018 if (rider->landingAssist != 0) { 2019 f32 zf = 0.0f; 2020 rider->bike.angVel.z = zf; 2021 rider->bike.angVel.y = zf; 2022 rider->bike.angVel.x = zf;
src/game/bikenew.c lines 2018–2024 at 9cdb194 · upstream ↗ 2462 if (rider->airFrames > 0) { 2463 bike_handle_landing(rider);
src/game/bikenew.c lines 2462–2463 at 9cdb194 · upstream ↗ 471 axisPos[1] = rider->bike.pos.y + rider->bike.fwd.y * fwdOffset; ⋮ 476 pointY = axisPos[1] + dist * rider->bike.up.y; ⋮ 481 diff = groundY - pointY; 482 tolerance = 0.002f; 483 slope = -1.0f; ⋮ 489 dist -= slope * diff; ⋮ 495 pointY = axisPos[1] + dist * rider->bike.up.y; ⋮ 498 diff = groundY - pointY; ⋮ 508 slope = -1.0f; ⋮ 514 dist += 0.5f; ⋮ 533 wheel->groundDist = dist;
src/game/bikenew.c lines 470–533 at 9cdb194 · upstream ↗ 252 /* 0x28 */ f32 groundDist; /* along the bike's up axis */include/structs/rider.h line 252 at 9cdb194 · upstream ↗ 47#define ANIM_NONE (-1)include/structs/rider.h line 47 at 9cdb194 · 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 Excitebike 64's US Rev 1 build, a rough landing (
roughLanding, commented "frames since a rough landing (more drag)") raises the bike's drag coefficient, the one multiplied by the square of its speed and applied against its velocity, to at least 7.0; otherwise it's 0.21 in the air and, on the ground, 0.21 times the rear wheel's surface drag, softened during a wheelie (wheelieTimernot 0) to (drag − 1) × 0.25 + 1.bike_update_jumpadds each update's ticks to the counter, which starts at 1, and clears it at 60, so the extra drag lasts 59 updates, about a second (computed). On a surface whose drag is 1, that's about 33 times the usual coefficient (computed).Codechasmlol/excitebike64revisedHow we know
1546 if (rider->speed != 0.0f) { ⋮ 1551 minDrag = 0.0f; 1552 if (rider->roughLanding != 0) { 1553 minDrag = 7.0f; ⋮ 1555 drag = 0.21000001f; 1556 if (rider->airFrames <= 0) { 1557 drag = (rider->wheelieTimer != 0 ? (rider->rearWheel.surface->drag - 1.0f) * 0.25f + 1.0f : rider->rearWheel.surface->drag) * 0.21000001f; ⋮ 1559 if (drag < minDrag) { 1560 drag = minDrag; ⋮ 1562 drag = drag * rider->speed * rider->speed;
src/game/bikenew.c lines 1546–1562 at 9cdb194 · upstream ↗ 1581 rate = drag / rider->speed; 1582 force.x -= rider->bike.vel.x * rate; 1583 force.y -= rider->bike.vel.y * rate; 1584 force.z -= rider->bike.vel.z * rate;
src/game/bikenew.c lines 1581–1584 at 9cdb194 · upstream ↗ 2226 if (rider->roughLanding != 0) { 2227 rider->roughLanding += ((u8 *)&gPhysTicks)[3]; /* its low byte */ 2228 if (rider->roughLanding >= 60) { 2229 rider->roughLanding = 0;
src/game/bikenew.c lines 2226–2231 at 9cdb194 · upstream ↗ 401 /* 0x5CD */ u8 roughLanding; /* frames since a rough landing (more drag), 0 none */include/structs/rider.h line 401 at 9cdb194 · 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 Excitebike 64's US Rev 1 build,
bike_update_jumpsetslandingAssist(commented "the bike will land straight: pitched to the ground") on a falling bike more than 30 updates into a jump and not in a wheelie, once its predicted landing (landingTime) is under 0.25 seconds away: always for a CPU rider, and for a human when the pitch part of the misalignment between the bike's up axis and the landing point's ground normal, the dot product of their cross product with the bike's right axis, is smaller in size than 10 times the rider'slandingstat. While it's set,bike_align_to_groundsets the bike's turning to a pitch toward the ground's slope at twice the angle over the time left plus 0.1 seconds, at most 2 radians a second; it returns without setting anything when the landing point's ground normal has a y below 0.5 or the angle's sine is 0. Either way, while the flag is set the player's air pitch input isn't read. For unit axes that dot product is at most 1, and the six profiles of floats at the head ofsRiderStatsTablesetlandingfrom 0.25 to 5, so the human test passes for every one (computed); the code reads the stat nowhere else.Codechasmlol/excitebike64revisedHow we know
2429 if (rider->landingAssist != 0) { 2430 bike_align_to_ground(rider); 2431 } else if (rider->landingTime < 0.25f && rider->bike.vel.y < 0.0f && rider->airFrames > FRAMES(30) && rider->wheelieTimer == 0) { 2432 if (rider->isCpu == 0) { ⋮ 2442 height_get_ground_normal(&vec.x); 2443 axis.x = rider->bike.up.y * vec.z - vec.y * rider->bike.up.z; 2444 axis.y = rider->bike.up.z * vec.x - vec.z * rider->bike.up.x; 2445 axis.z = rider->bike.up.x * vec.y - vec.x * rider->bike.up.y; 2446 tilt = vec3_dot(&axis.x, &rider->bike.right.x); 2447 limit = rider->landing * 10.0f; 2448 if (tilt > 0.0f ? tilt < limit : -tilt < limit) { 2449 rider->landingAssist = 1; ⋮
src/game/bikenew.c lines 2429–2453 at 9cdb194 · upstream ↗ 2088/* In the air: pitches the bike towards the slope of the ground where it will land. */ ⋮ 2110 if (normal.y < 0.5f) { 2111 return; ⋮ 2116 sinAngle = vec3_dot(&axis.x, &rider->bike.right.x); ⋮ 2120 if (sinAngle == 0.0f) { 2121 return; ⋮ 2123 angleWord = math_asini((s32)(sinAngle * 16384.0f)); 2124 angle = (f32)(s16)angleWord * 3.1415927f * 3.0517578e-05f; 2125 time = rider->landingTime; 2126 time += 0.1f; 2127 rate = (angle + angle) / time; 2128 if (rate > 2.0f) { ⋮
src/game/bikenew.c lines 2088–2136 at 9cdb194 · upstream ↗ 1641 if (rider->landingAssist == 0) { ⋮ 1647 pitchError = rider->airPitchInput; ⋮ 1653 localTorque.x += ((f32)(pitchError / 4) * 3.1415927f * 3.0517578e-05f - rider->localAngVel.x * 0.0625f) * (250.0f * FRAME_RATE);
src/game/bikenew.c lines 1641–1653 at 9cdb194 · upstream ↗ 585/* The unit normal of the ground triangle found last. */ 586void height_get_ground_normal(f32 *out)
src/game/height.c lines 585–586 at 9cdb194 · upstream ↗ 400 /* 0x5CC */ u8 landingAssist; /* the bike will land straight: pitched to the ground (bike_align_to_ground) */include/structs/rider.h line 400 at 9cdb194 · upstream ↗ 333void bike_apply_rider_stats(Rider *rider, s32 profile) ⋮ 335 if (rider->isCpu == 0) { 336 rider->bike.mass = 250.0f; 337 rider->landing = sRiderStatsTable[profile].landing;
src/game/bikenew.c lines 333–337 at 9cdb194 · upstream ↗ 3725u32 sRiderStatsTable[0x50] = {src/data/game_800C2280.c lines 3725–3740 at 9cdb194 · 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.
- ^ abcIn 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.
Sources
Code
- chasmlol. 2026. “Excitebike 64 decompilation (chasmlol/excitebike64).” Commit 9cdb194. chasmlol/excitebike64 @ 9cdb194 · licence, all uses
- n64decomp. 2026. “mk64: a decompilation of Mario Kart 64 (n64decomp/mk64).” Commit 58cfcb0. n64decomp/mk64 @ 58cfcb0 · licence, all uses