Puyo Puyo Tsu/Falling Pair Control

From Puyo Nexus Wiki
Jump to navigation Jump to search

This page describes overall handling of the falling pairs by the game, with pointers to detailed articles.

Falling pair objects

Falling pairs are instantiated as two objects in memory, within boundaries of the memory pool managed by the memory allocator, between addresses 0xFFD100 (inclusive) and 0xFFE000 (exclusive).

Each object describes a distinct puyo from that pair, each being attached a different callback that will handle their behavior and display throughout their fall.

The main puyo's callback is not guaranteed to be run before the slave's callback. This can lead to the slave puyo lagging 1 frame behind in very specific circumstances (out of the player's hands).

Main puyo object

The main puyo is the only puyo being controlled by the player. The game flickers a white outline around it during its fall. Its object's parent structure points to the relevant player status. All gamepad inputs are handled by its callback routine.

Here's the main puyo callback routine:

Slave puyo object

No gamepad inputs are handled by the callback routines of the slave puyo. Its position is a relative position, updated in accordance to the main puyo's current coordinates. The object's parent structure points to the main puyo object. Pair split is made effective by breaking that link to the main puyo: the callback overwrites the parent structure pointer with the address of the relevant player status object. From that moment on, it lives its own life independently from the main puyo and there is no way it could be linked to it again. Position is not relative anymore, but absolute calculations are used.

Here's the slave puyo callback routine:

Phases

For each puyo of the falling pair, the following phases occur within their callback routines:

  1. initialization
  2. player-controlled fall
  3. pair split and free-fall
  4. lockout and placement on the board

What each puyo's callback does at each phase differs a bit, but is mostly similar.

Initialization

This phase runs for a single frame right after the puyo has been created by the gen_cur_fal_pair() routine.

It initializes a field of the puyo's object and computes its initial absolute coordinates on the screen by calling either get_pair_absolute_pos() (main puyo) or compute_slave_puyo_coords() (slave puyo).

Here's get_pair_absolute_pos():

The routine calls get_board_screen_pxoffset() to get pixel offsets from which the board is drawn on the screen, depending on the player and current game mode. It then converts absolute board coordinates of the main puyo into pixel-coordinates, accounting for the relevant offset and the size of a single cell (16 pixels * 16 pixels).

Here's compute_slave_puyo_coords():

This routine doesn't call the same subroutine to calculate pixel-wise coordinates as the slave puyo's object doesn't store its coordinates on the board. Instead, it looks for those coordinate sin its parent structure, the main puyo. It also has to account for potential rotation, thus reading the current state of a potentially pending rotation animation. Based on that step, it computes (x,y) pixel coordinates displacements values and applies them to the absolute coordinates of the puyo on the screen.

For reference, here is the get_board_screen_pxoffset() routine:

While the offset table used by the routine only contains standard values, it can be noted that changing those will shift the player's board on the screen. This could have been implemented to have a single board centered on the screen, for an hypothetical endless mode. Changing those offsets will actually work without having the game complaining.

Player-controlled fall

Phase 2 of the falling puyo callbacks loops while the player has control over the pair movements. Refer to the callback disassembly screenshots for clarity.

The following updates occur for the main puyo:

  1. prepare the current player inputs which are considered valid for the current frame
  2. conditionnally update the game's RNG, to make it less predictable
  3. handle lateral movement (d-pad left/right arrows)
  4. handle rotation and relevant collision
  5. handle soft-drop (d-pad down arrow) and blocking

Player inputs are acknowledged in that order: lateral movement has priority over rotation, then over soft-drop. If one were to affect another, that order would matter. Soft drop stops while the right or left arrow is pressed.

The following updates occur simultaneously for the slave puyo:

  1. conditionnally update the game's RNG, to make it less predictable
  2. advance the current rotation animation by one step, if needed
  3. compute new absolute (on-screen) pixel coordinates

These updates occur while the pair is not blocked by some obstacle under either puyo. This check is done at the end of each sequence. Passing the check advances to the next phase/checkpoint.

During this phase, the slave puyo callback does simply nothing but update the sprite coordinates on the screen.

Pair split and free-fall

Once the pair is blocked by something, both callbacks advance to the next checkpoint by branching to loc_5FAC (main puyo) or loc_63BC (slave puyo).

An initialization occurs before going through the checkpoint and yielding execution. This takes a single, simultaneous frame for both puyos.

For the main puyo:

  1. initialization updates on-screen coordinates and blocked status under both puyos to check which one should free-fall;
  2. initializes free-fall parameters if the main puyo shall fall;
  3. yields execution.

If the main puyo should not free-fall, the callback skips to the placement phase and yields execution.

For the slave puyo:

  1. initialization updates on-screen coordinates;
  2. splits pair by overwriting the link to the main puyo as a parent object: the new parent object is the relevant player status object;
  3. yields execution.

Gravity initialization takes an additional step (thus an additional frame) in the case of the slave puyo. This is because the callback may be run before the main puyo's callback, hence not reflecting the pair being blocked by an obstacle. During that additional frame, if the puyo should not free-fall, the callback then skips to the placement phase and yields execution again.

Internal details of the game routines relative to gravity are discussed on the page Puyo Puyo Tsu/Free fall, including formulas speed formulas and parameters.

Lockout and placement

When the gravity routine detects that the puyo has reached the floor, execution yields once again, calling the placement routine on the very next frame after free-fall. Both callbacks mark their respective object for cleanup and call the routine which will place the puyo on the virtual board representation in RAM.

Notable facts

  • Board placement on the screen could be different, programmers probably accounted for centered boards in an hypothetical single-player endless mode.
  • Pair split takes 2 frames to complete if the main puyo should free-fall, or 3 frames if it is the slave puyo. Technically, from the frame the pair is detected as being blocked, a single frame passes without anything happening, then the main puyo may begin its fall on the second frame after the blocking event. The slave puyo may not begin its fall before the third frame.