diff --git a/crates/client/src/debug.rs b/crates/client/src/debug.rs index 722de16..fa31a8c 100644 --- a/crates/client/src/debug.rs +++ b/crates/client/src/debug.rs @@ -6,6 +6,7 @@ //! //! - **F1 + V**: filled terrain with vertex points overlaid, showing where the mesher placed geometry without losing the surface. //! - **F1 + B**: filled terrain with the triangle edges overlaid, showing the size and shape of the emitted quads. +//! - **F1 + I**: the debug statistics panel. //! //! Holding a [`SOLO_MODIFIER`] (either Shift) as well drops the filled pass, leaving the debug geometry alone against the clear colour: **F1 + Shift + V** for points only, **F1 + Shift + B** for wireframe only. //! @@ -20,6 +21,13 @@ const DEBUG_MODIFIER: KeyCode = KeyCode::F1; /// The keys that, held alongside [`DEBUG_MODIFIER`], select the solo form of a debug view. Both shifts are accepted so the chord is reachable with either hand. const SOLO_MODIFIER: [KeyCode; 2] = [KeyCode::ShiftLeft, KeyCode::ShiftRight]; +const VERTEX_POINTS_OVERLAY_KEY: KeyCode = KeyCode::KeyV; + +const WIREFRAME_OVERLAY_KEY: KeyCode = KeyCode::KeyB; + +/// The key that, held alongside [`DEBUG_MODIFIER`], toggles the statistics panel. +const STATS_KEY: KeyCode = KeyCode::KeyI; + /// A debug operation requested by the input layer, applied by the caller. /// /// The layer deliberately returns an intent rather than acting directly, so it owns no renderer or window handles and stays a pure function of key events. @@ -27,6 +35,8 @@ const SOLO_MODIFIER: [KeyCode; 2] = [KeyCode::ShiftLeft, KeyCode::ShiftRight]; pub(crate) enum DebugAction { /// Applies the given rasterisation mode to the renderer. SetRenderMode(RenderMode), + /// Enables or disables emission of the statistics panel. + SetStatsOverlay(bool), } /// Owns debug-only input state and translates key events into [`DebugAction`]s. @@ -38,6 +48,8 @@ pub(crate) struct DebugControls { solo_held: bool, /// The rasterisation mode most recently requested, used to make each chord a toggle back to [`RenderMode::Filled`]. render_mode: RenderMode, + /// Whether the statistics panel is being emitted. + stats_enabled: bool, } impl DebugControls { @@ -60,6 +72,12 @@ impl DebugControls { return None; } + // The statistics chord is resolved before the raster table so the two axes never contend for a key. The solo modifier selects between overlaid and standalone geometry and has no meaning for a panel that draws none, so it is ignored here. + if code == STATS_KEY { + self.stats_enabled = !self.stats_enabled; + return Some(DebugAction::SetStatsOverlay(self.stats_enabled)); + } + let requested = render_mode_for_key(code, self.solo_held)?; // Re-pressing the chord for the active mode returns to the normal path, so a single chord both enables and disables its mode. @@ -78,10 +96,10 @@ impl DebugControls { /// This is the single table a new rasterisation debug mode is added to: one key, one overlaid form, one solo form. const fn render_mode_for_key(code: KeyCode, solo: bool) -> Option { match (code, solo) { - (KeyCode::KeyV, false) => Some(RenderMode::FilledPoints), - (KeyCode::KeyV, true) => Some(RenderMode::Points), - (KeyCode::KeyB, false) => Some(RenderMode::FilledWireframe), - (KeyCode::KeyB, true) => Some(RenderMode::Wireframe), + (VERTEX_POINTS_OVERLAY_KEY, false) => Some(RenderMode::FilledPoints), + (VERTEX_POINTS_OVERLAY_KEY, true) => Some(RenderMode::Points), + (WIREFRAME_OVERLAY_KEY, false) => Some(RenderMode::FilledWireframe), + (WIREFRAME_OVERLAY_KEY, true) => Some(RenderMode::Wireframe), _ => None, } } diff --git a/crates/client/src/main.rs b/crates/client/src/main.rs index a6f8d09..7f5475f 100644 --- a/crates/client/src/main.rs +++ b/crates/client/src/main.rs @@ -72,6 +72,8 @@ struct App { last_center: Option, /// Streams chunk meshes in and out around the camera. `None` until the renderer is initialised on resume. chunks: Option, + /// Whether the debug statistics panel is being emitted. Collection is unconditional; only emission is gated on this. + stats_overlay: bool, } impl Default for App { @@ -92,6 +94,7 @@ impl Default for App { connected: false, last_center: None, chunks: None, + stats_overlay: false, } } } @@ -108,6 +111,10 @@ impl App { info!(?mode, "render mode toggled"); } } + debug::DebugAction::SetStatsOverlay(enabled) => { + self.stats_overlay = enabled; + info!(enabled, "statistics overlay toggled"); + } } } } diff --git a/crates/client/src/tests/debug.rs b/crates/client/src/tests/debug.rs index e02b0fc..a3e50e0 100644 --- a/crates/client/src/tests/debug.rs +++ b/crates/client/src/tests/debug.rs @@ -141,3 +141,62 @@ fn solo_modifier_alone_produces_no_action() { assert_eq!(controls.handle_key(KeyCode::ShiftLeft, true), None); assert_eq!(tap(&mut controls, KeyCode::KeyV), None); } + +#[test] +fn the_stats_chord_toggles_the_overlay_on_and_off() { + let mut controls = DebugControls::default(); + controls.handle_key(DEBUG_MODIFIER, true); + + assert_eq!( + tap(&mut controls, STATS_KEY), + Some(DebugAction::SetStatsOverlay(true)) + ); + assert_eq!( + tap(&mut controls, STATS_KEY), + Some(DebugAction::SetStatsOverlay(false)) + ); +} + +#[test] +fn the_stats_chord_leaves_the_render_mode_untouched() { + let mut controls = DebugControls::default(); + controls.handle_key(DEBUG_MODIFIER, true); + tap(&mut controls, KeyCode::KeyV); + + tap(&mut controls, STATS_KEY); + + // The raster axis must survive a toggle on the statistics axis; a single-enum design would have reset it. + assert_eq!(controls.render_mode, RenderMode::FilledPoints); +} + +#[test] +fn a_raster_chord_leaves_the_stats_overlay_untouched() { + let mut controls = DebugControls::default(); + controls.handle_key(DEBUG_MODIFIER, true); + tap(&mut controls, STATS_KEY); + + assert_eq!( + tap(&mut controls, KeyCode::KeyB), + Some(DebugAction::SetRenderMode(RenderMode::FilledWireframe)) + ); + assert!(controls.stats_enabled); +} + +#[test] +fn the_solo_modifier_does_not_change_the_stats_chord() { + let mut controls = DebugControls::default(); + controls.handle_key(DEBUG_MODIFIER, true); + controls.handle_key(KeyCode::ShiftLeft, true); + + assert_eq!( + tap(&mut controls, STATS_KEY), + Some(DebugAction::SetStatsOverlay(true)) + ); +} + +#[test] +fn the_stats_chord_requires_the_debug_modifier() { + let mut controls = DebugControls::default(); + assert_eq!(tap(&mut controls, STATS_KEY), None); + assert!(!controls.stats_enabled); +}