Movement

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] /* db[0xe4, 0x98, 0xd0] */
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.

Exploits

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 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 walks more than one pixel into a horizontal pipe
  • Mario dies

Mario's speed and subspeed are reset when:

  • Mario enters a screen transition
  • When 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.

Vertical movement

Vertical movement behaves much like horizontal movement, where a positive speed (Player_Y_Speed) represents downwards movement and a negative speed represents upwards movement. Unlike with horizontal movement, Mario's subspeed (Player_Y_MoveForce) is applied to his subpixel value, as vertical movement would be jarring otherwise.

Speed cap

The vertical speed cap for all sprite objects (including Mario) is implemented poorly. It checks for whether the object's speed and subspeed both exceed their respective intended caps (SprObject_Y_Speed,x >= $02 and SprObject_Y_MoveForce,x >= 0x80), forgetting to account for when it's speed is above the cap, but it's subspeed is below the cap (SprObject_Y_Speed,x > $02 and SprObject_Y_MoveForce,x < 0x80). This allows Mario (and all other sprite objects) to exceed the intended speed cap, with precise subspeed manipulation (e.g. differing jump heights, ceiling bonks, enemy bounces). This is generally useless on NTSC, however it can be used in the PAL version to clip into the floor.

Gravity

Mario's gravity is only set under 4 different conditions:

  • When going through a screen transition, Mario's downwards gravity is set to 0x28
  • When Mario is above bounds in a water area, Mario's downwards gravity is set to 0x18
  • When Mario lands on a spring, his upwards gravity is set to 0x70
  • When Mario jumps or swims (bouncing off a spring does not count, however jumping off a spring does count), his upwards and downwards gravity is set following this algorithm:
if Player_State == 0 or (SwimmingFlag != 0 and (JumpSwimTimer != 0 or Player_Y_Speed >= 0)) then /* If Mario is on the ground, or Mario is swimming and either JumpSwimTimer is set or his speed is nonnegative */
    JumpSwimTimer := 32 /* Reset timer */
    y_register := 0
    Player_YMF_Dummy := 0 /* Reset y subpixel */
    
    JumpOrigin_Y_Position := Player_Y_Position /* Used for an edge case at the very beginning of Mario's jump where Player_Y_Speed < 0 and A_B_Buttons & PreviousA_B_Buttons & 0b10000000 == 0 */
    Player_State := 1
    
    for i := 0; i < 4 do
        if Player_XSpeedAbsolute < [9, 16, 25, 28][i] then
            break
        end
        
        y_register := y_register + 1 /* Increment for gravity calculation */
    end
    
    if SwimmingFlag != 0 then /* If Mario is swimming */
        y_register := 5
        
        if Whirlpool_Flag != 0 then /* If Mario is in a whirlpool */
            y_register := y_register + 1
        end
    end
    
    VerticalForce := JumpMForceData[y_register] /* db[0x20, 0x20, 0x1e, 0x28, 0x28, 0x0d, 0x04] */
    VerticalForceDown := FallMForceData[y_register] /* db[0x70, 0x70, 0x60, 0x90, 0x90, 0x0a, 0x09] */
    Player_Y_MoveForce := InitMForceData[y_register] /* db[0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00] */
    Player_Y_Speed := PlayerYSpdData[y_register] /* db[0xfc, 0xfc, 0xfc, 0xfb, 0xfb, 0xfe, 0xff] */
    
    if SwimmingFlag != 0 and Player_Y_Position < 20 then /* Check if above bounds in water areas */
        Player_Y_Speed := 0
    end
end

Note that upwards and downwards gravity are stored in VerticalForce and VerticalForceDown respectively. Only VerticalForce is ever applied to Mario's speed and subspeed. VerticalForceDown is copied to VerticalForce when Mario is in the air and either his speed is greater or equal to zero, or the A button has been released.

Speed resets

Mario's speed is set in the following conditions:

  • When Mario jumps, his speed is set to PlayerYSpdData[y_register], and his subspeed is set to InitMForceData[y_register] (see pseudocode above for dbs).
  • When Mario bumps a soft block, his vertical speed is set to 0 if the block does not break, or -2 if it does. His subspeed is not changed.
  • When Mario bumps a hard block (including when the bump timer has not reset yet, as all blocks are hard) or the bottom of a moving platform, his speed is set to 1. His subspeed is not changed. Note that all blocks in water areas are hard.
  • When Mario dies onscreen, his speed is set to -4 and his gravity is set and frozen to 0x28. His subspeed is not changed.
  • In SMB1, if you bounce off a Cheep Cheep, Bullet Bill, Podoboo, Hammer Bro, Lakitu, or a Blooper Mario's speed is set to -3. Any other enemy will set his speed to -4. His subspeed is not changed. [ADD SMB2J]
  • When Mario lands on anything, his speed and subspeed is reset to 0.
  • When Mario is climbing, his speed is set to -1 and his subspeed is set to 0x20 when climbing up, or 1 and 0xff when climbing down.
  • When Mario bounces on a red spring, his speed is set to -12 if A is held, or -7 otherwise. His subspeed is not explicitly reset however it is always reset beforehand when he lands on the spring. [ADD SMB2J]