data/claims/sf64.yamlStar Fox 64 (the sonicdcer/sf64 decompilation)
Game: Star Fox 64.
In the US 1.1 version of Star Fox 64, the Arwing rolls when Z or R is pressed twice. The first press sets that button's counter to 10, which Player_UpdateArwingRoll counts down once per player update, and a second press while it is above 0 starts the roll, turning the ship 30 degrees an update for 10 updates and then slowing by 5 degrees an update until it stops. barrelRollAlpha falls by 30 every update while it is above 0, and in each update the roll's count is still above 0 after counting down (9 of its 10) it then gains 100, capped at 180, a net rise of 70 an update. While that value is above 0, the enemy-shot update (Effect_EnemyShot_Update) widens the shot's box around the player by 100 units on each axis, sends a shot inside it off in a new direction, and applies no damage. sf64/barrel-roll
About: Star Fox 64 Barrel roll
Value: 10 player updates (the double-press counter and the roll's full-speed count)
Codesf64: a decompilation of Star Fox 64 (sonicdcer/sf64) ·src/engine/fox_play.c#L3397-L3419if (gInputPress->button & Z_TRIG) {
player->sfx.bank = 1;
if (player->rollInputTimerL != 0) {
player->rollState = 1;
player->rollTimer = 10;
player->rollRate = player->baseRollRate = 30;
…
player->rollInputTimerL = 10;
…
if (gInputPress->button & R_TRIG) {
…
if (player->rollInputTimerR != 0) {
…sf64: a decompilation of Star Fox 64 (sonicdcer/sf64) · src/engine/fox_play.c#L4823-L4839if (player->barrelRollAlpha > 0) {
player->barrelRollAlpha -= 30;
if (player->barrelRollAlpha <= 0) {
player->barrelRollAlpha = 0;
…
if (player->rollInputTimerL != 0) {
player->rollInputTimerL--;
}
if (player->rollInputTimerR != 0) {
player->rollInputTimerR--;sf64: a decompilation of Star Fox 64 (sonicdcer/sf64) · src/engine/fox_play.c#L4846-L4870if (player->rollTimer != 0) {
player->rollTimer--;
}
…
if (player->rollState != 0) {
player->rollInputTimerR = 0;
player->rollInputTimerL = 0;
…
player->zRotBarrelRoll += player->rollRate;
if (player->rollTimer == 0) {
if (player->rollRate > 0) {
player->rollRate -= 5;
…sf64: a decompilation of Star Fox 64 (sonicdcer/sf64) · src/engine/fox_play.c#L5534-L5549void Player_UpdateOnRails(Player* player) {
…
Player_ArwingBank(player);
Player_ArwingBoost(player);
Player_ArwingBrake(player);
Play_dummy_800B41E0(player);
Player_UpdateArwingRoll(player);sf64: a decompilation of Star Fox 64 (sonicdcer/sf64) · src/engine/fox_effect.c#L2902-L2926if (gPlayer[0].barrelRollAlpha != 0) {
var_fa0 = 100.0f;
}
…
if (fabsf(gPlayer[0].trueZpos - this->obj.pos.z) < (50.0f + var_fa0)) {
if ((fabsf(gPlayer[0].pos.x - this->obj.pos.x) < (30.0f + var_fa0)) &&
(fabsf(gPlayer[0].pos.y - this->obj.pos.y) < (30.0f + var_fa0))) {
if ((gPlayer[0].barrelRollAlpha != 0) || (gPlayer[0].meteoWarpTimer != 0)) {
this->obj.rot.y = 90.0f;
this->obj.rot.x = RAND_FLOAT(360.0f);
…
gPlayer[0].deflectCount++;
…Checked by claude, 2026-10-22 — read both button branches of Player_ArwingBank (the function holding L3397-L3419), Player_UpdateArwingRoll whole, the call order in Player_UpdateOnRails (bank before roll update; Player_Update360 at L5604-L5608 calls them in the same order), and the deflect and damage tests in Effect_EnemyShot_Update; no VERSION_ conditional in either fileComputed, not measured: since the press is read before the counters count down in the same update, a second press up to 9 updates after the first starts a roll, and barrelRollAlpha runs 100, 170, 180 for seven updates, then 150, 120, 90, 60, 30, so it is above 0 for the first 14 updates of a roll. The same value draws the roll's glow (src/engine/fox_display.c#L916). The decompilers' comment on deflectCount reads "counts shots deflected by barrel rolls. never used" (include/sf64player.h#L316). A meteo warp (meteoWarpTimer) also deflects. The Landmaster's roll (Player_UseTankJets) has its own rules and isn't covered here. Player updates haven't been tied to displayed frames.
Cited by: Barrel roll, Star Fox 64
In the US 1.1 version of Star Fox 64, the Arwing's boost and brake share one meter, boostMeter, which counts up from 0 as it is used and stops at 90. Holding the boost button adds 3 a player update on rails and 1.5 in all-range mode (Player_ArwingBoost), and holding brake adds the same amounts (Player_ArwingBrake). Each of the two functions takes back 0.5 a update on rails (0.35 in all-range mode) whenever its own action isn't running, so the meter falls by 1 an update on rails when neither button is held. Reaching 90, holding both buttons with the meter above 0, or letting go of both with the meter above 0 sets boostCooldown, which blocks both actions until the meter is back to 0. sf64/boost-meter
About: Star Fox 64 Boost meter
Value: 90 meter units (full use)
Codesf64: a decompilation of Star Fox 64 (sonicdcer/sf64) ·src/engine/fox_play.c#L4951-L4968void Player_ArwingBoost(Player* player) {
…
if ((player->boostMeter != 0.0f) && (gInputHold->button & gBrakeButton[player->num]) &&
(gInputHold->button & gBoostButton[player->num])) {
player->boostCooldown = true;
}
…
if (gLevelMode == LEVELMODE_ON_RAILS) {
boostDepleteRate = 3.0f;
boostRecoverRate = 0.5f;
…
boostDepleteRate = 1.5f;
…sf64: a decompilation of Star Fox 64 (sonicdcer/sf64) · src/engine/fox_play.c#L5024-L5031if (!(gInputHold->button & gBrakeButton[player->num]) && !(gInputHold->button & gBoostButton[player->num])) {
player->boostCooldown = true;
if (player->boostMeter == 0.0f) {
player->boostCooldown = false;
}
}
if ((gInputHold->button & gBoostButton[player->num]) && !(gInputHold->button & gBrakeButton[player->num]) &&
(player->state != PLAYERSTATE_U_TURN) && !player->boostCooldown) {sf64: a decompilation of Star Fox 64 (sonicdcer/sf64) · src/engine/fox_play.c#L5047-L5051player->boostMeter += boostDepleteRate;
if (player->boostMeter > 90.0f) {
player->boostMeter = 90.0f;
player->boostCooldown = true;sf64: a decompilation of Star Fox 64 (sonicdcer/sf64) · src/engine/fox_play.c#L5064-L5069} else {
if (player->boostMeter > 0.0f) {
player->boostMeter -= boostRecoverRate;
if (player->boostMeter <= 0.0f) {
player->boostMeter = 0.0f;
player->boostCooldown = false;sf64: a decompilation of Star Fox 64 (sonicdcer/sf64) · src/engine/fox_play.c#L5092-L5098if (gLevelMode == LEVELMODE_ON_RAILS) {
brakeDepleteRate = 3.0f;
brakeRecoverRate = 0.5f;
} else {
brakeDepleteRate = 1.5f;
brakeRecoverRate = 0.35f;sf64: a decompilation of Star Fox 64 (sonicdcer/sf64) · src/engine/fox_play.c#L5130-L5165if ((gInputHold->button & gBrakeButton[player->num]) && !(gInputHold->button & gBoostButton[player->num]) &&
(player->state != PLAYERSTATE_U_TURN) && !player->boostCooldown) {
…
player->boostMeter += brakeDepleteRate;
if (player->boostMeter > 90.0f) {
player->boostCooldown = true;
player->boostMeter = 90.0f;
…
} else if (player->boostMeter > 0.0f) {
player->boostMeter -= brakeRecoverRate;
if (player->boostMeter <= 0.0f) {
player->boostMeter = 0.0f;
…sf64: a decompilation of Star Fox 64 (sonicdcer/sf64) · src/engine/fox_play.c#L5601-L5606void Player_Update360(Player* player) {
switch (player->form) {
case FORM_ARWING:
Player_ArwingBank(player);
Player_ArwingBoost(player);
Player_ArwingBrake(player);Checked by claude, 2026-10-22 — read Player_ArwingBoost and Player_ArwingBrake whole, and the two callers (Player_UpdateOnRails at L5545-L5547 and Player_Update360), which run boost then brake each update; the recovery branches are the else of each function's own action test; no VERSION_ conditional in the fileComputed from these rates, not measured: boosting on rails the meter gains 3 and the brake function's else branch takes 0.5, a net 2.5 an update, so a full meter lasts 36 updates and refills in about 90 (1 an update); in all-range mode the net is 1.15, about 78 updates, and the refill 0.7, about 129. The HUD's gauge reading of the field isn't read. Loops, U-turns, and a meteo warp also set boostCooldown (other lines of src/engine/fox_play.c). The Landmaster has its own function, Player_TankBoostBrake. Player updates haven't been tied to displayed frames.
Cited by: Boost meter, Star Fox 64
In the US 1.1 version of Star Fox 64, holding A counts a charge counter up by 1 a player update to a cap of 21, and an update without A held counts it down by 1 (Player_UpdateLockOn). A fresh press of A, whose own update first adds 1 to the counter, fires the charged shot (PLAYERSHOT_LOCK_ON) if an enemy is locked on or the counter is then above 10 (so 10 or more before the press). Either path fires only if the player's charged-shot slot (gPlayerShots[14 - player->num]) is free, holds a shot of another type, or holds a charged shot that has lost its target (the locked path) or grown past scale 1 (the counter path); a press that fires nothing sets the counter to 0. Once the counter is above 20, while A is held, nothing is locked, and Z and R aren't both held, it launches a search shot every fourth game frame, and the first targetable enemy within 300 units of that shot on each axis (200 in all-range mode) is locked on for a count of 20, which counts down only while A is up. Each update while its target stays locked, the charged shot turns toward it (PlayerShot_UpdateLockOnShot). sf64/lock-on
About: Star Fox 64 Lock-on shot
Value: 10 charge counts before the press (a press from this many fires)
Codesf64: a decompilation of Star Fox 64 (sonicdcer/sf64) ·src/engine/fox_play.c#L3186-L3220bool Player_UpdateLockOn(Player* player) {
…
if (gInputHold->button & A_BUTTON) {
gChargeTimers[player->num]++;
if (gChargeTimers[player->num] > 21) {
gChargeTimers[player->num] = 21;
}
…
((gGameFrameCount % 4) == 0) && Player_CanLockOn(player->num)) {
if (gChargeTimers[player->num] > 20) {
…
Player_SetupArwingShot(player, &gPlayerShots[i], 0.0f, 0.0f, PLAYERSHOT_LOCK_SEARCH,
…sf64: a decompilation of Star Fox 64 (sonicdcer/sf64) · src/engine/fox_play.c#L3164-L3172bool Player_CanLockOn(s32 playerNum) {
…
if ((actor->obj.status == OBJ_ACTIVE) && (actor->lockOnTimers[playerNum] != 0)) {
return false;sf64: a decompilation of Star Fox 64 (sonicdcer/sf64) · src/engine/fox_play.c#L3222-L3260if (gInputPress->button & A_BUTTON) {
for (i = 0; i < ARRAY_COUNT(gActors); i++) {
if ((gActors[i].obj.status == OBJ_ACTIVE) && (gActors[i].lockOnTimers[player->num] != 0)) {
if ((gPlayerShots[14 - player->num].obj.status == SHOT_FREE) ||
(gPlayerShots[14 - player->num].obj.id != PLAYERSHOT_LOCK_ON) ||
((gPlayerShots[14 - player->num].obj.id == PLAYERSHOT_LOCK_ON) &&
(gPlayerShots[14 - player->num].unk_60 != 0))) {
…
Player_SetupArwingShot(player, &gPlayerShots[14 - player->num], 0.0f, 0.0f, PLAYERSHOT_LOCK_ON,
…
break;
…
…sf64: a decompilation of Star Fox 64 (sonicdcer/sf64) · src/sys/sys_joybus.c#L69-L70gControllerPress[i].button =
(gControllerHold[i].button ^ sPrevController[i].button) & gControllerHold[i].button;sf64: a decompilation of Star Fox 64 (sonicdcer/sf64) · src/engine/fox_beam.c#L1758-L1769if (gLevelMode == LEVELMODE_ON_RAILS) {
lockRange = 300.0f;
} else {
lockRange = 200.0f;
…
if ((actor->obj.status == OBJ_ACTIVE) && (actor->info.targetOffset != 0.0f) &&
((actor->lockOnTimers[shot->sourceId] == 0) && (fabsf(shot->obj.pos.x - actor->obj.pos.x) <= lockRange) &&
(fabsf(shot->obj.pos.y - (actor->obj.pos.y + actor->info.targetOffset)) <= lockRange) &&
(fabsf(shot->obj.pos.z - actor->obj.pos.z) <= lockRange))) {
actor->lockOnTimers[shot->sourceId] = 20;sf64: a decompilation of Star Fox 64 (sonicdcer/sf64) · src/engine/fox_enmy.c#L2848-L2852} else if (this->lockOnTimers[TEAM_ID_FOX] != 0) {
if (!(gControllerHold[gMainController].button & A_BUTTON)) {
this->lockOnTimers[TEAM_ID_FOX]--;
}
gChargeTimers[0] = 0;sf64: a decompilation of Star Fox 64 (sonicdcer/sf64) · src/engine/fox_beam.c#L2174-L2222if (shot->unk_60 == 0) {
…
(actor->lockOnTimers[shot->sourceId] != 0)) {
var_a3 = 1;
actor->lockOnTimers[shot->sourceId] = 2;
sLockOnPos = actor->obj.pos;
…
if (var_a3 != 0) {
…
Math_SmoothStepToAngle(&shot->vec_2C.y, sp58, 1.0f, shot->unk_50, 0.0f);
Math_SmoothStepToAngle(&shot->vec_2C.x, sp5C, 1.0f, shot->unk_50, 0.0f);
…
…Checked by claude, 2026-10-22 — read Player_UpdateLockOn whole and Player_CanLockOn, the press mask in Controller_UpdateInput (a press is a button down this read and up the last), PlayerShot_FindLockTarget and PlayerShot_SearchLockOnTarget, the lock countdown in Actor_Update, and PlayerShot_UpdateLockOnShot's steering and its call from the shot update (L2332, outside Aquas); no VERSION_ conditional in the cited filesThe shot fires on a new press of A, not on its release: after a full charge the counter falls from 21 by 1 an update with A up and the press's own update adds 1, so by the atlas's arithmetic a press after as many as 11 updates with A up still fires it. A lock zeroes the charge counter each update (src/engine/fox_enmy.c#L2852), and a search shot dies when A is let go (src/engine/fox_beam.c#L1814-L1816). The turn step unk_50 grows by up to 3 an update toward 360 (src/engine/fox_beam.c#L2226). The 300 and 200 limits are in the game's world units. B fires a smart bomb through the same shot type at a locked enemy (src/engine/fox_play.c#L3263-L3290). The search-shot loop carries the decompilers' comment "bug? should be 11". Player updates haven't been tied to displayed frames.
Cited by: Lock-on shot, Star Fox 64
In the US 1.1 version of Star Fox 64, Player_Setup gives the player 255 shield units (less with a Versus handicap), and healing stops at Play_GetMaxShields: 383 with three or more gold rings, otherwise 255. A hit's damage, doubled in Expert mode, drains from the shield at 2 units an update (Player_UpdateShields), and an enemy laser's damage is 20; a hit while gHasShield is set does none. Each Arwing wing starts at 60 health (10 in Expert mode). A hit the code gives direction 1 or 2 takes 20 from the right or left wing, directions 3 and 4 take 15 from one wing at random, and direction 0, which enemy shots pass, takes none; a wing at 0 breaks. While either wing is broken or absent (WINGSTATE_NONE; the test is <= WINGSTATE_BROKEN), Player_Shoot sets the lasers back to single every update. sf64/shields-and-wings
About: Star Fox 64 Locational damage
Value: 255 shield units at the start
Codesf64: a decompilation of Star Fox 64 (sonicdcer/sf64) ·src/engine/fox_play.c#L4405-L4436void Player_Setup(Player* playerx) {
…
player->shields = 255;
…
if (gVersusMode) {
switch (gHandicap[gPlayerNum]) {
case 1:
player->shields = 191;
…
player->shields = 127;
…
if (gExpertMode) {
…sf64: a decompilation of Star Fox 64 (sonicdcer/sf64) · src/engine/fox_play.c#L64-L69s32 Play_GetMaxShields(void) {
if (gGoldRingCount[0] >= 3) {
return 383;
}
return 255;sf64: a decompilation of Star Fox 64 (sonicdcer/sf64) · src/engine/fox_play.c#L5463-L5484void Player_UpdateShields(Player* player) {
if (player->damage > 0) {
player->damage -= 2;
…
player->shields -= 2;
…
if (player->heal > 0) {
…
player->shields += 2;
if (player->shields >= Play_GetMaxShields()) {
player->shields = Play_GetMaxShields();sf64: a decompilation of Star Fox 64 (sonicdcer/sf64) · src/engine/fox_play.c#L867-L895if (gExpertMode) {
player->damage = damage * 2;
} else {
player->damage = damage;
}
…
if (gHasShield[player->num]) {
…
player->damage = 0;sf64: a decompilation of Star Fox 64 (sonicdcer/sf64) · src/engine/fox_play.c#L916-L978switch (player->hitDirection) {
case 0:
…
case 1:
…
Player_DamageWings(player, 1, 20);
…
case 2:
…
Player_DamageWings(player, 2, 20);
…
case 3:
…sf64: a decompilation of Star Fox 64 (sonicdcer/sf64) · src/engine/fox_play.c#L819-L827void Player_DamageWings(Player* player, s32 side, s32 damage) {
if ((player->form == FORM_ARWING) && (gShieldAlpha[player->num] < 1.0f)) {
if (side == 1) {
…
if (player->arwing.rightWingState == WINGSTATE_INTACT) {
gRightWingHealth[player->num] -= damage;
if (gRightWingHealth[player->num] <= 0) {
…
player->arwing.rightWingState = WINGSTATE_BROKEN;sf64: a decompilation of Star Fox 64 (sonicdcer/sf64) · src/engine/fox_play.c#L3299-L3305void Player_Shoot(Player* player) {
switch (player->form) {
case FORM_ARWING:
if ((player->arwing.rightWingState <= WINGSTATE_BROKEN) ||
(player->arwing.leftWingState <= WINGSTATE_BROKEN)) {
gLaserStrength[player->num] = LASERS_SINGLE;sf64: a decompilation of Star Fox 64 (sonicdcer/sf64) · include/sf64player.h#L27-L29/* 0 */ WINGSTATE_NONE,
/* 1 */ WINGSTATE_BROKEN,
/* 2 */ WINGSTATE_INTACTsf64: a decompilation of Star Fox 64 (sonicdcer/sf64) · src/engine/fox_effect.c#L2925-L2926if ((gPlayer[0].barrelRollAlpha == 0) && (gPlayer[0].mercyTimer == 0)) {
Player_ApplyDamage(&gPlayer[0], 0, this->info.damage);sf64: a decompilation of Star Fox 64 (sonicdcer/sf64) · src/engine/fox_edata_info.c#L472/* OBJ_EFFECT_ENEMY_LASER */ {(void*) Effect_EnemyLaser_Draw, 1, (ObjectFunc) Effect_EnemyShot_Update, NULL, 100.0f, 0, 0, 20, 0, 0.0f, 0 },sf64: a decompilation of Star Fox 64 (sonicdcer/sf64) · include/sf64object.h#L153-L168typedef struct ObjectInfo {
…
/* 0x10 */ f32 cullDistance; // z coordinate of something
/* 0x14 */ s16 unk_14; // can be -1, 0, 1. governs camera-related behavior in effects (billboarding?)
/* 0x16 */ s16 unk_16; // can be 0, 1, 2. affects death behavior?
/* 0x18 */ u8 damage;Checked by claude, 2026-10-22 — read Player_Setup's shield and wing lines, Play_GetMaxShields, Player_ApplyDamage whole (the Expert doubling, the gHasShield branch, and all five hit directions), Player_DamageWings for both sides, Player_UpdateShields, the top of Player_Shoot, the enemy-shot damage call, and the enemy laser's ObjectInfo row against the struct's field order (damage is the eighth field); no VERSION_ conditional in the cited filesBy the atlas's arithmetic, not measured, 255 units take 13 enemy-laser hits and three side hits break a wing. The shield drains over updates, so a hit's damage shows over several updates. The gHasShield branch also sets gShieldAlpha to 2 (src/engine/fox_play.c#L894), and Player_DamageWings skips the wings while that value is 1 or more. What sets gHasShield and how a broken wing is repaired (on rails, and the all-range timer gAllRangeWingRepairTimer) aren't read. Expert mode and the gold-ring cap are named by the decompilers' variables (gExpertMode, gGoldRingCount).
Cited by: Locational damage, Star Fox 64