Horizontal Movement

At any point, Mario may be moving left or right, determined by the sign of his speed (Player_X_Speed). His speed is measured in subpixels/frame. In non-water areas, his speed is hard capped at ±40 when RunningTimer is set, or soft capped at ±24 otherwise. In water areas, his speed is always hard capped at ±24, and soft capped at ±16 on the ground. Note that a "soft cap" refers to the fact that in practice it is possible to exceed this cap by holding either no directional inputs or the opposite of the directional input corresponding to the direction Mario is traveling. An exception exists during the pipe entrance cutscenes (where GameEngineSubroutine == 7) where Mario's speed is hard capped to 12.

Mario's subspeed (Player_X_MoveForce) is only used in calculating acceleration, and does not contribute to updating his position.

RunningTimer is always set to 10 when B is held, Mario is on the ground and the directional inputs held correspond to the direction Mario is moving (Left_Right_Buttons == Player_MovingDir, where Left_Right_Buttons is set to SavedJoypadBits & 0x03). It decrements by 1 every frame these conditions aren't met.

Acceleration

Explanation

Mario's horizontal acceleration is stored in FrictionAdderHigh and FrictionAdderLow, following this algorithm:

$00 := 0

if Player_State != 0 then /* if Mario is in the air */
    if Player_XSpeedAbsolute < 25 then
        $00 := $00 + 1
        
        if RunningSpeed then /* RunningSpeed is set if Player_XSpeedAbsolute >= 28 and is only unset if Left_Right_Buttons == Player_MovingDir and Player_XSpeedAbsolute < 28 */
            $00 := $00 + 1
        end
    end
else /* if Mario is on the ground */
    if AreaType == 0 then /* if in water area */
        $00 := $00 + 1
    else if Left_Right_Buttons != Player_MovingDir then
        $00 := $00 + 1
        
        if RunningSpeed then
            $00 := $00 + 1
        end
    else if !(A_B_Buttons & #B_Button) and !RunningTimer then /* If B button is not pressed and RunningTimer is not set */
        $00 := $00 + 1
    end
end

FrictionAdderLow := FrictionData[$00] /* [0xe4, 0x98, 0xd0] for Mario, [0xb4, 0x68, 0xa0] for Luigi */
FrictionAdderHigh := 0

if PlayerFacingDir != Player_MovingDir then
    /* Double Acceleration */
    FrictionAdderLow := FrictionAdderLow << 1 /* Rolls bit 7 into carry */
    FrictionAdderHigh := FrictionAdderHigh << 1 /* Rolls carry bit in */
end

Note that pressing L+R sets PlayerFacingDir to 3 (0b00000011), but Player_MovingDir is based solely on the sign of Mario's speed. This means that when L+R is pressed, Mario's horizontal acceleration will always be double it's original value.

Mario's horizontal acceleration is then applied first to subspeed then to his speed depending on the sign of his speed and directional inputs (note that if Mario's speed is zero, his subspeed will not be updated if no buttons are held; see the last entry of #Speed Resets). His speed is then capped to his maximum speed which depends on if RunningTimer is set.

Fast Acceleration

As Mario accelerates faster when he is facing the opposite direction he is going, one can deliberately take advantage of this, performing a trick called a fast acceleration. TASes usually perform what's called a L+R fast acceleration, where the inputs L+R, R+A are performed at the beginning of a section. RTA runners, who are either console runners physically unable to press the Left and Right D-pad buttons together, or emulator players who follow a no L+R rule present on most speedrun leaderboards can still perform a fast acceleration, by performing the inputs L, [nothing], R+A. Note that [nothing] means no Left, Right, or A inputs, as at least one other button has to be held (see the last entry of #Speed Resets). The same idea is then applied by both RTA runners and TASers to gain maximum running speed quicker.

Subspeed Manipulation

As there exists no possible acceleration value that sets FrictionAdderLow to 0 (on NTSC), Mario's subspeed will fluctuate even when his speed is capped. His subspeed can be frozen by releasing all directional inputs while in the air, useful for manipulating the speed at which Mario will accelerate. A common example is releasing right for one frame in order to maintain a subspeed of 0xF0 following a L+R fast acceleration, allowing Mario to reach the maximum running speed slightly faster.

Speed Resets

Mario's speed is reset to zero when:

  • Mario collides with a wall
  • Mario collides with the left side of the screen while holding either Left (0x02), L+R (0x03) or nothing (0x00)
  • Mario dies

Mario's speed and subspeed are reset to zero when:

  • Mario enters a screen transition
  • Mario's absolute speed is less than 11, and any button (other than A and the direction Mario is traveling) is pressed

Miscellaneous

  • When Mario collects a powerup, the game will consider Mario to be on the ground for exactly one frame, allowing RunningTimer to be set. This can be exploited as you can obtain running speed in the air after grabbing a powerup. This is used in the category "1-1 as Fire Mario" where the fire flower grab is called a darG grab.
  • Leftward movement is fundamentally different than rightward movement, as e.g. +0xD0 speed is stored as 00 D0, however -0xD0 speed is stored as FF 30 (upper byte signed two's complement), allowing Mario to move one subpixel left after only one frame of input from standby (00 00). This allows for fast y-subpixel manipulation off the left edge of a platform, as Mario can oscillate between -1 and 1 speed very quickly.

Wind (SMB2J)

In SMB2J, depending on whether the most significant bit of the frame counter is set, wind pushes Mario by one pixel every two or four frames (FrameCounter & 0b10000000 ? 2 : 4). This can be exploited as a method to clip into walls with much more leniency than normal, or even to fully clip while Mario is traveling downwards, which is otherwise impossible.