The Legend of Zelda: Ocarina of Time
Nintendo's 1998 Nintendo 64 Zelda, read through the zeldaret decompilation.
On this page
The atlas reads Ocarina of Time through the Zelda Reverse Engineering Team’s decompilation, C that rebuilds each release’s ROM. Its default build is a GameCube debug ROM; the claims here cite code that compiles the same way for the Nintendo 64’s NTSC 1.0 release. Names and comments quoted here are theirs. Nothing has been measured in the game yet.
Pressing Z locks Link onto the actor Navi hovers over, or holds his facing if there’s nothing to lock onto1. The camera takes the target as its view target2, and Link’s model turns toward it while the stick steers his movement separately3. The game sorts the stick into four quarters around his facing4.
A then depends on the quarter. Back or to a side, Link hops that way5: a backflip, higher and shorter, or a lower, faster side hop6. Forward or neutral while Z-targeting, he jump-slashes if he holds a melee weapon and can use items5,7. A separate handler rolls him on A with the stick forward when no hostile lock-on is active, a dodge roll8 that ignores damage for a short window9.
Mechanics
| Mechanic | Kind | How |
|---|---|---|
| Lock-on targeting | Lock and face | Z locks onto the actor Navi hovers over; the camera takes it as its view target and Link's model turns to face it |
| Evasive hop | Stick quarter | A with the stick back or to a side: a backflip at 5.8 up and 6.0 back, or a side hop at 3.5 up and 8.5 across |
| Dodge roll | Invulnerable roll | A with the stick forward; 10 updates of invulnerability from the animation's frame 8 |
Every claim about this game, by concept (10)
References
- ^In the N64 NTSC 1.0 code, pressing Z locks Link onto the actor Navi hovers over, unless that actor has lock-on disabled; with nothing to lock onto, Z starts "parallel" mode instead. Under the Hold setting the lock lasts while Z is held; under Switch, a flag keeps it after Z is let go.Codezeldaret/oot
How we know
3934 CHECK_BTN_ALL(sControlInput->press.button, BTN_Z)) { ⋮ 3938 nextLockOnActor = play->actorCtx.attention.naviHoverActor; ⋮ 3946 usingHoldTargeting = (gSaveContext.zTargetSetting != 0) || (this->actor.category != ACTORCAT_PLAYER); ⋮ 3950 if ((nextLockOnActor != NULL) && !(nextLockOnActor->flags & ACTOR_FLAG_LOCK_ON_DISABLED)) {
src/overlays/actors/ovl_player_actor/z_player.c lines 3932–3950 at 14759ab · upstream ↗ 3962 if (!usingHoldTargeting) { 3963 this->stateFlags2 |= PLAYER_STATE2_LOCK_ON_WITH_SWITCH; ⋮ 3966 this->focusActor = nextLockOnActor;
src/overlays/actors/ovl_player_actor/z_player.c lines 3962–3966 at 14759ab · upstream ↗ 3976 } else { 3977 // Lock-on was not started above. Set Parallel Mode. ⋮ 3979 Player_SetParallel(this);
src/overlays/actors/ovl_player_actor/z_player.c lines 3976–3979 at 14759ab · upstream ↗ 3898 } else if (zButtonHeld || (this->stateFlags2 & PLAYER_STATE2_LOCK_ON_WITH_SWITCH) || 3899 (this->autoLockOnActor != NULL)) {
src/overlays/actors/ovl_player_actor/z_player.c lines 3898–3899 at 14759ab · upstream ↗ 9typedef enum ZTargetSetting { ⋮ 12} ZTargetSetting;
include/save.h lines 9–12 at 14759ab · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-25.
Checked by a second AI model, 2026-10-21.
Checked by a second AI model, 2026-10-25.
- ^In the N64 NTSC 1.0 code, unless an earlier branch applies (hookshot flight, pushing or pulling, and a few others), while Link has a focus actor his update makes that actor the main camera's view target and picks a mode for it: talk mode if Link's actor has the talk flag set, else, with the friendly-focus flag set, boomerang-follow mode if a boomerang is out or the friendly Z-target mode, and otherwise the unfriendly Z-target mode. In parallel mode it picks Z-aim mode when either of two checks the decomp leaves unnamed (func_8002DD78, func_808334B4) passes, else Z-wall-climb mode with an unnamed state flag (stateFlags1 bit 21) set, else the Z-parallel mode.Codezeldaret/ootrevised
How we know
11379 } else if ((focusActor = this->focusActor) != NULL) { 11380 if (ACTOR_FLAGS_CHECK_ALL(&this->actor, ACTOR_FLAG_TALK)) { 11381 camMode = CAM_MODE_TALK; 11382 } else if (this->stateFlags1 & PLAYER_STATE1_FRIENDLY_ACTOR_FOCUS) { 11383 if (this->stateFlags1 & PLAYER_STATE1_BOOMERANG_THROWN) { 11384 camMode = CAM_MODE_FOLLOW_BOOMERANG; 11385 } else { 11386 camMode = CAM_MODE_Z_TARGET_FRIENDLY; ⋮ 11388 } else { 11389 camMode = CAM_MODE_Z_TARGET_UNFRIENDLY; ⋮ 11391 Camera_SetViewParam(Play_GetCamera(play, CAM_ID_MAIN), CAM_VIEW_TARGET, focusActor);
src/overlays/actors/ovl_player_actor/z_player.c lines 11379–11391 at 14759ab · upstream ↗ 11403 } else if (this->stateFlags1 & (PLAYER_STATE1_PARALLEL | PLAYER_STATE1_LOCK_ON_FORCED_TO_RELEASE)) { 11404 if (func_8002DD78(this) || func_808334B4(this)) { 11405 camMode = CAM_MODE_Z_AIM; 11406 } else if (this->stateFlags1 & PLAYER_STATE1_21) { 11407 camMode = CAM_MODE_Z_WALL_CLIMB; 11408 } else { 11409 camMode = CAM_MODE_Z_PARALLEL;
src/overlays/actors/ovl_player_actor/z_player.c lines 11403–11409 at 14759ab · upstream ↗ 11431 Camera_RequestMode(Play_GetCamera(play, CAM_ID_MAIN), camMode);src/overlays/actors/ovl_player_actor/z_player.c line 11431 at 14759ab · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-21.
Revised after a check by a second AI model, 2026-10-21.
- ^In the N64 NTSC 1.0 code, while Link has a lock-on target and the reticle counter (
reticleSpinCounter, which the decompilers' comment says counts up while a reticle is active) isn't 0, Link's model turns each update toward the locked-on actor's focus point, and in parallel mode toward the parallel angle; the direction he moves in is a separate yaw, which the stick sets relative to the camera. Two state flags the decomp leaves unnamed skip the turn, and the Z-target run and the roll set one of them.Codezeldaret/ootHow we know
3765 * Updates Shape Yaw (`shape.rot.y`). In other words, the Y rotation of Player's model. 3766 * This does not affect the direction Player will move in. ⋮ 3776 if (!(this->stateFlags2 & (PLAYER_STATE2_5 | PLAYER_STATE2_6))) { ⋮ 3780 ((play->actorCtx.attention.reticleSpinCounter != 0) || (this->actor.category != ACTORCAT_PLAYER))) { 3781 Math_ScaledStepToS(&this->actor.shape.rot.y, Math_Vec3f_Yaw(&this->actor.world.pos, &focusActor->focus.pos), 3782 4000); ⋮ 3784 !(this->stateFlags2 & (PLAYER_STATE2_5 | PLAYER_STATE2_6))) { 3785 Math_ScaledStepToS(&this->actor.shape.rot.y, this->parallelYaw, 4000);
src/overlays/actors/ovl_player_actor/z_player.c lines 3765–3785 at 14759ab · upstream ↗ 430 /* 0x4B */ u8 reticleSpinCounter; // Counts up when a reticle is active, used for the spinning animationinclude/actor.h line 430 at 14759ab · upstream ↗ 4146 } else { 4147 *outYawTarget += Camera_GetInputDirYaw(GET_ACTIVE_CAM(play)); 4148 return true;
src/overlays/actors/ovl_player_actor/z_player.c lines 4146–4148 at 14759ab · upstream ↗ 8832 this->stateFlags2 |= PLAYER_STATE2_5;src/overlays/actors/ovl_player_actor/z_player.c line 8832 at 14759ab · upstream ↗ 9695 this->stateFlags2 |= PLAYER_STATE2_5;src/overlays/actors/ovl_player_actor/z_player.c line 9695 at 14759ab · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-25.
Checked by a second AI model, 2026-10-21.
Checked by a second AI model, 2026-10-25.
- ^In the N64 NTSC 1.0 code, each update sorts the control stick into one of four quarters relative to the way Link's model faces: forward, left, backward, or right, each 90° wide and centered on its direction, after turning the stick angle by the camera's. Below a magnitude of 55 the stick counts as no direction.Codezeldaret/oot
How we know
2212 sControlStickWorldYaw = Camera_GetInputDirYaw(GET_ACTIVE_CAM(play)) + sControlStickAngle; ⋮ 2216 if (sControlStickMagnitude < 55.0f) { 2217 direction = PLAYER_STICK_DIR_NONE; ⋮ 2221 direction = (u16)((s16)(sControlStickWorldYaw - this->actor.shape.rot.y) + 0x2000) >> 14;
src/overlays/actors/ovl_player_actor/z_player.c lines 2211–2223 at 14759ab · upstream ↗ 639 /* -1 */ PLAYER_STICK_DIR_NONE = -1, 640 /* 0 */ PLAYER_STICK_DIR_FORWARD, 641 /* 1 */ PLAYER_STICK_DIR_LEFT, 642 /* 2 */ PLAYER_STICK_DIR_BACKWARD, 643 /* 3 */ PLAYER_STICK_DIR_RIGHT
include/player.h lines 639–643 at 14759ab · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-21.
Checked by a second AI model, 2026-10-21.
- ^ abIn the N64 NTSC 1.0 code, the action handler the decomp calls Player_ActionHandler_10 acts on a press of A, except in a room of the indoors type, on floor type 7, or on a floor with floor effect 1 (both unnamed): with the stick backward, left, or right it starts a hop that way; with the stick forward or neutral, only while Z-targeting, it starts a jump slash if Link holds a melee weapon and can use items, and a roll otherwise.Codezeldaret/oot
How we know
6388 if (CHECK_BTN_ALL(sControlInput->press.button, BTN_A) && (play->roomCtx.curRoom.type != ROOM_TYPE_INDOORS) && 6389 (sFloorType != FLOOR_TYPE_7) && 6390 (SurfaceType_GetFloorEffect(&play->colCtx, this->actor.floorPoly, this->actor.floorBgId) != FLOOR_EFFECT_1)) { 6391 controlStickDirection = this->controlStickDirections[this->controlStickDataIndex]; ⋮ 6393 if (controlStickDirection <= PLAYER_STICK_DIR_FORWARD) { 6394 if (Player_IsZTargeting(this)) { ⋮ 6398 } else { 6399 Player_SetupRoll(this, play); ⋮ 6401 } else { 6402 if ((Player_GetMeleeWeaponHeld(this) != 0) && Player_CanUpdateItems(this)) { 6403 func_8083BA90(play, this, PLAYER_MWA_JUMPSLASH_START, 5.0f, 5.0f); 6404 } else { ⋮
src/overlays/actors/ovl_player_actor/z_player.c lines 6385–6417 at 14759ab · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-21.
Checked by a second AI model, 2026-10-21.
- ^In the N64 NTSC 1.0 code, the backward hop (a backflip, by its animation) launches Link at a vertical velocity of 5.8 and a horizontal speed of 6.0 aimed straight behind his facing, and a sideways hop at 3.5 up and 8.5 aimed 90° to the left or right. The vertical value is multiplied by a water factor, sWaterSpeedFactor, which this pass didn't read.Codezeldaret/oot
How we know
6368void func_8083BCD0(Player* this, PlayState* play, s32 controlStickDirection) { 6369 func_80838940(this, D_80853D4C[controlStickDirection][0], !(controlStickDirection & 1) ? 5.8f : 3.5f, play, ⋮ 6377 this->yaw = this->actor.shape.rot.y + (controlStickDirection << 0xE); 6378 this->speedXZ = !(controlStickDirection & 1) ? 6.0f : 8.5f;
src/overlays/actors/ovl_player_actor/z_player.c lines 6368–6378 at 14759ab · upstream ↗ 4982void func_80838940(Player* this, LinkAnimationHeader* anim, f32 arg2, PlayState* play, u16 sfxId) { ⋮ 4989 this->actor.velocity.y = arg2 * sWaterSpeedFactor;
src/overlays/actors/ovl_player_actor/z_player.c lines 4982–4989 at 14759ab · upstream ↗ 1120static LinkAnimationHeader* D_80853D4C[][3] = { ⋮ 1123 { &gPlayerAnim_link_fighter_Lside_jump, &gPlayerAnim_link_fighter_Lside_jump_end, ⋮ 1125 { &gPlayerAnim_link_fighter_backturn_jump, &gPlayerAnim_link_fighter_backturn_jump_end, ⋮ 1127 { &gPlayerAnim_link_fighter_Rside_jump, &gPlayerAnim_link_fighter_Rside_jump_end,
src/overlays/actors/ovl_player_actor/z_player.c lines 1120–1128 at 14759ab · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-21.
Checked by a second AI model, 2026-10-21.
- ^In the N64 NTSC 1.0 code, the jump slash started from the Z-targeting A press (stick forward or neutral, holding a melee weapon) sends Link forward along his facing at a speed of 5.0 with a vertical velocity of 5.0.Codezeldaret/oot
How we know
6403 func_8083BA90(play, this, PLAYER_MWA_JUMPSLASH_START, 5.0f, 5.0f);src/overlays/actors/ovl_player_actor/z_player.c line 6403 at 14759ab · upstream ↗ 6313void func_8083BA90(PlayState* play, Player* this, s32 arg2, f32 xzSpeed, f32 yVelocity) { ⋮ 6319 this->yaw = this->actor.shape.rot.y; 6320 this->speedXZ = xzSpeed; 6321 this->actor.velocity.y = yVelocity;
src/overlays/actors/ovl_player_actor/z_player.c lines 6313–6321 at 14759ab · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-21.
Checked by a second AI model, 2026-10-21.
- ^In the N64 NTSC 1.0 code, Link rolls when A is pressed with the stick forward, provided there is no hostile lock-on, his upper body isn't busy, an unnamed state flag (stateFlags1 bit 23) is clear, and the floor isn't floor type 7; with the stick elsewhere the same press puts away a held item or toggles Navi. The roll animation plays at 1.25 times speed (1.5 in the PAL N64 builds).Codezeldaret/oot
How we know
6489s32 Player_ActionHandler_Roll(Player* this, PlayState* play) { 6490 if (!Player_UpdateHostileLockOn(this) && !sUpperBodyIsBusy && !(this->stateFlags1 & PLAYER_STATE1_23) && 6491 CHECK_BTN_ALL(sControlInput->press.button, BTN_A)) { 6492 if (Player_TryRoll(this, play)) { 6493 return true; 6494 } else if ((this->putAwayCooldownTimer == 0) && (this->heldItemAction >= PLAYER_IA_SWORD_MASTER)) { 6495 Player_UseItem(play, this, ITEM_NONE); 6496 } else { 6497 this->stateFlags2 ^= PLAYER_STATE2_NAVI_ACTIVE;
src/overlays/actors/ovl_player_actor/z_player.c lines 6489–6498 at 14759ab · upstream ↗ 6350void Player_SetupRoll(Player* this, PlayState* play) { 6351 Player_SetupAction(play, this, Player_Action_Roll, 0); ⋮ 6354 FRAMERATE_CONST(1.25f, 1.5f) * sWaterSpeedFactor); ⋮ 6357s32 Player_TryRoll(Player* this, PlayState* play) { 6358 if ((this->controlStickDirections[this->controlStickDataIndex] == PLAYER_STICK_DIR_FORWARD) && 6359 (sFloorType != FLOOR_TYPE_7)) {
src/overlays/actors/ovl_player_actor/z_player.c lines 6350–6360 at 14759ab · upstream ↗ 46#if !OOT_PAL_N64 47#define FRAMERATE_CONST(value60Hz, value50Hz) (value60Hz) 48#else 49#define FRAMERATE_CONST(value60Hz, value50Hz) (value50Hz) 50#endif
include/versions.h lines 44–50 at 14759ab · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-21.
Checked by a second AI model, 2026-10-21.
- ^In the N64 NTSC 1.0 code, a roll makes Link invulnerable for 10 updates from frame 8 of its animation (8 updates in the PAL N64 builds): damage is ignored but, per the decompilers' comment, not the responses to it such as knockback.Codezeldaret/oot
How we know
9700 if (LinkAnimation_OnFrame(&this->skelAnime, 8.0f)) { 9701 Player_SetInvulnerability(this, FRAMERATE_CONST(-10, -8));
src/overlays/actors/ovl_player_actor/z_player.c lines 9700–9702 at 14759ab · upstream ↗ 4584 * @param timer must be a negative value representing the number of invulnerability frames. 4585 * @note Intangibility prevents taking damage and responses to damage like knockback, while invulnerability only 4586 * prevents taking damage. ⋮ 4588void Player_SetInvulnerability(Player* this, s32 timer) { 4589 if (this->invincibilityTimer > timer) { 4590 this->invincibilityTimer = timer;
src/overlays/actors/ovl_player_actor/z_player.c lines 4584–4590 at 14759ab · upstream ↗ 11709 if (this->invincibilityTimer < 0) { 11710 this->invincibilityTimer++;
src/overlays/actors/ovl_player_actor/z_player.c lines 11709–11710 at 14759ab · upstream ↗ 979 /* 0x0A78 */ s8 invincibilityTimer; // prevents damage when nonzero. Positive values are intangibility, negative are invulnerabilityinclude/player.h line 979 at 14759ab · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-21.
Checked by a second AI model, 2026-10-21.
- ^In the N64 NTSC 1.0 code, the action that holds Link standing while he Z-targets (Player_Action_808407CC in the decomp) offers the hop handler to A, and hands control to the idle action once Z-targeting ends; the idle action's own handler list leaves the hop handler out.Codezeldaret/oot
How we know
4174static s8 sActionHandlerList2[] = { 4175 PLAYER_ACTION_HANDLER_13, PLAYER_ACTION_HANDLER_1, PLAYER_ACTION_HANDLER_2, PLAYER_ACTION_HANDLER_5, 4176 PLAYER_ACTION_HANDLER_3, PLAYER_ACTION_HANDLER_TALK, PLAYER_ACTION_HANDLER_9, PLAYER_ACTION_HANDLER_10, 4177 PLAYER_ACTION_HANDLER_11, PLAYER_ACTION_HANDLER_7, PLAYER_ACTION_HANDLER_8, -PLAYER_ACTION_HANDLER_ROLL,
src/overlays/actors/ovl_player_actor/z_player.c lines 4174–4178 at 14759ab · upstream ↗ 4201static s8 sActionHandlerListIdle[] = { 4202 PLAYER_ACTION_HANDLER_0, PLAYER_ACTION_HANDLER_11, PLAYER_ACTION_HANDLER_1, PLAYER_ACTION_HANDLER_2, 4203 PLAYER_ACTION_HANDLER_3, PLAYER_ACTION_HANDLER_5, PLAYER_ACTION_HANDLER_TALK, PLAYER_ACTION_HANDLER_9, 4204 PLAYER_ACTION_HANDLER_8, PLAYER_ACTION_HANDLER_7, -PLAYER_ACTION_HANDLER_ROLL,
src/overlays/actors/ovl_player_actor/z_player.c lines 4201–4205 at 14759ab · upstream ↗ 8158 if (!Player_TryActionHandlerList(play, this, sActionHandlerList2, true)) { 8159 if (Player_UpdateHostileLockOn(this)) { 8160 func_8083CEAC(this, play); 8161 return; ⋮ 8164 if (!Player_FriendlyLockOnOrParallel(this)) { 8165 Player_SetupActionPreserveAnimMovement(play, this, Player_Action_Idle, 1); 8166 this->yaw = this->actor.shape.rot.y; 8167 return;
src/overlays/actors/ovl_player_actor/z_player.c lines 8158–8167 at 14759ab · upstream ↗ 4259 Player_ActionHandler_10, // PLAYER_ACTION_HANDLER_10src/overlays/actors/ovl_player_actor/z_player.c line 4259 at 14759ab · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-21.
Checked by a second AI model, 2026-10-21.
Sources
Code
- zeldaret. 2026. “oot: a decompilation of The Legend of Zelda: Ocarina of Time (zeldaret/oot).” Commit 14759ab. zeldaret/oot @ 14759ab · licence, all uses