Legend of Zelda/Technical Information
(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.
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.