| name | Debugging Guide |
| description | This skill should be used when the user asks about "debug panel", "F4 panel", "debug_register", "debug_watch", "sync test", "desync", "rollback debug", "--sync-test", "--p2p-test", "frame stepping", "time scale", "profiling", "debug group", "debugging ZX games", "inspect values", "live editing", or mentions debugging Nethercore ZX games, finding desyncs, testing multiplayer synchronization, or runtime value inspection. |
| version | 1.0.0 |
Debugging Guide for Nethercore ZX
Debug ZX games using the built-in inspection system and testing tools. Press F4 to open the debug panel during development.
Debug System Overview
The debug inspection system provides:
- Live value editing (sliders, checkboxes, color pickers)
- Read-only watches for monitoring
- Grouped organization for complex games
- Frame control (pause, step, time scale)
- Zero overhead in release builds (compiles out)
Reference nethercore/include/zx.rs lines 1370-1478 for complete FFI signatures.
F4 Debug Panel
Press F4 during gameplay to toggle the debug panel. The panel displays all registered values and provides interactive controls.
Keyboard Shortcuts
| Key | Action |
|---|
| F4 | Toggle debug panel |
| F5 | Pause/unpause game |
| F6 | Step one frame (while paused) |
| F7 | Decrease time scale |
| F8 | Increase time scale |
Registering Editable Values
Register values during init() to make them editable in the F4 panel:
static mut PLAYER_SPEED: f32 = 5.0;
static mut GRAVITY: f32 = 25.0;
static mut GOD_MODE: u8 = 0;
static mut ENEMY_COUNT: i32 = 5;
#[no_mangle]
pub extern "C" fn init() {
unsafe {
debug_register_f32(b"Speed".as_ptr(), 5, &PLAYER_SPEED);
debug_register_f32(b"Gravity".as_ptr(), 7, &GRAVITY);
debug_register_bool(b"God Mode".as_ptr(), 8, &GOD_MODE);
debug_register_i32(b"Enemies".as_ptr(), 7, &ENEMY_COUNT);
}
}
Range-Constrained Values (Sliders)
Use range variants for slider UI with min/max bounds:
static mut DIFFICULTY: i32 = 2;
static mut SPEED: f32 = 5.0;
fn init() {
unsafe {
debug_register_i32_range(b"Difficulty".as_ptr(), 10, &DIFFICULTY, 1, 5);
debug_register_f32_range(b"Speed".as_ptr(), 5, &SPEED, 0.0, 20.0);
}
}
Compound Types
Register vectors, colors, and rects:
static mut PLAYER_POS: [f32; 3] = [0.0, 0.0, 0.0];
static mut TINT_COLOR: [u8; 4] = [255, 255, 255, 255];
fn init() {
unsafe {
debug_register_vec3(b"Position".as_ptr(), 8, PLAYER_POS.as_ptr());
debug_register_color(b"Tint".as_ptr(), 4, TINT_COLOR.as_ptr());
}
}
Watch Functions (Read-Only)
Use watch functions to display values without allowing editing:
static mut FRAME_COUNT: u32 = 0;
static mut FPS: f32 = 0.0;
static mut PLAYER_VEL: [f32; 2] = [0.0, 0.0];
fn init() {
unsafe {
debug_watch_u32(b"Frame".as_ptr(), 5, &FRAME_COUNT);
debug_watch_f32(b"FPS".as_ptr(), 3, &FPS);
debug_watch_vec2(b"Velocity".as_ptr(), 8, PLAYER_VEL.as_ptr());
}
}
Organizing with Groups
Group related values for a cleaner debug panel:
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_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(), , &SHOW_HITBOXES);
();
}
}
Groups are collapsible in the UI - click the header to expand/collapse.
Frame Control
Query debug state to respect pause/time scale:
fn update() {
unsafe {
if debug_is_paused() != 0 {
return;
}
let dt = delta_time() * debug_get_time_scale();
PLAYER_VEL_Y += GRAVITY * dt;
PLAYER_X += PLAYER_VEL_X * dt;
}
}
Time Scale Values:
- F7 decreases: 0.5x, 0.25x, 0.1x
- F8 increases: 2x, 4x, 8x
- Default: 1.0x
Multiplayer Sync Testing
Test rollback determinism with CLI flags:
Sync Test Mode
Runs two parallel simulations and compares state:
nether run --sync-test
This launches two game instances in parallel:
- Both receive identical inputs
- State is compared after each frame
- Any divergence is reported as a desync
Desync causes:
- Using external randomness instead of
random()
- Reading wall-clock time instead of
delta_time()/tick_count()
- Uninitialized memory
- Modifying state in
render() instead of update()
P2P Test Mode
Tests actual network rollback with two instances:
nether run --p2p-test
Creates a local two-player session:
- Player 1 window (local)
- Player 2 window (local)
- GGRS netcode active between them
Use this to verify:
- Input synchronization works
- Rollbacks don't cause visual glitches
- State serialization is correct
Common Debugging Patterns
Visualizing Hitboxes
static mut SHOW_HITBOXES: u8 = 0;
fn init() {
unsafe {
debug_register_bool(b"Hitboxes".as_ptr(), 8, &SHOW_HITBOXES);
}
}
fn render() {
unsafe {
if SHOW_HITBOXES != 0 {
for entity in entities.iter() {
draw_rect(
entity.x - entity.half_w,
entity.y - entity.half_h,
entity.half_w * 2.0,
entity.half_h * 2.0,
0x00FF0080,
);
}
}
}
}
Debug HUD
fn render() {
unsafe {
let frame = tick_count();
draw_text_str(&format!("Frame: {}", frame), 10.0, 10.0, 16.0, 0xFFFFFFFF);
}
}
Slow Motion for Animation Debugging
Use time scale controls (F7/F8) to:
- Slow animations to 0.25x
- Check interpolation smoothness
- Verify state transitions
- Frame-step with F6 for exact positioning
Debugging Desyncs
When --sync-test reports a desync:
- Check random usage - Every
random() call must happen in same order
- Verify state location - All game state in static variables?
- Review render() - No state modifications allowed
- Check floating point - Consistent operations across runs
- Inspect conditionals - Same branches taken each simulation
Add debug watches to narrow down which variable diverges first.
Helper Functions
Rust helper functions in zx.rs:
debug_f32("speed", &SPEED);
debug_i32("count", &COUNT);
debug_bool("enabled", &ENABLED);
debug_group("Player");
debug_group_close();
Available Registration Functions
| Function | Type | UI Control |
|---|
debug_register_i8/i16/i32 | Signed integers | Number input |
debug_register_u8/u16/u32 | Unsigned integers | Number input |
debug_register_f32 | Float | Number input |
debug_register_bool | Boolean (u8) | Checkbox |
debug_register_*_range | Numbers | Slider |
debug_register_vec2/vec3 | Float arrays | Multi-input |
debug_register_rect | 4 i16 | Multi-input |
debug_register_color | 4 u8 RGBA | Color picker |
debug_register_fixed_* | Fixed-point | Decimal display |
Performance Notes
- Debug functions compile to no-ops in release builds
- Registration happens once in
init(), not per-frame
- Watches read memory directly, no runtime cost
- F4 panel rendering uses separate draw path
Additional Resources
examples/debug-setup-rust.md - Complete Rust debug setup
references/sync-test-checklist.md - Desync debugging checklist
nethercore/include/zx.rs - Debug FFI (lines 1370-1478)
nethercore/docs/book/src/api/debug.md - Full API reference