Skip to main content

proof_engine/debug/
inspector.rs

1//! Runtime inspector, variable monitor, and in-game command registry.
2//!
3//! The inspector provides:
4//! - `InspectorValue` — a tagged-union value type for inspector fields
5//! - `Inspectable` trait — objects that can expose and receive inspector fields
6//! - `RuntimeInspector` — a registry of named inspectable objects
7//! - `InspectorWatcher` — polls for field changes between frames
8//! - `VariableMonitor` — floating debug table of per-frame named values
9//! - `CommandRegistry` — simple in-game console command registry
10
11use std::any::Any;
12use std::collections::HashMap;
13
14// ── InspectorValue ────────────────────────────────────────────────────────────
15
16/// A dynamically-typed value for use in the inspector UI.
17#[derive(Debug, Clone, PartialEq)]
18pub enum InspectorValue {
19    Bool(bool),
20    Int(i64),
21    Float(f64),
22    String(String),
23    Vec2([f32; 2]),
24    Vec3([f32; 3]),
25    Vec4([f32; 4]),
26    /// RGBA color in 0–1 range.
27    Color([f32; 4]),
28    List(Vec<InspectorValue>),
29    Map(HashMap<String, InspectorValue>),
30    Enum { variant: String, variants: Vec<String> },
31}
32
33impl InspectorValue {
34    /// Returns `true` if this value is a numeric type.
35    pub fn is_numeric(&self) -> bool {
36        matches!(self, InspectorValue::Int(_) | InspectorValue::Float(_))
37    }
38
39    /// Coerce to f64, returning `None` if not numeric.
40    pub fn as_f64(&self) -> Option<f64> {
41        match self {
42            InspectorValue::Int(v)   => Some(*v as f64),
43            InspectorValue::Float(v) => Some(*v),
44            _                        => None,
45        }
46    }
47
48    /// Coerce to i64, returning `None` if not numeric.
49    pub fn as_i64(&self) -> Option<i64> {
50        match self {
51            InspectorValue::Int(v)   => Some(*v),
52            InspectorValue::Float(v) => Some(*v as i64),
53            _                        => None,
54        }
55    }
56
57    /// Return a human-readable short representation.
58    pub fn display(&self) -> String {
59        match self {
60            InspectorValue::Bool(v)     => v.to_string(),
61            InspectorValue::Int(v)      => v.to_string(),
62            InspectorValue::Float(v)    => format!("{:.4}", v),
63            InspectorValue::String(v)   => v.clone(),
64            InspectorValue::Vec2(v)     => format!("[{:.3}, {:.3}]", v[0], v[1]),
65            InspectorValue::Vec3(v)     => format!("[{:.3}, {:.3}, {:.3}]", v[0], v[1], v[2]),
66            InspectorValue::Vec4(v)     => format!("[{:.3}, {:.3}, {:.3}, {:.3}]", v[0], v[1], v[2], v[3]),
67            InspectorValue::Color(v)    => format!("rgba({:.2},{:.2},{:.2},{:.2})", v[0], v[1], v[2], v[3]),
68            InspectorValue::List(v)     => format!("[{}]", v.len()),
69            InspectorValue::Map(v)      => format!("{{{}}}", v.len()),
70            InspectorValue::Enum { variant, .. } => variant.clone(),
71        }
72    }
73
74    /// Type name as a &str for display purposes.
75    pub fn type_name(&self) -> &'static str {
76        match self {
77            InspectorValue::Bool(_)    => "bool",
78            InspectorValue::Int(_)     => "int",
79            InspectorValue::Float(_)   => "float",
80            InspectorValue::String(_)  => "string",
81            InspectorValue::Vec2(_)    => "vec2",
82            InspectorValue::Vec3(_)    => "vec3",
83            InspectorValue::Vec4(_)    => "vec4",
84            InspectorValue::Color(_)   => "color",
85            InspectorValue::List(_)    => "list",
86            InspectorValue::Map(_)     => "map",
87            InspectorValue::Enum { .. } => "enum",
88        }
89    }
90}
91
92impl Default for InspectorValue {
93    fn default() -> Self { InspectorValue::Int(0) }
94}
95
96impl From<bool>   for InspectorValue { fn from(v: bool)   -> Self { InspectorValue::Bool(v) } }
97impl From<i32>    for InspectorValue { fn from(v: i32)    -> Self { InspectorValue::Int(v as i64) } }
98impl From<i64>    for InspectorValue { fn from(v: i64)    -> Self { InspectorValue::Int(v) } }
99impl From<f32>    for InspectorValue { fn from(v: f32)    -> Self { InspectorValue::Float(v as f64) } }
100impl From<f64>    for InspectorValue { fn from(v: f64)    -> Self { InspectorValue::Float(v) } }
101impl From<String> for InspectorValue { fn from(v: String) -> Self { InspectorValue::String(v) } }
102impl From<&str>   for InspectorValue { fn from(v: &str)   -> Self { InspectorValue::String(v.to_owned()) } }
103
104// ── InspectorField ────────────────────────────────────────────────────────────
105
106/// A single named field exposed by an `Inspectable` object.
107#[derive(Debug, Clone)]
108pub struct InspectorField {
109    /// Field name / key.
110    pub name:     String,
111    /// Current value.
112    pub value:    InspectorValue,
113    /// Whether the field can be modified via `apply_changes`.
114    pub editable: bool,
115    /// Optional tooltip string.
116    pub tooltip:  Option<String>,
117    /// Numeric range (min, max) for clamping UI sliders.
118    pub range:    Option<(f64, f64)>,
119    /// Step size for numeric increment/decrement.
120    pub step:     Option<f64>,
121}
122
123impl InspectorField {
124    pub fn new(name: impl Into<String>, value: InspectorValue) -> Self {
125        Self {
126            name:     name.into(),
127            value,
128            editable: false,
129            tooltip:  None,
130            range:    None,
131            step:     None,
132        }
133    }
134
135    pub fn editable(mut self) -> Self { self.editable = true; self }
136    pub fn with_tooltip(mut self, tip: impl Into<String>) -> Self { self.tooltip = Some(tip.into()); self }
137    pub fn with_range(mut self, min: f64, max: f64) -> Self { self.range = Some((min, max)); self }
138    pub fn with_step(mut self, step: f64) -> Self { self.step = Some(step); self }
139
140    pub fn readonly(name: impl Into<String>, value: InspectorValue) -> Self {
141        Self::new(name, value)
142    }
143
144    pub fn read_write(name: impl Into<String>, value: InspectorValue) -> Self {
145        Self::new(name, value).editable()
146    }
147}
148
149// ── Inspectable ───────────────────────────────────────────────────────────────
150
151/// Objects that can expose their fields to the runtime inspector.
152pub trait Inspectable {
153    /// Return a list of inspector fields describing this object's current state.
154    fn inspect(&self) -> Vec<InspectorField>;
155
156    /// Apply a list of (name, new_value) changes from the inspector.
157    fn apply_changes(&mut self, changes: Vec<(String, InspectorValue)>);
158}
159
160// ── Inspectable impls ─────────────────────────────────────────────────────────
161
162impl Inspectable for f32 {
163    fn inspect(&self) -> Vec<InspectorField> {
164        vec![InspectorField::read_write("value", InspectorValue::Float(*self as f64))
165            .with_step(0.01).with_range(f64::NEG_INFINITY, f64::INFINITY)]
166    }
167    fn apply_changes(&mut self, changes: Vec<(String, InspectorValue)>) {
168        for (name, val) in changes {
169            if name == "value" {
170                if let Some(v) = val.as_f64() { *self = v as f32; }
171            }
172        }
173    }
174}
175
176impl Inspectable for f64 {
177    fn inspect(&self) -> Vec<InspectorField> {
178        vec![InspectorField::read_write("value", InspectorValue::Float(*self))
179            .with_step(0.001)]
180    }
181    fn apply_changes(&mut self, changes: Vec<(String, InspectorValue)>) {
182        for (name, val) in changes {
183            if name == "value" {
184                if let Some(v) = val.as_f64() { *self = v; }
185            }
186        }
187    }
188}
189
190impl Inspectable for i32 {
191    fn inspect(&self) -> Vec<InspectorField> {
192        vec![InspectorField::read_write("value", InspectorValue::Int(*self as i64))
193            .with_step(1.0)]
194    }
195    fn apply_changes(&mut self, changes: Vec<(String, InspectorValue)>) {
196        for (name, val) in changes {
197            if name == "value" {
198                if let Some(v) = val.as_i64() { *self = v as i32; }
199            }
200        }
201    }
202}
203
204impl Inspectable for i64 {
205    fn inspect(&self) -> Vec<InspectorField> {
206        vec![InspectorField::read_write("value", InspectorValue::Int(*self))
207            .with_step(1.0)]
208    }
209    fn apply_changes(&mut self, changes: Vec<(String, InspectorValue)>) {
210        for (name, val) in changes {
211            if name == "value" {
212                if let Some(v) = val.as_i64() { *self = v; }
213            }
214        }
215    }
216}
217
218impl Inspectable for bool {
219    fn inspect(&self) -> Vec<InspectorField> {
220        vec![InspectorField::read_write("value", InspectorValue::Bool(*self))]
221    }
222    fn apply_changes(&mut self, changes: Vec<(String, InspectorValue)>) {
223        for (name, val) in changes {
224            if name == "value" {
225                if let InspectorValue::Bool(v) = val { *self = v; }
226            }
227        }
228    }
229}
230
231impl Inspectable for String {
232    fn inspect(&self) -> Vec<InspectorField> {
233        vec![InspectorField::read_write("value", InspectorValue::String(self.clone()))]
234    }
235    fn apply_changes(&mut self, changes: Vec<(String, InspectorValue)>) {
236        for (name, val) in changes {
237            if name == "value" {
238                if let InspectorValue::String(v) = val { *self = v; }
239            }
240        }
241    }
242}
243
244/// A 2-component float vector.
245#[derive(Debug, Clone, Copy, PartialEq, Default)]
246pub struct Vec2([f32; 2]);
247
248impl Vec2 {
249    pub fn new(x: f32, y: f32) -> Self { Self([x, y]) }
250    pub fn x(&self) -> f32 { self.0[0] }
251    pub fn y(&self) -> f32 { self.0[1] }
252}
253
254impl Inspectable for Vec2 {
255    fn inspect(&self) -> Vec<InspectorField> {
256        vec![InspectorField::read_write("value", InspectorValue::Vec2(self.0)).with_step(0.01)]
257    }
258    fn apply_changes(&mut self, changes: Vec<(String, InspectorValue)>) {
259        for (name, val) in changes {
260            if name == "value" {
261                if let InspectorValue::Vec2(v) = val { self.0 = v; }
262            }
263        }
264    }
265}
266
267/// A 3-component float vector.
268#[derive(Debug, Clone, Copy, PartialEq, Default)]
269pub struct Vec3([f32; 3]);
270
271impl Vec3 {
272    pub fn new(x: f32, y: f32, z: f32) -> Self { Self([x, y, z]) }
273    pub fn x(&self) -> f32 { self.0[0] }
274    pub fn y(&self) -> f32 { self.0[1] }
275    pub fn z(&self) -> f32 { self.0[2] }
276}
277
278impl Inspectable for Vec3 {
279    fn inspect(&self) -> Vec<InspectorField> {
280        vec![InspectorField::read_write("value", InspectorValue::Vec3(self.0)).with_step(0.01)]
281    }
282    fn apply_changes(&mut self, changes: Vec<(String, InspectorValue)>) {
283        for (name, val) in changes {
284            if name == "value" {
285                if let InspectorValue::Vec3(v) = val { self.0 = v; }
286            }
287        }
288    }
289}
290
291/// A 4-component float vector.
292#[derive(Debug, Clone, Copy, PartialEq, Default)]
293pub struct Vec4([f32; 4]);
294
295impl Vec4 {
296    pub fn new(x: f32, y: f32, z: f32, w: f32) -> Self { Self([x, y, z, w]) }
297}
298
299impl Inspectable for Vec4 {
300    fn inspect(&self) -> Vec<InspectorField> {
301        vec![InspectorField::read_write("value", InspectorValue::Vec4(self.0)).with_step(0.01)]
302    }
303    fn apply_changes(&mut self, changes: Vec<(String, InspectorValue)>) {
304        for (name, val) in changes {
305            if name == "value" {
306                if let InspectorValue::Vec4(v) = val { self.0 = v; }
307            }
308        }
309    }
310}
311
312impl Inspectable for [f32; 3] {
313    fn inspect(&self) -> Vec<InspectorField> {
314        vec![InspectorField::read_write("value", InspectorValue::Vec3(*self)).with_step(0.01)]
315    }
316    fn apply_changes(&mut self, changes: Vec<(String, InspectorValue)>) {
317        for (name, val) in changes {
318            if name == "value" {
319                if let InspectorValue::Vec3(v) = val { *self = v; }
320            }
321        }
322    }
323}
324
325impl Inspectable for [f32; 4] {
326    fn inspect(&self) -> Vec<InspectorField> {
327        vec![InspectorField::read_write("value", InspectorValue::Vec4(*self)).with_step(0.01)]
328    }
329    fn apply_changes(&mut self, changes: Vec<(String, InspectorValue)>) {
330        for (name, val) in changes {
331            if name == "value" {
332                if let InspectorValue::Vec4(v) = val { *self = v; }
333            }
334        }
335    }
336}
337
338// ── InspectorWatcher ──────────────────────────────────────────────────────────
339
340/// Stores last-seen field values for a registered object and detects changes.
341pub struct InspectorWatcher {
342    pub object_name: String,
343    last_seen: HashMap<String, InspectorValue>,
344}
345
346impl InspectorWatcher {
347    pub fn new(object_name: impl Into<String>) -> Self {
348        Self { object_name: object_name.into(), last_seen: HashMap::new() }
349    }
350
351    /// Update the watcher with the current fields and return a list of
352    /// `(field_name, old_value, new_value)` tuples for any changed fields.
353    pub fn check_changes(&mut self, current_fields: &[InspectorField])
354        -> Vec<(String, InspectorValue, InspectorValue)>
355    {
356        let mut changes = Vec::new();
357
358        for field in current_fields {
359            match self.last_seen.get(&field.name) {
360                Some(old) if old != &field.value => {
361                    changes.push((field.name.clone(), old.clone(), field.value.clone()));
362                    self.last_seen.insert(field.name.clone(), field.value.clone());
363                }
364                None => {
365                    // First observation — not a "change", just record it.
366                    self.last_seen.insert(field.name.clone(), field.value.clone());
367                }
368                _ => {}
369            }
370        }
371
372        changes
373    }
374
375    /// Force-record all current fields without reporting changes.
376    pub fn reset(&mut self, fields: &[InspectorField]) {
377        self.last_seen.clear();
378        for f in fields {
379            self.last_seen.insert(f.name.clone(), f.value.clone());
380        }
381    }
382
383    pub fn field_count(&self) -> usize { self.last_seen.len() }
384}
385
386// ── ObjectEntry ───────────────────────────────────────────────────────────────
387
388/// Wraps a boxed `Any` alongside a function that can produce inspector fields
389/// from it, since we can't store `dyn Inspectable` directly with `Any`.
390struct ObjectEntry {
391    data:    Box<dyn Any>,
392    inspect: Box<dyn Fn(&dyn Any) -> Vec<InspectorField>>,
393    apply:   Box<dyn Fn(&mut dyn Any, Vec<(String, InspectorValue)>)>,
394}
395
396// ── RuntimeInspector ──────────────────────────────────────────────────────────
397
398/// Registry of named runtime objects that can be inspected and modified.
399///
400/// Objects are stored as type-erased `Any`; the inspector functions are
401/// stored as closures that know the concrete type at registration time.
402pub struct RuntimeInspector {
403    objects: HashMap<String, ObjectEntry>,
404    /// Insertion order for deterministic display.
405    order:   Vec<String>,
406}
407
408impl RuntimeInspector {
409    pub fn new() -> Self {
410        Self { objects: HashMap::new(), order: Vec::new() }
411    }
412
413    /// Register an `Inspectable` object under `name`.
414    ///
415    /// The object is cloned into the registry.  Mutations via `apply` modify
416    /// the stored copy; use `get_object::<T>()` to retrieve the current value.
417    pub fn register<T>(&mut self, name: impl Into<String>, obj: T)
418    where
419        T: Inspectable + Any + 'static,
420    {
421        let name = name.into();
422        if !self.objects.contains_key(&name) {
423            self.order.push(name.clone());
424        }
425        let entry = ObjectEntry {
426            data: Box::new(obj),
427            inspect: Box::new(|any| {
428                if let Some(v) = any.downcast_ref::<T>() {
429                    v.inspect()
430                } else {
431                    Vec::new()
432                }
433            }),
434            apply: Box::new(|any, changes| {
435                if let Some(v) = any.downcast_mut::<T>() {
436                    v.apply_changes(changes);
437                }
438            }),
439        };
440        self.objects.insert(name, entry);
441    }
442
443    /// Return the inspector fields for the named object, or `None` if not found.
444    pub fn inspect(&self, name: &str) -> Option<Vec<InspectorField>> {
445        self.objects.get(name).map(|e| (e.inspect)(e.data.as_ref()))
446    }
447
448    /// Apply changes to the named object.
449    pub fn apply(&mut self, name: &str, changes: Vec<(String, InspectorValue)>) {
450        if let Some(e) = self.objects.get_mut(name) {
451            (e.apply)(e.data.as_mut(), changes);
452        }
453    }
454
455    /// Create an `InspectorWatcher` for the named object.
456    ///
457    /// The watcher is seeded with the current field values.
458    pub fn watch(&self, name: &str) -> Option<InspectorWatcher> {
459        let fields = self.inspect(name)?;
460        let mut watcher = InspectorWatcher::new(name);
461        watcher.reset(&fields);
462        Some(watcher)
463    }
464
465    /// Get a reference to the stored object as type `T`.
466    pub fn get_object<T: Any>(&self, name: &str) -> Option<&T> {
467        self.objects.get(name)?.data.downcast_ref::<T>()
468    }
469
470    /// Get a mutable reference to the stored object as type `T`.
471    pub fn get_object_mut<T: Any>(&mut self, name: &str) -> Option<&mut T> {
472        self.objects.get_mut(name)?.data.downcast_mut::<T>()
473    }
474
475    /// Remove an object from the registry.
476    pub fn unregister(&mut self, name: &str) {
477        self.objects.remove(name);
478        self.order.retain(|n| n != name);
479    }
480
481    /// Iterate over all registered object names in insertion order.
482    pub fn names(&self) -> &[String] { &self.order }
483
484    /// Total number of registered objects.
485    pub fn len(&self) -> usize { self.objects.len() }
486
487    pub fn is_empty(&self) -> bool { self.objects.is_empty() }
488
489    /// Render a simple text table of all registered objects and their fields.
490    pub fn format_table(&self) -> String {
491        let mut lines = vec!["=== Runtime Inspector ===".to_owned()];
492        for name in &self.order {
493            lines.push(format!("  [{}]", name));
494            if let Some(fields) = self.inspect(name) {
495                for f in fields {
496                    let rw = if f.editable { "rw" } else { "ro" };
497                    lines.push(format!("    {:20} ({:3}) = {}", f.name, rw, f.value.display()));
498                }
499            }
500        }
501        lines.join("\n")
502    }
503}
504
505impl Default for RuntimeInspector {
506    fn default() -> Self { Self::new() }
507}
508
509// ── VariableMonitor ───────────────────────────────────────────────────────────
510
511/// A lightweight per-frame named-variable display.
512///
513/// Call `set(name, value)` each frame; `render()` produces a formatted
514/// table string showing the current values.
515pub struct VariableMonitor {
516    vars:    HashMap<String, MonitorVar>,
517    order:   Vec<String>,
518    title:   String,
519    max_key_width: usize,
520}
521
522#[derive(Debug, Clone)]
523struct MonitorVar {
524    value:    InspectorValue,
525    category: Option<String>,
526    color:    Option<String>,
527}
528
529impl VariableMonitor {
530    pub fn new() -> Self {
531        Self {
532            vars:          HashMap::new(),
533            order:         Vec::new(),
534            title:         "Variables".to_owned(),
535            max_key_width: 0,
536        }
537    }
538
539    pub fn with_title(mut self, title: impl Into<String>) -> Self {
540        self.title = title.into();
541        self
542    }
543
544    /// Set or update the named variable.
545    pub fn set(&mut self, name: impl Into<String>, value: impl Into<InspectorValue>) {
546        let name = name.into();
547        let value = value.into();
548        if !self.vars.contains_key(&name) {
549            self.order.push(name.clone());
550        }
551        self.max_key_width = self.max_key_width.max(name.len());
552        self.vars.insert(name, MonitorVar { value, category: None, color: None });
553    }
554
555    /// Set a variable with a category tag.
556    pub fn set_categorized(&mut self, name: impl Into<String>, value: impl Into<InspectorValue>, category: impl Into<String>) {
557        let name     = name.into();
558        let value    = value.into();
559        let category = category.into();
560        if !self.vars.contains_key(&name) {
561            self.order.push(name.clone());
562        }
563        self.max_key_width = self.max_key_width.max(name.len());
564        self.vars.insert(name, MonitorVar { value, category: Some(category), color: None });
565    }
566
567    /// Remove a named variable.
568    pub fn remove(&mut self, name: &str) {
569        self.vars.remove(name);
570        self.order.retain(|n| n != name);
571        self.max_key_width = self.order.iter()
572            .map(|n| n.len())
573            .max()
574            .unwrap_or(0);
575    }
576
577    /// Clear all variables.
578    pub fn clear(&mut self) {
579        self.vars.clear();
580        self.order.clear();
581        self.max_key_width = 0;
582    }
583
584    /// Format all variables as a display table.
585    pub fn render(&self) -> String {
586        if self.vars.is_empty() { return format!("[ {} — (empty) ]", self.title); }
587
588        let kw = self.max_key_width.max(4);
589        let total = kw + 3 + 24; // key + " │ " + value
590        let border = format!("┌{}┐", "─".repeat(total + 2));
591        let title  = format!("│ {:<width$} │", self.title, width = total);
592        let sep    = format!("├{}┤", "─".repeat(total + 2));
593        let bottom = format!("└{}┘", "─".repeat(total + 2));
594
595        let mut lines = vec![border, title, sep];
596
597        // Group by category
598        let mut last_cat: Option<String> = None;
599        for name in &self.order {
600            if let Some(var) = self.vars.get(name) {
601                let cat = var.category.as_deref();
602                if cat != last_cat.as_deref() {
603                    if let Some(c) = cat {
604                        let cat_line = format!("│ {:─<width$} │", format!("─ {} ", c), width = total);
605                        lines.push(cat_line);
606                    }
607                    last_cat = cat.map(str::to_owned);
608                }
609                let val_str = var.value.display();
610                lines.push(format!("│ {:<kw$} │ {:<24} │", name, val_str, kw = kw));
611            }
612        }
613
614        lines.push(bottom);
615        lines.join("\n")
616    }
617
618    /// Number of variables registered.
619    pub fn len(&self) -> usize { self.vars.len() }
620    pub fn is_empty(&self) -> bool { self.vars.is_empty() }
621
622    /// Get the current value of a named variable.
623    pub fn get(&self, name: &str) -> Option<&InspectorValue> {
624        self.vars.get(name).map(|v| &v.value)
625    }
626}
627
628impl Default for VariableMonitor {
629    fn default() -> Self { Self::new() }
630}
631
632// ── CommandRegistry ───────────────────────────────────────────────────────────
633
634/// A simple in-game console command registry.
635///
636/// Commands are registered with a name, description, and a handler function
637/// that accepts `Vec<String>` args and returns a `String` result (or an error
638/// string).
639pub struct CommandRegistry {
640    commands: Vec<RegisteredCommand>,
641}
642
643struct RegisteredCommand {
644    name:        String,
645    description: String,
646    aliases:     Vec<String>,
647    handler:     Box<dyn Fn(Vec<String>) -> String + Send + Sync>,
648}
649
650impl CommandRegistry {
651    pub fn new() -> Self {
652        let mut reg = Self { commands: Vec::new() };
653        reg.register_builtins();
654        reg
655    }
656
657    fn register_builtins(&mut self) {
658        self.register(
659            "help",
660            "List all available commands",
661            Box::new(|_args| "Available commands: help, echo, version, clear".to_owned()),
662        );
663        self.register(
664            "echo",
665            "Echo arguments back",
666            Box::new(|args| args.join(" ")),
667        );
668        self.register(
669            "version",
670            "Print engine version",
671            Box::new(|_args| "Proof Engine v0.1.0".to_owned()),
672        );
673    }
674
675    /// Register a command with a name, description, and handler.
676    pub fn register(
677        &mut self,
678        name:        impl Into<String>,
679        description: impl Into<String>,
680        handler:     Box<dyn Fn(Vec<String>) -> String + Send + Sync>,
681    ) {
682        let name = name.into();
683        // Replace if already exists
684        self.commands.retain(|c| c.name != name);
685        self.commands.push(RegisteredCommand {
686            name,
687            description: description.into(),
688            aliases: Vec::new(),
689            handler,
690        });
691    }
692
693    /// Add an alias for an existing command.
694    pub fn add_alias(&mut self, name: &str, alias: impl Into<String>) {
695        if let Some(cmd) = self.commands.iter_mut().find(|c| c.name == name) {
696            cmd.aliases.push(alias.into());
697        }
698    }
699
700    /// Unregister a command by name.
701    pub fn unregister(&mut self, name: &str) {
702        self.commands.retain(|c| c.name != name);
703    }
704
705    /// Execute a raw input string.  Parses the first token as the command name
706    /// and the remainder as space-separated arguments.
707    pub fn execute(&self, input: &str) -> Result<String, String> {
708        let tokens = tokenize(input);
709        if tokens.is_empty() { return Ok(String::new()); }
710
711        let cmd_name = &tokens[0];
712        let args: Vec<String> = tokens[1..].to_vec();
713
714        // Match by name or alias
715        let cmd = self.commands.iter().find(|c| {
716            c.name == *cmd_name || c.aliases.iter().any(|a| a == cmd_name)
717        });
718
719        match cmd {
720            Some(c) => Ok((c.handler)(args)),
721            None    => Err(format!("Unknown command: '{}'. Type 'help' for a list.", cmd_name)),
722        }
723    }
724
725    /// Return all command names that start with `prefix`, sorted.
726    pub fn completions(&self, prefix: &str) -> Vec<String> {
727        let mut matches: Vec<String> = self.commands.iter()
728            .flat_map(|c| {
729                let mut names: Vec<String> = std::iter::once(c.name.clone())
730                    .chain(c.aliases.iter().cloned())
731                    .collect();
732                names.retain(|n| n.starts_with(prefix));
733                names
734            })
735            .collect();
736        matches.sort();
737        matches.dedup();
738        matches
739    }
740
741    /// Return a formatted help string for all registered commands.
742    pub fn help_text(&self) -> String {
743        let mut lines = Vec::new();
744        for c in &self.commands {
745            let aliases = if c.aliases.is_empty() {
746                String::new()
747            } else {
748                format!(" (aliases: {})", c.aliases.join(", "))
749            };
750            lines.push(format!("  {:16} — {}{}", c.name, c.description, aliases));
751        }
752        lines.join("\n")
753    }
754
755    /// Number of registered commands.
756    pub fn len(&self) -> usize { self.commands.len() }
757    pub fn is_empty(&self) -> bool { self.commands.is_empty() }
758}
759
760impl Default for CommandRegistry {
761    fn default() -> Self { Self::new() }
762}
763
764/// Tokenize a command line, respecting quoted strings.
765fn tokenize(input: &str) -> Vec<String> {
766    let mut tokens  = Vec::new();
767    let mut current = String::new();
768    let mut in_quote = false;
769    let mut quote_ch = '"';
770
771    for c in input.chars() {
772        match c {
773            '"' | '\'' if !in_quote => { in_quote = true; quote_ch = c; }
774            c if in_quote && c == quote_ch => { in_quote = false; }
775            ' ' | '\t' if !in_quote => {
776                if !current.is_empty() { tokens.push(std::mem::take(&mut current)); }
777            }
778            _ => current.push(c),
779        }
780    }
781    if !current.is_empty() { tokens.push(current); }
782    tokens
783}
784
785// ── FieldDiff ─────────────────────────────────────────────────────────────────
786
787/// A compact summary of differences between two field snapshots.
788#[derive(Debug, Clone)]
789pub struct FieldDiff {
790    pub field:     String,
791    pub old_value: InspectorValue,
792    pub new_value: InspectorValue,
793}
794
795impl FieldDiff {
796    pub fn compute(old: &[InspectorField], new: &[InspectorField]) -> Vec<FieldDiff> {
797        let old_map: HashMap<&str, &InspectorValue> = old.iter()
798            .map(|f| (f.name.as_str(), &f.value))
799            .collect();
800        let mut diffs = Vec::new();
801        for nf in new {
802            if let Some(&ov) = old_map.get(nf.name.as_str()) {
803                if ov != &nf.value {
804                    diffs.push(FieldDiff {
805                        field:     nf.name.clone(),
806                        old_value: ov.clone(),
807                        new_value: nf.value.clone(),
808                    });
809                }
810            }
811        }
812        diffs
813    }
814}
815
816// ── InspectorHistory ──────────────────────────────────────────────────────────
817
818/// Records inspector field snapshots over time for undo/redo capability.
819pub struct InspectorHistory {
820    object_name: String,
821    undo_stack:  Vec<Vec<InspectorField>>,
822    redo_stack:  Vec<Vec<InspectorField>>,
823    max_depth:   usize,
824}
825
826impl InspectorHistory {
827    pub fn new(object_name: impl Into<String>, max_depth: usize) -> Self {
828        Self {
829            object_name: object_name.into(),
830            undo_stack:  Vec::new(),
831            redo_stack:  Vec::new(),
832            max_depth:   max_depth.max(1),
833        }
834    }
835
836    /// Push a snapshot before making a change.
837    pub fn push_snapshot(&mut self, fields: Vec<InspectorField>) {
838        self.redo_stack.clear();
839        self.undo_stack.push(fields);
840        if self.undo_stack.len() > self.max_depth {
841            self.undo_stack.remove(0);
842        }
843    }
844
845    /// Pop the last snapshot for undo.
846    pub fn undo(&mut self) -> Option<Vec<InspectorField>> {
847        let snap = self.undo_stack.pop()?;
848        Some(snap)
849    }
850
851    /// Return true if undo is available.
852    pub fn can_undo(&self) -> bool { !self.undo_stack.is_empty() }
853
854    /// Number of undo steps available.
855    pub fn undo_depth(&self) -> usize { self.undo_stack.len() }
856
857    pub fn object_name(&self) -> &str { &self.object_name }
858}
859
860// ── tests ─────────────────────────────────────────────────────────────────────
861
862#[cfg(test)]
863mod tests {
864    use super::*;
865
866    #[test]
867    fn inspector_value_display() {
868        assert_eq!(InspectorValue::Bool(true).display(), "true");
869        assert_eq!(InspectorValue::Int(42).display(), "42");
870        assert_eq!(InspectorValue::String("hello".into()).display(), "hello");
871    }
872
873    #[test]
874    fn inspector_value_as_f64() {
875        assert_eq!(InspectorValue::Int(5).as_f64(), Some(5.0));
876        assert_eq!(InspectorValue::Float(3.14).as_f64(), Some(3.14));
877        assert_eq!(InspectorValue::Bool(true).as_f64(), None);
878    }
879
880    #[test]
881    fn inspectable_f32() {
882        let mut v: f32 = 1.0;
883        let fields = v.inspect();
884        assert_eq!(fields.len(), 1);
885        assert!(fields[0].editable);
886        v.apply_changes(vec![("value".to_owned(), InspectorValue::Float(2.5))]);
887        assert!((v - 2.5_f32).abs() < 1e-5);
888    }
889
890    #[test]
891    fn inspectable_bool() {
892        let mut b = false;
893        let fields = b.inspect();
894        assert_eq!(fields[0].value, InspectorValue::Bool(false));
895        b.apply_changes(vec![("value".to_owned(), InspectorValue::Bool(true))]);
896        assert!(b);
897    }
898
899    #[test]
900    fn inspectable_array3() {
901        let mut arr: [f32; 3] = [1.0, 2.0, 3.0];
902        let fields = arr.inspect();
903        assert_eq!(fields.len(), 1);
904        arr.apply_changes(vec![("value".to_owned(), InspectorValue::Vec3([4.0, 5.0, 6.0]))]);
905        assert_eq!(arr, [4.0, 5.0, 6.0]);
906    }
907
908    #[test]
909    fn runtime_inspector_register_inspect() {
910        let mut ri = RuntimeInspector::new();
911        ri.register("speed", 42.0_f32);
912        let fields = ri.inspect("speed").unwrap();
913        assert_eq!(fields.len(), 1);
914        if let InspectorValue::Float(v) = &fields[0].value {
915            assert!((*v - 42.0).abs() < 1e-5);
916        } else {
917            panic!("expected Float");
918        }
919    }
920
921    #[test]
922    fn runtime_inspector_apply() {
923        let mut ri = RuntimeInspector::new();
924        ri.register("hp", 100_i32);
925        ri.apply("hp", vec![("value".to_owned(), InspectorValue::Int(50))]);
926        let v = ri.get_object::<i32>("hp").copied().unwrap();
927        assert_eq!(v, 50);
928    }
929
930    #[test]
931    fn inspector_watcher_detects_changes() {
932        let mut watcher = InspectorWatcher::new("obj");
933        let fields1 = vec![
934            InspectorField::readonly("x", InspectorValue::Float(1.0)),
935        ];
936        // First call seeds the watcher; no changes returned.
937        let changes = watcher.check_changes(&fields1);
938        assert!(changes.is_empty(), "first check should not report changes");
939
940        let fields2 = vec![
941            InspectorField::readonly("x", InspectorValue::Float(2.0)),
942        ];
943        let changes = watcher.check_changes(&fields2);
944        assert_eq!(changes.len(), 1);
945        assert_eq!(changes[0].0, "x");
946    }
947
948    #[test]
949    fn variable_monitor_set_render() {
950        let mut mon = VariableMonitor::new();
951        mon.set("fps",    60.0_f64);
952        mon.set("frames", 1234_i64);
953        mon.set("name",   "test");
954        let out = mon.render();
955        assert!(out.contains("fps"),    "should contain fps");
956        assert!(out.contains("frames"), "should contain frames");
957        assert!(out.contains("test"),   "should contain value");
958    }
959
960    #[test]
961    fn variable_monitor_remove() {
962        let mut mon = VariableMonitor::new();
963        mon.set("a", 1_i64);
964        mon.set("b", 2_i64);
965        mon.remove("a");
966        assert_eq!(mon.len(), 1);
967        assert!(mon.get("a").is_none());
968    }
969
970    #[test]
971    fn command_registry_execute() {
972        let reg = CommandRegistry::new();
973        let result = reg.execute("echo hello world").unwrap();
974        assert_eq!(result, "hello world");
975    }
976
977    #[test]
978    fn command_registry_unknown_command() {
979        let reg = CommandRegistry::new();
980        let result = reg.execute("unknown_cmd");
981        assert!(result.is_err());
982    }
983
984    #[test]
985    fn command_registry_completions() {
986        let reg = CommandRegistry::new();
987        let c = reg.completions("he");
988        assert!(c.contains(&"help".to_owned()));
989    }
990
991    #[test]
992    fn command_registry_register_custom() {
993        let mut reg = CommandRegistry::new();
994        reg.register("greet", "Say hello", Box::new(|args| {
995            format!("Hello, {}!", args.first().map(|s| s.as_str()).unwrap_or("World"))
996        }));
997        let out = reg.execute("greet Alice").unwrap();
998        assert_eq!(out, "Hello, Alice!");
999    }
1000
1001    #[test]
1002    fn field_diff_compute() {
1003        let old = vec![
1004            InspectorField::readonly("x", InspectorValue::Float(1.0)),
1005            InspectorField::readonly("y", InspectorValue::Float(2.0)),
1006        ];
1007        let new_fields = vec![
1008            InspectorField::readonly("x", InspectorValue::Float(3.0)),
1009            InspectorField::readonly("y", InspectorValue::Float(2.0)),
1010        ];
1011        let diffs = FieldDiff::compute(&old, &new_fields);
1012        assert_eq!(diffs.len(), 1);
1013        assert_eq!(diffs[0].field, "x");
1014    }
1015
1016    #[test]
1017    fn inspector_history_undo() {
1018        let mut hist = InspectorHistory::new("obj", 10);
1019        let snap = vec![InspectorField::readonly("hp", InspectorValue::Int(100))];
1020        hist.push_snapshot(snap.clone());
1021        assert!(hist.can_undo());
1022        let restored = hist.undo().unwrap();
1023        assert_eq!(restored[0].name, "hp");
1024        assert!(!hist.can_undo());
1025    }
1026}