Nintendo NES Tetris bugs and mechanics explained
- High levels & crashing the game
- The game over condition
- Unfinished 2-player mode
- Random events
- PAL cheats
- Game reset bugs
- Counters outside expected ranges
- Long levels
- Offscreen tiles & the top row bug
- Glitched palette
- Fastest sideways piece movement
- Soft drop bugs
High levels & crashing the game
Every time a piece locks onto the playfield (including when no line gets cleared as a result), the player is awarded with a score bonus for line clears. The formula for the score increase is (level + 1) * base_score
, where the base score is 0 for clearing no lines, 40 for 1 line, 100 for 2 lines, 300 for 3 lines, and 1200 for 4 lines. Unlike more modern systems, the NES’ CPU has no built-in support for multiplication, and as such it must be manually implemented using other native operations. The suboptimal but straightforward approach used in Tetris is to add one of the operands a number of times equal to the other operand. In this case, it is the base score for the appropriate line clear count that is added level + 1
times. Performing this operation becomes quite time-consuming as the current level number goes up, especially given that each addition accounts for the packed binary-coded decimal format of the score and that the score cap is conditionally applied after each iteration. By 702 lines, it is guaranteed for the score to be 999999, causing many arithmetic carries each time a non-0 value is added and for the score to be reset to 999999 in each iteration, furthering the computation time problem.
At sufficiently late levels, the line clear score addition code is slow enough that computations for one tick of gameplay last longer than the time it takes for one video frame to be drawn to the screen. In short, it means that the CPU is not keeping up with the demand of updating the game state quickly enough. This is a common occurrance in many NES games and is usually resolved with lag - compromising the game speed and skipping video updates to ensure all necessary computations are run correctly. Nintendo Tetris is quite unique, however, in that it is computationally a simple enough game that no player was skilled enough to reach such a situation under normal conditions until the 2020s. This means that the game’s handling of long CPU times was of essentially no importance to its developers around the time of the game’s release, and only became relevant in hindsight. As it turns out, in case of computations that take too long, Tetris does indeed delay the start of its computations for the next tick in order to finish the previous’, but it does not prevent some video updates and other minor updates from being applied in the meantime, temporarily interrupting the flow of execution. Rather fortunately, the game tick following the one that includes the lengthy score addition is always quick to compute, and so the CPU gets to catch up immediately. Additionally, such a one-frame delay in core game logic computations doesn’t end up having a perceivable effect, as the bottleneck ends up being a visual update whose timing does not get affected by this occurrance. However, this still does pave the way for some bugs that do very significantly affect the game, as described below.
To help with synchronizing the game logic with the video framerate, Tetris enlists the help of a feature of the NES’ hardware that initiates a CPU interrupt soon after each frame finishes being drawn onto the screen. This interrupt quite literally interrupts whatever the processor was doing previously and causes it to instead run a routine called the interrupt handler. At this point, it is the programmers’ responsibility to perform any necessary computations and then return to executing the previously interrupted code while at the same time ensuring that the interrupt handler does not unintentionally interfere with the program flow. While this is done perfectly fine for quickly-computed game ticks, including at all levels that were assumed to be humanly playable, it is possible to eventually run into a situation where the interrupt handler corrupts data that was being used by the interrupted code. The earliest such possibility is at the transition to level 155 of the NTSC version of the game, or to level 180 of the PAL version of the game, where it is possible to interrupt code responsible for handling so-called jump tables (analogous to switch statements/expressions in modern programming). Both this code and the interrupt handler make use of the first two bytes of RAM for completely different purposes, and so an untimely interrupt can throw off the jump table logic, leading to an unexpected place in memory being interpreted and executed as code and almost always resulting in a game crash unless carefully planned for. With proper preparation, this bug can be exploited for arbitrary code execution, i.e. the ability to run custom code within the game through mere player inputs.
The game over condition
A game of NES Tetris ends in a loss if and only if a piece locks while overlapping a solid tile on the playfield. Because valid piece movements always move a piece into empty playfield tiles, this can only happen if a piece spawns overlapping a solid tile on the playfield, is never moved, and locks onto the playfield in its starting position due to not being able to move down. Notably, this means that, perhaps counter-intuitively, a piece that overlaps tiles in its initial position does not always indicate a topout, as it can proceed to move out from the overlapped tiles in some cases.
Unfinished 2-player mode
NES Tetris contains significant amounts of code for a 2-player mode embedded in its program ROM. The mode is normally completely inaccessible, but the game genie code ZALAPP
can be used to turn it on. Although the 2-player mode is theoretically playable, it is very visibly unfinished and suffers from numerous bugs.
Random events
A single 16-bit PRNG state dictates pseudorandom events in the game. This value is initialized with $8988, and on every update bit 9 XOR bit 1 is inserted as the highest bit, shifting all other bits to the right. This forms a 15-bit Fibonacci LFSR, with bit 0 also being included as part of the bit shifting process but not playing a part in the LFSR. It has a period of 32767.
The state of the LFSR is updated once at the start of each frame, excluding some inter-menu frames. Due to residual code, it is also updated 2 or more times on the level select screen, in a manner that reduces the period of the PRNG to 7598. Notably, this means that the pool of possible initial PRNG states for a game is much smaller than 32767 unless a level is selected on the first possible frame of the level select screen appearing. The PRNG state may also go through additional updates as part of generating multiple random numbers within a frame, which is described in more detail below.
Random numbers selected by the PRNG are used in two cases: for selecting pieces and for generating the initial board of B-type games.
Piece selection
Twice at the start of the game and once every time a piece is placed, a random piece must be chosen. This process first involves selecting a random number between 0 and 7, utilizing the lowest 3 bits of the PRNG and the number of pieces that have been randomly chosen since the game was reset (not including resetting with the A + B + start + select button combo). If this number is not 7 and it does not correspond with the piece that was previously chosen, then the corresponding piece is chosen. Otherwise, the PRNG state is advanced and a piece is chosen, once again based on the lowest 3 bits of the PRNG and also the piece that was chosen the last time the algorithm was run. This mechanism has the likely intended effect of making consecutive duplicate pieces less common. Note that the use of consecutive LFSR states for random number generation causes biases in piece selection, including preventing some pairs of pieces from being chosen at the start of the game.
B-type board generation
The second use of the PRNG is for generating B-type boards. Generating a B-type board starts with filling the bottom 12 visible rows of the playfield with random tiles. For each of these 12 rows, the PRNG is used to pick 10 filled or empty tiles for that row, with the PRNG state being advanced once each time, and then it is advanced 1 or more times until the last 4 bits correspond to a value less than 10, which is then used to select the index of a tile that is emptied to guarantee that the entire row will not be filled. Then, a predetermined (per “height” setting) number of rows at the top of the 12x10 area of garbage, as well as (likely erroenously) the leftmost tile of the row below those, get set back to being fully empty. See the animation above for a visualisation of this process. The use of consecutive PRNG values means that there are also biases found in B-type board generation, such as the rightmost column having many more filled tiles than empty tiles on average.
PAL cheats
The officially released PAL version of the game includes two easily accessible cheats. Holding the Select button when a piece locks awards 10000 points in A-type games, or an immediate win in B-type games.
Game reset bugs
Holding A, B, start and select exclusively while in-game and not paused resets the game. This is not the same kind of reset as pressing the reset button on the console, as it does not fully reinitialize the game state. This can lead to a number of bugs:
- If performed at the same time as the music or a sound effect starts playing, that audio will play on the title screen. This includes:
- The first frame of gameplay, when the music first starts playing.
- Changes in the music speed, which cause the music to restart.
- The piece rotate sound effect.
- The piece lock sound effect.
- The game over sound effect.
- The level up sound effect.
- Line clear-related sound effects (some of which consist of multiple de facto sound effects that normally interrupt each other, but can be isolated with this bug).
- If performed during a line clear animation, the effects of the line clear can be carried over to a new game. This can also happen if the line clear animation is interrupted by pressing the start button during the demo.
- If performed in the middle of a soft drop, the duration of the soft drop can be carried over to a new game (see Soft drop bugs).
Counters outside expected ranges
The level counter is stored in memory as a standard 8-bit binary number. It is never checked for overflows, and as such level 255 is immediately followed by level 0. During gameplay, the current level indicator is displayed as two tiles - under normal circumstances, they are two decimal digits. As part of displaying this number, it gets converted to packed binary-coded decimal by means of a lookup table. However, this table only has entries for numbers from 0 to 29 inclusive, meaning that levels 30 and above use other unrelated data for this conversion, sometimes even including values that are not valid in binary-coded decimal, whose out-of-range digits are displayed as the letters A to F. For the curious, this unrelated data happens to be, in order:
- an array of 20 consecutive multiples of 10 starting at 0, which is used to help index playfield tiles,
- an array of 20 PPU addresses corresponding to the leftmost tiles of playfield rows,
- a piece of code which draws digits onto the screen, including the digits of the current level (which is quite ironic),
- a routine that draws one row of the playfield onto the screen,
- part of a routine which handles line clear animations.
Separately, the line counter and all 7 piece spawn counters function in the same way: they are stored as 2 bytes, with only the low byte in packed binary-coded decimal form, essentially forming a mixed radix number with radices 256, 10, 10. The largest number that can be stored in this fashion is 25599, after which the counters wrap around to 0. On-screen, the counters work as expected up to 999, but from 1000 onwards, the “hundreds” digits exceed 9 and are displayed as non-digit tiles from the pattern table, as indexed by floor(n / 100)
.
Long levels
The presumably intended condition for levelling up is for the line counter to be a multiple of 10 and for the line counter divided by 10 to be greater than the level number. In reality, the condition is a check for whether the level number minus the middle 8 bits of the line counter is negative when treated as a signed 8-bit integer (specifically, it is a cmp
& bpl
instruction pair in assembly). This is an erroenous approach, as the level number is stored in standard binary encoding, while the middle 8 bits of the line counter are the full line counter modulo 1600 and divided by 10, but with the lower nybble being stored in packed binary-coded decimal and the upper in binary. This mixing of numeric encodings without a prior conversion means that the line count is essentially inflated by 60 * the hundreds digit
for the purposes of the level up check.
A consequence of this can be observed at the start of games begun from levels 10-15, where the first level up occurs at 100 lines, and games begun from levels 16-19, where the first level up occurs 60 lines before the expected 10 * (starting level number + 1)
lines.
Additionally, sufficiently long games eventually reach levels which take 810 lines to pass. This happens when the aforementioned subtraction results in a value of -123 or less and the cleared line count is 90 modulo 100. After 10 more lines, this result overflows to a positive value, and no level up occurs. It takes a further 800 lines for this difference to be negative again and thus for the level number to increment, totalling 810 lines required to clear the level. The earliest this can happen is level 219, requiring starting from a level in the range 0-9.
Offscreen tiles & the top row bug
Although only 200 of them are visible, the game’s playfield consists of 256 tiles. The invisible tiles can be modelled as simultaneously existing above the visible grid of tiles and below them. See the video above for a visual representation of this. Note that because the 56 invisible tiles cannot form an integer number of rows, there is a discrepancy between how these tiles “connect” to the top and the bottom of the board.
The bottommost 20 invisible tiles are accessible in typical gameplay. If a piece is placed such that at least one of its tiles ends up in one of these two invisible rows, all such tiles stay there for as long as the top row bug, described below, is not triggered. These tiles can then be bumped into by a piece that is near the top of the visible playfield.
Usually, when a row is filled in NES Tetris, all the tiles above that row up to the tiles in the topmost visible row are moved downwards, overwriting the filled row. When the first visible row in particular is filled, a bug in the code causes all tiles to be shifted downwards, with the lowest invisible row disappearing into memory that is never read. This includes the filled 1st visible row, which ends up being detected as filled again in the 2nd visible row. Notably, this bug also moves the bottom visible row into the top invisible row, meaning that with enough clears of the top row, tiles were once at the bottom of the visible playfield will end up at the bottom of the invisible playfield, able to be bumped into from the top. The animation above shows the effects of filling two rows at the top of the visible playfield, resulting in 3 line clears.
A special case of the top row bug is the situation where 4 filled rows are simultaneously formed at the top of the visible playfield. This results in the first 3 filled rows counting as 4 cleared rows, while the final filled row remains undetected and uncleared. This bug can be seen in this video. Note that placing a piece with its origin tile (for a piece that has not yet moved, this is the tile located in the 6th 1-indexed column of the top visible row) on the row above the filled row will subsequently clear it - this is possible to do with a T, J, L, or I piece.
A unique bug can be triggered via repeated applications of the top row bug, as shown in the video above: forming an uncleared filled row, moving it downwards until it is no longer visible, then clearing it from above with a T, J, L, or I piece (the only pieces which can be placed with their central square directly above the floor) causes a buggy line clear animation which erases part of the normally static UI. This is due to line clear animations utilizing a lookup table of PPU addresses to position the animation - a table which, in this case, gets indexed out of bounds, resulting in unrelated program ROM being read as a row’s PPU address.
Glitched palette
The colors of the tiles on the playfield are entirely dependent on the current level number. Initially, one of ten palette is chosen based on the level number of modulo 10. More concretely, 10 is subtracted from the level number 0 or more times, until the next such subtraction would render the result negative. The outcome of this looped subtraction is then multiplied by 4 and used to index into a table in ROM of 10 entries of 4 bytes of color data each. This approach is fine for calculating the level number modulo 10 during the initial levels of the game, but breaks from level 138 onwards. In these levels, subtracting 10 would yield a result between 128 and 245, which are negative numbers when interpreted as signed 8-bit values, and so no subtraction is performed. Instead, the level number gets directly multiplied by 4 with 8-bit overflow and the result is used to index into the aforementioned table, usually leading to an out-of-bounds access that sets up the piece palette with unrelated data. The only exceptions to this are levels 192-201, which map to the first 10 entries of the table, though not in accordance with the level number modulo 10. Interestingly, since multiplying a number by 4 and truncating it to its lower 8 bits ends up completely discarding the original number’s upper 2 bits, glitched color palettes repeat after 64 levels.
Fastest sideways piece movement
If using only either the left or the right button for movement, the fastest a piece can be moved in one direction is one tile every 2 frames. This is because an unobstructed piece will only move if left or right is newly pressed, necessitating that the button was released on the previous frame, or once one of these buttons has been held for a certain amount of time, which is much slower than manual represses.
One way to work around this limit is by pausing the game to allow the button to be repressed while the game is paused. This allows for two piece movements in the same direction on two consecutive gameplay frames, i.e. frames on which the game is not paused. However, this cannot be chained indefinitely, as pausing itself is bound by the same “every other frame” limitation as left/right movement. The fastest possible piece movement with this technique, counting only gameplay frames, is therefore 2 frames of movement, 1 frame of no movement, 2 frames of movement, 1 frame of no movement, etc.
Another workaround is the use of both the left and the right buttons at the same time. If either left or right is held, pressing the opposite directional button (which cannot normally be done with a standard controller) will cause the piece to move, but with both left and right now being held, right always outprioritises left as the movement direction. Therefore, it is possible to press right on one frame, then press left while continuing holding right on the next frame in order to move to the right twice on two consecutive frames. This only works to the right - the symmetrical version of these inputs results in a movement to the left followed by a movement to the right, due to the inherent priority of the right button over the left button. Like pausing, this trick cannot be performed consecutively, and so at most this technique allows for 2 frames of movement followed by 1 frame of no movement, in a loop. Nevertheless, by alternating between the pause technique and the left+right technique, it is possible to move a piece to the right on arbitrarily many consecutive gameplay frames.
Soft drop bugs
The down button can be used to force pieces to fall faster. More specifically, after the first 3 frames of holding down without holding left or right or pressing up, and every 2 frames thereafter, the active piece will move downwards, or lock in place if it cannot move downwards. This mechanic is often referred to as “soft dropping” or “pushdown”. To incentivise soft dropping, the game also awards points for sufficiently long soft drops ending in the piece locking.
The score bonus was almost certainly intended to be one point for every row a piece moves down in one continuous soft drop. In reality, while the score bonus gets calculated in this way (by means of a counter which tracks how many times the piece has moved down or locked as a result of soft dropping, which is normally 1 more than the row count), it is presumably erroenously added onto the current score. The number of points to add (stored in binary) is directly added to the score (stored in packed binary-coded decimal) as if it was also stored in binary. Following this, a value of 6 is added to the score at all positions where a score digit exceeds the decimal range, going from the least significant digit to the most significant, which accounts for arithmetic carries and preserves the binary-coded decimal format of the score. This last step also features flawed logic for carrying from the 10s digit to the 100s digit which zeroes out the ones digit in case of a carry. All of the above means that while the soft drop bonus is sometimes awarded correctly, in other cases the player will receive fewer points than expected, and the resulting increase in score can be quite unintuitive.
Because the visible playfield is 20 rows high and all orientations of all pieces in the spawn position have a tile in the topmost visible row, the maximum duration of a soft drop is 19 rows under normal conditions. There is a way to bypass this limit once in a game: if the game is reset with the in-game reset button combo (see Game reset bugs) right after a piece locks during a soft drop, the value of the soft drop counter can be preserved up until the start of a new game. At this point, soft dropping the first piece will “continue” the previously interrupted soft drop, further incrementing the counter from where it was left off. This trick can be repeated arbitrarily many times in a row to achieve a soft drop with any counter value in the 8-bit range. However, because the game checks whether the counter value minus 2 is negative to determine whether to add any soft drop points to the score at all, all counter values from 130 to 255 result in no score change, and as such the maximum number of points that can be scored in this way is 85, achievable by placing a piece with a counter value of 128 so that 127 = 0x7f points, essentially misinterpreted as 7 tens and 15 ones, are awarded.
It is worth noting that on levels 29 and above on the NTSC version of the game and levels 19 and above on the PAL version, it is only possible to receive credit for every other row during a soft drop, as in between frames where soft dropping moves a piece down, the piece also moves down due to gravity. Given this and the 3 frames it takes for a soft drop to begin, the maximum score bonus that can be obtained on these levels for a single piece is 8.