Awesome-omni-skill Debugging Guide
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.
git clone https://github.com/diegosouzapw/awesome-omni-skill
T=$(mktemp -d) && git clone --depth=1 https://github.com/diegosouzapw/awesome-omni-skill "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/development/debugging-guide" ~/.claude/skills/diegosouzapw-awesome-omni-skill-debugging-guide && rm -rf "$T"
skills/development/debugging-guide/SKILL.mdDebugging 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 { // Basic registration 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(), 13, &SHOW_HITBOXES); debug_group_end(); } }
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 { // Skip update when paused via debug panel if debug_is_paused() != 0 { return; } // Apply time scale to delta_time let dt = delta_time() * debug_get_time_scale(); // Use scaled dt for all time-based logic 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
instead ofrender()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 { // Draw wireframe boxes around collision volumes 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, // Semi-transparent green ); } } } }
Debug HUD
fn render() { unsafe { // Show frame counter and FPS let frame = tick_count(); // Draw debug text at top-left 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
call must happen in same orderrandom() - 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:
// String-based helpers debug_f32("speed", &SPEED); // debug_register_f32 wrapper debug_i32("count", &COUNT); // debug_register_i32 wrapper debug_bool("enabled", &ENABLED); // debug_register_bool wrapper debug_group("Player"); // debug_group_begin wrapper debug_group_close(); // debug_group_end wrapper
Available Registration Functions
| Function | Type | UI Control |
|---|---|---|
| Signed integers | Number input |
| Unsigned integers | Number input |
| Float | Number input |
| Boolean (u8) | Checkbox |
| Numbers | Slider |
| Float arrays | Multi-input |
| 4 i16 | Multi-input |
| 4 u8 RGBA | Color picker |
| Fixed-point | Decimal display |
Performance Notes
- Debug functions compile to no-ops in release builds
- Registration happens once in
, not per-frameinit() - Watches read memory directly, no runtime cost
- F4 panel rendering uses separate draw path
Additional Resources
- Complete Rust debug setupexamples/debug-setup-rust.md
- Desync debugging checklistreferences/sync-test-checklist.md
- Debug FFI (lines 1370-1478)nethercore/include/zx.rs
- Full API referencenethercore/docs/book/src/api/debug.md