Every aspect of Mario’s moment-to-moment behavior is driven by a single active action stored inDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/n64decomp/sm64/llms.txt
Use this file to discover all available pages before exploring further.
MarioState.action. Each frame, execute_mario_action() reads that value, selects the appropriate group executor, and runs the corresponding handler function. Transitions between actions are performed through a small family of setter functions that apply entry-side effects before committing the new action.
The design means that adding or modifying behavior generally requires either editing an existing act_* handler or writing a new one and registering it in the group’s switch statement.
Action value layout
An action value is a 32-bit integer where the bits serve two roles simultaneously:ACT_ID_MASK = 0x1FF) are the unique index within the action’s group. Bits 6–8 form the group field (ACT_GROUP_MASK = 0x1C0). Bits 9–31 are flag bits that expose properties of the action to code that does not need to know the exact action:
ACT_WALKING = 0x04000440 decodes as:
- ID
0x040within the moving group ACT_GROUP_MOVING(0x40)ACT_FLAG_MOVING(0x400)ACT_FLAG_ALLOW_FIRST_PERSON(0x04000000)
execute_mario_action()
This function is called once per frame from the object behavior of Mario’s object (bhvMario). It is declared in src/game/mario.h and implemented in src/game/mario.c.
src/game/mario.c:1702):
The
while (inLoop) loop is intentional. When a group executor calls set_mario_action() and returns TRUE, the loop immediately re-dispatches to the new action’s group within the same frame. This can chain multiple action transitions in a single tick — for example, landing from a jump can chain directly into a walk step without a one-frame gap.Action source files
Each group executor and itsact_* handlers live in a dedicated file:
mario_actions_stationary.c
Idle, sleeping, crouching, reading signs, star dance, death animations, and all landing-stop actions. Group
ACT_GROUP_STATIONARY.mario_actions_moving.c
Walking, running, braking, butt slide, stomach slide, dive slide, ground knockback, and landing roll actions. Group
ACT_GROUP_MOVING.mario_actions_airborne.c
Jump, double jump, triple jump, backflip, long jump, side flip, wall kick, freefall, dive, flying, twirling, ground pound, lava boost, and airborne knockback. Group
ACT_GROUP_AIRBORNE.mario_actions_submerged.c
Water idle, breaststroke, flutter kick, water throw, water punch, water plunge, metal-water variants, drowning, whirlpool. Group
ACT_GROUP_SUBMERGED.mario_actions_automatic.c
Pole climbing, ceiling hanging, hang-moving, ledge grab, ledge climb, cannon, Hoot riding, tornado twirl. Group
ACT_GROUP_AUTOMATIC.mario_actions_cutscene.c
Intro cutscene, star dance, door transitions, warp spawns, death exits, credits, reading NPC dialog, Bowser throw. Group
ACT_GROUP_CUTSCENE.mario_actions_object.c
Punching, picking up objects, placing down, throwing, heavy throw, picking up Bowser, holding Bowser, releasing Bowser. Group
ACT_GROUP_OBJECT.mario_execute_moving_action, etc.) contains a switch on the lower action ID bits and calls the appropriate act_* function, returning its result to the inLoop check in execute_mario_action().
Action handler signature
Every action handler follows the same signature and return convention:- Return
FALSE— action continues normally next frame (inLoop = FALSEstops the while loop). - Return
TRUE— action changed internally; the while loop will re-dispatch within this frame. - Calling
set_mario_action(m, NEW_ACTION, arg)and returning its result (TRUE) is the standard transition pattern.
Key action-setting functions
All declared insrc/game/mario.h, implemented in src/game/mario.c:
The core transition function. Dispatches to a group-specific entry helper (e.g.
set_mario_action_airborne) that sets initial velocity values for the new action, then commits by writing action, prevAction, actionArg, actionState = 0, and actionTimer = 0.Calls
mario_stop_riding_and_holding(m) to drop any held or ridden object, then calls set_mario_action(). Used whenever a transition must cleanly release objects (e.g. damage knockback while carrying something).Sets
m->hurtCounter to the given value (which drives the hurt animation countdown), then calls set_mario_action(). Used by all damage-dealing interactions.Selects the correct jump action based on
m->prevAction and m->doubleJumpTimer. Handles the single → double → triple jump chain, steep-slope jump override, and quicksand jump variant.Applies quicksand and steep-slope overrides, then falls through to
set_mario_action(). Used by actions that want to initiate a jump but respect terrain constraints.Resets
angleVel, restores the camera to its default mode, then transitions to ACT_WALKING or ACT_HOLD_WALKING depending on whether Mario is holding an object. Called when Mario surfaces from water.Common action exit checks
Two helper functions implement the standard input checks shared across many stationary and moving action handlers:Selected action constants
Stationary actions (group 0x000)
Stationary actions (group 0x000)
Moving actions (group 0x040)
Moving actions (group 0x040)
Airborne actions (group 0x080)
Airborne actions (group 0x080)
Submerged actions (group 0x0C0)
Submerged actions (group 0x0C0)
Automatic actions (group 0x140)
Automatic actions (group 0x140)
Build flags: NON_MATCHING and AVOID_UB
The decomp uses two compile-time defines that affect action code behavior:When set, allows GCC/Clang to be used instead of the original SGI IDO compiler. Required for non-N64 builds. Code conditioned on
#ifndef NON_MATCHING contains GLOBAL_ASM blocks or other IDO-specific constructs that only compile with the original toolchain.When set, patches sources of undefined behavior that happen to work on MIPS/IDO but are illegal in standard C. The key example is Functions marked
BAD_RETURN, defined in include/types.h:BAD_RETURN(s32) are declared with a return type but do not actually return a value in all paths. With AVOID_UB=1 they become void functions, avoiding the undefined behavior that modern compilers may exploit for misoptimisation. macros.h enforces that non-IDO builds must define both NON_MATCHING=1 and AVOID_UB=1.