Landing angle
How a vehicle's tilt against the ground as it lands from a jump decides whether it lands cleanly, loses speed, or crashes.
On this page
A racing game with jumps has to decide what a landing at the wrong angle costs. Excitebike 64 compares how far each wheel is from the ground when the first touches down and sorts the landing into five bands: clean, rough on either side of it, then a crash on either side of those1. A rough landing raises the bike’s drag to at least 7.0 for about a second2.
- GradedExcitebike 64 (2000). 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.1,2,3
Games
| Game | Year | How | Note |
|---|---|---|---|
| Excitebike 64 | 2000 | 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 |
Every claim about this mechanic, by game (3)
References
- ^ 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.
- ^In 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.
Sources
Code
- chasmlol. 2026. “Excitebike 64 decompilation (chasmlol/excitebike64).” Commit 9cdb194. chasmlol/excitebike64 @ 9cdb194 · licence, all uses