Disassembly Notes (NES)
Demo
If you sit idle on the title screen for a while, a demo plays. It's triggered by the frame counter rolling over to 00
8 times in a row. The frame counter cycles through from 00
-FF
. So this means it takes anywhere from ~29-34 seconds to start ((256 frames * 7 + [1,256] frames) / 60 frames per second). It can be disabled a number of ways, but the simplest way is editing the jump offset (to 0
) when it decides to load the demo code (at $9905
), accomplishable through the Game Genie code AAAPSP
. Next, while the demo board is a generatable one (seed 03BE
), it's stored directly in the ROM ($CF00
-$CF7F
) and loaded into memory. The capsules generated do not match this seed, but they're stored right after the board ($CF80
-$CFFF
). The controls are stored in pairs of bytes, the first byte being the buttons pressed (bitmask - A, B, Select, Start, Up, Down, Left, Right) and the second being the number of frames to hold down those buttons. These are stored right after the board/capsules at $D000
-$D1FF
.
Pill movement
This is all the code responsible for moving the pill each frame. The disassembly and function descriptions are from Brian Huffman's disassembly; this version has added detailed English descriptions of the code. I also did a cursory look through the code for what $06f1 does, but I couldn't find anything obvious that read from that address. So either it's dead code, or it's read via indirect addressing.
The input handling deserves a brief comment as well. L5C_INPUT_OLD stores which buttons the user is currently pressing (despite its name suggesting otherwise), one button per bit, in standard controller order, with the bottom bit being the right arrow. L5B_INPUT_NEW has the same shape as L5C_INPUT_OLD, but each bit is set if the corresponding button started being pressed this frame; that is, if it is pressed this frame but was not pressed last frame.
jsr R8D70_DROP_PILL ; may go to state 1 ; 8D66 20 70 8D jsr R8DBF_PILL_MOVE ; 8D69 20 BF 8D jsr R8E2B_PILL_ROTATE ; 8D6C 20 2B 8E ; -------------------------------------- ; Called once, in L8D56. R8D70_DROP_PILL: begin lda L43_CLOCK ; 8D70 A5 43 and #$01 ; 8D72 29 01 beq L8D7E ; 8D74 F0 08 Every other frame, lda L5C_INPUT_OLD ; 8D76 A5 5C and #$0F ; 8D78 29 0F cmp #$04 ; "down" ; 8D7A C9 04 if the user is pressing down but not left, right, or up, beq L8D94 ; 8D7C F0 16 skip ahead to L8D94. L8D7E: inc L92_DROP_TIMER ; 8D7E E6 92 Increase the drop counter. lda L8A_EXTRA_SPEED ; 8D80 A5 8A sta LA0_TEMP ; redundant copy ; 8D82 85 A0 ldx L8B_SPEED ; 8D84 A6 8B lda LA38D,x ; $0F,$19,$1F ; 8D86 BD 8D A3 Load the base speed offset for the player-selected speed (low, med, hi), add LA0_TEMP ; 8D89 18 65 A0 then add in the offset for having played a long time. tax ; 8D8C AA lda LA795,x ; drop timer threshold ; 8D8D BD 95 A7 Load the gravity value. cmp L92_DROP_TIMER ; 8D90 C5 92 If the drop counter is <= the gravity value, bcs break ; 8D92 B0 2A return from this function. L8D94: dec L86_PILL_ROW ; 8D94 C6 86 Move down. lda #$00 ; 8D96 A9 00 sta L92_DROP_TIMER ; 8D98 85 92 Reset the drop counter to 0. jsr R90D3_COLLISION ; 8D9A 20 D3 90 if_eq ; 8D9D D0 06 If we are over in-bounds empty space, lda L86_PILL_ROW ; 8D9F A5 86 cmp #$FF ; 8DA1 C9 FF and not below the board, bne break ; 8DA3 D0 19 return from this function. end_if inc L86_PILL_ROW ; 8DA5 E6 86 Move up. lda #$07 ; 8DA7 A9 07 sta L06F1_ ; 8DA9 8D F1 06 Set $06F1 to 7. jsr R8F52_PLACE_PILL ; 8DAC 20 52 8F Lock the pill in place. ; make the program crash if L0740_CHECKSUM_FAIL is nonzero lda L0740_CHECKSUM_FAIL ; 8DAF AD 40 07 if_ne ; 8DB2 F0 05 lda L53_SPRITE_NUM ; 8DB4 A5 53 pha ; 8DB6 48 pha ; 8DB7 48 pha ; 8DB8 48 end_if inc L97_GAME_STATE ; 8DB9 E6 97 jmp break ; 8DBB 4C BE 8D end rts ; 8DBE 60 ; -------------------------------------- ; Increment or decrement pill column based on controller input. ; Repeat every 6 frames after holding for 16. R8DBF_PILL_MOVE: begin lda L5B_INPUT_NEW ; 8DBF A5 5B and #$03 ; left or right ; 8DC1 29 03 if_eq ; 8DC3 D0 15 If the user did not start pressing left or right, lda L5C_INPUT_OLD ; 8DC5 A5 5C and #$03 ; left or right ; 8DC7 29 03 beq break ; 8DC9 F0 5F and neither left nor right is currently pressed, return from this function. Otherwise, inc L93_REPEAT_TIMER ; 8DCB E6 93 increase the DAS counter. lda L93_REPEAT_TIMER ; 8DCD A5 93 cmp #$10 ; 8DCF C9 10 bmi break ; 8DD1 30 57 If the DAS counter is under 16, return from this function. Otherwise, lda #$0A ; 8DD3 A9 0A sta L93_REPEAT_TIMER ; 8DD5 85 93 set the DAS counter to 10. else ; 8DD7 4C E3 8D If the user started pressing left or right, lda #$00 ; 8DDA A9 00 sta L93_REPEAT_TIMER ; 8DDC 85 93 set the DAS counter to 0, lda #$03 ; 8DDE A9 03 sta L06F1_ ; 8DE0 8D F1 06 and set $06F1 to 3. end_if lda L5C_INPUT_OLD ; 8DE3 A5 5C and #$01 ; "right" ; 8DE5 29 01 if_ne ; 8DE7 F0 20 If the user is pressing right, lda LA5_PILL_DIR ; 8DE9 A5 A5 and #$01 ; 1 = U/D, 0 = L/R ; 8DEB 29 01 add #$06 ; 8DED 18 69 06 cmp L85_PILL_COLUMN ; 8DF0 C5 85 if_ne ; 8DF2 F0 15 and the pill is not in the rightmost column for its orientation, inc L85_PILL_COLUMN ; 8DF4 E6 85 move right. jsr R90D3_COLLISION ; 8DF6 20 D3 90 if_eq ; 8DF9 D0 08 If that would put us over in-bounds empty space, lda #$03 ; 8DFB A9 03 sta L06F1_ ; 8DFD 8D F1 06 set $06F1 to 3. else ; 8E00 4C 09 8E If that would put us out of bounds or over a non-empty space, dec L85_PILL_COLUMN ; 8E03 C6 85 move left lda #$0F ; 8E05 A9 0F sta L93_REPEAT_TIMER ; 8E07 85 93 and set the DAS counter to 15. end_if end_if end_if lda L5C_INPUT_OLD ; 8E09 A5 5C and #$02 ; "left" ; 8E0B 29 02 if_ne ; 8E0D F0 1B If the user is pressing left, lda L85_PILL_COLUMN ; 8E0F A5 85 cmp #$00 ; 8E11 C9 00 if_ne ; 8E13 F0 15 and we're not in the left-most column, dec L85_PILL_COLUMN ; 8E15 C6 85 move left. jsr R90D3_COLLISION ; 8E17 20 D3 90 if_eq ; 8E1A D0 08 If that would put us over in-bounds empty space, lda #$03 ; 8E1C A9 03 sta L06F1_ ; 8E1E 8D F1 06 set $06F1 to 3. else ; 8E21 4C 2A 8E If that would put us out of bounds or over a non-empty space, inc L85_PILL_COLUMN ; 8E24 E6 85 move right, lda #$0F ; 8E26 A9 0F sta L93_REPEAT_TIMER ; 8E28 85 93 and set the DAS counter to 16. end_if end_if end_if end rts ; 8E2A 60 ; ---------------------------------------------------------------------------- ; Pill collision detection. ; Returns A = 0, Z set for no collision. ; Returns A = FF, Z clear for collision. R90D3_COLLISION: .scope ldx LA6_PILL_SIZE ; 90D3 A6 A6 dex ; 90D5 CA dex ; 90D6 CA lda LA5_PILL_DIR ; 90D7 A5 A5 and #$01 ; 1 = U/D, 0 = L/R ; 90D9 29 01 if_eq ; 90DB D0 13 If the current pill is horizontal, lda LA455,x ; $00,$FF,$FF ; 90DD BD 55 A4 add L85_PILL_COLUMN ; 90E0 18 65 85 and the pill's column is negative, bmi collision ; 90E3 30 4C it's a collision. lda LA458,x ; $F9,$F9,$FA ; 90E5 BD 58 A4 add L85_PILL_COLUMN ; 90E8 18 65 85 and the pill's column - 7 is nonnegative, bpl collision ; 90EB 10 44 it's a collision. else ; 90ED 4C F8 90 If the current pill is vertical, lda LA45B,x ; $00,$FF,$FF,... ; 90F0 BD 5B A4 add L86_PILL_ROW ; 90F3 18 65 86 and the pill's row is negative, bmi collision ; 90F6 30 39 it's a collision. end_if lda #$00 ; 90F8 A9 00 sta L57_ ; 90FA 85 57 $57 = 0 ldx L86_PILL_ROW ; 90FC A6 86 lda LA474,x ; 120 - x*8 ; 90FE BD 74 A4 add L85_PILL_COLUMN ; 9101 18 65 85 sta L47_TEMP ; 9104 85 47 $47 = pill's current position lda LA6_PILL_SIZE ; 9106 A5 A6 sub #$02 ; 9108 38 E9 02 asl a ; 910B 0A sta L49_TEMP ; 910C 85 49 $49 = 0 ; L49_TEMP = LA6_PILL_SIZE - 2 lda LA5_PILL_DIR ; 910E A5 A5 and #$01 ; 1 = U/D, 0 = L/R ; 9110 29 01 add L49_TEMP ; 9112 18 65 49 asl a ; 9115 0A asl a ; 9116 0A tax ; 9117 AA x = 0 for horizontal pill, 4 for vertical pill ; x = ((LA5_PILL_DIR & #1) + L49_TEMP) << 2 lda #$04 ; 9118 A9 04 sta L48_TEMP ; 911A 85 48 for(i=0; i<4; i++) loop lda LA4C4,x ; 0,1,0,0, f8,0,0,0 ; 911C BD C4 A4 add L47_TEMP ; 911F 18 65 47 tay ; 9122 A8 y = pill's position + { horizontal pill -> 0 or 1; vertical pill -> 0 or -8 } lda ($57),y ; 9123 B1 57 cmp #$FF ; 9125 C9 FF If the board at index y is not empty, bne collision ; 9127 D0 08 it's a collision. inx ; 9129 E8 dec L48_TEMP ; 912A C6 48 while_ne ; 912C D0 EE lda #$00 ; 912E A9 00 It's not a collision. rts ; 9130 60 collision: lda #$FF ; 9131 A9 FF rts ; 9133 60 .endscope ; -------------------------------------- ; Increment or decrement LA5_PILL_DIR (mod 4) depending on controller input. R8E2B_PILL_ROTATE: lda LA5_PILL_DIR ; 8E2B A5 A5 sta L4A_TEMP ; 8E2D 85 4A lda L85_PILL_COLUMN ; 8E2F A5 85 sta L4B_TEMP ; 8E31 85 4B lda L5B_INPUT_NEW ; 8E33 A5 5B and #$80 ; "A" ; 8E35 29 80 if_ne ; 8E37 F0 10 If the user started pressing A, lda #$05 ; 8E39 A9 05 sta L06F1_ ; 8E3B 8D F1 06 set $06f1 to 5, dec LA5_PILL_DIR ; 8E3E C6 A5 lda LA5_PILL_DIR ; 8E40 A5 A5 and #$03 ; 8E42 29 03 sta LA5_PILL_DIR ; 8E44 85 A5 subtract one (mod 4) from the current rotation count, jsr R8E60_CHECK_ROTATE ; 8E46 20 60 8E and then roll back the change if there's a problem. end_if lda L5B_INPUT_NEW ; 8E49 A5 5B and #$40 ; "B" ; 8E4B 29 40 If the user started pressing B, if_ne ; 8E4D F0 10 lda #$05 ; 8E4F A9 05 sta L06F1_ ; 8E51 8D F1 06 set $06f1 to 5, inc LA5_PILL_DIR ; 8E54 E6 A5 lda LA5_PILL_DIR ; 8E56 A5 A5 and #$03 ; 8E58 29 03 sta LA5_PILL_DIR ; 8E5A 85 A5 add one (mod 4) to the current rotation count, jsr R8E60_CHECK_ROTATE ; 8E5C 20 60 8E and then roll back the change if there's a problem. end_if rts ; 8E5F 60 ; -------------------------------------- ; Adjust or undo pill rotation if there are collisions. ; Previous DIR and COLUMN values are passed in L4A_TEMP/L4B_TEMP. R8E60_CHECK_ROTATE: begin lda LA5_PILL_DIR ; 8E60 A5 A5 and #$01 ; 1 = U/D, 0 = L/R ; 8E62 29 01 if_eq ; 8E64 D0 19 If the current pill is horizontal, jsr R90D3_COLLISION ; 8E66 20 D3 90 if_eq ; 8E69 D0 12 and over in-bounds empty space, lda L5C_INPUT_OLD ; 8E6B A5 5C and #$02 ; "left" ; 8E6D 29 02 beq break ; 8E6F F0 1B and the user is not pressing left, return from this function. dec L85_PILL_COLUMN ; 8E71 C6 85 Move left. jsr R90D3_COLLISION ; 8E73 20 D3 90 beq break ; 8E76 F0 14 If the pill is over in-bounds empty space, return from this function. inc L85_PILL_COLUMN ; 8E78 E6 85 Move right. jmp break ; 8E7A 4C 8C 8E Return from this function. end_if dec L85_PILL_COLUMN ; 8E7D C6 85 Move left. end_if jsr R90D3_COLLISION ; 8E7F 20 D3 90 If the current pill is out of bounds or over non-empty space, beq break ; 8E82 F0 08 lda L4A_TEMP ; 8E84 A5 4A sta LA5_PILL_DIR ; 8E86 85 A5 lda L4B_TEMP ; 8E88 A5 4B sta L85_PILL_COLUMN ; 8E8A 85 85 roll back to the position and rotation we were at before we started the rotation. end rts ; 8E8C 60
External resources
Brian Huffman's disassembly notes