From 33dd6d008b90bd9c6b751378e916b689fc48b064 Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Sun, 19 Apr 2026 08:44:09 +0100 Subject: [PATCH] implements PLN-0032 --- .../src/debugger.rs | 130 +++++++++++------- .../prometeu-host-desktop-winit/src/runner.rs | 114 ++++++++++++++- discussion/index.ndjson | 2 +- ...st-debugger-and-certification-alignment.md | 4 +- 4 files changed, 192 insertions(+), 58 deletions(-) diff --git a/crates/host/prometeu-host-desktop-winit/src/debugger.rs b/crates/host/prometeu-host-desktop-winit/src/debugger.rs index 2127459b..2a67d1a0 100644 --- a/crates/host/prometeu-host-desktop-winit/src/debugger.rs +++ b/crates/host/prometeu-host-desktop-winit/src/debugger.rs @@ -2,6 +2,7 @@ use prometeu_drivers::hardware::Hardware; use prometeu_firmware::{BootTarget, Firmware}; use prometeu_hal::cartridge_loader::CartridgeLoader; use prometeu_hal::debugger_protocol::*; +use prometeu_hal::telemetry::{CertificationConfig, TelemetryFrame}; use prometeu_system::CrashReport; use std::io::{Read, Write}; use std::net::{TcpListener, TcpStream}; @@ -12,6 +13,8 @@ use std::net::{TcpListener, TcpStream}; /// Prometeu Debugger) to observe and control the execution of the virtual machine. /// /// Communication is based on JSONL (JSON lines) over TCP. +/// Detailed inspection and certification events are synthesized in the host +/// from telemetry snapshots and host/runtime integration state. pub struct HostDebugger { /// If true, the VM will not start execution until a 'start' command is received. pub waiting_for_start: bool, @@ -230,6 +233,70 @@ impl HostDebugger { } } + pub(crate) fn cert_event_from_snapshot( + tag: u16, + telemetry: TelemetryFrame, + cert_config: &CertificationConfig, + frame_index: u64, + ) -> Option { + let (rule, used, limit) = match tag { + 0xCA01 => ( + "cycles_budget".to_string(), + telemetry.cycles_used, + cert_config.cycles_budget_per_frame.unwrap_or(0), + ), + 0xCA02 => ( + "max_syscalls".to_string(), + telemetry.syscalls as u64, + cert_config.max_syscalls_per_frame.unwrap_or(0) as u64, + ), + 0xCA03 => ( + "max_host_cpu_us".to_string(), + telemetry.host_cpu_time_us, + cert_config.max_host_cpu_us_per_frame.unwrap_or(0), + ), + 0xCA04 => ( + "max_glyph_slots_used".to_string(), + telemetry.glyph_slots_used as u64, + cert_config.max_glyph_slots_used.unwrap_or(0) as u64, + ), + 0xCA05 => ( + "max_sound_slots_used".to_string(), + telemetry.sound_slots_used as u64, + cert_config.max_sound_slots_used.unwrap_or(0) as u64, + ), + 0xCA06 => ( + "max_heap_bytes".to_string(), + telemetry.heap_used_bytes as u64, + cert_config.max_heap_bytes.unwrap_or(0) as u64, + ), + 0xCA07 => ( + "max_logs_per_frame".to_string(), + telemetry.logs_count as u64, + cert_config.max_logs_per_frame.unwrap_or(0) as u64, + ), + _ => return None, + }; + + Some(DebugEvent::Cert { rule, used, limit, frame_index }) + } + + pub(crate) fn telemetry_event_from_snapshot(telemetry: TelemetryFrame) -> DebugEvent { + DebugEvent::Telemetry { + frame_index: telemetry.frame_index, + vm_steps: telemetry.vm_steps, + syscalls: telemetry.syscalls, + cycles: telemetry.cycles_used, + cycles_budget: telemetry.cycles_budget, + host_cpu_time_us: telemetry.host_cpu_time_us, + violations: telemetry.violations, + glyph_slots_used: telemetry.glyph_slots_used, + glyph_slots_total: telemetry.glyph_slots_total, + sound_slots_used: telemetry.sound_slots_used, + sound_slots_total: telemetry.sound_slots_total, + } + } + /// Scans the system for new information to push to the debugger client. fn stream_events(&mut self, firmware: &mut Firmware) { if let Some(report) = firmware.os.last_crash_report.as_ref() { @@ -251,46 +318,15 @@ impl HostDebugger { }); } - // Map Certification tags (0xCA01-0xCA03) to 'Cert' protocol events. - if event.tag >= 0xCA01 && event.tag <= 0xCA03 { - let tel = firmware.os.atomic_telemetry.snapshot(); - let cert_config = &firmware.os.certifier.config; - - let (rule, used, limit) = match event.tag { - 0xCA01 => ( - "cycles_budget".to_string(), - tel.cycles_used, - cert_config.cycles_budget_per_frame.unwrap_or(0), - ), - 0xCA02 => ( - "max_syscalls".to_string(), - tel.syscalls as u64, - cert_config.max_syscalls_per_frame.unwrap_or(0) as u64, - ), - 0xCA03 => ( - "max_host_cpu_us".to_string(), - tel.host_cpu_time_us, - cert_config.max_host_cpu_us_per_frame.unwrap_or(0), - ), - 0xCA04 => ( - "max_glyph_slots_used".to_string(), - tel.glyph_slots_used as u64, - cert_config.max_glyph_slots_used.unwrap_or(0) as u64, - ), - 0xCA05 => ( - "max_sound_slots_used".to_string(), - tel.sound_slots_used as u64, - cert_config.max_sound_slots_used.unwrap_or(0) as u64, - ), - _ => ("unknown".to_string(), 0, 0), - }; - - self.send_event(DebugEvent::Cert { - rule, - used, - limit, - frame_index: firmware.os.logical_frame_index, - }); + if (0xCA01..=0xCA07).contains(&event.tag) + && let Some(cert_event) = Self::cert_event_from_snapshot( + event.tag, + firmware.os.atomic_telemetry.snapshot(), + &firmware.os.certifier.config, + firmware.os.logical_frame_index, + ) + { + self.send_event(cert_event); } self.send_event(DebugEvent::Log { @@ -304,19 +340,7 @@ impl HostDebugger { let current_frame = firmware.os.logical_frame_index; if current_frame > self.last_telemetry_frame { let tel = firmware.os.atomic_telemetry.snapshot(); - self.send_event(DebugEvent::Telemetry { - frame_index: tel.frame_index, - vm_steps: tel.vm_steps, - syscalls: tel.syscalls, - cycles: tel.cycles_used, - cycles_budget: tel.cycles_budget, - host_cpu_time_us: tel.host_cpu_time_us, - violations: tel.violations, - glyph_slots_used: tel.glyph_slots_used, - glyph_slots_total: tel.glyph_slots_total, - sound_slots_used: tel.sound_slots_used, - sound_slots_total: tel.sound_slots_total, - }); + self.send_event(Self::telemetry_event_from_snapshot(tel)); self.last_telemetry_frame = current_frame; } } diff --git a/crates/host/prometeu-host-desktop-winit/src/runner.rs b/crates/host/prometeu-host-desktop-winit/src/runner.rs index cb1cd9cb..73f0f7e4 100644 --- a/crates/host/prometeu-host-desktop-winit/src/runner.rs +++ b/crates/host/prometeu-host-desktop-winit/src/runner.rs @@ -58,7 +58,8 @@ pub struct HostRunner { /// Performance metrics collector. stats: HostStats, - /// Remote debugger interface. + /// Remote debugger interface. It consumes host-owned telemetry and runtime state + /// directly instead of depending on guest-visible inspection syscalls. debugger: HostDebugger, /// Flag to enable/disable the technical telemetry display. @@ -225,7 +226,8 @@ impl ApplicationHandler for HostRunner { // 1. Process pending debug commands from the network. self.debugger.check_commands(&mut self.firmware, &mut self.hardware); - // Sync inspection mode state. + // Sync inspection mode state. This is host-owned overlay/debugger control, + // not a guest-visible debug ABI switch. self.firmware.os.inspection_active = self.overlay_enabled || self.debugger.stream.is_some(); // 2. Maintain filesystem connection if it was lost (e.g., directory removed). @@ -307,9 +309,117 @@ mod tests { use super::*; use prometeu_firmware::BootTarget; use prometeu_hal::debugger_protocol::DEVTOOLS_PROTOCOL_VERSION; + use prometeu_hal::telemetry::{CertificationConfig, TelemetryFrame}; use std::io::{Read, Write}; use std::net::TcpStream; + #[test] + fn host_debugger_maps_cert_events_from_host_owned_sources() { + let telemetry = TelemetryFrame { + glyph_slots_used: 3, + heap_used_bytes: 4096, + logs_count: 7, + ..Default::default() + }; + let config = CertificationConfig { + enabled: true, + max_glyph_slots_used: Some(2), + max_heap_bytes: Some(2048), + max_logs_per_frame: Some(5), + ..Default::default() + }; + + match HostDebugger::cert_event_from_snapshot(0xCA04, telemetry, &config, 12) { + Some(prometeu_hal::debugger_protocol::DebugEvent::Cert { + rule, + used, + limit, + frame_index, + }) => { + assert_eq!(rule, "max_glyph_slots_used"); + assert_eq!(used, 3); + assert_eq!(limit, 2); + assert_eq!(frame_index, 12); + } + other => panic!("unexpected glyph cert event: {:?}", other), + } + match HostDebugger::cert_event_from_snapshot(0xCA06, telemetry, &config, 12) { + Some(prometeu_hal::debugger_protocol::DebugEvent::Cert { + rule, + used, + limit, + frame_index, + }) => { + assert_eq!(rule, "max_heap_bytes"); + assert_eq!(used, 4096); + assert_eq!(limit, 2048); + assert_eq!(frame_index, 12); + } + other => panic!("unexpected heap cert event: {:?}", other), + } + match HostDebugger::cert_event_from_snapshot(0xCA07, telemetry, &config, 12) { + Some(prometeu_hal::debugger_protocol::DebugEvent::Cert { + rule, + used, + limit, + frame_index, + }) => { + assert_eq!(rule, "max_logs_per_frame"); + assert_eq!(used, 7); + assert_eq!(limit, 5); + assert_eq!(frame_index, 12); + } + other => panic!("unexpected logs cert event: {:?}", other), + } + } + + #[test] + fn host_debugger_maps_telemetry_from_atomic_snapshot_fields() { + let telemetry = TelemetryFrame { + frame_index: 8, + vm_steps: 123, + syscalls: 9, + cycles_used: 456, + cycles_budget: 789, + host_cpu_time_us: 321, + violations: 2, + glyph_slots_used: 1, + glyph_slots_total: 16, + sound_slots_used: 0, + sound_slots_total: 8, + ..Default::default() + }; + + match HostDebugger::telemetry_event_from_snapshot(telemetry) { + prometeu_hal::debugger_protocol::DebugEvent::Telemetry { + frame_index, + vm_steps, + syscalls, + cycles, + cycles_budget, + host_cpu_time_us, + violations, + glyph_slots_used, + glyph_slots_total, + sound_slots_used, + sound_slots_total, + } => { + assert_eq!(frame_index, 8); + assert_eq!(vm_steps, 123); + assert_eq!(syscalls, 9); + assert_eq!(cycles, 456); + assert_eq!(cycles_budget, 789); + assert_eq!(host_cpu_time_us, 321); + assert_eq!(violations, 2); + assert_eq!(glyph_slots_used, 1); + assert_eq!(glyph_slots_total, 16); + assert_eq!(sound_slots_used, 0); + assert_eq!(sound_slots_total, 8); + } + other => panic!("unexpected telemetry event: {:?}", other), + } + } + #[test] #[ignore = "requires localhost TCP bind/connect; run via `cargo test -p prometeu-host-desktop-winit --lib -- --ignored`"] fn test_debug_port_opens() { diff --git a/discussion/index.ndjson b/discussion/index.ndjson index a4d12840..0b53408d 100644 --- a/discussion/index.ndjson +++ b/discussion/index.ndjson @@ -14,7 +14,7 @@ {"type":"discussion","id":"DSC-0009","status":"open","ticket":"perf-async-background-work-lanes-for-assets-and-fs","title":"Agenda - [PERF] Async Background Work Lanes for Assets and FS","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0008","file":"workflow/agendas/AGD-0008-perf-async-background-work-lanes-for-assets-and-fs.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]} {"type":"discussion","id":"DSC-0010","status":"open","ticket":"perf-host-desktop-frame-pacing-and-presentation","title":"Agenda - [PERF] Host Desktop Frame Pacing and Presentation","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0009","file":"workflow/agendas/AGD-0009-perf-host-desktop-frame-pacing-and-presentation.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]} {"type":"discussion","id":"DSC-0011","status":"open","ticket":"perf-gfx-render-pipeline-and-dirty-regions","title":"Agenda - [PERF] GFX Render Pipeline and Dirty Regions","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0010","file":"workflow/agendas/AGD-0010-perf-gfx-render-pipeline-and-dirty-regions.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]} -{"type":"discussion","id":"DSC-0012","status":"review","ticket":"perf-runtime-introspection-syscalls","title":"Agenda - [PERF] Runtime Introspection Syscalls","created_at":"2026-03-27","updated_at":"2026-04-19","tags":["perf","runtime","syscall","telemetry","debug","asset"],"agendas":[{"id":"AGD-0011","file":"workflow/agendas/AGD-0011-perf-runtime-introspection-syscalls.md","status":"accepted","created_at":"2026-03-27","updated_at":"2026-04-18"}],"decisions":[{"id":"DEC-0009","file":"workflow/decisions/DEC-0009-host-owned-debug-and-certification.md","status":"accepted","created_at":"2026-04-18","updated_at":"2026-04-19","ref_agenda":"AGD-0011"}],"plans":[{"id":"PLN-0030","file":"workflow/plans/PLN-0030-dec9-spec-boundary-propagation.md","status":"done","created_at":"2026-04-19","updated_at":"2026-04-19","ref_decisions":["DEC-0009"]},{"id":"PLN-0031","file":"workflow/plans/PLN-0031-dec9-runtime-bank-abi-cleanup.md","status":"done","created_at":"2026-04-19","updated_at":"2026-04-19","ref_decisions":["DEC-0009"]},{"id":"PLN-0032","file":"workflow/plans/PLN-0032-dec9-host-debugger-and-certification-alignment.md","status":"review","created_at":"2026-04-19","updated_at":"2026-04-19","ref_decisions":["DEC-0009"]}],"lessons":[]} +{"type":"discussion","id":"DSC-0012","status":"review","ticket":"perf-runtime-introspection-syscalls","title":"Agenda - [PERF] Runtime Introspection Syscalls","created_at":"2026-03-27","updated_at":"2026-04-19","tags":["perf","runtime","syscall","telemetry","debug","asset"],"agendas":[{"id":"AGD-0011","file":"workflow/agendas/AGD-0011-perf-runtime-introspection-syscalls.md","status":"accepted","created_at":"2026-03-27","updated_at":"2026-04-18"}],"decisions":[{"id":"DEC-0009","file":"workflow/decisions/DEC-0009-host-owned-debug-and-certification.md","status":"accepted","created_at":"2026-04-18","updated_at":"2026-04-19","ref_agenda":"AGD-0011"}],"plans":[{"id":"PLN-0030","file":"workflow/plans/PLN-0030-dec9-spec-boundary-propagation.md","status":"done","created_at":"2026-04-19","updated_at":"2026-04-19","ref_decisions":["DEC-0009"]},{"id":"PLN-0031","file":"workflow/plans/PLN-0031-dec9-runtime-bank-abi-cleanup.md","status":"done","created_at":"2026-04-19","updated_at":"2026-04-19","ref_decisions":["DEC-0009"]},{"id":"PLN-0032","file":"workflow/plans/PLN-0032-dec9-host-debugger-and-certification-alignment.md","status":"done","created_at":"2026-04-19","updated_at":"2026-04-19","ref_decisions":["DEC-0009"]}],"lessons":[]} {"type":"discussion","id":"DSC-0013","status":"done","ticket":"perf-host-debug-overlay-isolation","title":"Agenda - [PERF] Host Debug Overlay Isolation","created_at":"2026-03-27","updated_at":"2026-04-10","tags":[],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0027","file":"lessons/DSC-0013-perf-host-debug-overlay-isolation/LSN-0027-host-debug-overlay-isolation.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}]} {"type":"discussion","id":"DSC-0024","status":"done","ticket":"generic-memory-bank-slot-contract","title":"Agenda - Generic Memory Bank Slot Contract","created_at":"2026-04-10","updated_at":"2026-04-10","tags":["runtime","asset","memory-bank","slots","host"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0029","file":"lessons/DSC-0024-generic-memory-bank-slot-contract/LSN-0029-slot-first-bank-telemetry-belongs-in-asset-manager.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}]} {"type":"discussion","id":"DSC-0025","status":"done","ticket":"scene-bank-and-viewport-cache-refactor","title":"Scene Bank and Viewport Cache Refactor","created_at":"2026-04-11","updated_at":"2026-04-14","tags":["gfx","tilemap","runtime","render"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0030","file":"lessons/DSC-0025-scene-bank-and-viewport-cache-refactor/LSN-0030-canonical-scene-cache-and-resolver-split.md","status":"done","created_at":"2026-04-14","updated_at":"2026-04-14"}]} diff --git a/discussion/workflow/plans/PLN-0032-dec9-host-debugger-and-certification-alignment.md b/discussion/workflow/plans/PLN-0032-dec9-host-debugger-and-certification-alignment.md index f53ef6b7..36f21f55 100644 --- a/discussion/workflow/plans/PLN-0032-dec9-host-debugger-and-certification-alignment.md +++ b/discussion/workflow/plans/PLN-0032-dec9-host-debugger-and-certification-alignment.md @@ -2,9 +2,9 @@ id: PLN-0032 ticket: perf-runtime-introspection-syscalls title: DEC-0009 Host Debugger and Certification Alignment -status: review +status: done created: 2026-04-19 -completed: +completed: 2026-04-19 tags: [host, debug, certification, telemetry, desktop] ---