Legend of Zelda/Technical Information

From Red Candle
Revision as of 10:37, 17 November 2017 by Fiskbit (talk | contribs) (Technical Information - Forces table of contents.)
Jump to: navigation, search

(Note that details and naming are subject to change as more is learned; analysis of the game is ongoing)

Object system

The game allocates memory for up to 19 objects of varying types. These types and their indices are:

  • #$00 - Link
  • #$01-0B - dynamic objects
  • #$0D - the sword
  • #$0E - the sword or rod beam
  • #$0F - the boomerang or the bait
  • #$10-11 - flames and bombs
  • #$12 - the rod or an arrow
  • #$13 - and the room treasure


Dynamic objects consist of enemies, their projectiles, NPCs, and Link's ladder, and are allocated dynamically rather than having a static index. Link's weapons have their own slots so they can be used even when the enemy cap has been reached, though some share slots to limit their memory footprint, which means they can't be used at the same time. For bombs and flames, they are dynamically assigned the first free slot of the two available, so a mixture of two of these can be on the screen at once. Note that nothing is ever assigned to slot #$0C, though its timer and memory are sometimes used for other purposes.

Object data is stored in a number of arrays that are indexed with the object index. Many of these arrays are available to all objects, while some are limited to only dynamic objects and sometimes Link. The following are many of the most relevant ones:

$28 - object_timer: A timer that automatically decrements each frame, usually dedicated to the object.

$3C - object_stun_timer: A long timer that decrements when the long_timer_tick ($26) goes below 0, usually used to indicate an enemy is stunned by the boomerang. Because this timer can be set at any time, the first tick will usually be too short.

$70 - object_pos_x: The X position of the object on the screen.

$84 - object_pos_y: The Y position of the object on the screen. Note that this is considered block-aligned when ending in #$D rather than #$0. This is to give some illusion of depth by drawing objects 2 pixels higher than the block they're on (only 2 because the PPU draws sprites 1 scanline lower than their specified Y coordinate). Unfortunately, this gives rise to various bugs, such as gels popping out of zols a full block too low.

$98 - object_face_direction: The direction the object is facing. Objects with grid-based movement will restrict this to the grid to avoid off-grid movement. For room treasures, this is the item ID. Like with most direction-based tracking in this game, the direction order is up, down, left, right (UDLR, most to least significant) in the low nybble.

$AC - object_status: Used for many different state-related tracking for different objects. Many objects use this to track what state the AI is in. Link uses it for his attack sequences as well as entirely disabling his handling (movement and collision). Link's weapon object slots are empty when this is clear.

$C0 - object_knockback_direction: Indicates which direction the object should travel when the knockback timer is set. UDLR in the low nybble. When the object has been hit, bit 7 is set to indicate that the direction needs to be sanity-checked to prevent knockback from knocking an object off-grid, and if the object doesn't reverse direction when it collides with Link, bit 6 is also set, but this appears to go unused.

$D3 - object_knockback_timer: The number of movement ticks for which the object should undergo knockback. Like with standard movement, 4 ticks are consumed each frame, but the knockback speed is always 1 full pixel per tick. When this hits 0, the corresponding object_knockback_direction variable is cleared.

$034F - dynamic_id: The object ID for dynamic objects. If this is 0, the slot is unoccupied, except in rare circumstances when slots are being controlled by another object (eg gleeok controlling its attached heads). (No index 0)

$0394 - object_subgrid_offset: The object's position along its current gridline. When sitting on a grid intersection, this will be 0, and positive (right and down) and negative (left and up) movement will increase or decrease this in lock-step with the object's pixel position. This is clamped in either direction by subgrid_offset_limit_positive ($010E) and subgrid_offset_limit_negative ($010F), which are +/-8 for Link and +/-16 for enemies, and further movement is not allowed that frame if clamped. If this is 0 or either limit after movement is complete, it is set to 0 and collision is handled.

$03A8 - object_subpos: The object's subpixel position. Because grid-based objects can't move more than one direction at a time, this acts as the subpixel position for the current direction, whether horizontal or vertical. This doesn't get cleared when clamped to a grid intersection, and isn't cleared when changing directions. This results in quirks like faster movement when going diagonally by alternating positive/negative directions, or the boomerang having unpredictable speed when moving diagonally (because both directions interpret the subpos as being in their own direction). For dropped items, this variable acts as the timer (2 frames per tick).

$03BC - object_speed: An object's subpixel speed per movement tick. 4 ticks are done per frame, limiting an object's speed to just under 4 pixels. Because object movement is clamped when reaching new grid intersections, an object might only move 1-3 ticks on a given frame.

$03D0 - object_animation_timer: The number of frames before advancing to the next frame of the object's animation.

$03E4 - object_frame: The current frame of the object's animation.

$03F8 - dynamic_move_direction: The direction the object is moving in, for Link or dynamic objects. UDLR in the low nybble.

$0405 - dynamic_spawn_state: The object's state when spawning or dying. (No index 0)

$0485 - dynamic_health: The object's current health. For some reason, object health is handled in multiples of #$10, so it can't exceed an effective value of 15. If health is 0 and an object gets hit by any weapon it's vulnerable to, it will die, even if that weapon does 0 damage. (No index 0)

$049E - object_collision_tile: The ID of the last tile the object collided with.

$04B2 - dynamic_immunities: Flags indicating the weapons the object is immune to. These are:

  • Bit 0 - sword
  • Bit 1 - boomerang
  • Bit 2 - arrow
  • Bit 3 - bomb
  • Bit 4 - rod
  • Bit 5 - flame


Bits 6 and 7 appear to be unused, but are set normally set for enemies that have any immunities. (No index 0)

$04BF - dynamic_properties: Flags indicating properties of the object. These are:

  • Bit 0 - The object's AI is responsible for drawing the object and calling weapon and Link collision functions, rather than these being done automatically after the AI has run.
  • Bit 1 - The object is drawn using 1 tile, not 2.
  • Bit 2 - The object's AI is responsible for drawing the object.
  • Bit 3 - The object's AI is responsible for setting its own frame attributes (in $04 and $05) before calling the object drawing functions.
  • Bit 4 - The object always reverses direction when colliding with an obstacle (block, wall, or screen boundary), overriding the more complicated turning behavior normally exhibited. This appears to be unused.
  • Bit 5 - Disables weapon collision for the object.
  • Bit 6 - Moves the object's collision center 4 pixels to the left, under the (often incorrect) assumption that thin objects are centered by being positioned 4 pixels to the right. This is why many vertical projectiles can be dodged more easily on the right.
  • Bit 7 - Makes the object not reverse direction when colliding with Link.


(No index 0)

$04F0 - object_iframes: The number of frames before the object will handle object collision again. The low two bits determine which palette the object's sprite will use.

Forced drops

Some item drops can be forced and take priority over the standard drop system. Forced drops are handled by two consecutive kill counters and a bomb flag. The first counter, the ten count ($50), controls whether a forced 5 rupee is dropped. The bomb flag ($51) controls whether the ten count drop should be a bomb, instead. The second counter, the fairy count ($0627), controls whether fairies are dropped. All of these variables are cleared when Link collides with an enemy (including bubbles and even the recorder whirlwind).

When Link kills an enemy (that is, the enemy makes a dying sound), both counters are incremented. This occurs at kill time, before the dying animation. Item drops occur later, after the dying animation completes. When the drop is calculated, the game checks the counters to decide what to drop. If the fairy count is 16, then a fairy is dropped and the ten count and bomb flag are cleared. Otherwise, if the ten count is 10, either a 5 rupee is dropped if the bomb flag is clear or a bomb is dropped if the flag is set, and the ten count and bomb flag are then cleared. So, in the case of any forced drop, the ten count and bomb flag are cleared, but the fairy count is not.

Some enemies cannot drop items. Because they don't run the drop code, they also can't drop forced items, and thus won't consume the counters. These enemies are those in the no-drop group, as well as stalfos, gibdos, and like likes in slot 1 (because these enemies may be carrying the room treasure, and it would be undesirable if they dropped both the treasure and an item). We'll call these no-drop enemies.

If the ten count transitions from 9 to 10 due to a bomb kill, the bomb flag is set. The ten count cannot exceed 10 and will hold at that value even if additional kills occur before the forced item can be dropped, such as by killing a group of enemies at once or killing no-drop enemies. Once any forced drop occurs, the counter is reset to 0.

The fairy counter, however, has no hard limit. It will continue to increase with each kill. Because of this, the fairy drop can be skipped. Any drop occurring when the fairy counter is 16 will be a fairy, so killing a group of enemies to bring the count above 16 at drop time will skip the fairy. Alternatively, because no-drop enemies can't drop forced items, they can die with the counter at 16 without performing the drop. Skipping the fairy will avoid resetting the ten count and bomb flag.

Because the fairy counter isn't cleared when the drop occurs, multiple fairies can be dropped from the same count. For example, if the counter is 12 and 4 enemies are quickly killed before any of them can calculate a drop, the counter will be 16 when the drops are handled, so all 4 drops will be fairies.

Note that while the fairy counter has no hard limit, it is an 8-bit value and will overflow to 0 at 256 kills, allowing it to force more fairies at the next 16.

There are some notable special cases and edge cases with counters. These include:

Dodongos: Killing a dodongo does not increment either counter, whether killed with bombs or the sword. If the dodongo is killed with a sword, however, the ten count is set to 10 and the bomb flag is set, forcing a bomb at drop time. This method of forcing the bomb can result in unexpected behavior. If Link takes a hit after killing the dodongo, but before the drop, the forced bomb will be lost. Furthermore, if the fairy count is 16 when the drop occurs, the fairy will be dropped instead of the bomb and the ten count and bomb flag will be cleared. Finally, if Link rapidly kills multiple dodongos with a sword, only one bomb will drop because the forced drop system has no way of queuing up multiple forced bombs.

Vulnerable unkillable enemies: Enemies such as the manhandla core and flying gleeok heads are invincible, but actually handle weapon collision and will clear their iframes and unkill themselves each frame. This means they count as a kill every frame that a weapon is touching them. Bombing these enemies is a good way to force a bomb because it will quickly max out the ten count and set the bomb flag, but it will also be adding to the fairy counter, which could override the bomb drop when killing the boss. The old man in the dungeon also behaves this way, but because he doesn't clear his iframes, he'll only count as one kill per attack.

Multi-part enemies: Manhandla's hands, gleeok's attached heads, and moldorm's and lanmola's segments each increment the consecutive counters when killed, but the death of the whole enemy does not grant an additional consecutive kill; that is, killing the last part will only add 1 to the counters, not 2. A perfect bomb on manhandla will increase the counters by 5 instead of 4, though, because the core is also hit and counts as a kill.

Leader enemies: When a ringleader (such as the level 2 goriya leader) is killed, the other enemies in the room die by being directly transitioned to the dying state (by setting dynamic_spawn_state to #$10) and will not impact the consecutive counters. The ringleader itself counts as normal, however. Ghini leaders also share this same behavior (through a similar mechanism).

Zols: While splitting enemies (zols and vires) don't normally count as kills when split, zols are vulnerable to an additional weapon hit on the frame they split, allowing them to be killed and counted as a consecutive kill. This is due to a bug where zols delete themselves before splitting (in case there aren't enough slots for the gels they're creating), but instead of exiting immediately afterward, their AI continues to do meaningful work on the deleted object, including collision checks. The deletion (through ClearObject at Bank7_FEB1) clears the object_iframes variable, which guarantees the zol can take a hit during this one last check. Because deletion clears the object's dynamic_id variable, the zol doesn't run again and thus can't drop an item nor increment the global counter. Its health is not cleared by ClearObject, so the weapons it collides with must do enough damage to kill it. Note that if a gel was spawned into the zol's slot, the gel will be hit, instead.

Vires are more robustly written and don't suffer from this bug.

Sub-frame behavior: Because everything in the game must be handled sequentially, events handled earlier can directly impact those handled later, so the order of execution can result in surprising behavior. For example, if an enemy is killed on the same frame that multiple other enemies are dropping items, and that kill sets the fairy counter to 16, then any drops handled before the kill was registered would be normal random drops, and drops after the kill would be forced fairies, despite all of the drops happening on the same frame.

Additional references

A Moonmap for Legend of Zelda - A technical overview of Zelda, primarily covering graphics layout, level format, and sound.

6502 Instruction Reference - The full 6502 instruction set, including cycle counts.

Nesdev Wiki - Comprehensive documentation on everything related to NES hardware behavior.