Mechanics (NES)

From Dr. Mario
Revision as of 03:52, 14 February 2023 by Dmwit (talk | contribs) (→‎Movement)
Jump to navigation Jump to search

DAS

Tapping the left or right arrow moves the pill one column; but you can also press and hold an arrow to move the pill multiple columns. The first extra column is delayed a bit, to give the user time to lift their finger off the button and get precise single-column moves easily, but remaining columns of movement happen rapidly.

In detail: in addition to the pill location, the game tracks a hidden DAS counter, and directional arrows affect this counter. (For the purposes of this paragraph, only left and right are directions; up and down do not interact in any special way with the DAS counter.) If a pill is under player control and a direction transitions from unpressed to pressed, the pill is immediately moved in the selected direction and the DAS counter is set to 0. While the button is held down, the counter increments by one per frame. Whenever it reaches 15, it resets to 10 and the pill moves in the selected direction. If the pill is obstructed by a virus or capsule when it attempts to shift sideways, the DAS counter is set back up to 15, and so attempts to move again next frame, which may work if the pill has dropped a row or rotated from horizontal to vertical. Bumping against the side of the bottle instead of a virus or capsule has no special effect on the DAS counter.

Practically speaking, this means a few things.

  1. The frame delays between shifts if you press and hold a directional arrow are 0, 16, 6, 6, 6, 6, ... under normal circumstances.
  2. You can keep a high DAS counter from one pill to the next. After you let go of the left or right button, simply don't press it again until the pill has locked and you don't have control for a little while (e.g. during clear animations, interpill gravity, or the animation as a pill is "tossed" into the bottle). Then you can press and hold either direction (doesn't have to be the same one as before) and DAS will start where it left off when the pill comes under control. (You can also hold the button through the whole non-control phase; that can be more comfortable than releasing and re-pressing in some situations.)
  3. If the DAS counter is high because you were bumping into the side of the bottle last time you had a directional arrow held down, the frame delay between the next pill coming under your control and it shifting one column could be anywhere from 0-6 frames. If it was bumping into a virus or capsule instead, it will always shift one column on the first frame of control.
  4. You can charge the DAS fully by simply tapping left or right while next to a virus or capsule, then finish the rest of the pill's fall normally, and it will remain charged (even across multiple pills) until you switch it off by starting a left or right maneuver while the pill is under your control.

Gravity

While pills are under player control, they are slowly forced down the playfield even if the player does not request downward motion. Every ten pills, this speed increases. See also #Fall time for the details about how unsupported board elements are forced to drop.

In detail: the game tracks two variables, coarse speed and fine speed. The coarse speed is user-selected from three options, called LOW, MED, and HI in the game UI. These names correspond to a number:

UI Name LOW MED HI
Coarse speed 15 25 31

The fine speed begins at 0 at level start. Between the 8th and 9th pills, it increases by one, then increases by one every 10 pills after that, until it reaches 49, where it plateaus. The speed at which the pill drops is computed (as a number of frames the game allows you to wait before forcing the pill down one row) by adding the coarse and fine speeds, then using the result to index into the following table:

Combined speed 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
NTSC frames per row 70 68 66 64 62 60 58 56 54 52 50 48 46 44 42 40 38 36 34 32 30 28 26 24 22 20 19 18 17 16 15 14 13 12 11 10 10 9 9 8 8 7 7 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 4 4 4 4 4 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 1
PAL frames per row 57 55 54 52 50 49 47 45 44 42 40 39 37 35 34 32 30 29 27 25 24 22 20 19 17 16 15 14 13 12 11 10 10 9 8 7 7 6 6 6 5 5 5 5 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1

For example, suppose you have locked 24 pills on a level, and are playing at MED speed. The coarse speed is 25 (for MED); the fine speed is 2 (because it increased after pills 8 and 18); the combined speed is 27 (=25+2); and the frames per row is 18 for NTSC or 14 for PAL.

The rows of the above table are stored in game memory (and so can be modified with judicious use of game genie codes or other memory hacks); see Memory addresses for details on where to look. For technical reasons, the frames per row table is stored with all values one smaller than those shown here. (For example, the NTSC table begins 69, 67, 65, ...; not 70, 68, 66, ....)

Why didn't they make LOW speed have index 0 and just shift the table down? It is a mystery. Probably they experimented with different values for the coarse speed lookup table for a while, and just never cleaned it up once they'd settled on a selection.

Rotation

Capsules rotate within a 2x2 box around the bottom left axis, with the exception of a wall kick. Pressing the <A> button rotates the capsule clockwise, while pressing the <B> button rotates the capsule counterclockwise.

Note: I find these animations easiest to follow when looking at one side of the capsule.

Wall Kick

If the capsule is blocked from rotating on the right side (when rotating from a vertical to horizontal orientation) and also has room to move to the left, it will be kicked one position to the left. This happens regardless of what is blocking the rotation, whether it be a virus, capsule piece, or the wall of the bottle.

Left/rotate anomaly

When pressing the <L> direction and rotating the capsule with either the <A> or <B> buttons from a vertical to horizontal orientation on the same frame, instead of moving 1 position to the left, it will move 2.

It has been theorized that this was added to the game to allow a player to place a capsule in a horizontal orientation underneath 2 other columns to the left. Because capsules rotate around the bottom left axis, this is possible with only 1 move to the right, but requires 2 to the left.

Level generation

TODO. But here are some links of interest in the meantime: Tetris Concept thread about level generation Academic paper about level generation

While on the level selection screen, the 2-byte seed is advanced each frame by an LFSR with tap bits 7 and 15. Or, to say that another way: each frame, the seed is divided by two (rounding down), and the second-to-last bits of each byte of the old seed are xor'd together and put in the first bit of the new seed. For example, if the old seed is 0x9453, you do the following:

1001 0100 0101 0011    bits of the old seed
0100 1010 0010 1001    shift right one bit
        v         v
       /_________/
      +                xor these bits
  ___/
 /
v
1100 1010 0010 1001    copy all the other bits from the previous step

So the seed on the next frame would be 0xCA29.

If you start the game on the earliest frame it is possible to do so, you will have seed 0x44c4.

Trash

In 2-player mode, making more than one clear with a single pill (a "combo") sends some trash to your opponent, delaying them from playing further and cluttering their board. Combos are also coalesced if they happen fast enough.

In detail: the game has a frame counter, and each player has a hidden counter indicating the amount of pending trash they are to be sent at the next opportunity and four variables each storing a color to make trash out of. Each player's four color variables will be called "attack registers" in what follows. The attack registers are encoded using 0 for yellow, 1 for red, and 2 for blue, but we will consistently refer to colors instead of their number encoding in the following for ease of reading.

When the console starts, or on soft resets, all attack registers are set to yellow. Additionally, at the start of every game, the pending trash counters are set to 0 and player 1's attack registers are set to equal player 2's attack registers.

Making a combo increments the opponent's pending trash counter by the number of clears made in the combo, up to a maximum of 4. Additionally, making any clear (even if not a combo) sets some of the opponent's attack registers, in order of the clears involved [clarification needed: how is the order determined between clears that happen simultaneously?]: the first clear sets the first attack register to the color of that clear; if there is a second clear, then the second clear of the combo sets the second attack register to the color of that clear; and so on if there are further clears [clarification needed: when a combo has more than four clears, what happens? are clears 5 and up discarded, or do they wrap, or do they write into "out of bounds" memory, or what?].

When the opportunity to make trash arises (see #Turn order for details on when this occurs) the current player's pending trash counter is consulted to determine how much trash to produce. The positions of the trash always occur in the same pattern:

Pending trash counter 2 3 4
Trash pattern Trash-Pattern-NES-2.png Trash-Pattern-NES-3.png Trash-Pattern-NES-4.png

There are 4 columns that the 2-trash and 3-trash patterns can start in, and 2 columns that the 4-trash pattern can start in. When dropping two pieces of trash, the column is chosen by taking the frame counter mod 4. For three or four pieces of trash, the details of which column choice corresponds to which value of the counter mod 4 are not yet available. The colors of the trash are given by the player's attack registers, left-to-right: the first attack register determines the color of the left-most trash, the second attack register determines the color of the second trash, and so on as needed. The pending trash counter is reset to 0 (but the attack registers are not modified).

Here are some practical consequences:

  • Making two 2-combos doesn't actually modify the opponent's final two attack registers, but it does read from them, so they will get trash of a consistent color in the final two columns if you do this repeatedly.
  • When receiving quad trash, you are a bit more likely to receive yellow in the final two columns, because it's so much harder to modify the 3rd and 4th attack registers away from their starting values of yellow.
  • Player 2's attack registers are preserved between matches and even games, giving some hysteresis.
  • If you get consecutive 2-trashes, they will almost always fall in the same columns, because trash falls at a rate of 4 frames per row, so the frame counter will increment by a multiple of 4 and not change what columns are chosen for the trash.

Big Combo Glitch

If you clear a lot of viruses with a single pill, the game glitches. Effects include graphical errors, locking up, removing viruses that seem unrelated to the clear, and drastic score increases. This needs some careful writeup. In the meantime, here's some information that could be useful for somebody interested in learning more or doing such a writeup, and willing to put in some effort themselves:

A writeup of some TAS-related info, some of which is related to the big combo glitch.

A TAS of Dr. Mario 0-20 in under 6 minutes that makes extensive use of the glitch.

A report of a high score based on the glitch.

Some quotes from dmhero on Discord:

After a bit of investigation, I found that it keeps a counter of the number of viruses you've cleared so far and uses this as an offset for a lookup table with values to use as multipliers. This looks like 01 02 04 08 10 20 20 20 20... Which matches what the manual describes, doubling the value of consecutive virus clears until you reach the 6th virus and everything after should be worth 32x. When I said it uses the values as multipliers, it's actually basically doing a do while loop, always adding the value for a single virus clear once and then decrementing the value in the lookup table and looping until it hits 0. The thing is, that lookup table eventually runs out and we start to see values other than 20. Most notably on the 14th virus, which happens to be 00, which when decremented rolls back to FF and the loop runs 256 times. The basic knowledge I've seen around this bug is that if you trigger it with a horizontal clear, it clears the entire board (NNE), but it will hang/freeze the game with a vertical clear.

So I'll try and elaborate on this in a later post, but after some debugging tonight I determined the causes of both horizontal and vertical "too many viruses cleared" bugs. It basically boils down to what I previously stated where it takes too long to determine the score and the next frame is computed. The virus clearing code stores the position on the board (00-7F) that it needs to get to until it is done clearing in a temporary variable. When a new frame is rendered, that temporary variable is set to the value of the 2p controller input. This value defaults to 00. With a horizontal clear, the loop increases the position by 1 with each iteration of the loop, which ends up clearing everything to the right and below of the furthest right position in the clear until the position reaches the end (7F) and rolls back to the start (00). With a vertical clear, however, the loop increases the position by 8 every iteration because it only needs to check positions in that column. If the clear does not happen in the first column, this will cause the game to hang indefinitely in this loop. There is a trick to get out of it though, if you press the correct buttons on the 2p controller, the value just has to be one of the positions in that column. The bottom 4 bits are controlled by up, down, left, right, respectively, but down, left, and right should be all that matters. Setting the correct end value with the 2p controller will cause the loop to end as it should have and continue play as normal.

Interestingly enough, it appears that you can't use the 2p controller to get out of the vertical bug when the clear occurs on the 4th/8th column, positions 3/B and 7/F. This is because you would need to hold down both the left and right inputs to get any of these values and this isn't allowed in the code. If left is already held, pressing right doesn't update the input value and vice versa. You can test this out for yourself by adding breakpoints at $9263 (the instruction before the horizontal clearing loop) and $94C9 (the instruction before the vertical clearing loop). Play the game normally and trigger a horizontal or vertical clear. It appears that if you do both, the horizontal one runs first. When the breakpoint is hit, update the memory value at $49 to 00 and continue in the debugger. The manual memory update is what will actually happen automatically in the game if it takes longer than 1 frame to process. To get out of the vertical bug (if not on the 4th/8th columns), use the 2p input: 1st column - will automatically fix itself, 2nd column - press Right, 3rd column - press Left, 5th column - press Down, 6th column - press Down and Right at the same time, 7th column - press Down and Left at the same time. If on the 4th/8th columns, you'll have to manually update the memory value at $49 to something ending in 3 or B/7 or F respectively.

Movement

Here are the precise rules governing pill movement. For the full details, check the disassembly notes. This is a nitty-gritty explanation; for the high-level consequences, see the #DAS, #Gravity, and #Rotation sections.

  1. The pill attempts to make progress down the board if the gravity counter rolls over, or if the frame counter is odd and the only directional arrow the user is pressing is down. When those conditions are met, if there's open space below the pill, and the pill is not at the bottom of the board, it moves down one row; otherwise it locks in place.
  2. The DAS counter is updated. There are three cases: if the user is not pressing left or right, nothing happens; if the user started pressing left or right this frame, the counter is set to 0; and if the user is holding left, right, or both (i.e. they are pressing it this frame and were pressing it last frame), the counter is incremented and values 16 or over are rolled back to 10.
  3. The pill attempts to make horizontal progress if the user started pressing left or right this frame, or if the DAS counter rolled over to 10. The following two steps are (both) performed in order.
    1. If the user is pressing right, there are three cases: if the pill is touching the right wall, nothing further happens; if the pill is blocked to the right (by something other than the right wall), the DAS counter is set to 15; and if the pill has space open to the right, it moves into that space.
    2. If the user is pressing left, there are three cases: if the pill is touching the left wall, nothing further happens; if the pill is blocked to the left (by something other than the left wall), the DAS counter is set to 15; and if the pill has space open to the left, it moves into that space.
  4. The pill attempts to rotate if the user started pressing A or B this frame. The following two steps are (both) performed in order.
    1. If the user started pressing A, a clockwise rotation is attempted (see below).
    2. If the user started pressing B, a counterclockwise rotation is attempted (see below).

When attempting to make a rotation, the outcome depends on what nearby spaces are open and whether the rotation would leave the pill vertical or horizontal.

  • To vertical, two cases: if the space the pill would occupy is open, the rotation happens; otherwise nothing happens.
  • To horizontal, three cases:
    • If the space the pill would occupy is open and either the user is not pressing left or the space just to the left of the pill is already occupied, the rotation happens and nothing more.
    • If the space just to the left of the pill is open, and either the user is pressing left or the pill would cover already-occupied space, the pill rotates and then moves left one column.
    • Otherwise, the space just to the left of the pill is occupied and the pill would be over already-occupied space. Nothing happens.

Turn order

One full turn consists of the following phases:

  1. Pill animation (1-player only): the current pill is animated moving from the lookahead location to the top of the bottle.
  2. Player control: pressing buttons on the controller affects the location of the pill until it locks in position by attempting to move down and being obstructed by a virus, pill half, or the bottom of the bottle.
  3. Resolution: the consequences of the pill being added to the board at the locked position are calculated. This is a loop that itself consists of three phases:
    1. Clearing: Four-in-a-rows of any color horizontally and vertically are removed from the board. If this clears half of a connected pill, the other half is disconnected.
    2. Falling: Any pills or pill halves left on the board which are no longer supported from underneath fall down the board. If anything changes during this phase, the whole resolution phase is begun again from the top.
    3. Trash (2-player only): If the current player's trash counter isn't zero, some trash is inserted at the top of the board and the whole resolution phase is begun again from the top.

In other words, the resolution loop ends only when nothing falls and no trash is added. N.B. The exact phase order and the conditions for changing from one phase to another are speculative, based on some observations of the game and educated guesses, but are not the result of careful reading of the source code. Our understanding of the exact details here may change in the future with further testing.

Draws

If both players finish the game on the same frame, a draw will occur. Different states of the game at the moment of the draw will result in different (potentially surprising) outcomes. If both players top out on the same frame, neither player is awarded a crown. If both players clear their last virus on the same frame, and player one has zero or one crown, both players are awarded a crown; but if player one has two crowns, they are given a third and awarded the match win with no indication that there was a draw, even if player two also had two crowns.