Claim set · data/claims/alttp-av.yaml

The Legend of Zelda: A Link to the Past screen and sound effects (kan's US disassembly)

Game: The Legend of Zelda: A Link to the Past.

On the game over screen and on the exits that use it, a circle closes on Link: an indirect HDMA channel (mode $41, register $2126, window 1's left edge) feeds the window a 224-line table of left and right edges, and the three window-select registers get $33, $03, and $33, which enable window 1 inverted for BG1, BG2, BG3, sprites, and the color window, and it copies the main and subscreen layer choices into the window-enable registers, so every layer shown is masked outside it. Each frame the radius changes by 7 lines, from 126 to 0 as the circle closes and from 0 to 126 as it opens, so either takes 18 frames (computed). For each line within the radius of the point 12 lines below Link's position, the code divides the line's distance by the radius to index a 129-entry quarter-circle table (255 at the center falling to 0 at the edge), multiplies the entry by the radius, keeps the high byte, and doubles it to get the half-width, so the window is about twice as wide as it is tall (computed); lines outside get the word $00FF, an empty window. Outdoors the routine also sets the fixed color to black. It builds each table elsewhere and copies it into the HDMA buffer only once the beam has passed line 192. On the game over screen the window-select registers then change to $33, 0, and $30, so BG3 and the sprites stay visible outside the circle. alttp-av/iris-spotlight

About: The Legend of Zelda: A Link to the Past HDMA

Value
{
 "step": 7,
 "radius": 126,
 "frames": 18,
 "lines": 224,
 "copyAfterLine": 192,
 "quarterCircle": [
  255,
  255,
  255,
  255,
  255,
  255,
  255,
  255,
  255,
  255,
  255,
  255,
  254,
  254,
  254,
  254,
  253,
  253,
  253,
  253,
  252,
  252,
  252,
  251,
  251,
  251,
  250,
  250,
  249,
  249,
  248,
  248,
  247,
  247,
  246,
  246,
  245,
  245,
  244,
  243,
  243,
  242,
  241,
  241,
  240,
  239,
  238,
  238,
  237,
  236,
  235,
  234,
  233,
  233,
  232,
  231,
  230,
  229,
  228,
  227,
  226,
  225,
  223,
  222,
  221,
  220,
  219,
  218,
  216,
  215,
  214,
  213,
  211,
  210,
  208,
  207,
  205,
  204,
  202,
  201,
  199,
  198,
  196,
  194,
  193,
  191,
  189,
  187,
  185,
  183,
  182,
  180,
  177,
  175,
  173,
  171,
  169,
  167,
  164,
  162,
  159,
  157,
  154,
  151,
  149,
  146,
  143,
  140,
  137,
  134,
  130,
  127,
  123,
  120,
  116,
  112,
  108,
  103,
  99,
  94,
  89,
  83,
  77,
  70,
  63,
  55,
  45,
  31,
  0
 ]
}
Codeusdasm: A Link to the Past US Disassembly · bank_00.asm#L14925-L15032IrisSpotlight_close: |00F28B| REP #$10 |00F28D| LDY.w #$0000 |00F290| LDX.w #$007E … #IrisSpotlight_open: |00F295| REP #$10 |00F297| LDY.w #$0002 |00F29A| LDX.w #$0000 … |00F29D| STY.w $067E |00F2A0| STX.w $067C … |00F2A6| LDX.w #$2641 |00F2A9| STX.w DMA6MODE |00F2AC| STX.w DMA7MODE |00F2AF| LDX.w #.hdma_table … |00F2C8| LDA.b #$33 |00F2CA| STA.b $96 |00F2CC| LDA.b #$03 |00F2CE| STA.b $97 |00F2D0| LDA.b #$33 |00F2D2| STA.b $98 … |00F2DC| LDA.b $1B |00F2DE| BNE .indoors |00F2E0| LDA.b #$20 ; Fixed color RGB: #000000 |00F2E2| STA.b $9C |00F2E4| LDA.b #$40 |00F2E6| STA.b $9D |00F2E8| LDA.b #$80 |00F2EA| STA.b $9E … |00F2EE| JSL IrisSpotlight_ConfigureTable |00F2F2| LDA.b #$80 |00F2F4| STA.b $9B … .hdma_table |00F2FB| db $F8 : dw $1B00 ; 120 lines continuous |00F2FE| db $F8 : dw $1BF0 ; 120 lines continuous |00F301| db $00 ; end … .delta |00F302| dw -7 |00F304| dw 7 … .target |00F30A| dw $0000 |00F30C| dw $007Eusdasm: A Link to the Past US Disassembly · bank_00.asm#L15037-L15192IrisSpotlight_ConfigureTable: … |00F317| LDA.b $20 |00F319| SEC |00F31A| SBC.b $E8 |00F31C| CLC |00F31D| ADC.w #$000C |00F320| STA.b $0E |00F322| SEC |00F323| SBC.w $067C |00F326| STA.w $0674 |00F329| LDA.b $0E |00F32B| CLC |00F32C| ADC.w $067C |00F32F| STA.w $0676 … .next_check |00F361| LDA.w #$00FF |00F364| STA.b $08 |00F366| LDA.b $06 |00F368| CMP.w $0676 |00F36B| BCS .past_calced_size |00F36D| LDA.w $067A |00F370| BEQ .at_zero |00F372| DEC.w $067A .at_zero |00F375| JSR IrisSpotlight_CalculateCircleValue … |00F39C| INC.b $04 |00F39E| DEC.b $06 |00F3A0| JMP.w .next_check … .wait_for_scanline … |00F3A9| LDA.w OPVCT |00F3AC| AND.w #$00FF |00F3AF| CMP.w #$00C0 |00F3B2| BCC .wait_for_scanline |00F3B4| LDX.w #$0000 .copy_table |00F3B7| LDA.l $7F7000,X |00F3BB| STA.w $1B00,X … |00F3C5| LDX.w $067E |00F3C8| LDA.w $067C |00F3CB| CLC |00F3CC| ADC.w .delta,X |00F3CF| STA.w $067C |00F3D2| CMP.w .target,X |00F3D5| BNE .exitusdasm: A Link to the Past US Disassembly · bank_00.asm#L15270-L15383.multiplicand |00F44B| db 255, 255, 255, 255, 255, 255, 255, 255 … |00F4C3| db 89, 83, 77, 70, 63, 55, 45, 31 |00F4CB| db 0 … IrisSpotlight_CalculateCircleValue: |00F4CC| SEP #$30 |00F4CE| STA.w WRDIVH |00F4D1| STZ.w WRDIVL |00F4D4| LDA.w $067C |00F4D7| STA.w WRDIVB … |00F4E2| LDA.w RDDIV |00F4E5| LSR A … |00F4E8| TAX |00F4E9| LDY.w .multiplicand,X |00F4EC| STY.b $0A |00F4EE| STY.w WRMPYA |00F4F1| LDA.w $067C |00F4F4| STA.w WRMPYB … |00F4FD| LDA.w RDMPYH |00F500| STA.b $00 |00F502| REP #$30 |00F504| ASL.b $00 |00F506| LDA.b $0A |00F508| BEQ .exit |00F50A| LDA.b $00 |00F50C| CLC |00F50D| ADC.w $0670 |00F510| STA.b $02 |00F512| LDA.w $0670 |00F515| SEC |00F516| SBC.b $00 … |00F530| XBA |00F531| ORA.b $00usdasm: A Link to the Past US Disassembly · bank_00.asm#L312-L321NoIRQThread: |00814C| LDA.b $96 |00814E| STA.w W12SEL |008151| LDA.b $97 |008153| STA.w W34SEL |008156| LDA.b $98 |008158| STA.w WOBJSELusdasm: A Link to the Past US Disassembly · bank_02.asm#L5015-L5017 — the exit to the overworld; the next submodule calls the table builder each frame|029A12| JSL IrisSpotlight_close |029A16| INC.b $11usdasm: A Link to the Past US Disassembly · bank_02.asm#L5026-L5027Spotlight_ExitToOverworld: |029A19| JSL IrisSpotlight_ConfigureTableusdasm: A Link to the Past US Disassembly · bank_09.asm#L20977-L20982|09F33F| JSL InitializeGAMEOVERLetters |09F343| JSL IrisSpotlight_close |09F347| LDA.b #$30 |09F349| STA.b $98 |09F34B| STZ.b $97usdasm: A Link to the Past US Disassembly · bank_00.asm#L14977-L14981|00F2D4| LDA.b $1C |00F2D6| STA.b $1E |00F2D8| LDA.b $1D |00F2DA| STA.b $1Fusdasm: A Link to the Past US Disassembly · bank_00.asm#L323-L348 — the NMI's copy of the color math, fixed color, and layer bytes to their registers|00815B| LDA.b $99 |00815D| STA.w CGWSEL |008160| LDA.b $9A |008162| STA.w CGADSUB |008165| LDA.b $9C |008167| STA.w COLDATA |00816A| LDA.b $9D |00816C| STA.w COLDATA |00816F| LDA.b $9E |008171| STA.w COLDATA |008174| LDA.b $1C |008176| STA.w TM |008179| LDA.b $1D |00817B| STA.w TS |00817E| LDA.b $1E |008180| STA.w TMW |008183| LDA.b $1F |008185| STA.w TSWPPU registers · TMW - Main screen layer window enable ($212E write)| |||+- Apply enabled windows to main screen BG1PPU registers · TSW - Subscreen layer window enable ($212F write)| |||+- Apply enabled windows to subscreen BG1PPU registers · COLDATA - Fixed color data ($2132 write)|||+-++++- Color valuePPU registers · COLDATA - Fixed color data ($2132 write)||+------- Write color value to red channelPPU registers · COLDATA - Fixed color data ($2132 write)|+-------- Write color value to green channelPPU registers · COLDATA - Fixed color data ($2132 write)+--------- Write color value to blue channelPPU registers · WH0 - Window 1 left position ($2126 write)Window 1 left edge positionPPU registers · W12SEL - Window Mask Settings for BG1 and BG2 ($2123 write)|||| |||+- Invert window 1 for BG1PPU registers · WOBJSEL - Window Mask Settings for OBJ and Color Window ($2125 write)||+------- Enable window 1 for colorChecked by claude-agent, 2026-11-08 — read IrisSpotlight_close/open, ConfigureTable, CalculateCircleValue and its 129-byte table, the NMI's copies of $96–$98 to the window-select registers and of the layer and fixed-color bytes, the copy of the layer choices into the window-enable bytes, and the exit and game over callers; the 18 frames and the width are computed from the step, the target, and the doublingRevised after a check by a second AI model, 2026-11-08.

The radius counts in screen lines. The table builder walks from the outermost line pair inward, one line above and one below the center at a time, and the counter it hands the division starts at the radius and drops by 1 a line, so the index is about 128 × distance / radius. A 1 bit in a window-select pair's high position enables window 1 for the layer and the low one inverts it.

The world map is a Mode 7 picture tilted into perspective by HDMA: two indirect HDMA channels (mode $42) write the same table into the matrix registers A ($211B) and D ($211E), one value a line for 224 lines, while B and C stay 0. Zoomed out, the value falls from $0177 on the top line to $0108 on the bottom; zoomed in, from $0088 to $0060. In the registers' 8.8 fixed point that is 1.46 to 1.03 map pixels per screen pixel zoomed out and 0.53 to 0.38 zoomed in (computed), so the top of the map is drawn smaller, as if farther away, and zooming in magnifies it about 2.75 times (computed). The map switches between the two tables at once, by repointing both channels; it opens zoomed in, but zoomed out when the flute calls it up. Each frame the vblank code sets the rotation center from two words: X is $0100, and Y is set to the map's vertical scroll plus $0100 whenever the code sets that scroll. alttp-av/world-map-perspective

About: The Legend of Zelda: A Link to the Past Mode 7

Value
{
 "lines": 224,
 "zoomedOut": [
  375,
  374,
  373,
  373,
  372,
  371,
  371,
  370,
  369,
  369,
  368,
  367,
  367,
  366,
  365,
  365,
  364,
  363,
  363,
  361,
  361,
  360,
  359,
  359,
  358,
  357,
  357,
  356,
  355,
  355,
  354,
  354,
  353,
  352,
  352,
  351,
  351,
  350,
  349,
  349,
  348,
  348,
  347,
  346,
  346,
  345,
  345,
  344,
  343,
  343,
  342,
  342,
  341,
  341,
  340,
  339,
  339,
  338,
  338,
  337,
  337,
  336,
  335,
  335,
  334,
  334,
  333,
  333,
  332,
  332,
  331,
  331,
  330,
  330,
  328,
  327,
  327,
  326,
  326,
  325,
  325,
  324,
  324,
  323,
  323,
  322,
  322,
  321,
  321,
  320,
  320,
  319,
  319,
  318,
  318,
  317,
  317,
  316,
  316,
  315,
  315,
  314,
  314,
  313,
  313,
  312,
  312,
  311,
  311,
  310,
  310,
  309,
  309,
  309,
  308,
  308,
  307,
  307,
  306,
  306,
  305,
  305,
  304,
  304,
  303,
  303,
  303,
  302,
  302,
  301,
  301,
  300,
  300,
  299,
  299,
  299,
  298,
  298,
  297,
  297,
  295,
  295,
  294,
  294,
  294,
  293,
  293,
  292,
  292,
  292,
  291,
  291,
  290,
  290,
  289,
  289,
  289,
  288,
  288,
  287,
  287,
  287,
  286,
  286,
  285,
  285,
  285,
  284,
  284,
  283,
  283,
  283,
  282,
  282,
  281,
  281,
  281,
  280,
  280,
  279,
  279,
  279,
  278,
  278,
  278,
  277,
  277,
  276,
  276,
  276,
  275,
  275,
  275,
  274,
  274,
  273,
  273,
  273,
  272,
  272,
  272,
  271,
  271,
  271,
  270,
  270,
  269,
  269,
  269,
  268,
  268,
  268,
  267,
  267,
  267,
  266,
  266,
  266,
  265,
  265,
  265,
  264,
  264,
  264
 ],
 "zoomedIn": [
  136,
  136,
  135,
  135,
  135,
  135,
  135,
  134,
  134,
  134,
  133,
  133,
  133,
  133,
  132,
  132,
  132,
  132,
  132,
  131,
  131,
  131,
  130,
  130,
  130,
  130,
  130,
  129,
  129,
  129,
  129,
  129,
  128,
  128,
  128,
  127,
  127,
  127,
  127,
  127,
  126,
  126,
  126,
  126,
  126,
  125,
  125,
  125,
  124,
  124,
  124,
  124,
  124,
  124,
  123,
  123,
  123,
  123,
  123,
  122,
  122,
  122,
  121,
  121,
  121,
  121,
  121,
  121,
  120,
  120,
  120,
  120,
  120,
  120,
  119,
  119,
  119,
  118,
  118,
  118,
  118,
  118,
  118,
  117,
  117,
  117,
  117,
  117,
  117,
  116,
  116,
  116,
  116,
  115,
  115,
  115,
  115,
  115,
  115,
  114,
  114,
  114,
  114,
  114,
  114,
  113,
  113,
  113,
  113,
  112,
  112,
  112,
  112,
  112,
  112,
  112,
  111,
  111,
  111,
  111,
  111,
  111,
  110,
  110,
  110,
  110,
  110,
  109,
  109,
  109,
  109,
  109,
  109,
  108,
  108,
  108,
  108,
  108,
  108,
  108,
  107,
  107,
  107,
  107,
  107,
  106,
  106,
  106,
  106,
  106,
  106,
  106,
  105,
  105,
  105,
  105,
  105,
  105,
  105,
  104,
  104,
  104,
  104,
  104,
  103,
  103,
  103,
  103,
  103,
  103,
  103,
  103,
  102,
  102,
  102,
  102,
  102,
  102,
  102,
  101,
  101,
  101,
  101,
  101,
  101,
  100,
  100,
  100,
  100,
  100,
  100,
  100,
  100,
  99,
  99,
  99,
  99,
  99,
  99,
  99,
  99,
  98,
  98,
  98,
  98,
  98,
  97,
  97,
  97,
  97,
  97,
  97,
  97,
  97,
  97,
  96,
  96,
  96,
  96,
  96,
  96,
  96,
  96,
  96
 ]
}
Codeusdasm: A Link to the Past US Disassembly · bank_0A.asm#L580-L586|0ABA23| LDA.b #$07 |0ABA25| STA.w BGMODE |0ABA28| STA.b $94 |0ABA2A| LDA.b #$80 |0ABA2C| STA.w M7SELusdasm: A Link to the Past US Disassembly · bank_0A.asm#L1073-L1138WorldMap_SetUpHDMA: |0ABC96| REP #$20 |0ABC98| LDA.w #$0080 |0ABC9B| STA.b $E0 |0ABC9D| LDA.w #$00C8 |0ABCA0| STA.b $E6 |0ABCA2| ADC.w #$0100 |0ABCA5| STA.w $063A |0ABCA8| LDA.w #$0100 |0ABCAB| STA.w $0638 |0ABCAE| LDA.w #$1B42 |0ABCB1| STA.w DMA6MODE |0ABCB4| LDA.w #$1E42 |0ABCB7| STA.w DMA7MODE … |0ABCC6| STZ.w M7B |0ABCC9| STZ.w M7B |0ABCCC| STZ.w M7C |0ABCCF| STZ.w M7C … |0ABCE0| LDA.b $10 |0ABCE2| CMP.b #$14 |0ABCE4| BEQ .attract_mode |0ABCE6| LDA.b $11 |0ABCE8| CMP.b #$0A |0ABCEA| BNE .not_flute_map |0ABCEC| JMP.w .flute_map … .not_flute_map |0ABCEF| LDA.b #$04 |0ABCF1| STA.w $0635 |0ABCF4| LDA.b #$0C |0ABCF6| STA.w $0637 |0ABCF9| LDA.b #$01 |0ABCFB| STA.w $0636usdasm: A Link to the Past US Disassembly · bank_0A.asm#L1201-L1262.normal_map |0ABD4A| LDA.w #WorldMapHDMA_ZoomedIn |0ABD4D| STA.w DMA6ADDRL |0ABD50| STA.w DMA7ADDRL … .flute_map |0ABD76| LDA.b #$04 |0ABD78| STA.w $0635 |0ABD7B| LDA.b #$21 |0ABD7D| STA.w $0637 |0ABD80| STZ.w $0636 |0ABD83| REP #$21 |0ABD85| LDA.w #WorldMapHDMA_ZoomedOut |0ABD88| STA.w DMA6ADDRL |0ABD8B| STA.w DMA7ADDRL … |0ABDA0| LDA.b #$C0 |0ABDA2| STA.b $9Busdasm: A Link to the Past US Disassembly · bank_0A.asm#L1294-L1304WorldMapHDMA_ZoomedOut: |0ABDCF| db $F0 : dw WorldMapHDMA_ZoomedOut_Part1 ; 112 lines, continuous |0ABDD2| db $F0 : dw WorldMapHDMA_ZoomedOut_Part2 ; 112 lines, continuous |0ABDD5| db $00 ; end … WorldMapHDMA_ZoomedIn: |0ABDD6| db $F0 : dw WorldMapHDMA_ZoomedIn_Part1 ; 112 lines, continuous |0ABDD9| db $F0 : dw WorldMapHDMA_ZoomedIn_Part2 ; 112 lines, continuoususdasm: A Link to the Past US Disassembly · bank_0A.asm#L3662-L3890WorldMapHDMA_ZoomedOut_Part1: |0ADD27| dw $0177 ; scanline 0 |0ADD29| dw $0176 ; scanline 1 … |0ADEE5| dw $0108 ; scanline 223usdasm: A Link to the Past US Disassembly · bank_0A.asm#L3894-L4122WorldMapHDMA_ZoomedIn_Part1: |0ADEE7| dw $0088 ; scanline 0 |0ADEE9| dw $0088 ; scanline 1 … |0AE0A5| dw $0060 ; scanline 223usdasm: A Link to the Past US Disassembly · bank_0A.asm#L746-L761WorldMap_PlayerControl: |0ABAE6| LDA.w $0636 |0ABAE9| ASL A |0ABAEA| BCC .no_zoom_change |0ABAEC| TAX |0ABAED| LSR A |0ABAEE| STA.w $0636 |0ABAF1| LDA.l WorldMapHDMA_ZoomPointers,X |0ABAF5| STA.w DMA6ADDRL |0ABAF8| STA.w DMA7ADDRLusdasm: A Link to the Past US Disassembly · bank_0A.asm#L740-L742WorldMapHDMA_ZoomPointers: |0ABAE2| dw WorldMapHDMA_ZoomedOut |0ABAE4| dw WorldMapHDMA_ZoomedInusdasm: A Link to the Past US Disassembly · bank_00.asm#L395-L413|0081D8| CMP.b #$07 |0081DA| BNE .not_mode7 |0081DC| STZ.w M7B |0081DF| STZ.w M7B |0081E2| STZ.w M7C |0081E5| STZ.w M7C |0081E8| LDA.w $0638 |0081EB| STA.w M7X |0081EE| LDA.w $0639 |0081F1| STA.w M7X |0081F4| LDA.w $063A |0081F7| STA.w M7Yusdasm: A Link to the Past US Disassembly · bank_0A.asm#L822-L908 — the centering, the zoomed-out scroll, and the vertical pan, each setting the rotation center's Y with the scroll|0ABB49| STA.b $E6 |0ABB4B| CLC |0ABB4C| ADC.w #$0100 |0ABB4F| STA.w $063A … |0ABB7E| LDA.w #$00C8 |0ABB81| STA.b $E6 |0ABB83| ADC.w #$0100 |0ABB86| STA.w $063A … |0ABBAC| STA.b $E6 |0ABBAE| CLC |0ABBAF| ADC.w #$0100 |0ABBB2| STA.w $063APPU registers · M7A - Mode 7 matrix A and Multiplication factor 1 ($211B write twice)Mode 7 matrix A (8.8 fixed point)Checked by claude-agent, 2026-11-08 — read WorldMap_FadeOut's mode switch, WorldMap_SetUpHDMA and its three branches, the two pointer tables and their 224-word tables, the zoom switch in WorldMap_PlayerControl, its three writes of the rotation center's Y, and the NMI's Mode 7 writes; the factors and the ratio are computed from the end valuesRevised after a check by a second AI model, 2026-11-08.

The factors divide each value by 256: $0177 is 375/256 and $0108 is 264/256; $0088 is 136/256 and $0060 is 96/256, and 375/136 and 264/96 are both about 2.75. A larger matrix value steps farther through the map per screen pixel, so it draws the map smaller. The table pointers address the A register ($1B) for one channel and D ($1E) for the other. The opening sequence's map view uses a third table built in RAM.

Zoomed in, the world map scrolls with the d-pad one line a frame vertically and two pixels a frame horizontally, and stops at fixed limits: from −72 to 224 vertically and from −224 to 480 horizontally. On opening, or on zooming in, the code centers the view on Link: its vertical scroll is Y/16 − 72 and its horizontal scroll 128 + 2.5 × (X/16 − 128), from his overworld Y and X, both made even; the code scales the distance from 128 without its sign and then restores it. Zoomed out, the scroll is fixed at 128 across and 200 down. Each zoom change plays a sound effect (bank 3, number $24) and locks out the next change for 8 frames. alttp-av/world-map-pan

About: The Legend of Zelda: A Link to the Past Mode 7

Value
{
 "panX": 2,
 "panY": 1,
 "top": -72,
 "bottom": 224,
 "left": -224,
 "right": 480,
 "lockout": 8,
 "zoomedOutScroll": {
  "x": 128,
  "y": 200
 }
}
Codeusdasm: A Link to the Past US Disassembly · bank_0A.asm#L718-L736WorldMap_PanMovements: |0ABAC6| dw $0000 ; none |0ABAC8| dw $0000 ; none |0ABACA| dw $0001 ; down |0ABACC| dw $0002 ; right |0ABACE| dw $FFFF ; up |0ABAD0| dw $FFFE ; left … WorldMap_PanBoundaries: |0ABAD6| db $00, $00 ; none |0ABAD8| db $00, $00 ; none |0ABADA| db $E0, $00 ; down |0ABADC| db $E0, $01 ; right |0ABADE| db $B8, $FF ; up |0ABAE0| db $20, $FF ; leftusdasm: A Link to the Past US Disassembly · bank_0A.asm#L774-L934.ignore_change |0ABB0A| LDA.b $B2 |0ABB0C| BEQ .not_switching |0ABB0E| DEC.b $B2 … .not_switching |0ABB13| LDA.b $F6 |0ABB15| AND.b #$70 |0ABB17| BEQ .continue |0ABB19| LDA.b #$24 ; SFX3.24 |0ABB1B| STA.w $012F |0ABB1E| LDA.b #$08 |0ABB20| STA.b $B2 … .zoomed_in |0ABB38| REP #$20 |0ABB3A| LDA.l $7EC108 |0ABB3E| LSR A |0ABB3F| LSR A |0ABB40| LSR A |0ABB41| LSR A |0ABB42| SEC |0ABB43| SBC.w #$0048 |0ABB46| AND.w #$FFFE |0ABB49| STA.b $E6 … |0ABB52| LDA.l $7EC10A |0ABB56| LSR A |0ABB57| LSR A |0ABB58| LSR A |0ABB59| LSR A |0ABB5A| SEC |0ABB5B| SBC.w #$0080 |0ABB5E| STA.b $02 |0ABB60| BPL .x_positive_a |0ABB62| EOR.w #$FFFF |0ABB65| INC A .x_positive_a |0ABB66| STA.b $00 |0ABB68| ASL A |0ABB69| ASL A |0ABB6A| CLC |0ABB6B| ADC.b $00 |0ABB6D| LSR A |0ABB6E| LDX.b $03 |0ABB70| BPL .x_positive_b |0ABB72| EOR.w #$FFFF |0ABB75| INC A .x_positive_b |0ABB76| CLC |0ABB77| ADC.w #$0080 |0ABB7A| BRA .set_scroll … .not_zoomed_in |0ABB7C| REP #$21 |0ABB7E| LDA.w #$00C8 |0ABB81| STA.b $E6 … |0ABB89| LDA.w #$0080 … .set_scroll |0ABB8C| AND.w #$FFFE |0ABB8F| STA.b $E0 … |0ABB93| LDA.w $0636 |0ABB96| BEQ .map_zoomed_out |0ABB98| LDA.b $F0 |0ABB9A| AND.b #$0C |0ABB9C| TAX |0ABB9D| REP #$20 |0ABB9F| LDA.b $E6 |0ABBA1| CMP.l WorldMap_PanBoundaries,X |0ABBA5| BEQ .at_vertical_boundary |0ABBA7| CLC |0ABBA8| ADC.l WorldMap_PanMovements,X |0ABBAC| STA.b $E6 … |0ABBB7| LDA.b $F0 |0ABBB9| AND.b #$03 |0ABBBB| ASL A |0ABBBC| INC A |0ABBBD| ASL A |0ABBBE| TAX |0ABBBF| REP #$20 |0ABBC1| LDA.b $E0 |0ABBC3| CMP.l WorldMap_PanBoundaries,X |0ABBC7| BEQ .at_horizontal_boundary |0ABBC9| CLC |0ABBCA| ADC.l WorldMap_PanMovements,X |0ABBCE| STA.b $E0usdasm: A Link to the Past US Disassembly · bank_0A.asm#L1137-L1197 — the same centering when the map opens|0ABD00| LDA.l $7EC108 … |0ABD08| SEC |0ABD09| SBC.w #$0048 … |0ABD1D| LDA.l $7EC10A … |0ABD42| ADC.w #$0080 |0ABD45| AND.w #$FFFE |0ABD48| STA.b $E0usdasm: A Link to the Past US Disassembly · bank_0A.asm#L547-L556 — Link's overworld Y and X saved when the map opens|0AB9F0| LDX.b $8A |0AB9F2| CPX.b #$80 ; SPOW |0AB9F4| BCS .special_overworld |0AB9F6| LDA.b $20 |0AB9F8| STA.l $7EC108 |0AB9FC| LDA.b $22 |0AB9FE| STA.l $7EC10AChecked by claude-agent, 2026-11-08 — read WorldMap_PlayerControl's zoom lockout, its centering branches, and its pan code against the movement and boundary tables, the opening centering in WorldMap_SetUpHDMA, and the save of Link's coordinates in WorldMap_FadeOut; the 2.5 factor is the routine's ×5 then halvingRevised after a check by a second AI model, 2026-11-08.

The pan index takes the vertical d-pad bits ($0C of the held byte) for the vertical entry and the horizontal bits ($03), doubled and offset by one entry, for the horizontal one; the disassembly's comments name the table's entries down, right, up, and left. A scroll moves only while it isn't exactly at the limit for that direction. The limits are the table's words read as signed: $FFB8 is −72, $00E0 is 224, $FF20 is −224, and $01E0 is 480.

The opening's spinning Triforce is drawn in software: a second thread, with its own stack, direct page, and registers, clears a 2,048-byte bitmap, builds a rotation from sine and cosine tables, rotates and projects each point of the shape, calls the shape's drawing routine, and flags the bitmap ready; at the next vblank the interrupt code copies all 2,048 bytes by DMA to VRAM word address $5800. The game's own code and the drawing thread share the processor by interrupts: while the polyhedron is on, the vblank interrupt ends by switching stacks to the drawing thread and sets a line interrupt, at line $90 (144) in the opening, whose handler switches back. The drawing thread starts a new picture only when the game has asked for one and the last one has been copied. alttp-av/triforce-polyhedron-thread

Value: bitmap bytes 2048 · vram word 22528 · irq line 144

Codeusdasm: A Link to the Past US Disassembly · bank_0C.asm#L419-L444TriforceInitializePolyhedralModule: |0CC36F| JSL Polyhedral_InitializeThread |0CC373| JSR LoadTriforceSpritePalette |0CC376| LDA.b #$90 |0CC378| STA.b $FF … |0CC394| LDA.b #$01 |0CC396| STA.w $1F01 |0CC399| STA.w $1F03 |0CC39C| STA.w $012A |0CC39F| STA.w $1F00usdasm: A Link to the Past US Disassembly · bank_0C.asm#L387-L389 — the opening's call, which runs the routine above (L391)Intro_InitializeTriforcePolyThread: |0CC33C| LDA.b #$08 |0CC33E| STA.w $0AA4usdasm: A Link to the Past US Disassembly · bank_09.asm#L21842-L21912Polyhedral_InitializeThread: … |09F7F8| LDA.w #$001F31 |09F7FB| STA.w $1F0A |09F7FE| LDA.w #$000C |09F801| LDX.w #.registers |09F804| LDY.w #$001F32 |09F807| MVN ^Polyhedral_InitializeThread, $00 … .registers |09F810| db ^Polyhedral_RunThread ; data bank |09F811| dw $1F00 ; direct page |09F813| dw $0000 ; Y |09F815| dw $0000 ; X |09F817| dw $0000 ; A |09F819| db $30 ; P |09F81A| dl Polyhedral_RunThread … Polyhedral_RunThread: .wait |09F81D| LDA.b $001F00 |09F81F| BEQ .wait |09F821| LDA.b $001F0C |09F823| BNE .wait |09F825| JSL Polyhedral_EmptyBitMapBuffer |09F829| JSR Polyhedral_SetShapePointer |09F82C| JSR Polyhedral_SetRotationMatrix |09F82F| JSR Polyhedral_OperateRotation |09F832| JSR Polyhedral_DrawPolyhedron |09F835| STZ.b $001F00 |09F837| LDA.b #$FF |09F839| STA.b $001F0C |09F83B| BRA .waitusdasm: A Link to the Past US Disassembly · bank_09.asm#L21941-L21954Polyhedral_SetRotationMatrix: … |09F868| LDA.w Polyhedral_SineTable,Y … |09F875| LDA.w Polyhedral_CosineTable,Yusdasm: A Link to the Past US Disassembly · bank_09.asm#L22090-L22091|09F917| JSR Polyhedral_RotatePoint |09F91A| JSR Polyhedral_ProjectPointusdasm: A Link to the Past US Disassembly · bank_09.asm#L22729-L22747Polyhedral_EmptyBitMapBuffer: … |09FD08| LDA.w #$0000 |09FD0B| STA.l $7EE800 |09FD0F| LDX.w #$7EE800 |09FD12| LDY.w #$7EE802 |09FD15| LDA.w #$07FD |09FD18| MVN $7E, $7Eusdasm: A Link to the Past US Disassembly · bank_00.asm#L3389-L3421UpdateIRQGFX: |009347| LDA.w $1F0C |00934A| BEQ .exit |00934C| LDA.b #$80 |00934E| STA.w VMAIN |009351| REP #$20 |009353| LDA.w #$5800 ; VRAM $B000 |009356| STA.w VMADDR |009359| LDA.w #$1801 |00935C| STA.w DMA0MODE |00935F| LDA.w #$7EE800 |009362| STA.w DMA0ADDRL … |00936A| LDA.w #$0800 |00936D| STA.w DMA0SIZE |009370| SEP #$20 |009372| LDA.b #$01 |009374| STA.w MDMAEN |009377| STZ.w $1F0Cusdasm: A Link to the Past US Disassembly · bank_00.asm#L306-L309|008144| LDA.w $012A |008147| BEQ NoIRQThread |008149| JMP.w SwitchThreadusdasm: A Link to the Past US Disassembly · bank_00.asm#L458-L466SwitchThread: |00822D| JSR UpdateIRQGFX |008230| LDA.b $FF |008232| STA.w VTIMEL |008235| STZ.w VTIMEH |008238| LDA.b #$A1 ; enable NMI, IRQ, auto joypad |00823A| STA.w NMITIMENusdasm: A Link to the Past US Disassembly · bank_00.asm#L550-L567|0082C7| REP #$30 |0082C9| TSC |0082CA| TAX |0082CB| LDA.w $1F0A |0082CE| TCS |0082CF| STX.w $1F0A |0082D2| PLB |0082D3| PLD |0082D4| PLY |0082D5| PLX |0082D6| PLA |0082D7| RTIusdasm: A Link to the Past US Disassembly · bank_00.asm#L571-L651IRQ: |0082D8| SEI … |0082E4| LDA.w $012A |0082E7| BNE .polyhedral … .polyhedral |00831F| LDA.w TIMEUP |008322| REP #$30 |008324| TSC |008325| TAX |008326| LDA.w $1F0A |008329| TCS |00832A| STX.w $1F0A … |008332| RTIusdasm: A Link to the Past US Disassembly · bank_0C.asm#L1531-L1547.do_advance |0CCABC| LDA.b #$01 |0CCABE| STA.w $012A |0CCAC1| STA.w $1E02 |0CCAC4| LDA.w $1F00 |0CCAC7| BNE .exit |0CCAC9| JSR .run_sub |0CCACC| LDA.b #$01 |0CCACE| STA.w $1F00Checked by claude-agent, 2026-11-08 — read the opening's thread setup, Polyhedral_InitializeThread's register image, Polyhedral_RunThread's loop and the rotation, projection, and buffer-clear routines it calls, UpdateIRQGFX, the NMI's jump to SwitchThread and its stack swap, and the IRQ handler's swap back; the 2,048 bytes are the MVN count $07FD plus the 2 bytes it copies from, and the DMA size $0800Revised after a check by a second AI model, 2026-11-08.

The thread's saved stack pointer lives at $1F0A; each switch stores the current one there and loads the other, so the vblank handler returns into the drawing thread and the line interrupt returns into the game. The game's frame code therefore runs from line 144 until vblank, and the drawing from vblank until line 144, in the opening. The VRAM word address $5800 is byte address $B000, as the disassembly's comment says.

A dark room is darkened by subtractive color math, not by darker palettes. Every room's setup selects the subscreen as the addend ($02) and subtracts it from BG1, BG2, sprites in palettes 4 to 7, and the backdrop ($B3); when bit 0 of the room's first header byte is set, it also sets the fixed color to a gray of level 31, the brightest of 0 to 31, and a lit room gets a gray of 0. With the lantern and no torch lit, the code turns BG1 on the subscreen, so the subtraction takes that layer's colors instead, and each frame scrolls BG1 to set its cone picture in front of Link: one of four positions in the layer, chosen by his facing, offset from his position and clamped. Each torch he lights turns the subscreen off and lowers the gray: 8 with one torch, 4 with two, 0 with three or more, which leaves the room at full brightness. alttp-av/dark-room-lantern

About: The Legend of Zelda: A Link to the Past Color math

Value: Fixed gray by torches lit 31, 8, 4, 0 · cgwsel 2 · cgadsub 179

Codeusdasm: A Link to the Past US Disassembly · bank_01.asm#L10101-L10106 — Y is 0 here (L10085), the header's first byte|01B608| LDA.l $7EC005 |01B60C| STA.l $7EC006 |01B610| LDA.b [$0D],Y |01B612| AND.b #$01 |01B614| STA.l $7EC005usdasm: A Link to the Past US Disassembly · bank_02.asm#L469-L502|0282D2| LDA.b #$02 |0282D4| STA.b $99 |0282D6| LDA.b #$B3 |0282D8| STA.b $9A |0282DA| LDX.w $045A |0282DD| LDA.l $7EC005 |0282E1| BNE .do_fade |0282E3| LDX.b #$03 … .do_fade |0282FA| LDA.l RoomEffectFixedColors,X |0282FE| STA.l $7EC017 |028302| JSL ApplyGrayscaleFixed_Parameterizedusdasm: A Link to the Past US Disassembly · bank_02.asm#L6662-L6663RoomEffectFixedColors: |02A1E5| db $1F, $08, $04, $00usdasm: A Link to the Past US Disassembly · bank_01.asm#L23444-L23454ApplyGrayscaleFixed_Parameterized: |01FEC1| ORA.b #$20 |01FEC3| STA.b $9C |01FEC5| AND.b #$1F |01FEC7| ORA.b #$40 |01FEC9| STA.b $9D |01FECB| AND.b #$1F |01FECD| ORA.b #$80 |01FECF| STA.b $9Eusdasm: A Link to the Past US Disassembly · bank_01.asm#L21566-L21581 — the torch-lighting code|01F460| LDA.l $7EC005 |01F464| BEQ .no_fixed_coloring |01F466| LDA.w $045A |01F469| INC.w $045A |01F46C| CMP.b #$03 |01F46E| BCS .no_fixed_coloring |01F470| STZ.b $1D |01F472| LDX.w $045A |01F475| LDA.l RoomEffectFixedColors,X |01F479| STA.l $7EC017usdasm: A Link to the Past US Disassembly · bank_0D.asm#L17051-L17074RestoreTorchBackground: |0DFA33| LDA.l $7EF34A |0DFA37| BEQ .exit |0DFA39| LDA.l $7EC005 |0DFA3D| BEQ .exit |0DFA3F| LDA.w $0458 |0DFA42| BNE .exit |0DFA44| LDA.w $045A |0DFA47| BNE .exit |0DFA49| INC.w $0458 |0DFA4C| LDA.w $0414 |0DFA4F| CMP.b #$02 |0DFA51| BEQ .exit |0DFA53| LDA.b #$01 |0DFA55| STA.b $1Dusdasm: A Link to the Past US Disassembly · bank_09.asm#L1186 — the item table's entry that names the byte the lantern sets|09850C| dw $7EF34A ; LAMPusdasm: A Link to the Past US Disassembly · bank_00.asm#L15385-L15528.horizontal |00F53E| dw $0000, $0100, $0000, $0100 .vertical |00F546| dw $0000, $0000, $0100, $0100 .adjust |00F54E| dw $0034, $FFFE, $0038, $0006 .margin |00F556| dw $0040, $0040, $0052, $FF50 .max |00F55E| dw $0080, $0180, $00A0, $00A0 … OrientLampLightCone: |00F567| LDA.w $0458 |00F56A| BEQ EXIT_00F566 … |00F574| LDA.b $2F |00F576| AND.w #$00FF |00F579| STA.b $00 |00F57B| TAX … OrientVerticalLampCone: |00F5B7| LDA.b $22 |00F5B9| SEC |00F5BA| SBC.w #$0077 |00F5BD| STA.b $00 |00F5BF| LDA.b $E2 |00F5C1| SEC |00F5C2| SBC.b $00 |00F5C4| CLC |00F5C5| ADC.l .horizontal,X |00F5C9| STA.b $E0 … |00F5E7| BPL .lower_in_bounds |00F5E9| LDA.w #$0000 .lower_in_bounds |00F5EC| CMP.l .max,X |00F5F0| BCC .upper_in_bounds |00F5F2| LDA.l .max,X .upper_in_bounds |00F5F6| SEC |00F5F7| SBC.l .margin,X |00F5FB| STA.b $E6usdasm: A Link to the Past US Disassembly · bank_00.asm#L15980-L15998|00F850| LDA.b $E2 |00F852| ADC.w $011A |00F855| STA.w $011E … |00F861| LDA.b $E0 |00F863| CLC |00F864| ADC.w $011A |00F867| STA.w $0120 |00F86A| LDA.b $E6 |00F86C| CLC |00F86D| ADC.w $011C |00F870| STA.w $0124usdasm: A Link to the Past US Disassembly · bank_00.asm#L350-L357|008188| LDA.w $0120 |00818B| STA.w BG1HOFS … |008194| LDA.w $0124 |008197| STA.w BG1VOFSusdasm: A Link to the Past US Disassembly · bank_00.asm#L323-L348 — the NMI's copy of the color math, fixed color, and layer bytes to their registers|00815B| LDA.b $99 |00815D| STA.w CGWSEL |008160| LDA.b $9A |008162| STA.w CGADSUB |008165| LDA.b $9C |008167| STA.w COLDATA |00816A| LDA.b $9D |00816C| STA.w COLDATA |00816F| LDA.b $9E |008171| STA.w COLDATA |008174| LDA.b $1C |008176| STA.w TM |008179| LDA.b $1D |00817B| STA.w TS |00817E| LDA.b $1E |008180| STA.w TMW |008183| LDA.b $1F |008185| STA.w TSWPPU registers · TS - Subscreen layer enable ($212D write)| |||+- Enable BG1 on subscreenPPU registers · CGADSUB - Color math designation ($2131 write)|||| |||+- BG1 color math enablePPU registers · CGADSUB - Color math designation ($2131 write)|||| ||+-- BG2 color math enablePPU registers · CGADSUB - Color math designation ($2131 write)||+------- Backdrop color math enablePPU registers · COLDATA - Fixed color data ($2132 write)|||+-++++- Color valuePPU registers · COLDATA - Fixed color data ($2132 write)||+------- Write color value to red channelPPU registers · COLDATA - Fixed color data ($2132 write)|+-------- Write color value to green channelPPU registers · COLDATA - Fixed color data ($2132 write)+--------- Write color value to blue channelPPU registers · CGWSEL - Color addition select ($2130 write)|||| +-- Addend (0 = fixed color, 1 = subscreen)PPU registers · CGADSUB - Color math designation ($2131 write)+--------- Operator type (0 = add, 1 = subtract)PPU registers · CGADSUB - Color math designation ($2131 write)|||+------ OBJ color math enable (palettes 4-7 only)Checked by claude-agent, 2026-11-08 — read the room header's dark bit, the room setup's color math (set before the dark test) and fixed gray, the NMI's copy to the registers, the four-entry gray table and the routine that writes it to all three color channels, the torch-lighting count, the lantern check that puts BG1 on the subscreen, and the cone placement through BG1's scroll to the NMIRevised after a check by a second AI model, 2026-11-08.

$B3 sets subtract (bit 7), the backdrop (bit 5), sprites (bit 4), and BG2 and BG1 (bits 1 and 0); $02 sets the subscreen as the addend. The gray is written as the same 5-bit level to the red, green, and blue fixed-color channels. The lantern code skips the subscreen when the room's effect value is 2, and a lit room (bit 0 clear) gets index 3, a gray of 0. The four cone positions are the layer's four 256-pixel quadrants. Which torches burn out, and when, isn't read here.

Before the story counter reaches 2 (Zelda's rescue), and on screen $70 until bit $20 of its saved flags is set, the overworld's rain is BG1 alone on the subscreen, halved and added to BG2, the sprites, and the backdrop (color math $72). Every 4 frames the code steps the layer's scroll through a four-step cycle that alternately adds 4,352 (17 × 256) to its vertical scroll and 256 to its horizontal scroll. Lightning is a change of color math on fixed values of the game's 8-bit frame counter: on 3, 36, and 88 it becomes $32, the same add without halving, which brightens the screen, and on 5, 44, and 90 it goes back to $72; on 36 the thunder sound plays (bank 2, number $36). In each 256 frames the screen therefore flashes for 2, 8, and 2 frames (computed). alttp-av/rain-and-lightning

About: The Legend of Zelda: A Link to the Past Color math

Value: every 4 · Vertical shift (× 256) 0, 17, 0, 17 · Horizontal shift (× 256) 1, 0, 1, 0 · flash on 3, 36, 88 · flash off 5, 44, 90 · thunder at 36 · flash frames 2, 8, 2

Codeusdasm: A Link to the Past US Disassembly · bank_02.asm#L7205-L7279OverworldOverlay_HandleRain: |02A4CD| LDA.b $8A |02A4CF| CMP.b #$70 ; OW 70 |02A4D1| BEQ .mire_screen |02A4D3| LDA.l $7EF3C5 |02A4D7| CMP.b #$02 |02A4D9| BCS .exit … .mire_screen |02A4DB| LDA.l $7EF2F0 |02A4DF| AND.b #$20 |02A4E1| BNE .exit |02A4E3| LDA.b $1A |02A4E5| CMP.b #$03 |02A4E7| BEQ .flash |02A4E9| CMP.b #$05 |02A4EB| BEQ .unflash |02A4ED| CMP.b #$24 |02A4EF| BEQ .thunder |02A4F1| CMP.b #$2C |02A4F3| BEQ .unflash |02A4F5| CMP.b #$58 |02A4F7| BEQ .flash |02A4F9| CMP.b #$5A |02A4FB| BNE .no_effect .unflash |02A4FD| LDA.b #$72 |02A4FF| BRA .set_color_math .thunder |02A501| LDX.b #$36 ; SFX2.36 |02A503| STX.w $012E .flash |02A506| LDA.b #$32 .set_color_math |02A508| STA.b $9A … .no_effect |02A50A| LDA.b $1A |02A50C| AND.b #$03 |02A50E| BNE .exit |02A510| LDA.w $0494 |02A513| INC A |02A514| AND.b #$03 |02A516| STA.w $0494 |02A519| TAX |02A51A| LDA.b $E1 |02A51C| CLC |02A51D| ADC.l OWOverlayHShift,X |02A521| STA.b $E1 |02A523| LDA.b $E7 |02A525| CLC |02A526| ADC.l OWOverlayVShift,X |02A52A| STA.b $E7usdasm: A Link to the Past US Disassembly · bank_02.asm#L7125-L7129OWOverlayHShift: |02A46D| db 1, 0, 1, 0 … OWOverlayVShift: |02A471| db 0, 17, 0, 17usdasm: A Link to the Past US Disassembly · bank_02.asm#L8689-L8717|02AFE9| CMP.w #$0070 ; OW 70 |02AFEC| BNE .not_mire |02AFEE| LDA.l $7EF2F0 |02AFF2| AND.w #$0020 |02AFF5| BNE .load_overlay |02AFF7| BRA .load_rain … .not_mire |02AFF9| LDX.w #$0096 ; OW 96 |02AFFC| LDA.l $7EF3C5 |02B000| AND.w #$00FF |02B003| CMP.w #$0002 |02B006| BCS .load_overlay .load_rain |02B008| LDX.w #$009F ; OW 9Fusdasm: A Link to the Past US Disassembly · bank_02.asm#L8743-L8817|02B033| SEP #$30 |02B035| LDA.b #$82 |02B037| STA.b $99 |02B039| LDA.b #$16 |02B03B| STA.b $1C |02B03D| LDA.b #$01 |02B03F| STA.b $1D … |02B050| LDA.b #$72 … |02B066| CPX.b #$9F ; OW 9F |02B068| BEQ .continue_with_subscreen … .continue_with_subscreen |02B08F| STA.b $9Ausdasm: A Link to the Past US Disassembly · bank_0A.asm#L560-L570 — the disassembly's label for the counter's value 2|0ABA04| LDA.l $7EF3C5 |0ABA08| CMP.b #$02 |0ABA0A| BCS .zelda_rescured … .zelda_rescuredusdasm: A Link to the Past US Disassembly · bank_00.asm#L68-L70.do_frame |008051| INC.b $1Ausdasm: A Link to the Past US Disassembly · bank_00.asm#L323-L348 — the NMI's copy of the color math, fixed color, and layer bytes to their registers|00815B| LDA.b $99 |00815D| STA.w CGWSEL |008160| LDA.b $9A |008162| STA.w CGADSUB |008165| LDA.b $9C |008167| STA.w COLDATA |00816A| LDA.b $9D |00816C| STA.w COLDATA |00816F| LDA.b $9E |008171| STA.w COLDATA |008174| LDA.b $1C |008176| STA.w TM |008179| LDA.b $1D |00817B| STA.w TS |00817E| LDA.b $1E |008180| STA.w TMW |008183| LDA.b $1F |008185| STA.w TSWPPU registers · TM - Main screen layer enable ($212C write)| |||+- Enable BG1 on main screenPPU registers · TS - Subscreen layer enable ($212D write)| |||+- Enable BG1 on subscreenPPU registers · CGADSUB - Color math designation ($2131 write)|||| |||+- BG1 color math enablePPU registers · CGADSUB - Color math designation ($2131 write)|||| ||+-- BG2 color math enablePPU registers · CGADSUB - Color math designation ($2131 write)||+------- Backdrop color math enablePPU registers · CGWSEL - Color addition select ($2130 write)|||| +-- Addend (0 = fixed color, 1 = subscreen)PPU registers · CGADSUB - Color math designation ($2131 write)|+-------- Half color mathChecked by claude-agent, 2026-11-08 — read OverworldOverlay_HandleRain and its two shift tables, the overlay setup's rain choice, its layer and color math writes, the frame counter's increment, the label at the story counter's test, and the NMI's copy to the registersRevised after a check by a second AI model, 2026-11-08.

$72 sets half (bit 6), the backdrop (bit 5), sprites (bit 4), and BG2 (bit 1); $32 is the same without half. The scroll steps add to the high bytes of BG1's scroll words. The flash counts are the hexadecimal compares $03, $24, $58 and $05, $2C, $5A. The frame counter is the main loop's, so a lag frame doesn't advance it.

Each overworld area chooses its moving overlay, a BG1 picture alone on the subscreen: on screens $00 and $40 a fog (overlay $9D), except that screen $00 gets overlay $9E once bit $40 of special screen $80's saved flags is set; on screens $03, $05, and $07 overlay $95; on $43, $45, and $47 overlay $9C; on $70 the rain ($9F) until bit $20 of its saved flags is set, then overlay $9C; elsewhere the rain before the story counter reaches 2 and overlay $96 after. The fog, the rain, overlay $9E, and three special areas' overlays ($93, $94, and $97) are halved and added to BG2, the sprites, and the backdrop ($72); overlays $95 and $9C are added to the backdrop alone ($20). The fogs $97 and $9D drift on their own by 1/8 pixel a frame right and down (computed). Other overlays follow the camera's horizontal steps: at half the step, or a quarter for $95 and $9E. Overlay $9C instead rises by 1/8 pixel a frame (computed) and keeps BG2's horizontal scroll, except on screen $47, where neither drift runs. alttp-av/overworld-overlays

About: The Legend of Zelda: A Link to the Past Color math

Value
{
 "fogDrift": 0.125,
 "parallax": {
  "default": 0.5,
  "0x95": 0.25,
  "0x9E": 0.25
 }
}
Codeusdasm: A Link to the Past US Disassembly · bank_02.asm#L8584-L8717|02AF55| LDY.w #$0390 |02AF58| LDA.b $8A |02AF5A| CMP.w #$0080 ; SPOW |02AF5D| BCC .normal_overworld |02AF5F| LDX.w #$0097 ; OW 97 |02AF62| LDA.b $A0 |02AF64| CMP.w #$0180 ; OW 80 |02AF67| BNE .not_mastersword … .not_mastersword |02AF7B| LDX.w #$0094 ; OW 94 |02AF7E| CMP.w #$0181 ; OW 81 |02AF81| BEQ .do_load_special |02AF83| LDX.w #$0093 ; OW 93 |02AF86| CMP.w #$0189 ; OW 89 … .normal_overworld |02AFA3| AND.w #$003F ; OW 00, OW 40 |02AFA6| BNE .not_any_woods |02AFA8| LDA.b $8A |02AFAA| AND.w #$0040 |02AFAD| BNE .skull_woods |02AFAF| LDX.w #$0080 ; OW 80 |02AFB2| LDA.l $7EF280,X |02AFB6| LDX.w #$009E ; OW 9E |02AFB9| AND.w #$0040 |02AFBC| BNE .load_overlay .skull_woods |02AFBE| LDX.w #$009D ; OW 9D |02AFC1| BRA .load_overlay … .not_any_woods |02AFC3| LDX.w #$0095 ; OW 95 |02AFC6| LDA.b $8A |02AFC8| CMP.w #$0003 ; OW 03 |02AFCB| BEQ .load_overlay |02AFCD| CMP.w #$0005 ; OW 05 |02AFD0| BEQ .load_overlay |02AFD2| CMP.w #$0007 ; OW 07 |02AFD5| BEQ .load_overlay |02AFD7| LDX.w #$009C ; OW 9C |02AFDA| CMP.w #$0043 ; OW 43 |02AFDD| BEQ .load_overlay |02AFDF| CMP.w #$0045 ; OW 45 |02AFE2| BEQ .load_overlay |02AFE4| CMP.w #$0047 ; OW 47 |02AFE7| BEQ .load_overlay |02AFE9| CMP.w #$0070 ; OW 70 |02AFEC| BNE .not_mire |02AFEE| LDA.l $7EF2F0 |02AFF2| AND.w #$0020 |02AFF5| BNE .load_overlay |02AFF7| BRA .load_rain … .not_mire |02AFF9| LDX.w #$0096 ; OW 96 |02AFFC| LDA.l $7EF3C5 |02B000| AND.w #$00FF |02B003| CMP.w #$0002 |02B006| BCS .load_overlay .load_rain |02B008| LDX.w #$009F ; OW 9Fusdasm: A Link to the Past US Disassembly · bank_02.asm#L8743-L8817|02B033| SEP #$30 |02B035| LDA.b #$82 |02B037| STA.b $99 |02B039| LDA.b #$16 |02B03B| STA.b $1C |02B03D| LDA.b #$01 |02B03F| STA.b $1D … |02B050| LDA.b #$72 |02B052| CPX.b #$97 ; OW 97 |02B054| BEQ .continue_with_subscreen |02B056| CPX.b #$94 ; OW 94 |02B058| BEQ .continue_with_subscreen |02B05A| CPX.b #$93 ; OW 93 |02B05C| BEQ .continue_with_subscreen |02B05E| CPX.b #$9D ; OW 9D |02B060| BEQ .continue_with_subscreen |02B062| CPX.b #$9E ; OW 9E |02B064| BEQ .continue_with_subscreen |02B066| CPX.b #$9F ; OW 9F |02B068| BEQ .continue_with_subscreen |02B06A| LDA.b #$20 |02B06C| CPX.b #$95 ; OW 95 |02B06E| BEQ .continue_with_subscreen |02B070| CPX.b #$9C ; OW 9C |02B072| BEQ .continue_with_subscreen … .continue_with_subscreen |02B08F| STA.b $9Ausdasm: A Link to the Past US Disassembly · bank_02.asm#L11290-L11339|02BCB3| LDA.b $04 |02BCB5| STA.w $069F |02BCB8| LDX.b $8C |02BCBA| CPX.b #$97 ; OW 97 |02BCBC| BEQ OverworldHandleBGOverlayScroll |02BCBE| CPX.b #$9D ; OW 9D |02BCC0| BEQ OverworldHandleBGOverlayScroll |02BCC2| LDA.b $04 |02BCC4| BEQ OverworldHandleBGOverlayScroll |02BCC6| STZ.b $00 |02BCC8| LSR A |02BCC9| ROR.b $00 |02BCCB| LDX.b $8C |02BCCD| CPX.b #$95 ; OW 95 |02BCCF| BEQ .dm_overlay |02BCD1| CPX.b #$9E ; OW 9E |02BCD3| BNE .not_clear_lw_overlay .dm_overlay |02BCD5| LSR A |02BCD6| ROR.b $00 … .set_scroll_add_x |02BCEA| STA.b $06 |02BCEC| LDA.w $0620 |02BCEF| CLC |02BCF0| ADC.b $00 |02BCF2| STA.w $0620 |02BCF5| LDA.b $E0 |02BCF7| ADC.b $06 |02BCF9| STA.b $E0usdasm: A Link to the Past US Disassembly · bank_02.asm#L11343-L11392OverworldHandleBGOverlayScroll: |02BCFB| LDX.b $8A |02BCFD| CPX.b #$47 ; OW 47 |02BCFF| BEQ .no_overlay_scroll |02BD01| LDX.b $8C |02BD03| CPX.b #$9C ; OW 9C |02BD05| BEQ .dark_dm_overlay_scroll |02BD07| CPX.b #$97 ; OW 97 |02BD09| BEQ .grove_fog_scroll |02BD0B| CPX.b #$9D ; OW 9D |02BD0D| BNE .no_overlay_scroll .grove_fog_scroll |02BD0F| LDA.w $0622 |02BD12| CLC |02BD13| ADC.w #$2000 |02BD16| STA.w $0622 |02BD19| LDA.b $E6 |02BD1B| ADC.w #$0000 |02BD1E| STA.b $E6 |02BD20| LDA.w $0620 |02BD23| CLC |02BD24| ADC.w #$2000 |02BD27| STA.w $0620 |02BD2A| LDA.b $E0 |02BD2C| ADC.w #$0000 |02BD2F| STA.b $E0 |02BD31| BRA .no_overlay_scroll .dark_dm_overlay_scroll |02BD33| LDA.w $0622 |02BD36| SEC |02BD37| SBC.w #$2000 |02BD3A| STA.w $0622 |02BD3D| LDA.b $E6 |02BD3F| SBC.w #$0000 |02BD42| CLC |02BD43| ADC.w $069E |02BD46| STA.b $E6 |02BD48| LDA.b $E2 |02BD4A| STA.b $E0usdasm: A Link to the Past US Disassembly · bank_0A.asm#L560-L570 — the disassembly's label for the counter's value 2|0ABA04| LDA.l $7EF3C5 |0ABA08| CMP.b #$02 |0ABA0A| BCS .zelda_rescured … .zelda_rescuredusdasm: A Link to the Past US Disassembly · bank_00.asm#L323-L348 — the NMI's copy of the color math, fixed color, and layer bytes to their registers|00815B| LDA.b $99 |00815D| STA.w CGWSEL |008160| LDA.b $9A |008162| STA.w CGADSUB |008165| LDA.b $9C |008167| STA.w COLDATA |00816A| LDA.b $9D |00816C| STA.w COLDATA |00816F| LDA.b $9E |008171| STA.w COLDATA |008174| LDA.b $1C |008176| STA.w TM |008179| LDA.b $1D |00817B| STA.w TS |00817E| LDA.b $1E |008180| STA.w TMW |008183| LDA.b $1F |008185| STA.w TSWPPU registers · TM - Main screen layer enable ($212C write)| |||+- Enable BG1 on main screenPPU registers · TS - Subscreen layer enable ($212D write)| |||+- Enable BG1 on subscreenPPU registers · CGADSUB - Color math designation ($2131 write)|||| |||+- BG1 color math enablePPU registers · CGADSUB - Color math designation ($2131 write)|||| ||+-- BG2 color math enablePPU registers · CGADSUB - Color math designation ($2131 write)||+------- Backdrop color math enablePPU registers · CGADSUB - Color math designation ($2131 write)|+-------- Half color mathPPU registers · CGWSEL - Color addition select ($2130 write)|||| +-- Addend (0 = fixed color, 1 = subscreen)Checked by claude-agent, 2026-11-08 — read the overlay choice in ReloadSubscreenOverlay, including screen $70's test, its layer and color math writes, the NMI's copy to the registers, the camera code's overlay parallax for horizontal steps, and OverworldHandleBGOverlayScroll's drifts; the 1/8 pixel is $2000 added to a 16-bit fraction with the carry into the scrollRevised after a check by a second AI model, 2026-11-08.

$1C = $16 puts BG2, BG3, and sprites on the main screen and $1D = $01 puts BG1 alone on the subscreen; $82 in the color-select register picks the subscreen as the addend. Overlay $97 is special screen $80's own, and the code drops it once the same bit $40 is set; $94 and $93 belong to special screens $81 and $89. The half and quarter come from one or two right shifts of the camera's step into a fraction.

The warp between the worlds bends the picture with a wave that runs down the screen: the routine sets two indirect HDMA channels to mode $42 on the horizontal scroll registers of BG2 ($210F) and BG1 ($210D) and fills a 224-word buffer with BG2's current horizontal scroll. Every second frame it moves the buffer's values down by one band of 8 lines, copying the band at line 208 into the one at 216 and so on up to the band at line 0 into the one at 8, and writes a new offset into the top band. The offset swings like a spring: a speed changes by $40 (1/4 pixel) each update toward a limit of −$200 (2 pixels), then back toward $200, which sets the offset swinging between −9 and 9 pixels, a swing every 33 updates, or 66 frames (computed by stepping the code). Each band's write covers its 1st, 3rd, 5th, and 7th words; if the channels read the buffer through the window effect's table, one word a line (assumed, not confirmed), the lines between keep the unbent scroll. Once a color counter of the warp reaches $30 and the offset lies between 0 and 7, the top band goes back to the unbent scroll, and when the bottom bands have all returned to it, HDMA turns off. alttp-av/mirror-warp-wave

About: The Legend of Zelda: A Link to the Past HDMA

Value: band lines 8 · every frames 2 · speed step 64 · speed limit 512 · swing -9, 9 · period updates 33 · period frames 66

Codeusdasm: A Link to the Past US Disassembly · bank_00.asm#L16676-L16738InitializeMirrorHDMA: |00FDEE| STZ.b $9B … |00FE10| LDA.w #$FFC0 |00FE13| STA.w $06A6 |00FE16| LDA.w #$0040 |00FE19| STA.w $06A8 |00FE1C| LDA.w #$FE00 |00FE1F| STA.w $06A2 |00FE22| LDA.w #$0200 |00FE25| STA.w $06A4 … |00FE2E| LDA.w #$0F42 |00FE31| STA.w DMA7MODE |00FE34| LDA.w #$0D42 |00FE37| STA.w DMA6MODE |00FE3A| LDX.b #$3E |00FE3C| LDA.b $E2 … InitializeHDMATable: .next |00FE3E| STA.w $1B00,X |00FE41| STA.w $1B40,X |00FE44| STA.w $1B80,X |00FE47| STA.w $1BC0,X |00FE4A| STA.w $1C00,X |00FE4D| STA.w $1C40,X |00FE50| STA.w $1C80,X |00FE53| DEX |00FE54| DEX |00FE55| BPL .next |00FE57| SEP #$20 |00FE59| LDA.b #$C0 |00FE5B| STA.b $9Busdasm: A Link to the Past US Disassembly · bank_00.asm#L16755-L16892MirrorWarp_BuildWavingHDMATable: |00FE64| JSL MirrorWarp_RunAnimationSubmodules |00FE68| LDA.b $1A |00FE6A| LSR A |00FE6B| BCS EXIT_00FE5D |00FE6D| REP #$30 |00FE6F| LDX.w #$01A0 |00FE72| LDY.w #$01B0 … .next |00FE7F| LDA.w $1B00,X |00FE82| STA.w $1B00,Y |00FE85| STA.w $1B04,Y |00FE88| STA.w $1B08,Y |00FE8B| STA.w $1B0C,Y |00FE8E| TXA |00FE8F| SEC |00FE90| SBC.w #$0010 |00FE93| TAX … |00FE9D| TYA |00FE9E| SEC |00FE9F| SBC.w #$0010 |00FEA2| TAY … |00FEAC| CPY.w #$0000 |00FEAF| BNE .next … |00FEB1| LDX.w $06A0 |00FEB4| LDA.w $06AC |00FEB7| CLC |00FEB8| ADC.w $06A6,X |00FEBB| PHA |00FEBC| SEC |00FEBD| SBC.w $06A2,X |00FEC0| EOR.w $06A2,X |00FEC3| BMI .negative |00FEC5| STZ.w $06AA |00FEC8| STZ.w $06AE |00FECB| LDA.w $06A0 |00FECE| EOR.w #$0002 |00FED1| STA.w $06A0 |00FED4| PLA |00FED5| LDA.w $06A2,X |00FED8| PHA .negative |00FED9| PLA |00FEDA| STA.w $06AC |00FEDD| CLC |00FEDE| ADC.w $06AE |00FEE1| PHA |00FEE2| AND.w #$00FF |00FEE5| STA.w $06AE … |00FEF3| XBA |00FEF4| CLC |00FEF5| ADC.w $06AA |00FEF8| STA.w $06AA |00FEFB| TAX |00FEFC| LDA.l $7EC007 |00FF00| CMP.w #$0030 |00FF03| BCC .dont_advance |00FF05| TXA |00FF06| AND.w #$FFF8 |00FF09| BNE .dont_advance … |00FF17| LDX.w #$0000 |00FF1A| INC.b $B0 … .dont_advance |00FF1C| TXA |00FF1D| CLC |00FF1E| ADC.b $E2 |00FF20| STA.w $1B00 |00FF23| STA.w $1B04 |00FF26| STA.w $1B08 |00FF29| STA.w $1B0Cusdasm: A Link to the Past US Disassembly · bank_00.asm#L16903-L16971MirrorWarp_BuildDewavingHDMATable: |00FF2F| JSL MirrorWarp_RunAnimationSubmodules |00FF33| LDA.b $1A |00FF35| LSR A |00FF36| BCS EXIT_00FF2E … |00FF7C| LDA.w $1C80 |00FF7F| ORA.w $1C90 |00FF82| ORA.w $1CA0 |00FF85| ORA.w $1CB0 |00FF88| CMP.b $E2 |00FF8A| BNE .exit |00FF8C| SEP #$20 |00FF8E| STZ.b $9B |00FF90| INC.b $B0usdasm: A Link to the Past US Disassembly · bank_02.asm#L9112-L9119|02B202| LDA.b $B0 |02B204| JSL JumpTableLong |02B208| dl MirrorWarp_Initialize ; 0x00 |02B20B| dl MirrorWarp_BuildAndEnableHDMATable ; 0x01 |02B20E| dl MirrorWarp_BuildWavingHDMATable ; 0x02 |02B211| dl MirrorWarp_BuildDewavingHDMATable ; 0x03usdasm: A Link to the Past US Disassembly · bank_00.asm#L15013-L15015 — the window effect's indirect table, which the warp's setup doesn't replace|00F2FB| db $F8 : dw $1B00 ; 120 lines continuous |00F2FE| db $F8 : dw $1BF0 ; 120 lines continuous |00F301| db $00 ; endusdasm: A Link to the Past US Disassembly · bank_00.asm#L68-L70.do_frame |008051| INC.b $1APPU registers · BGnHOFS - BG1-4 horizontal scroll offset ($210D, $210F, $2111, $2113 write twice)BGn horizontal scrollChecked by claude-agent, 2026-11-08 — read InitializeMirrorHDMA, the waving and dewaving builders, and the warp's submodule list; the swing and its period come from stepping the speed, limit, and fraction code for 80 updates (scratch/mirror-sim.mjs), and the band layout from the copy loop's 16-byte stepsRevised after a check by a second AI model, 2026-11-08.

The speed is in 1/256 pixel: its low byte carries in a fraction and its high byte, sign-extended, moves the offset. When the speed passes its limit the code also sets the offset to 0, which after the first swing lands within 2 pixels of where it was. The first swing, from a speed of 0, reaches −9 and jumps back to −2. The copy loop moves 16 bytes a band and writes 4 words of it (offsets 0, 4, 8, and 12). Both channels read the same buffer, so BG1 and BG2 bend together. The setup writes only the channels' modes and targets, not their table address; the address the window effect leaves is the one quoted here, and the warp's band layout assumes it.

A palette fade moves each color component one level at a time, on steps a table spreads through the fade rather than in proportion: for each filtered color (colors 32 to 215 and 224 to 239, and the backdrop) and each of its red, green, and blue components, the component's value in the saved palette, v from 0 to 31, picks a pair of 16-bit masks, and on step n the component moves by 1 toward black (or back up, for a fade-in) unless bit 15 − (n mod 16) of the first mask (n below 16) or the second (n of 16 or more) is set. Each pair leaves exactly v of the steps 0 to 30 clear (computed from the table), so after a fade-out's final step, step 30, every component of every filtered color is at 0; a component of 16 moves on every even step, and one of 1 only on step 15. Colors that are black in the saved palette are skipped. alttp-av/palette-fade

About: The Legend of Zelda: A Link to the Past Screen fade

Value
{
 "steps": 31,
 "maskWords": [
  65535,
  65535,
  65534,
  65535,
  32767,
  32767,
  32735,
  64511,
  32639,
  32639,
  32247,
  61375,
  31711,
  31711,
  30651,
  56815,
  30583,
  30583,
  28381,
  47991,
  28087,
  28087,
  23405,
  46811,
  23387,
  23387,
  22198,
  44395,
  21845,
  44395,
  21845,
  43691,
  21845,
  21845,
  10837,
  21845,
  10837,
  10837,
  10570,
  21141,
  9509,
  9509,
  9362,
  18725,
  4681,
  4681,
  4386,
  17545,
  4369,
  4369,
  2116,
  8721,
  1057,
  1057,
  520,
  4161,
  257,
  257,
  32,
  1025,
  1,
  1,
  0,
  1
 ]
}
Codeusdasm: A Link to the Past US Disassembly · bank_00.asm#L13076-L13097PaletteFilterColorAdd: |00E880| dw $FFFF, $0001 |00E884| dw $FFE0, $0020 |00E888| dw $FC00, $0400 … PaletteFilterColorMasks: |00E88C| dw $FFFF, $FFFF, $FFFE, $FFFF … |00E904| dw $0001, $0001, $0000, $0001usdasm: A Link to the Past US Disassembly · bank_00.asm#L13112-L13250ApplyPaletteFilter: |00E914| REP #$30 |00E916| LDX.w #PaletteFilterColorMasks |00E919| LDA.l $7EC007 |00E91D| CMP.w #$0010 |00E920| BCC .darken_color |00E922| INX |00E923| INX .darken_color |00E924| STX.b $B7 |00E926| AND.w #$000F |00E929| ASL A |00E92A| TAX |00E92B| LDA.w BitMasks,X |00E92E| STA.b $0C … |00E933| LDA.l $7EC009 |00E937| TAX |00E938| LDA.w PaletteFilterColorAdd+0,X |00E93B| STA.b $06 |00E93D| LDA.w PaletteFilterColorAdd+4,X |00E940| STA.b $08 |00E942| LDA.w PaletteFilterColorAdd+8,X |00E945| STA.b $0A |00E947| LDX.w #$0040 |00E94A| JSR PaletteFilter_FilterColors … |00E99C| LDA.l $7EC009 |00E9A0| BNE .lightening |00E9A2| LDA.l $7EC007 |00E9A6| INC A |00E9A7| STA.l $7EC007 |00E9AB| CMP.l $7EC00B |00E9AF| BNE .continue_filtering .swap |00E9B1| LDA.l $7EC009 |00E9B5| EOR.w #$0002 |00E9B8| STA.l $7EC009 |00E9BC| LDA.w #$0000 |00E9BF| STA.l $7EC007 … .lightening |00E9CC| LDA.l $7EC007 |00E9D0| CMP.l $7EC00B |00E9D4| BEQ .swap |00E9D6| LDA.l $7EC007 |00E9DA| DEC A |00E9DB| STA.l $7EC007usdasm: A Link to the Past US Disassembly · bank_00.asm#L13258-L13330PaletteFilter_FilterColors: .next_color |00E9E4| LDA.l $7EC500,X |00E9E8| STA.b $04 |00E9EA| LDA.l $7EC300,X |00E9EE| BEQ .color_is_black |00E9F0| AND.w #$001F |00E9F3| ASL A |00E9F4| ASL A |00E9F5| TAY |00E9F6| LDA.b ($B7),Y |00E9F8| AND.b $0C |00E9FA| BNE .no_red |00E9FC| LDA.b $04 |00E9FE| ADC.b $06 |00EA00| STA.b $04 … |00EA34| INX |00EA35| INX |00EA36| CPX.w #$01B0 |00EA39| BCC .next_color |00EA3B| BNE .dont_skip_palette_5 |00EA3D| TXA |00EA3E| CLC |00EA3F| ADC.w #$0010 |00EA42| TAX .dont_skip_palette_5 |00EA43| CPX.w #$01E0 |00EA46| BNE .next_colorusdasm: A Link to the Past US Disassembly · bank_00.asm#L13153-L13159 — the backdrop, color 0, filtered the same way|00E94D| LDA.l $7EC500 |00E951| STA.b $04 … |00E953| LDA.l $7EC300 |00E957| AND.w #$001Fusdasm: A Link to the Past US Disassembly · bank_00.asm#L4154-L4156BitMasks: |0098C0| dw $8000 ; Sewers |0098C2| dw $4000 ; Hyrule CastleChecked by claude-agent, 2026-11-08 — read ApplyPaletteFilter, PaletteFilter_FilterColors and its color range, the add table, the 64-word mask table, and BitMasks; the per-value step counts come from counting each pair's clear bits over steps 0 to 30 by scriptRevised after a check by a second AI model, 2026-11-08.

The saved palette is at $7EC300 and the working one at $7EC500, 2 bytes a color, so byte offsets $40 to $1AF and $1C0 to $1DF are colors 32 to 215 and 224 to 239. The add table's three words step red ($0001), green ($0020), and blue ($0400) down or up. The counter runs from 0 to the target the caller sets; the transitions that reset it set 31, so the steps are 0 to 30, and no pair's step 31 is clear. BitMasks is a shared table of single bits, $8000 shifted right by its index, whose comments name dungeons for its other use.

Three of the overworld's transitions (its submodules $0D, $17, and $24) darken and coarsen the picture together: they reset the fade counter to 0 with a target of 31, then each frame step the palette fade once and, on frames that find the counter odd, grow the mosaic size by 1, written with BG1, BG2, and BG3 enabled ($07) and BG mode value $09. Over the fade's 31 frames the size reaches 15, blocks 16 pixels square (computed), and the screen is then blanked. The transition back in, submodules $16 and $29, shrinks the size by 1 on odd counts in the same way. alttp-av/mosaic-transition

About: The Legend of Zelda: A Link to the Past Mosaic

Value: frames 31 · max size 15 · block pixels 16

Codeusdasm: A Link to the Past US Disassembly · bank_02.asm#L8395-L8426StartOverworldMosaicTransition: |02AE5E| JSR ConditionalMosaicControl |02AE61| LDA.b $B0 |02AE63| JSL JumpTableLocal |02AE67| dw OverworldMosaicTransition_HandleSong ; 0x00 |02AE69| dw ApplyPaletteFilter_bounce ; 0x01 |02AE6B| dw OverworldMosaicTransition_HandleScreensAndLoadShroom ; 0x02 … |02AE83| JMP.w ResetTransitionPropsAndAdvance_ResetInterface … OverworldMosaicTransition_HandleScreensAndLoadShroom: |02AE86| LDA.b #$80 |02AE88| STA.b $13usdasm: A Link to the Past US Disassembly · bank_02.asm#L2399-L2413ResetTransitionPropsAndAdvance_ResetInterface: |028CA9| STZ.w $0200 … |028CAC| REP #$30 |028CAE| LDA.w #$0000 |028CB1| STA.l $7EC011 |028CB5| STA.l $7EC009 |028CB9| STA.l $7EC007 |028CBD| LDA.w #$001F |028CC0| STA.l $7EC00Busdasm: A Link to the Past US Disassembly · bank_02.asm#L6791-L6792ApplyPaletteFilter_bounce: |02A2A0| JSL ApplyPaletteFilterusdasm: A Link to the Past US Disassembly · bank_02.asm#L12450-L12471ConditionalMosaicControl: |02C2E4| LDA.l $7EC007 |02C2E8| LSR A |02C2E9| BCC CopyMosaicControl … MosaicControlIncrease: |02C2EB| LDA.l $7EC011 |02C2EF| CLC |02C2F0| ADC.b #$10 |02C2F2| STA.l $7EC011 … CopyMosaicControl: |02C2F6| LDA.b #$09 |02C2F8| STA.b $94 |02C2FA| LDA.l $7EC011 |02C2FE| ORA.b #$07 |02C300| STA.b $95usdasm: A Link to the Past US Disassembly · bank_02.asm#L8859-L8870Module09_FadeBackInFromMosaic: |02B0D2| LDA.l $7EC007 |02B0D6| LSR A |02B0D7| BCC .delay |02B0D9| LDA.l $7EC011 |02B0DD| SEC |02B0DE| SBC.b #$10 |02B0E0| STA.l $7EC011 .delay |02B0E4| JSR CopyMosaicControlusdasm: A Link to the Past US Disassembly · bank_02.asm#L7084-L7113|02A427| dw StartOverworldMosaicTransition ; 0x0D … |02A439| dw Module09_FadeBackInFromMosaic ; 0x16 |02A43B| dw StartOverworldMosaicTransition ; 0x17 … |02A455| dw StartOverworldMosaicTransition ; 0x24 … |02A45F| dw Module09_FadeBackInFromMosaic ; 0x29usdasm: A Link to the Past US Disassembly · bank_00.asm#L386-L387|0081CC| LDA.b $95 |0081CE| STA.w MOSAICA palette fade moves each color component one level at a time, on steps a table spreads through the fade rather than in proportion: for each filtered color (colors 32 to 215 and 224 to 239, and the backdrop) and each of its red, green, and blue components, the component's value in the saved palette, v from 0 to 31, picks a pair of 16-bit masks, and on step n the component moves by 1 toward black (or back up, for a fade-in) unless bit 15 − (n mod 16) of the first mask (n below 16) or the second (n of 16 or more) is set. Each pair leaves exactly v of the steps 0 to 30 clear (computed from the table), so after a fade-out's final step, step 30, every component of every filtered color is at 0; a component of 16 moves on every even step, and one of 1 only on step 15. Colors that are black in the saved palette are skipped. Code — the fade's steps and its counterPPU registers · MOSAIC - Screen pixelation ($2106 write)++++------ Mosaic size in pixels (0 = 1x1, ..., 15 = 16x16)PPU registers · MOSAIC - Screen pixelation ($2106 write)|||| |||+- Enable BG1 mosaicPPU registers · MOSAIC - Screen pixelation ($2106 write)|||| ||+-- Enable BG2 mosaicPPU registers · MOSAIC - Screen pixelation ($2106 write)|||| |+--- Enable BG3 mosaicChecked by claude-agent, 2026-11-08 — read StartOverworldMosaicTransition and its three submodules, the reset of the fade counter and target, ConditionalMosaicControl and CopyMosaicControl, the fade-in's decrement, the overworld module's submodule list, and the NMI's mosaic write; the size reached is computed from the counter's odd values 1 to 29 before the fade endsRevised after a check by a second AI model, 2026-11-08.

The mosaic check runs before the fade's step in each frame, so it sees the counter at 0 to 30 and grows the size on its 15 odd values; on the frame the counter reaches 31 the fade swaps direction, resets the counter to 0, and moves on. The size is the register's high nibble, so adding $10 adds 1. Where in the overworld these submodules run isn't read here.

The Ether medallion flashes the screen by brightening the palette: on a flash frame the code adds 14 to the red, green, and blue of each of colors 32 to 127 in the saved palette, capping each at 31, and writes the results to the working palette; the backdrop takes the brightened color 32 unless it is black in the saved palette. A restore frame copies the saved colors 32 to 127 back and puts color 32 in the backdrop. Before the lightning's step counter leaves 0, a per-spell counter, raised by 1 each frame, picks flash or restore by its bit 2, so the screen alternates every 4 frames; after, only step $0B flashes. The flashing stops when the spell's state byte is 0. alttp-av/ether-flash

About: The Legend of Zelda: A Link to the Past Palette swap

Value: add 14 · cap 31 · colors 32, 127 · alternate frames 4

Codeusdasm: A Link to the Past US Disassembly · bank_08.asm#L8655-L8705Ancilla18_EtherSpell: |08AAA0| LDA.b $11 |08AAA2| BEQ .submode_fine … .submode_fine |08AAA7| LDA.w $0C54,X |08AAAA| BEQ .done_palette_flash |08AAAC| LDA.w $031D |08AAAF| BNE .lightning_descended |08AAB1| INC.w $0BF0,X |08AAB4| LDA.w $0BF0,X |08AAB7| AND.b #$04 |08AAB9| BEQ .oversaturate |08AABB| BRA .restore … .lightning_descended |08AABD| LDA.w $031D |08AAC0| CMP.b #$0B |08AAC2| BNE .restore … .oversaturate |08AAC4| PHX |08AAC5| JSL RefreshEquipmentPalettes_zap |08AAC9| JSL PaletteFilter_Oversaturation |08AACD| PLX |08AACE| BRA .done_palette_flash … .restore |08AAD0| PHX |08AAD1| JSL RefreshEquipmentPalettes_sword_and_mail |08AAD5| JSL RestorePalettesAfterFlashusdasm: A Link to the Past US Disassembly · bank_0E.asm#L10851-L10979PaletteFilter_Oversaturation: |0ED757| REP #$20 |0ED759| LDX.b #$00 … .next_color |0ED75B| LDA.l $7EC340,X |0ED75F| JSR OversaturateColor |0ED762| STA.l $7EC540,X … |0ED7D4| LDA.l $7EC3F0,X |0ED7D8| JSR OversaturateColor |0ED7DB| STA.l $7EC5F0,X |0ED7DF| INX |0ED7E0| INX |0ED7E1| CPX.b #$10 … .done |0ED7E8| REP #$10 |0ED7EA| LDA.l $7EC540 |0ED7EE| TAY |0ED7EF| LDA.l $7EC300 |0ED7F3| BNE .dont_copy_that |0ED7F5| TAY .dont_copy_that |0ED7F6| TYA |0ED7F7| STA.l $7EC500 … OversaturateColor: |0ED7FE| STA.b $00 |0ED800| AND.w #$001F |0ED803| CLC |0ED804| ADC.w #$000E |0ED807| CMP.w #$001F |0ED80A| BCC .oversaturated_r |0ED80C| LDA.w #$001F .oversaturated_r |0ED80F| STA.b $02 |0ED811| LDA.b $00 |0ED813| AND.w #$03E0 |0ED816| CLC |0ED817| ADC.w #$01C0 |0ED81A| CMP.w #$03E0 |0ED81D| BCC .oversaturated_g |0ED81F| LDA.w #$03E0 .oversaturated_g |0ED822| STA.b $04 |0ED824| LDA.b $00 |0ED826| AND.w #$7C00 |0ED829| CLC |0ED82A| ADC.w #$3800 |0ED82D| CMP.w #$7C00 |0ED830| BCC .oversaturated_b |0ED832| LDA.w #$7C00 .oversaturated_b |0ED835| ORA.b $02 |0ED837| ORA.b $04 |0ED839| RTSusdasm: A Link to the Past US Disassembly · bank_0E.asm#L10983-L11033RestorePalettesAfterFlash: |0ED83A| REP #$20 |0ED83C| LDX.b #$00 … .next_color |0ED83E| LDA.l $7EC340,X |0ED842| STA.l $7EC540,X … |0ED896| LDA.l $7EC3F0,X |0ED89A| STA.l $7EC5F0,X |0ED89E| INX |0ED89F| INX |0ED8A0| CPX.b #$10 |0ED8A2| BNE .next_color … |0ED8A4| LDA.l $7EC540 |0ED8A8| STA.l $7EC500usdasm: A Link to the Past US Disassembly · bank_08.asm#L765 — the spell's entry in the ancilla table (type $18)|0883AD| dw Ancilla18_EtherSpellChecked by claude-agent, 2026-11-08 — read Ancilla18_EtherSpell's flash test, PaletteFilter_Oversaturation's twelve 16-byte rows over 8 words each and its backdrop step, OversaturateColor's three clamped adds, and RestorePalettesAfterFlash's twelve rows and backdrop copyRevised after a check by a second AI model, 2026-11-08.

The add is 14 in each channel's own position: $000E to red, $01C0 (14 × 32) to green, and $3800 (14 × 1,024) to blue, each capped at the channel's maximum. The saved palette's byte offsets $40 to $FF are colors 32 to 127, the background palettes 2 to 7. A cap that the color is already under adds the full 14, so a dark color brightens as much as a light one, up to the cap.

The Quake medallion shakes the screen vertically by ±3 pixels: on each frame of its active phase the code takes a stored offset, starting at 3, as the vertical scroll offset added to BG1's and BG2's vertical scroll, then negates the stored value, so the picture moves 3 pixels one way and 3 the other on alternate frames. Its finish clears both scroll offsets. alttp-av/quake-shake

About: The Legend of Zelda: A Link to the Past Screen shake

Value: amplitude 3 · period 2 (pixels)

Codeusdasm: A Link to the Past US Disassembly · bank_09.asm#L4612-L4613 — the spell's setup|0995F0| LDA.w #$0003 |0995F3| STA.l $7F581Eusdasm: A Link to the Past US Disassembly · bank_08.asm#L10894-L10903Ancilla1C_QuakeSpell: |08B66A| LDA.b $11 |08B66C| BNE .only_draw |08B66E| LDA.w $0C54,X |08B671| CMP.b #$02 |08B673| BEQ .finish_up |08B675| JSR QuakeSpell_ShakeScreenusdasm: A Link to the Past US Disassembly · bank_08.asm#L11005-L11021QuakeSpell_ShakeScreen: |08B6F7| REP #$20 |08B6F9| LDA.l $7F581E |08B6FD| STA.w $011C |08B700| EOR.w #$FFFF |08B703| INC A |08B704| STA.l $7F581E |08B708| SEP #$20 |08B70A| LDA.b $30 |08B70C| CLC |08B70D| ADC.w $011C |08B710| STA.b $30usdasm: A Link to the Past US Disassembly · bank_08.asm#L10958-L10961|08B6BA| STZ.w $011A |08B6BD| STZ.w $011B |08B6C0| STZ.w $011C |08B6C3| STZ.w $011Dusdasm: A Link to the Past US Disassembly · bank_00.asm#L15980-L15998 — the main loop's merge of the shake offsets into BG1's and BG2's scroll, which the NMI writes|00F850| LDA.b $E2 |00F852| ADC.w $011A |00F855| STA.w $011E |00F858| LDA.b $E8 |00F85A| CLC |00F85B| ADC.w $011C |00F85E| STA.w $0122 |00F861| LDA.b $E0 |00F863| CLC |00F864| ADC.w $011A |00F867| STA.w $0120 |00F86A| LDA.b $E6 |00F86C| CLC |00F86D| ADC.w $011C |00F870| STA.w $0124Checked by claude-agent, 2026-11-08 — read the spell's setup of the offset, its main routine's phase test, QuakeSpell_ShakeScreen, the finish's clears, and the main loop's scroll mergeRevised after a check by a second AI model, 2026-11-08.

The offset is added to BG1's and BG2's vertical scroll in the main loop's merge; BG3's scroll doesn't take it there. The routine also adds the offset to the byte at $30, which keeps Link still against the shaking background. The horizontal offset ($011A) stays 0 during the shake.

Each overworld screen's song comes from a 160-byte table, one byte per screen, whose low nibble is the song and high nibble an ambient sound. The game builds the table from one of four Light World versions, followed by the Dark World and special screens: the fourth if the story counter is 3 or more, else the third if the sword byte is 2 or more, else the second if the story counter is 2, else the first. Scrolling onto a screen whose song differs from the one playing sends the fade command $F1 at once, and the game keeps $F1 as the song playing; when the new screen is entered, a fade in that place is replaced by the screen's song. An ambient nibble of 0 sends 5 instead. In the Dark World without the Moon Pearl the crossing sends neither, so the song carries on; the mirror warp's arrival there plays song 4. alttp-av/area-music

Value
{
 "lightWorldTables": [
  {
   "when": "story counter under 2, sword under 2",
   "default": "$03",
   "others": {
    "$05": "$00 $01 $08 $09",
    "$13": "$12-$14 $1A-$1C $22-$24 $2A-$2C"
   }
  },
  {
   "when": "story counter 2, sword under 2",
   "default": "$02",
   "others": {
    "$55": "$00 $01 $08 $09",
    "$52": "$03-$07 $0B-$0E",
    "$07": "$18 $19 $20-$22 $28 $29"
   }
  },
  {
   "when": "story counter under 3, sword 2 or more",
   "default": "$02",
   "others": {
    "$52": "$00 $01 $03-$09 $0B-$0E",
    "$07": "$18 $19 $20-$22 $28 $29"
   }
  },
  {
   "when": "story counter 3 or more",
   "default": "$02",
   "others": {
    "$52": "$00 $01 $03-$09 $0B-$0E $30 $31 $38 $39"
   }
  }
 ],
 "darkWorld": {
  "default": "$09",
  "others": {
   "$9D": "$40 $41 $43-$49 $4B-$4E"
  }
 },
 "special": {
  "default": "$02",
  "others": {
   "$05": "$80",
   "$12": "$9F"
  }
 }
}
Codeusdasm: A Link to the Past US Disassembly · bank_02.asm#L12477-L12545 — the value's screen lists are counted from all 352 bytes of these lines (computed)LightWorldAmbiance: |02C303| db $05, $05, $03, $03, $03, $03, $03, $03 … |02C343| db $55, $55, $02, $52, $52, $52, $52, $52 … |02C383| db $52, $52, $02, $52, $52, $52, $52, $52 … |02C3C3| db $52, $52, $02, $52, $52, $52, $52, $52 … DarkWorldAmbiance: |02C403| db $9D, $9D, $09, $9D, $9D, $9D, $9D, $9D … |02C443| db $05, $02, $02, $02, $02, $02, $02, $02 … |02C45B| db $02, $02, $02, $02, $02, $02, $02, $12usdasm: A Link to the Past US Disassembly · bank_02.asm#L12532-L12600AdjustOverworldAmbiance: … |02C46C| LDX.w #$0000 |02C46F| LDY.w #$00C0 |02C472| LDA.l $7EF3C5 |02C476| CMP.b #$03 |02C478| BCS .copy_next |02C47A| LDY.w #$0080 |02C47D| LDA.l $7EF359 |02C481| CMP.b #$02 |02C483| BCS .copy_next |02C485| LDA.b #$05 |02C487| STA.b $00 |02C489| LDY.w #$0040 |02C48C| LDA.l $7EF3C5 |02C490| CMP.b #$02 |02C492| BCS .copy_next |02C494| LDY.w #$0000 … .copy_next |02C497| LDA.w LightWorldAmbiance,Y |02C49A| STA.l $7F5B00,X |02C49E| INY |02C49F| INX |02C4A0| CPX.w #$0040 |02C4A3| BNE .copy_next … |02C4A5| LDY.w #$0000 .copy_more |02C4A8| LDA.w DarkWorldAmbiance,Y |02C4AB| STA.l $7F5B00,X |02C4AF| INX |02C4B0| INY |02C4B1| CPY.w #$0060usdasm: A Link to the Past US Disassembly · bank_09.asm#L1168-L1169 — the item receipt table's save address for the swords|0984E8| dw $7EF359 ; FIGHTER SWORD |0984EA| dw $7EF359 ; MASTER SWORDusdasm: A Link to the Past US Disassembly · bank_09.asm#L1247-L1249.sram_value |098580| db $01 ; FIGHTER SWORD |098581| db $02 ; MASTER SWORDusdasm: A Link to the Past US Disassembly · bank_09.asm#L1199|098526| dw $7EF357 ; PEARLusdasm: A Link to the Past US Disassembly · bank_02.asm#L7727-L7764.not_haunted_grove |02AA7D| LDA.l ActualOverworldScreenID,X |02AA81| ORA.l $7EF3CA |02AA85| STA.b $8A … |02AA8B| LDA.l $7EF3CA |02AA8F| BEQ .light_world |02AA91| LDA.l $7EF357 |02AA95| BEQ .dont_restore_dw_music .light_world |02AA97| LDA.l $7F5B00,X |02AA9B| LSR A |02AA9C| LSR A |02AA9D| LSR A |02AA9E| LSR A |02AA9F| BNE .has_ambient_sfx |02AAA1| LDA.b #$05 |02AAA3| STA.w $012D .has_ambient_sfx |02AAA6| LDA.l $7F5B00,X |02AAAA| AND.b #$0F |02AAAC| CMP.w $0130 |02AAAF| BEQ .dont_restore_dw_music |02AAB1| LDA.b #$F1 ; SONG F1 - fade |02AAB3| STA.w $012Cusdasm: A Link to the Past US Disassembly · bank_02.asm#L12357-L12377 — in the routine that finishes the entry onto a screen (its label at L12308)|02C277| STZ.b $11 |02C279| STZ.b $B0 |02C27B| LDX.b $8A |02C27D| LDA.l $7F5B00,X |02C281| LSR A |02C282| LSR A |02C283| LSR A |02C284| LSR A |02C285| STA.w $012D |02C288| LDA.w $0130 |02C28B| CMP.b #$F1 ; SONG F1 - fade |02C28D| BNE .not_fade |02C28F| LDA.l $7F5B00,X |02C293| AND.b #$0F |02C295| STA.w $012C .not_fadeusdasm: A Link to the Past US Disassembly · bank_02.asm#L9208-L9228 — in the mirror warp's arrival (its label at L9171)|02B29F| LDX.b $8A |02B2A1| LDA.l $7F5B00,X |02B2A5| AND.b #$0F |02B2A7| STA.w $012C … |02B2B5| CPX.b #$40 ; DW |02B2B7| BCC .not_bunny_song |02B2B9| LDA.l $7EF357 |02B2BD| BNE .not_bunny_song |02B2BF| LDA.b #$04 ; SONG 04 |02B2C1| STA.w $012Cusdasm: A Link to the Past US Disassembly · bank_02.asm#L8893-L8923OverworldMosaicTransition_RecoverSongAndSetMoving: |02B105| LDA.w $0130 |02B108| STA.w $0133 |02B10B| LDA.b $8A |02B10D| CMP.b #$80 ; OW 80 |02B10F| BEQ .dont_set_song |02B111| CMP.b #$2A ; OW 2A |02B113| BEQ .dont_set_song |02B115| LDX.b $8A |02B117| LDA.l $7F5B00,X |02B11B| LSR A |02B11C| LSR A |02B11D| LSR A |02B11E| LSR A |02B11F| BNE .has_sfx |02B121| LDA.b #$05 ; SFX1.05 .has_sfx |02B123| STA.w $012D |02B126| LDA.l $7F5B00,X |02B12A| AND.b #$0F |02B12C| CMP.w $0130 |02B12F| BEQ .dont_set_song |02B131| STA.w $012Cusdasm: A Link to the Past US Disassembly · bank_00.asm#L239-L263 — the NMI sends a song request to the sound CPU's first port and keeps it as the song playing unless it is $F2 or above|0080DC| LDA.w $012C |0080DF| BNE .song_input … .song_input |0080EE| CMP.w $0133 |0080F1| BEQ .handle_sfx1 |0080F3| STA.w APUIO0 |0080F6| STA.w $0133 |0080F9| CMP.b #$F2 ; SONG F2 - half volume |0080FB| BCS .song_command |0080FD| STA.w $0130 .song_command |008100| STZ.w $012Cusdasm: A Link to the Past US Disassembly · sound.asm#L843-L855|1A9EF9:D000| dw Song01_TriforceIntro |1A9EFB:D002| dw Song02_LightWorldOverture |1A9EFD:D004| dw Song03_Rain |1A9EFF:D006| dw Song04_BunnyTheme |1A9F01:D008| dw Song05_LostWoods … |1A9F05:D00C| dw Song07_KakarikoVillage … |1A9F09:D010| dw Song09_DarkWorld … |1A9F11:D018| dw Song0D_SkullWoodsMarchChecked by claude-agent, 2026-11-08 — read the two ambiance tables (counted by script), the table builder's three tests and two copy loops, the screen-change and post-transition song code, the NMI's song port, the item receipt table's sword and pearl entries, and the song pointer tableRevised after a check by a second AI model, 2026-11-08.

The builder copies 64 Light World bytes and then 96 more (the Dark World's 64 and the special screens' 32), so the table covers screens $00 to $9F. The world byte is ORed into the screen number, so the Dark World screens are $40 to $7F and read the Dark World part. Before Zelda's rescue every Light World screen plays song 3, the rain; the Light World Overture (2) replaces it after, and the Lost Woods screens lose their own song (5) once the sword byte reaches 2, the Master Sword's value in the item table. Kakariko's song (7) goes at story counter 3. The song and sound data are binary includes in the disassembly, so what ambient values 1, 5, and 9 sound like isn't read here. The special areas' mosaic transitions send the song after the mosaic instead, skipping screens $80 and $2A.

The game sends sound to the sound CPU through four ports: songs on the first, ambient sounds on the second, and two sets of 63 effects on the third and fourth, which it writes every frame, zero when nothing is queued. The ambient set has 32 slots, two of them empty, with one sound in two slots and six slots that repeat entries $0B to $10. The effect sets share entries: the second set's slot $38 plays its slot $34, and the third set's slots 5 and $1D play the second set's effects 7 and $2D. Each set has an accompaniment table: a nonzero entry starts a second sound with the first, which the ambient set uses for every odd slot, pairing it with the next slot on the seventh voice. Each effect set also has an echo table, read when an effect starts; a nonzero entry adds the effect's voice to a set of voices the sound program keeps. alttp-av/sfx-tables

About: The Legend of Zelda: A Link to the Past Sound effects

Value
{
 "ports": {
  "song": "APUIO0",
  "ambient": "APUIO1",
  "effectsA": "APUIO2",
  "effectsB": "APUIO3"
 },
 "slots": {
  "ambient": 32,
  "effectsA": 63,
  "effectsB": 63
 },
 "accompaniment": {
  "ambient": "every odd slot n starts n+1",
  "effectsA": {
   "$0D": "$3F",
   "$13": "$3E",
   "$24": "$3D",
   "$29": "$3B",
   "$2C": "$3A",
   "$2E": "$39",
   "$2F": "$38",
   "$34": "$33",
   "$35": "$36"
  },
  "effectsB": {
   "$0F": "$3C",
   "$10": "$3B",
   "$1A": "$38",
   "$1B": "$3A",
   "$23": "$39",
   "$2D": "$37",
   "$2E": "$35",
   "$2F": "$33",
   "$35": "$34",
   "$3C": "$3D",
   "$3D": "$3E",
   "$3E": "$3F"
  }
 }
}
Codeusdasm: A Link to the Past US Disassembly · bank_00.asm#L264-L291 — the NMI, after its song port write (L239-L263).handle_sfx1 |008103| LDA.w $012D |008106| BNE .new_sfx1 … .new_sfx1 |008115| STA.w $0131 |008118| STA.w APUIO1 |00811B| STZ.w $012D .handle_SFX23 |00811E| LDA.w $012E |008121| STA.w APUIO2 |008124| LDA.w $012F |008127| STA.w APUIO3 |00812A| STZ.w $012E |00812D| STZ.w $012Fusdasm: A Link to the Past US Disassembly · sound.asm#L148-L180SFX1_Pointers: … |1A8B78:17C8| dw SFX1_05 |1A8B7A:17CA| dw SFX1_05 … |1A8B9E:17EE| dw SFX1_18 |1A8BA0:17F0| dw $0000 |1A8BA2:17F2| dw $0000 |1A8BA4:17F4| dw SFX1_0B … |1A8BAE:17FE| dw SFX1_10usdasm: A Link to the Past US Disassembly · sound.asm#L220-L283SFX2_Pointers: |1A8BD0:1820| dw SFX2_01 … |1A8C3C:188C| dw SFX2_37 |1A8C3E:188E| dw SFX2_34 |1A8C40:1890| dw SFX2_39 … |1A8C4C:189C| dw SFX2_3Fusdasm: A Link to the Past US Disassembly · sound.asm#L421-L484SFX3_Pointers: |1A8CCC:191C| dw SFX3_01 … |1A8CD2:1922| dw SFX3_04 |1A8CD4:1924| dw SFX2_07 |1A8CD6:1926| dw SFX3_06 … |1A8D02:1952| dw SFX3_1C |1A8D04:1954| dw SFX2_2D |1A8D06:1956| dw SFX3_1E … |1A8D48:1998| dw SFX3_3Fusdasm: A Link to the Past US Disassembly · sound.asm#L184-L216 — the value's pairs are read from all three accompaniment tables (L184-L216, L287-L350, L488-L551) by scriptSFX1_Accomp: |1A8BB0:1800| db $02 ; SFX1 01 |1A8BB1:1801| db $00 ; SFX1 02usdasm: A Link to the Past US Disassembly · sound.asm#L287-L301SFX2_Accomp: |1A8C4E:189E| db $00 ; SFX2 01 … |1A8C5A:18AA| db $3F ; SFX2 0Dusdasm: A Link to the Past US Disassembly · spc.asm#L2470-L2490 — the sound program's start of an ambient sound; voice 6 counting from 0 (bit 6), the seventh of eight|1A86FB:132D| mov.b X, $01 |1A86FD:132F| mov.w A, SFX1_Accomp-1+X |1A8700:1332| mov.b $01, A |1A8702:1334| bne .also_use_chan_6 |1A8704:1336| ret .also_use_chan_6 |1A8705:1337| mov.b X, #$0C |1A8707:1339| mov.b A, $01 |1A8709:133B| mov.w $03A0+X, A … |1A8716:1348| set6.b $1Ausdasm: A Link to the Past US Disassembly · spc.asm#L2619-L2624|1A87C5:13F7| mov.w A, $03A0+X |1A87C8:13FA| mov X, A |1A87C9:13FB| mov.w A, SFX3_Accomp-1+X |1A87CC:13FE| mov.b $03, A |1A87CE:1400| bne .validusdasm: A Link to the Past US Disassembly · spc.asm#L2664-L2670|1A8807:1439| mov.w A, $03A0+X |1A880A:143C| mov X, A |1A880B:143D| mov.w A, SFX2_Accomp-1+X |1A880E:1440| mov.b $02, A |1A8810:1442| bne HandleValidSFX2usdasm: A Link to the Past US Disassembly · spc.asm#L2286-L2292SFX2_HandleEcho: |1A8600:1232| mov.b A, $02 |1A8602:1234| and.b A, #$3F |1A8604:1236| mov X, A |1A8605:1237| mov.w A, SFX2_Echo-1+X |1A8608:123A| mov.w $03E2, Ausdasm: A Link to the Past US Disassembly · spc.asm#L2326-L2327|1A8633:1265| mov.w A, SFX3_Echo-1+X |1A8636:1268| mov.w $03E2, Ausdasm: A Link to the Past US Disassembly · spc.asm#L2378-L2392 — the effect's voice bit, in $03C1, joins the set at $03E3 when the echo entry is nonzero|1A867C:12AE| mov.w A, $03C1 |1A867F:12B1| mov.w $03C9, A |1A8682:12B4| or.b A, $1A |1A8684:12B6| mov.b $1A, A |1A8686:12B8| mov.w X, $03E2 |1A8689:12BB| beq .disabled |1A868B:12BD| or.w A, $03E3 |1A868E:12C0| mov.w $03E3, Ausdasm: A Link to the Past US Disassembly · spc.asm#L2593|1A8796:13C8| call SFX3_HandleEchousdasm: A Link to the Past US Disassembly · spc.asm#L2638|1A87D8:140A| call SFX2_HandleEchoChecked by claude-agent, 2026-11-08 — read the NMI's four port writes, the three pointer tables whole, the accompaniment tables (pairs listed by script), the sound program's three accompaniment reads, and the echo tables' reads and the set they feedRevised after a check by a second AI model, 2026-11-08.

The pointer tables' 63 entries leave ID 0 for nothing queued, and the effect IDs' top two bits are masked off before the echo lookup (the sound program ANDs the ID with $3F). The sounds themselves are binary includes in the disassembly, so which effect is which isn't read here. The echo tables feed a step that may clear a voice's echo when an effect takes it, under a flag in the sound program's memory that this claim doesn't trace.

The songs set the echo themselves, through four commands in their track data. $F5 sets which voices echo and the left and right echo volumes; $F6 sets the flag that disables echo writes; $F7 sets the echo delay, the feedback, and one of four 8-tap filters; $F8 slides the two echo volumes to new values over a given number of ticks. A change of delay first zeroes both echo volumes, the feedback, and the echo voices, disables echo writes, and then writes the new delay. The echo buffer starts at $D000 − $800 × the delay and is $800 bytes long per step of delay, so it ends at $D000, where the songs' pointer table begins (computed). The sound program starts with a delay of 1, 512 samples, and a main volume of $60 on both sides. The first filter is the identity filter, which leaves the echo unchanged. alttp-av/echo-commands

About: The Legend of Zelda: A Link to the Past Echo

Value
{
 "startDelay": 1,
 "masterVolume": 96,
 "filters": [
  [
   127,
   0,
   0,
   0,
   0,
   0,
   0,
   0
  ],
  [
   88,
   191,
   219,
   240,
   254,
   7,
   12,
   12
  ],
  [
   12,
   33,
   43,
   43,
   19,
   254,
   243,
   249
  ],
  [
   52,
   51,
   0,
   217,
   229,
   1,
   252,
   235
  ]
 ]
}
Codeusdasm: A Link to the Past US Disassembly · spc.asm#L1596-L1602 — the track command table, in which the disassembly numbers these $F5 to $F8|1A82E2:0F14| dw TrackCommand_F5_EchoBasicControl |1A82E4:0F16| dw TrackCommand_F6_EchoSilence |1A82E6:0F18| dw TrackCommand_F7_EchoFilter |1A82E8:0F1A| dw TrackCommand_F8_EchoSlideusdasm: A Link to the Past US Disassembly · spc.asm#L1293-L1375TrackCommand_F5_EchoBasicControl: |1A8190:0DC2| mov.w $03C3, A |1A8193:0DC5| mov.b $4A, A |1A8195:0DC7| call GetTrackByte |1A8198:0DCA| mov.b A, #$00 |1A819A:0DCC| movw.b $60, YA |1A819C:0DCE| call GetTrackByte |1A819F:0DD1| mov.b A, #$00 |1A81A1:0DD3| movw.b $62, YA |1A81A3:0DD5| clr5.b $48 … TrackCommand_F8_EchoSlide: |1A81A6:0DD8| mov.b $68, A |1A81A8:0DDA| call GetTrackByte |1A81AB:0DDD| mov.b $69, A |1A81AD:0DDF| setc |1A81AE:0DE0| sbc.b A, $61 |1A81B0:0DE2| mov.b X, $68 |1A81B2:0DE4| call MakeFraction |1A81B5:0DE7| movw.b $64, YA |1A81B7:0DE9| call GetTrackByte |1A81BA:0DEC| mov.b $6A, A |1A81BC:0DEE| setc |1A81BD:0DEF| sbc.b A, $63 |1A81BF:0DF1| mov.b X, $68 |1A81C1:0DF3| call MakeFraction … TrackCommand_F6_EchoSilence: |1A81C7:0DF9| movw.b $60, YA |1A81C9:0DFB| movw.b $62, YA |1A81CB:0DFD| set5.b $48 … TrackCommand_F7_EchoFilter: |1A81CE:0E00| call ConfigureEcho |1A81D1:0E03| call GetTrackByte |1A81D4:0E06| mov.b $4E, A |1A81D6:0E08| call GetTrackByte |1A81D9:0E0B| mov.b Y, #$08 |1A81DB:0E0D| mul YA |1A81DC:0E0E| mov X, A |1A81DD:0E0F| mov.b Y, #FIR0 .set_next_filter |1A81DF:0E11| mov.w A, EchoFilterParameters+X |1A81E2:0E14| call WriteToDSP |1A81E5:0E17| inc X |1A81E6:0E18| mov A, Y |1A81E7:0E19| clrc |1A81E8:0E1A| adc.b A, #$10 |1A81EA:0E1C| mov Y, A |1A81EB:0E1D| bpl .set_next_filterusdasm: A Link to the Past US Disassembly · spc.asm#L2171-L2190 — which of the program's bytes feed which sound chip register; so $61 and $63 are the echo volumes, $4E the feedback, $4A the echo voices, and $48 the flagsRegisterList: |1A857B:11AD| db EVOLL |1A857C:11AE| db EVOLR |1A857D:11AF| db EFB |1A857E:11B0| db EON |1A857F:11B1| db FLG … LoadValueFrom: |1A8585:11B7| db $61 ; EVOLL |1A8586:11B8| db $63 ; EVOLR |1A8587:11B9| db $4E ; EFB |1A8588:11BA| db $4A ; EON |1A8589:11BB| db $48 ; FLGusdasm: A Link to the Past US Disassembly · spc.asm#L1379-L1427ConfigureEcho: |1A81F0:0E22| mov.b $4D, A |1A81F2:0E24| mov.b Y, #EDL |1A81F4:0E26| mov.w DSPADDR, Y |1A81F7:0E29| mov.w A, DSPDATA |1A81FA:0E2C| cmp.b A, $4D |1A81FC:0E2E| beq .edl_same … |1A820A:0E3C| mov.b Y, #$04 .write_register |1A820C:0E3E| mov.w A, RegisterList-1+Y |1A820F:0E41| mov.w DSPADDR, A |1A8212:0E44| mov.b A, #$00 |1A8214:0E46| mov.w DSPDATA, A |1A8217:0E49| dbnz Y, .write_register |1A8219:0E4B| mov.b A, $48 |1A821B:0E4D| or.b A, #$20 |1A821D:0E4F| mov.b Y, #FLG |1A821F:0E51| call WriteToDSP |1A8222:0E54| mov.b A, $4D |1A8224:0E56| mov.b Y, #EDL |1A8226:0E58| call WriteToDSP .edl_same |1A8229:0E5B| asl A |1A822A:0E5C| asl A |1A822B:0E5D| asl A |1A822C:0E5E| eor.b A, #$FF |1A822E:0E60| setc |1A822F:0E61| adc.b A, #>SONG_POINTERS |1A8231:0E63| mov.b Y, #ESA |1A8233:0E65| jmp WriteToDSPusdasm: A Link to the Past US Disassembly · sound.asm#L11SONG_POINTERS = $D000usdasm: A Link to the Past US Disassembly · spc.asm#L36-L47; A = 1 |19FBE8:081A| inc A |19FBE9:081B| call ConfigureEcho |19FBEC:081E| set5.b $48 |19FBEE:0820| mov.b A, #$60 |19FBF0:0822| mov.b Y, #MVOLL |19FBF2:0824| call WriteToDSP |19FBF5:0827| mov.b Y, #MVOLR |19FBF7:0829| call WriteToDSPusdasm: A Link to the Past US Disassembly · spc.asm#L2163-L2167EchoFilterParameters: |1A855B:118D| db $7F, $00, $00, $00, $00, $00, $00, $00 |1A8563:1195| db $58, $BF, $DB, $F0, $FE, $07, $0C, $0C |1A856B:119D| db $0C, $21, $2B, $2B, $13, $FE, $F3, $F9 |1A8573:11A5| db $34, $33, $00, $D9, $E5, $01, $FC, $EBS-DSP registers · EON - Echo enable ($4D)|||| |||+- Enable echo on voice 0S-DSP registers · EVOLL, EVOLR - Echo volume ($2C, $3C)++++-++++- left/right channel volume (signed)S-DSP registers · EFB - Echo feedback ($0D)++++-++++- echo feedback (signed)S-DSP registers · FLG - Flags register ($6C)||+------- Disable echo writeS-DSP registers · EDL - Echo delay ($7D)++++- Echo delay time (delay = DDDD * 512 samples)S-DSP registers · EDL - Echo delay ($7D)EDL controls the size (DDDD * 2048 bytes) and length (DDDD * 512 samples or 16ms @ 32000Hz) of the echo buffer.S-DSP registers · ESA - Echo start address ($6D)++++-++++- Page pointer to start of the echo bufferS-DSP registers · MVOLL, MVOLR - Main volume ($0C, $1C)++++-++++- left/right channel volume (signed)S-DSP registers · FIRx - Echo FIR filter coefficients ($xF)FIRx are the signed coefficients of an 8-tap Finite impulse response (FIR) filter.S-DSP registers · FIRx - Echo FIR filter coefficients ($xF)The FIR identity filter ($7f $00 $00 $00 $00 $00 $00 $00)Checked by claude-agent, 2026-11-08 — read the four echo track commands, the register list and its source bytes, ConfigureEcho, the program's start, and the filter table, against the S-DSP register page for each register's meaning; the buffer's end is computed from ESA's page and EDL's 2,048 bytes a stepChecked by a second AI model, 2026-11-08.

The start address is computed from ConfigureEcho: the delay times 8, inverted and added to $D0 with the carry set, is $D0 − 8 × delay as a page number, and 8 pages are $800 bytes, the size the delay register gives each step. The filter writes start at FIR0 and add $10 to the register number each time, through FIR7. A delay step of 512 samples is 16 ms at the chip's 32,000 Hz. Which songs use echo, and with what values, is in the song data, which the disassembly includes as binary files, so no song's settings are read here.