Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/pmret/papermario/llms.txt

Use this file to discover all available pages before exploring further.

Paper Mario uses a custom bytecode scripting system called EVT (Event) to drive nearly all in-game logic: map initialization, NPC behavior, cutscenes, item pickups, battle actions, and more. Rather than hardcoding these sequences in C, the game executes compact arrays of Bytecode values interpreted by a lightweight virtual machine. This separation lets designers compose complex interactions from reusable building blocks without recompiling the engine.

How the VM works

The EVT interpreter manages up to 128 concurrent script threads (see MAX_SCRIPTS in include/macros.h). On every game frame, the engine calls update_scripts, which walks all active threads and executes instructions until each thread either blocks or completes. Each thread is represented by an Evt struct. The interpreter’s main loop calls evt_execute_next_command, which reads one instruction at a time from the thread’s program counter and dispatches to a handler function. The handler returns one of three internal values:
Return valueMeaning
EVT_CONTINUE (0)Move to the next instruction immediately
EVT_ABORT (1)Stop executing this thread for the current frame
EVT_FINISH (255)The thread has ended
These are distinct from the ApiStatus values returned by callable API functions (described below).

Script data layout

A script is a const Bytecode[] array — typedef’d as EvtScript — stored in ROM. Each instruction is laid out as:
Bytecode opcode;
Bytecode argc;
Bytecode argv[argc];
The EVT_CMD macro in include/script_api/macros.h constructs these entries and computes argc automatically at compile time, so you never write the argument count by hand. All high-level macros such as Call, Set, and IfEq expand to EVT_CMD calls.

ApiStatus: the callable return protocol

When a script executes a Call instruction, the VM calls a C function matching the API_CALLABLE signature:
ApiStatus name(Evt* script, bool isInitialCall)
That function returns one of four ApiStatus values defined in include/evt.h:
ValueConstantDescription
0ApiStatus_BLOCKSuspend the thread; call this function again next frame
1ApiStatus_DONE1Complete unconditionally
2ApiStatus_DONE2Complete; respects Evt->disableScripts
3ApiStatus_REPEATCall again immediately (same frame)
255ApiStatus_FINISHTerminate the thread, equivalent to Return
The isInitialCall parameter is true on the first call and false on subsequent calls when ApiStatus_BLOCK was returned. Use it to perform one-time setup (allocating state, starting an animation) before entering a per-frame polling loop.

The API_CALLABLE macro

API_CALLABLE (defined in include/macros.h) expands to the full function signature:
#define API_CALLABLE(name) ApiStatus name(Evt* script, bool isInitialCall)
A typical callable reads its arguments from script->ptrReadPos and returns an appropriate status:
API_CALLABLE(TranslateModel) {
    Bytecode* args = script->ptrReadPos;
    s32 modelIndex = evt_get_variable(script, *args++);
    f32 x = evt_get_float_variable(script, *args++);
    f32 y = evt_get_float_variable(script, *args++);
    f32 z = evt_get_float_variable(script, *args++);
    // ... perform the transform ...
    return ApiStatus_DONE2;
}
A blocking callable stores its state between calls using script->functionTemp[]:
API_CALLABLE(WaitForAnimation) {
    if (isInitialCall) {
        script->functionTemp[0] = /* animation handle */;
    }
    if (/* animation complete */) {
        return ApiStatus_DONE2;
    }
    return ApiStatus_BLOCK; // called again next frame
}

Script execution

You launch a script from another script using one of the execution opcodes:
MacroOpcodeBehavior
Exec(script)EVT_OP_EXECSpawns a new thread; parent continues immediately
ExecGetTID(script, outVar)EVT_OP_EXEC_GET_TIDSpawns a new thread; stores its thread ID
ExecWait(script)EVT_OP_EXEC_WAITSpawns a child thread; parent blocks until it finishes
Local variables (LVar0LVarF) and local flags (LFlag0LFlagF) are copied into the child thread when it spawns. ExecWait also copies them back when the child returns.

Script groups and priorities

Every thread belongs to a group (EventGroupFlags) and has a priority (EventPriority). Groups control which threads are suspended automatically during battles, menu transitions, or map exits. Priority determines execution order within a frame — higher-priority threads run first.

Opcode reference

Full listing of all EVT_OP_* bytecode instructions with argument descriptions.

Script API reference

Catalogue of API_CALLABLE functions available from scripts.

Writing EVT scripts

Practical guide to authoring scripts in C using the EVT macro system.

Build docs developers (and LLMs) love