Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Debug Functions

Runtime value inspection via the F4 Debug Inspector.

Overview

The debug system allows you to register game variables for live editing and monitoring. Press F4 to open the Debug Inspector during development.

Features:

  • Live value editing (sliders, color pickers)
  • Read-only watches
  • Grouped organization
  • Frame control (pause, step, time scale)
  • Zero overhead in release builds

Value Registration

Register editable values in init(). The debug panel will show controls for these values.

debug_register_i32

Registers an editable 32-bit signed integer.

Signature:

#![allow(unused)]
fn main() {
fn debug_register_i32(name_ptr: *const u8, name_len: u32, ptr: *const i32)
}

Example:

#![allow(unused)]
fn main() {
static mut ENEMY_COUNT: i32 = 5;

fn init() {
    unsafe {
        debug_register_i32(b"Enemy Count".as_ptr(), 11, &ENEMY_COUNT);
    }
}
}

debug_register_f32

Registers an editable 32-bit float.

Signature:

#![allow(unused)]
fn main() {
fn debug_register_f32(name_ptr: *const u8, name_len: u32, ptr: *const f32)
}

Example:

#![allow(unused)]
fn main() {
static mut GRAVITY: f32 = 9.8;
static mut JUMP_FORCE: f32 = 15.0;

fn init() {
    unsafe {
        debug_register_f32(b"Gravity".as_ptr(), 7, &GRAVITY);
        debug_register_f32(b"Jump Force".as_ptr(), 10, &JUMP_FORCE);
    }
}
}

debug_register_bool

Registers an editable boolean (checkbox).

Signature:

#![allow(unused)]
fn main() {
fn debug_register_bool(name_ptr: *const u8, name_len: u32, ptr: *const u8)
}

Example:

#![allow(unused)]
fn main() {
static mut GOD_MODE: u8 = 0; // 0 = false, 1 = true

fn init() {
    unsafe {
        debug_register_bool(b"God Mode".as_ptr(), 8, &GOD_MODE);
    }
}
}

debug_register_u8 / u16 / u32

Registers unsigned integers.

#![allow(unused)]
fn main() {
fn debug_register_u8(name_ptr: *const u8, name_len: u32, ptr: *const u8)
fn debug_register_u16(name_ptr: *const u8, name_len: u32, ptr: *const u16)
fn debug_register_u32(name_ptr: *const u8, name_len: u32, ptr: *const u32)
}

debug_register_i8 / i16

Registers signed integers.

#![allow(unused)]
fn main() {
fn debug_register_i8(name_ptr: *const u8, name_len: u32, ptr: *const i8)
fn debug_register_i16(name_ptr: *const u8, name_len: u32, ptr: *const i16)
}

Range-Constrained Registration

debug_register_i32_range

Registers an integer with min/max bounds (slider).

Signature:

#![allow(unused)]
fn main() {
fn debug_register_i32_range(
    name_ptr: *const u8, name_len: u32,
    ptr: *const i32,
    min: i32, max: i32
)
}

Example:

#![allow(unused)]
fn main() {
static mut DIFFICULTY: i32 = 2;

fn init() {
    unsafe {
        debug_register_i32_range(b"Difficulty".as_ptr(), 10, &DIFFICULTY, 1, 5);
    }
}
}

debug_register_f32_range

Registers a float with min/max bounds.

Signature:

#![allow(unused)]
fn main() {
fn debug_register_f32_range(
    name_ptr: *const u8, name_len: u32,
    ptr: *const f32,
    min: f32, max: f32
)
}

Example:

#![allow(unused)]
fn main() {
static mut PLAYER_SPEED: f32 = 5.0;

fn init() {
    unsafe {
        debug_register_f32_range(b"Speed".as_ptr(), 5, &PLAYER_SPEED, 0.0, 20.0);
    }
}
}

debug_register_u8_range / u16_range / i16_range

#![allow(unused)]
fn main() {
fn debug_register_u8_range(name_ptr: *const u8, name_len: u32, ptr: *const u8, min: u32, max: u32)
fn debug_register_u16_range(name_ptr: *const u8, name_len: u32, ptr: *const u16, min: u32, max: u32)
fn debug_register_i16_range(name_ptr: *const u8, name_len: u32, ptr: *const i16, min: i32, max: i32)
}

Compound Types

debug_register_vec2

Registers a 2D vector (two f32s).

Signature:

#![allow(unused)]
fn main() {
fn debug_register_vec2(name_ptr: *const u8, name_len: u32, ptr: *const f32)
}

Example:

#![allow(unused)]
fn main() {
static mut PLAYER_POS: [f32; 2] = [0.0, 0.0];

fn init() {
    unsafe {
        debug_register_vec2(b"Player Pos".as_ptr(), 10, PLAYER_POS.as_ptr());
    }
}
}

debug_register_vec3

Registers a 3D vector (three f32s).

Signature:

#![allow(unused)]
fn main() {
fn debug_register_vec3(name_ptr: *const u8, name_len: u32, ptr: *const f32)
}

debug_register_rect

Registers a rectangle (x, y, width, height as four f32s).

Signature:

#![allow(unused)]
fn main() {
fn debug_register_rect(name_ptr: *const u8, name_len: u32, ptr: *const f32)
}

debug_register_color

Registers a color (RGBA as four u8s).

Signature:

#![allow(unused)]
fn main() {
fn debug_register_color(name_ptr: *const u8, name_len: u32, ptr: *const u8)
}

Example:

#![allow(unused)]
fn main() {
static mut TINT_COLOR: [u8; 4] = [255, 255, 255, 255];

fn init() {
    unsafe {
        debug_register_color(b"Tint".as_ptr(), 4, TINT_COLOR.as_ptr());
    }
}
}

Fixed-Point Registration

For games using fixed-point math.

#![allow(unused)]
fn main() {
fn debug_register_fixed_i16_q8(name_ptr: *const u8, name_len: u32, ptr: *const i16)
fn debug_register_fixed_i32_q8(name_ptr: *const u8, name_len: u32, ptr: *const i32)
fn debug_register_fixed_i32_q16(name_ptr: *const u8, name_len: u32, ptr: *const i32)
fn debug_register_fixed_i32_q24(name_ptr: *const u8, name_len: u32, ptr: *const i32)
}

Watch Functions (Read-Only)

Watches display values without allowing editing.

#![allow(unused)]
fn main() {
fn debug_watch_i8(name_ptr: *const u8, name_len: u32, ptr: *const i8)
fn debug_watch_i16(name_ptr: *const u8, name_len: u32, ptr: *const i16)
fn debug_watch_i32(name_ptr: *const u8, name_len: u32, ptr: *const i32)
fn debug_watch_u8(name_ptr: *const u8, name_len: u32, ptr: *const u8)
fn debug_watch_u16(name_ptr: *const u8, name_len: u32, ptr: *const u16)
fn debug_watch_u32(name_ptr: *const u8, name_len: u32, ptr: *const u32)
fn debug_watch_f32(name_ptr: *const u8, name_len: u32, ptr: *const f32)
fn debug_watch_bool(name_ptr: *const u8, name_len: u32, ptr: *const u8)
fn debug_watch_vec2(name_ptr: *const u8, name_len: u32, ptr: *const f32)
fn debug_watch_vec3(name_ptr: *const u8, name_len: u32, ptr: *const f32)
fn debug_watch_rect(name_ptr: *const u8, name_len: u32, ptr: *const f32)
fn debug_watch_color(name_ptr: *const u8, name_len: u32, ptr: *const u8)
}

Example:

#![allow(unused)]
fn main() {
static mut FRAME_COUNT: u32 = 0;
static mut FPS: f32 = 0.0;

fn init() {
    unsafe {
        debug_watch_u32(b"Frame".as_ptr(), 5, &FRAME_COUNT);
        debug_watch_f32(b"FPS".as_ptr(), 3, &FPS);
    }
}
}

Grouping

debug_group_begin

Starts a collapsible group in the debug panel.

Signature:

#![allow(unused)]
fn main() {
fn debug_group_begin(name_ptr: *const u8, name_len: u32)
}

debug_group_end

Ends the current group.

Signature:

#![allow(unused)]
fn main() {
fn debug_group_end()
}

Example:

#![allow(unused)]
fn main() {
fn init() {
    unsafe {
        debug_group_begin(b"Player".as_ptr(), 6);
        debug_register_vec3(b"Position".as_ptr(), 8, PLAYER_POS.as_ptr());
        debug_register_f32(b"Health".as_ptr(), 6, &PLAYER_HEALTH);
        debug_register_f32(b"Speed".as_ptr(), 5, &PLAYER_SPEED);
        debug_group_end();

        debug_group_begin(b"Physics".as_ptr(), 7);
        debug_register_f32(b"Gravity".as_ptr(), 7, &GRAVITY);
        debug_register_f32(b"Friction".as_ptr(), 8, &FRICTION);
        debug_group_end();
    }
}
}

Debug Actions

Actions create buttons in the debug panel that trigger game functions when clicked. They’re useful for testing scenarios, spawning entities, or triggering events during development.

debug_register_action

Registers a simple action button with no parameters.

Signature:

#![allow(unused)]
fn main() {
fn debug_register_action(
    name_ptr: *const u8, name_len: u32,
    func_name_ptr: *const u8, func_name_len: u32
)
}

Parameters:

NameTypeDescription
name_ptr*const u8Pointer to button label string
name_lenu32Length of button label
func_name_ptr*const u8Pointer to exported WASM function name
func_name_lenu32Length of function name

Example:

#![allow(unused)]
fn main() {
#[no_mangle]
pub extern "C" fn spawn_enemy() {
    // Called when button is clicked
    unsafe {
        ENEMY_COUNT += 1;
    }
}

#[no_mangle]
pub extern "C" fn reset_game() {
    // Reset all game state
    unsafe {
        PLAYER_X = 0.0;
        PLAYER_Y = 0.0;
        ENEMY_COUNT = 0;
    }
}

fn init() {
    unsafe {
        debug_group_begin(b"Actions".as_ptr(), 7);
        debug_register_action(
            b"Spawn Enemy".as_ptr(), 11,
            b"spawn_enemy".as_ptr(), 11
        );
        debug_register_action(
            b"Reset Game".as_ptr(), 10,
            b"reset_game".as_ptr(), 10
        );
        debug_group_end();
    }
}
}

debug_action_begin

Begins building an action with parameters. Use with debug_action_param_* functions and debug_action_end() to create actions with input fields.

Signature:

#![allow(unused)]
fn main() {
fn debug_action_begin(
    name_ptr: *const u8, name_len: u32,
    func_name_ptr: *const u8, func_name_len: u32
)
}

Parameters:

NameTypeDescription
name_ptr*const u8Pointer to button label string
name_lenu32Length of button label
func_name_ptr*const u8Pointer to exported WASM function name
func_name_lenu32Length of function name

debug_action_param_i32

Adds an i32 parameter input field to the pending action.

Signature:

#![allow(unused)]
fn main() {
fn debug_action_param_i32(name_ptr: *const u8, name_len: u32, default_value: i32)
}

Parameters:

NameTypeDescription
name_ptr*const u8Pointer to parameter label string
name_lenu32Length of parameter label
default_valuei32Default value shown in input field

debug_action_param_f32

Adds an f32 parameter input field to the pending action.

Signature:

#![allow(unused)]
fn main() {
fn debug_action_param_f32(name_ptr: *const u8, name_len: u32, default_value: f32)
}

Parameters:

NameTypeDescription
name_ptr*const u8Pointer to parameter label string
name_lenu32Length of parameter label
default_valuef32Default value shown in input field

debug_action_end

Completes the action registration started with debug_action_begin().

Signature:

#![allow(unused)]
fn main() {
fn debug_action_end()
}

Complete Action with Parameters Example:

#![allow(unused)]
fn main() {
// Function called when action button is clicked
// Parameters are passed in the order they were registered
#[no_mangle]
pub extern "C" fn spawn_enemies_at(count: i32, x: f32, y: f32) {
    for _ in 0..count {
        unsafe {
            // Spawn enemy at specified position
            spawn_enemy(x, y);
        }
    }
}

#[no_mangle]
pub extern "C" fn teleport_player(x: f32, y: f32) {
    unsafe {
        PLAYER_X = x;
        PLAYER_Y = y;
    }
}

fn init() {
    unsafe {
        debug_group_begin(b"Actions".as_ptr(), 7);

        // Action with parameters
        debug_action_begin(
            b"Spawn Enemies".as_ptr(), 13,
            b"spawn_enemies_at".as_ptr(), 16
        );
        debug_action_param_i32(b"Count".as_ptr(), 5, 3);
        debug_action_param_f32(b"X".as_ptr(), 1, 100.0);
        debug_action_param_f32(b"Y".as_ptr(), 1, 100.0);
        debug_action_end();

        // Another parameterized action
        debug_action_begin(
            b"Teleport Player".as_ptr(), 15,
            b"teleport_player".as_ptr(), 15
        );
        debug_action_param_f32(b"X".as_ptr(), 1, 480.0);
        debug_action_param_f32(b"Y".as_ptr(), 1, 270.0);
        debug_action_end();

        debug_group_end();
    }
}
}

Frame Control

debug_is_paused

Check if game is paused via debug panel.

Signature:

#![allow(unused)]
fn main() {
fn debug_is_paused() -> i32
}

Returns: 1 if paused, 0 otherwise


debug_get_time_scale

Get the current time scale.

Signature:

#![allow(unused)]
fn main() {
fn debug_get_time_scale() -> f32
}

Returns: Time scale (1.0 = normal, 0.5 = half speed, 2.0 = double)

Example:

#![allow(unused)]
fn main() {
fn update() {
    unsafe {
        if debug_is_paused() != 0 {
            return; // Skip update when paused
        }

        let dt = delta_time() * debug_get_time_scale();
        // Use scaled delta time
    }
}
}

Debug Keyboard Shortcuts

KeyAction
F3Toggle Runtime Stats Panel
F4Toggle Debug Inspector
F5Pause/unpause
F6Step one frame (while paused)
F7Decrease time scale
F8Increase time scale

Complete Example

#![allow(unused)]
fn main() {
// Game state
static mut PLAYER_X: f32 = 0.0;
static mut PLAYER_Y: f32 = 0.0;
static mut PLAYER_VEL_X: f32 = 0.0;
static mut PLAYER_VEL_Y: f32 = 0.0;
static mut PLAYER_HEALTH: f32 = 100.0;

// Tuning parameters
static mut MOVE_SPEED: f32 = 5.0;
static mut JUMP_FORCE: f32 = 12.0;
static mut GRAVITY: f32 = 25.0;
static mut FRICTION: f32 = 0.9;

// Debug
static mut GOD_MODE: u8 = 0;
static mut SHOW_HITBOXES: u8 = 0;
static mut ENEMY_COUNT: i32 = 5;

fn init() {
    unsafe {
        // Player group
        debug_group_begin(b"Player".as_ptr(), 6);
        debug_watch_f32(b"X".as_ptr(), 1, &PLAYER_X);
        debug_watch_f32(b"Y".as_ptr(), 1, &PLAYER_Y);
        debug_watch_f32(b"Vel X".as_ptr(), 5, &PLAYER_VEL_X);
        debug_watch_f32(b"Vel Y".as_ptr(), 5, &PLAYER_VEL_Y);
        debug_register_f32_range(b"Health".as_ptr(), 6, &PLAYER_HEALTH, 0.0, 100.0);
        debug_group_end();

        // Physics group
        debug_group_begin(b"Physics".as_ptr(), 7);
        debug_register_f32_range(b"Move Speed".as_ptr(), 10, &MOVE_SPEED, 1.0, 20.0);
        debug_register_f32_range(b"Jump Force".as_ptr(), 10, &JUMP_FORCE, 5.0, 30.0);
        debug_register_f32_range(b"Gravity".as_ptr(), 7, &GRAVITY, 10.0, 50.0);
        debug_register_f32_range(b"Friction".as_ptr(), 8, &FRICTION, 0.5, 1.0);
        debug_group_end();

        // Debug options
        debug_group_begin(b"Debug".as_ptr(), 5);
        debug_register_bool(b"God Mode".as_ptr(), 8, &GOD_MODE);
        debug_register_bool(b"Show Hitboxes".as_ptr(), 13, &SHOW_HITBOXES);
        debug_register_i32_range(b"Enemy Count".as_ptr(), 11, &ENEMY_COUNT, 0, 20);
        debug_group_end();
    }
}

fn update() {
    unsafe {
        // Respect debug pause
        if debug_is_paused() != 0 {
            return;
        }

        let dt = delta_time() * debug_get_time_scale();

        // Use tunable values
        PLAYER_VEL_Y += GRAVITY * dt;
        PLAYER_VEL_X *= FRICTION;

        if button_held(0, BUTTON_RIGHT) != 0 {
            PLAYER_VEL_X = MOVE_SPEED;
        }
        if button_held(0, BUTTON_LEFT) != 0 {
            PLAYER_VEL_X = -MOVE_SPEED;
        }
        if button_pressed(0, BUTTON_A) != 0 {
            PLAYER_VEL_Y = -JUMP_FORCE;
        }

        PLAYER_X += PLAYER_VEL_X * dt;
        PLAYER_Y += PLAYER_VEL_Y * dt;
    }
}
}

See Also: System Functions