From TetrisWiki
Jump to navigation Jump to search

NES Tetris

Effective rate of DAS vs. tapping


The aim of this write-up is to compare the worst/average/best cases of DAS usage compared to manual rapid tapping. Comparisons will be made using a vertical I-piece traveling to the left wall (i.e., moving the piece 5 cells).

The NTSC NES technically runs at ~60.098814hz. The calculations here assume 60hz for simplicity since this does not affect the results very much (~0.16469% difference).

In the NTSC version of the game, DAS has a 16 frame start-up and a 10hz move rate (piece moves every 6th frame). Without discussing the particulars in-depth here, consider that DAS can be preserved from piece to piece under most circumstances.

Given the above, the naive assumption is that charged DAS is equivalent to tapping 10 times per second. However, under most circumstances, the piece is likely to enter the playfield ready to move earlier than the usual 6 frames. In fact, 10hz is the worst case for skillstop timing -- releasing left/right frame perfectly, just enough to get the final shift and no longer. The average case for skillstop is likely somewhere right in the middle (i.e. +3 frames). The best case (full charge) is also reasonably attainable via wall tech.

If we're charging DAS from scratch, the best case is when the piece is actively in the playfield. If we have no charge and hold left/right _before_ the piece appears, we have to wait a full 16 frames for the first shift. If the piece is already active, the first shift happens immediately (tap movement) and we only wait 15 more frames for the second shift.

Averaged across a shorter distance, the effective rates of charging from scratch appears rather low: the worst/best case for moving just two cells is 5.45hz and 7.5hz respectively. However, the parity with tapping improves when traversing greater distances. Of course, the player must take into account if current gravity and stack conditions allow for waiting through the full charge time, but -- given enough time -- the parity with manual tapping on simple recoveries looks rather favorable.

frames per cell total avg frame/cell effective rate
from scratch (worst case) 16 6 6 6 6 40 8 7.5hz
from scratch (best case) 1 15 6 6 6 34 6.8 8.82hz
charged (worst case) 6 6 6 6 6 30 6 10hz
charged (average case) 3 6 6 6 6 27 5.4 11.1hz
charged (best case) 1 6 6 6 6 25 5 12hz

The situation becomes even more advantageous if using the "extra tap" method to slightly raise the average rate -- you can get a bit of extra range by using a timed tap to get the final shift out a bit earlier than usual. The d-pad must be released for at least one frame in order to register the tap. The worst case is performing no better than just holding the direction (input pattern: ...1 111101). The best case is frame perfectly releasing on the frame of the final shift, then re-pressing on the next frame (bit pattern: ...1 01). So, the meaningful values for the last tap are in the range of 2~5 frames.

frames per cell total avg frame/cell effective rate
worst case 1 6 6 6 6 25 5.0 12hz
1 6 6 6 5 24 4.8 12.5hz
1 6 6 6 4 23 4.6 13.04hz
1 6 6 6 3 22 4.4 13.64hz
best case 1 6 6 6 2 21 4.2 14.29hz

TODO: discuss cases where physical tapping could prove advantageous due to dangerous upstack in the center of the playfield -- i.e. slope of movement and distribution of acceleration


The actual framerate of the PAL NES is ~50.006978hz. As with the NTSC section above, these calculations use 50hz for simplicity (~0.013956% difference, even less of a margin than NTSC).

Due to the difference in framerate between NTSC and PAL, the game was rebalanced to approximate the speed of the original game. However, because this would result in fractional numbers of frames, everything was rounded down to the nearest whole number. As a result, many frame timings are considerably faster. DAS has a 12 frame start-up and 12.5hz move rate (piece moves every 4th frame).

Due to the changes in the PAL version, DAS usage is almost always the most advantageous movement strategy. Even uncharged, it has parity with NTSC NES's charged DAS rate.

frames per cell total avg frame/cell effective rate
from scratch (worst case) 12 4 4 4 4 28 5.6 8.93hz
from scratch (best case) 1 11 4 4 4 24 4.8 10.42hz
charged (worst case) 4 4 4 4 4 20 4 12.5hz
charged (average case) 2 4 4 4 4 18 3.6 13.89hz
charged (best case) 1 4 4 4 4 17 3.4 14.71hz

The "extra tap" method is still usable, but the number of possibilities is much fewer due to the faster move rate. There is only a 2 frame timing window.

frames per cell total avg frame/cell effective rate
worst case 1 4 4 4 3 16 3.2 15.625hz
best case 1 4 4 4 2 15 3.0 16.67hz

B-Type garbage generation

If B-Type is the selected mode, it seems the garbage generation function is always called. The routine always generates 12 rows, then deletes the the unneeded ones according to whatever Height setting was selected.

There appears to be an off-by-one bug in the offsets used for the Height setting -- it appears the leftmost block of the top row of garbage will always be inadvertently deleted.

Based on the output of a quick test program I wrote, it appears the maximum number of holes that can appear in a row (before random removal step) is 3. As such (and along with the top-left cell removal bug), it is unlikely that a player can clear lines with the first piece. (The only hope would be an I-spin somewhere below the top line, but further testing seems to preclude this possibility as well.)

Here are the steps of the garbage generation:

  • for each of the bottom 12 rows:
    • row fill step: for each of the 10 cells, right-to-left:
      • call the randomizer
      • take the upper byte of the randomizer, mod 8
      • use the generated random value 0-7 to look up a result to insert (50% chance to be filled, various colors)
    • random removal step: call the randomizer
    • take the upper byte of the randomizer
    • take the lower nybble of that (AND 0x0F)
    • if the value >= 10 (0x0A), then jump back to start of random removal step
    • using the generated random value 0-9, clear the content of the corresponding cell of this row
    • call start-of-frame functions (which will wait for vsync before continuing this garbage generation loop)
  • (copy the playfield to a mirror at $0500 for some reason? probably leftover 2P code. also might have a bug, it uses $C8 as an index and that appears uninitialized)
  • using the Height setting selected, lookup playfield row offset
  • clear every cell in the playfield at or above that offset
  • (using 2P's (?) Height selection, clear every cell in playfield at or above row offset for mirror at $0500)

Random block color lookup table:

0 1 2 3 4 5 6 7
_ A _ B C C _ _

Height setting offset table:

0 1 2 3 4 5
C8 AA 96 78 64 50

Puyo Puyo Tetris

Control handling

Puyo Puyo Tetris offers a variety of presets for control configuration. In most presets, some actions have some form of alternate button or input (e.g., the default preset allows pieces to be moved by either the d-pad or analog stick).

The game engine handles this many-to-one relationship between inputs and actions with what one could consider just one "virtual input" per action. If an input for an action is already held, pressing the alternate input will not trigger that action again. (e.g., simultaneously pressing two movement/rotation buttons will not trigger a double tap shift or 180 rotation). Moreover, if both inputs for an action are held, releasing one of them will not do anything special; the game considers the action to have been continuously held.

If opposite movement directions are held simultaneously, the one most recently pressed takes priority. However, rather than cancel either input, the game still considers both to be continuously pressed. The game also tracks autorepeat separately for each direction; as soon as one of the two directions is released, the piece instantly moves in the opposite direction without needing to again wait for the initial delay.