From 63892dcfb9c06bb54f045c535dbad66a3fd2a450 Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Fri, 10 Apr 2026 08:59:02 +0100 Subject: [PATCH 01/10] [PERF] Runtime Telemetry Hot Path --- crates/console/prometeu-drivers/src/asset.rs | 245 ++++++++++-------- .../prometeu-hal/src/log/log_service.rs | 13 +- crates/console/prometeu-hal/src/telemetry.rs | 94 ++++++- .../src/virtual_machine_runtime.rs | 5 +- .../src/virtual_machine_runtime/lifecycle.rs | 2 + .../src/virtual_machine_runtime/tick.rs | 49 +++- crates/console/prometeu-vm/src/heap.rs | 38 ++- .../prometeu-vm/src/virtual_machine.rs | 5 + .../prometeu-host-desktop-winit/src/runner.rs | 19 +- discussion/index.ndjson | 4 +- .../LSN-0026-push-based-telemetry-model.md | 27 ++ ...GD-0007-perf-runtime-telemetry-hot-path.md | 22 ++ ...-0012-perf-host-debug-overlay-isolation.md | 17 +- ...EC-0005-perf-push-based-telemetry-model.md | 57 ++++ ...erf-push-based-telemetry-implementation.md | 81 ++++++ test-cartridges/stress-console/program.pbx | Bin 941 -> 942 bytes 16 files changed, 537 insertions(+), 141 deletions(-) create mode 100644 discussion/lessons/DSC-0008-perf-runtime-telemetry-hot-path/LSN-0026-push-based-telemetry-model.md create mode 100644 discussion/workflow/decisions/DEC-0005-perf-push-based-telemetry-model.md create mode 100644 discussion/workflow/plans/PLN-0005-perf-push-based-telemetry-implementation.md diff --git a/crates/console/prometeu-drivers/src/asset.rs b/crates/console/prometeu-drivers/src/asset.rs index 468f6bd6..ac0f1169 100644 --- a/crates/console/prometeu-drivers/src/asset.rs +++ b/crates/console/prometeu-drivers/src/asset.rs @@ -12,6 +12,7 @@ use prometeu_hal::sample::Sample; use prometeu_hal::sound_bank::SoundBank; use std::collections::HashMap; use std::io::Read; +use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::{Arc, Mutex, RwLock}; use std::thread; use std::time::Instant; @@ -53,10 +54,26 @@ impl ResidentEntry { /// This is internal to the AssetManager and not visible to peripherals. pub struct BankPolicy { /// Dedup table: asset_id -> resident entry (value + telemetry). - resident: Arc>>>, + pub resident: Arc>>>, /// Staging area: handle -> value ready to commit. - staging: Arc>>>, + pub staging: Arc, usize)>>>, + + /// Total bytes currently in resident storage. + pub used_bytes: Arc, + /// Bytes in staging awaiting commit. + pub inflight_bytes: Arc, +} + +impl Clone for BankPolicy { + fn clone(&self) -> Self { + Self { + resident: Arc::clone(&self.resident), + staging: Arc::clone(&self.staging), + used_bytes: Arc::clone(&self.used_bytes), + inflight_bytes: Arc::clone(&self.inflight_bytes), + } + } } impl BankPolicy { @@ -64,6 +81,8 @@ impl BankPolicy { Self { resident: Arc::new(RwLock::new(HashMap::new())), staging: Arc::new(RwLock::new(HashMap::new())), + used_bytes: Arc::new(AtomicUsize::new(0)), + inflight_bytes: Arc::new(AtomicUsize::new(0)), } } @@ -87,24 +106,32 @@ impl BankPolicy { None => { let entry = ResidentEntry::new(Arc::clone(&value), bytes); map.insert(asset_id, entry); + self.used_bytes.fetch_add(bytes, Ordering::Relaxed); value } } } /// Place a value into staging for a given handle. - pub fn stage(&self, handle: HandleId, value: Arc) { - self.staging.write().unwrap().insert(handle, value); + pub fn stage(&self, handle: HandleId, value: Arc, bytes: usize) { + self.staging.write().unwrap().insert(handle, (value, bytes)); + self.inflight_bytes.fetch_add(bytes, Ordering::Relaxed); } /// Take staged value (used by commit path). - pub fn take_staging(&self, handle: HandleId) -> Option> { - self.staging.write().unwrap().remove(&handle) + pub fn take_staging(&self, handle: HandleId) -> Option<(Arc, usize)> { + let entry = self.staging.write().unwrap().remove(&handle); + if let Some((_, bytes)) = entry.as_ref() { + self.inflight_bytes.fetch_sub(*bytes, Ordering::Relaxed); + } + entry } pub fn clear(&self) { self.resident.write().unwrap().clear(); self.staging.write().unwrap().clear(); + self.used_bytes.store(0, Ordering::Relaxed); + self.inflight_bytes.store(0, Ordering::Relaxed); } } @@ -127,6 +154,11 @@ pub struct AssetManager { /// Residency policy for sound banks. sound_policy: BankPolicy, + /// Count of occupied slots for GFX. + gfx_slots_occupied: AtomicUsize, + /// Count of occupied slots for sounds. + sound_slots_occupied: AtomicUsize, + // Commits that are ready to be applied at the next frame boundary. pending_commits: Mutex>, } @@ -263,6 +295,8 @@ impl AssetManager { sound_slots: Arc::new(RwLock::new(std::array::from_fn(|_| None))), gfx_policy: BankPolicy::new(), sound_policy: BankPolicy::new(), + gfx_slots_occupied: AtomicUsize::new(0), + sound_slots_occupied: AtomicUsize::new(0), handles: Arc::new(RwLock::new(HashMap::new())), next_handle_id: Mutex::new(1), assets_data: Arc::new(RwLock::new(assets_data)), @@ -379,7 +413,7 @@ impl AssetManager { let already_resident = match entry.bank_type { BankType::GLYPH => { if let Some(bank) = self.gfx_policy.get_resident(asset_id) { - self.gfx_policy.stage(handle_id, bank); + self.gfx_policy.stage(handle_id, bank, entry.decoded_size as usize); true } else { false @@ -387,7 +421,7 @@ impl AssetManager { } BankType::SOUNDS => { if let Some(bank) = self.sound_policy.get_resident(asset_id) { - self.sound_policy.stage(handle_id, bank); + self.sound_policy.stage(handle_id, bank, entry.decoded_size as usize); true } else { false @@ -414,10 +448,8 @@ impl AssetManager { let entry_clone = entry.clone(); // Capture policies for the worker thread - let gfx_policy_resident = Arc::clone(&self.gfx_policy.resident); - let gfx_policy_staging = Arc::clone(&self.gfx_policy.staging); - let sound_policy_resident = Arc::clone(&self.sound_policy.resident); - let sound_policy_staging = Arc::clone(&self.sound_policy.staging); + let gfx_policy = self.gfx_policy.clone(); + let sound_policy = self.sound_policy.clone(); thread::spawn(move || { // Update status to LOADING @@ -439,22 +471,13 @@ impl AssetManager { let result = Self::perform_load_glyph_bank(&entry_clone, assets_data); if let Ok(tilebank) = result { let bank_arc = Arc::new(tilebank); - let resident_arc = { - let mut map = gfx_policy_resident.write().unwrap(); - if let Some(existing) = map.get_mut(&asset_id) { - existing.last_used = Instant::now(); - existing.loads += 1; - Arc::clone(&existing.value) - } else { - let entry = ResidentEntry::new( - Arc::clone(&bank_arc), - entry_clone.decoded_size as usize, - ); - map.insert(asset_id, entry); - bank_arc - } - }; - gfx_policy_staging.write().unwrap().insert(handle_id, resident_arc); + let resident_arc = gfx_policy.put_resident( + asset_id, + bank_arc, + entry_clone.decoded_size as usize, + ); + gfx_policy.stage(handle_id, resident_arc, entry_clone.decoded_size as usize); + let mut handles_map = handles.write().unwrap(); if let Some(h) = handles_map.get_mut(&handle_id) { if h.status == LoadStatus::LOADING { @@ -472,22 +495,14 @@ impl AssetManager { let result = Self::perform_load_sound_bank(&entry_clone, assets_data); if let Ok(soundbank) = result { let bank_arc = Arc::new(soundbank); - let resident_arc = { - let mut map = sound_policy_resident.write().unwrap(); - if let Some(existing) = map.get_mut(&asset_id) { - existing.last_used = Instant::now(); - existing.loads += 1; - Arc::clone(&existing.value) - } else { - let entry = ResidentEntry::new( - Arc::clone(&bank_arc), - entry_clone.decoded_size as usize, - ); - map.insert(asset_id, entry); - bank_arc - } - }; - sound_policy_staging.write().unwrap().insert(handle_id, resident_arc); + let resident_arc = sound_policy.put_resident( + asset_id, + bank_arc, + entry_clone.decoded_size as usize, + ); + sound_policy + .stage(handle_id, resident_arc, entry_clone.decoded_size as usize); + let mut handles_map = handles.write().unwrap(); if let Some(h) = handles_map.get_mut(&handle_id) { if h.status == LoadStatus::LOADING { @@ -699,20 +714,26 @@ impl AssetManager { if h.status == LoadStatus::READY { match h.slot.asset_type { BankType::GLYPH => { - if let Some(bank) = self.gfx_policy.take_staging(handle_id) { + if let Some((bank, _)) = self.gfx_policy.take_staging(handle_id) { self.gfx_installer.install_glyph_bank(h.slot.index, bank); let mut slots = self.gfx_slots.write().unwrap(); if h.slot.index < slots.len() { + if slots[h.slot.index].is_none() { + self.gfx_slots_occupied.fetch_add(1, Ordering::Relaxed); + } slots[h.slot.index] = Some(h._asset_id); } h.status = LoadStatus::COMMITTED; } } BankType::SOUNDS => { - if let Some(bank) = self.sound_policy.take_staging(handle_id) { + if let Some((bank, _)) = self.sound_policy.take_staging(handle_id) { self.sound_installer.install_sound_bank(h.slot.index, bank); let mut slots = self.sound_slots.write().unwrap(); if h.slot.index < slots.len() { + if slots[h.slot.index].is_none() { + self.sound_slots_occupied.fetch_add(1, Ordering::Relaxed); + } slots[h.slot.index] = Some(h._asset_id); } h.status = LoadStatus::COMMITTED; @@ -727,38 +748,9 @@ impl AssetManager { pub fn bank_info(&self, kind: BankType) -> BankStats { match kind { BankType::GLYPH => { - let mut used_bytes = 0; - { - let resident = self.gfx_policy.resident.read().unwrap(); - for entry in resident.values() { - used_bytes += entry.bytes; - } - } - - let mut inflight_bytes = 0; - { - let staging = self.gfx_policy.staging.read().unwrap(); - let assets = self.assets.read().unwrap(); - let handles = self.handles.read().unwrap(); - - for (handle_id, _) in staging.iter() { - if let Some(h) = handles.get(handle_id) { - if let Some(entry) = assets.get(&h._asset_id) { - inflight_bytes += entry.decoded_size as usize; - } - } - } - } - - let mut slots_occupied = 0; - { - let slots = self.gfx_slots.read().unwrap(); - for s in slots.iter() { - if s.is_some() { - slots_occupied += 1; - } - } - } + let used_bytes = self.gfx_policy.used_bytes.load(Ordering::Relaxed); + let inflight_bytes = self.gfx_policy.inflight_bytes.load(Ordering::Relaxed); + let slots_occupied = self.gfx_slots_occupied.load(Ordering::Relaxed); BankStats { total_bytes: 16 * 1024 * 1024, @@ -770,38 +762,9 @@ impl AssetManager { } } BankType::SOUNDS => { - let mut used_bytes = 0; - { - let resident = self.sound_policy.resident.read().unwrap(); - for entry in resident.values() { - used_bytes += entry.bytes; - } - } - - let mut inflight_bytes = 0; - { - let staging = self.sound_policy.staging.read().unwrap(); - let assets = self.assets.read().unwrap(); - let handles = self.handles.read().unwrap(); - - for (handle_id, _) in staging.iter() { - if let Some(h) = handles.get(handle_id) { - if let Some(entry) = assets.get(&h._asset_id) { - inflight_bytes += entry.decoded_size as usize; - } - } - } - } - - let mut slots_occupied = 0; - { - let slots = self.sound_slots.read().unwrap(); - for s in slots.iter() { - if s.is_some() { - slots_occupied += 1; - } - } - } + let used_bytes = self.sound_policy.used_bytes.load(Ordering::Relaxed); + let inflight_bytes = self.sound_policy.inflight_bytes.load(Ordering::Relaxed); + let slots_occupied = self.sound_slots_occupied.load(Ordering::Relaxed); BankStats { total_bytes: 32 * 1024 * 1024, @@ -865,6 +828,8 @@ impl AssetManager { pub fn shutdown(&self) { self.gfx_policy.clear(); self.sound_policy.clear(); + self.gfx_slots_occupied.store(0, Ordering::Relaxed); + self.sound_slots_occupied.store(0, Ordering::Relaxed); self.handles.write().unwrap().clear(); self.pending_commits.lock().unwrap().clear(); self.gfx_slots.write().unwrap().fill(None); @@ -1049,8 +1014,8 @@ mod tests { assert_eq!(am.status(handle2), LoadStatus::READY); let staging = am.gfx_policy.staging.read().unwrap(); - let bank1 = staging.get(&handle1).unwrap(); - let bank2 = staging.get(&handle2).unwrap(); + let bank1 = &staging.get(&handle1).unwrap().0; + let bank2 = &staging.get(&handle2).unwrap().0; assert!(Arc::ptr_eq(bank1, bank2)); } @@ -1208,4 +1173,60 @@ mod tests { assert_eq!(am.status(handle), LoadStatus::CANCELED); assert_eq!(am.commit(handle), AssetOpStatus::InvalidState); } + + #[test] + fn test_asset_telemetry_incremental() { + let banks = Arc::new(MemoryBanks::new()); + let gfx_installer = Arc::clone(&banks) as Arc; + let sound_installer = Arc::clone(&banks) as Arc; + + let width = 16; + let height = 16; + let decoded_bytes = expected_glyph_decoded_size(width, height); + let data = test_glyph_asset_data(); + + let am = AssetManager::new( + vec![test_glyph_asset_entry("test_glyphs", width, height)], + AssetsPayloadSource::from_bytes(data), + gfx_installer, + sound_installer, + ); + + // Initially zero + let info = am.bank_info(BankType::GLYPH); + assert_eq!(info.used_bytes, 0); + assert_eq!(info.inflight_bytes, 0); + assert_eq!(info.slots_occupied, 0); + + // Loading + let handle = am.load(0, 0).expect("load must allocate handle"); + + // While LOADING or READY, it should be in inflight_bytes + let start = Instant::now(); + while am.status(handle) != LoadStatus::READY && start.elapsed().as_secs() < 5 { + thread::sleep(std::time::Duration::from_millis(10)); + } + + let info = am.bank_info(BankType::GLYPH); + // Note: put_resident happens in worker thread, then stage happens. + assert_eq!(info.used_bytes, decoded_bytes); + assert_eq!(info.inflight_bytes, decoded_bytes); + assert_eq!(info.slots_occupied, 0); + + // Commit + am.commit(handle); + am.apply_commits(); + + let info = am.bank_info(BankType::GLYPH); + assert_eq!(info.used_bytes, decoded_bytes); + assert_eq!(info.inflight_bytes, 0); + assert_eq!(info.slots_occupied, 1); + + // Shutdown resets + am.shutdown(); + let info = am.bank_info(BankType::GLYPH); + assert_eq!(info.used_bytes, 0); + assert_eq!(info.inflight_bytes, 0); + assert_eq!(info.slots_occupied, 0); + } } diff --git a/crates/console/prometeu-hal/src/log/log_service.rs b/crates/console/prometeu-hal/src/log/log_service.rs index 0d8f015c..e6d991e6 100644 --- a/crates/console/prometeu-hal/src/log/log_service.rs +++ b/crates/console/prometeu-hal/src/log/log_service.rs @@ -5,11 +5,17 @@ pub struct LogService { events: VecDeque, capacity: usize, next_seq: u64, + pub logs_count: u32, } impl LogService { pub fn new(capacity: usize) -> Self { - Self { events: VecDeque::with_capacity(capacity), capacity, next_seq: 0 } + Self { + events: VecDeque::with_capacity(capacity), + capacity, + next_seq: 0, + logs_count: 0, + } } pub fn log( @@ -34,6 +40,11 @@ impl LogService { msg, }); self.next_seq += 1; + self.logs_count += 1; + } + + pub fn reset_count(&mut self) { + self.logs_count = 0; } pub fn get_recent(&self, n: usize) -> Vec { diff --git a/crates/console/prometeu-hal/src/telemetry.rs b/crates/console/prometeu-hal/src/telemetry.rs index 9540d0a3..361cf11c 100644 --- a/crates/console/prometeu-hal/src/telemetry.rs +++ b/crates/console/prometeu-hal/src/telemetry.rs @@ -20,6 +20,13 @@ pub struct TelemetryFrame { pub audio_used_bytes: usize, pub audio_inflight_bytes: usize, pub audio_slots_occupied: u32, + + // RAM (Heap) + pub heap_used_bytes: usize, + pub heap_max_bytes: usize, + + // Log Pressure + pub logs_count: u32, } #[derive(Debug, Clone, Copy, Default)] @@ -28,6 +35,10 @@ pub struct CertificationConfig { pub cycles_budget_per_frame: Option, pub max_syscalls_per_frame: Option, pub max_host_cpu_us_per_frame: Option, + pub max_gfx_bytes: Option, + pub max_audio_bytes: Option, + pub max_heap_bytes: Option, + pub max_logs_per_frame: Option, } pub struct Certifier { @@ -51,6 +62,7 @@ impl Certifier { let mut violations = 0; + // 1. Cycles if let Some(budget) = self.config.cycles_budget_per_frame && telemetry.cycles_used > budget { @@ -68,6 +80,7 @@ impl Certifier { violations += 1; } + // 2. Syscalls if let Some(limit) = self.config.max_syscalls_per_frame && telemetry.syscalls > limit { @@ -85,6 +98,7 @@ impl Certifier { violations += 1; } + // 3. CPU Time if let Some(limit) = self.config.max_host_cpu_us_per_frame && telemetry.host_cpu_time_us > limit { @@ -102,6 +116,78 @@ impl Certifier { violations += 1; } + // 4. GFX Memory + if let Some(limit) = self.config.max_gfx_bytes + && telemetry.gfx_used_bytes > limit + { + log_service.log( + ts_ms, + telemetry.frame_index, + LogLevel::Warn, + LogSource::Pos, + 0xCA04, + format!( + "Cert: GFX bank exceeded memory limit ({} > {})", + telemetry.gfx_used_bytes, limit + ), + ); + violations += 1; + } + + // 5. Audio Memory + if let Some(limit) = self.config.max_audio_bytes + && telemetry.audio_used_bytes > limit + { + log_service.log( + ts_ms, + telemetry.frame_index, + LogLevel::Warn, + LogSource::Pos, + 0xCA05, + format!( + "Cert: Audio bank exceeded memory limit ({} > {})", + telemetry.audio_used_bytes, limit + ), + ); + violations += 1; + } + + // 6. Heap Memory + if let Some(limit) = self.config.max_heap_bytes + && telemetry.heap_used_bytes > limit + { + log_service.log( + ts_ms, + telemetry.frame_index, + LogLevel::Warn, + LogSource::Pos, + 0xCA06, + format!( + "Cert: Heap memory exceeded limit ({} > {})", + telemetry.heap_used_bytes, limit + ), + ); + violations += 1; + } + + // 7. Log Pressure + if let Some(limit) = self.config.max_logs_per_frame + && telemetry.logs_count > limit + { + log_service.log( + ts_ms, + telemetry.frame_index, + LogLevel::Warn, + LogSource::Pos, + 0xCA07, + format!( + "Cert: Log pressure exceeded limit ({} > {})", + telemetry.logs_count, limit + ), + ); + violations += 1; + } + violations } } @@ -118,6 +204,8 @@ mod tests { cycles_budget_per_frame: Some(100), max_syscalls_per_frame: Some(5), max_host_cpu_us_per_frame: Some(1000), + max_gfx_bytes: Some(1024), + ..Default::default() }; let cert = Certifier::new(config); let mut ls = LogService::new(10); @@ -126,13 +214,15 @@ mod tests { tel.cycles_used = 150; tel.syscalls = 10; tel.host_cpu_time_us = 500; + tel.gfx_used_bytes = 2048; let violations = cert.evaluate(&tel, &mut ls, 1000); - assert_eq!(violations, 2); + assert_eq!(violations, 3); let logs = ls.get_recent(10); - assert_eq!(logs.len(), 2); + assert_eq!(logs.len(), 3); assert!(logs[0].msg.contains("cycles_used")); assert!(logs[1].msg.contains("syscalls")); + assert!(logs[2].msg.contains("GFX bank")); } } diff --git a/crates/console/prometeu-system/src/virtual_machine_runtime.rs b/crates/console/prometeu-system/src/virtual_machine_runtime.rs index ea1a0bc7..a4bfa41e 100644 --- a/crates/console/prometeu-system/src/virtual_machine_runtime.rs +++ b/crates/console/prometeu-system/src/virtual_machine_runtime.rs @@ -37,13 +37,14 @@ pub struct VirtualMachineRuntime { pub certifier: Certifier, pub paused: bool, pub debug_step_request: bool, + pub inspection_active: bool, pub(crate) needs_prepare_entry_call: bool, pub(crate) boot_time: Instant, } impl VirtualMachineRuntime { - pub const CYCLES_PER_LOGICAL_FRAME: u64 = 5_000_000; - pub const SLICE_PER_TICK: u64 = 5_000_000; + pub const CYCLES_PER_LOGICAL_FRAME: u64 = 1_500_000; + pub const SLICE_PER_TICK: u64 = 1_500_000; pub const MAX_LOG_LEN: usize = 256; pub const MAX_LOGS_PER_FRAME: u32 = 10; } diff --git a/crates/console/prometeu-system/src/virtual_machine_runtime/lifecycle.rs b/crates/console/prometeu-system/src/virtual_machine_runtime/lifecycle.rs index 84046860..69184441 100644 --- a/crates/console/prometeu-system/src/virtual_machine_runtime/lifecycle.rs +++ b/crates/console/prometeu-system/src/virtual_machine_runtime/lifecycle.rs @@ -30,6 +30,7 @@ impl VirtualMachineRuntime { certifier: Certifier::new(cap_config.unwrap_or_default()), paused: false, debug_step_request: false, + inspection_active: false, needs_prepare_entry_call: false, boot_time, }; @@ -104,6 +105,7 @@ impl VirtualMachineRuntime { self.paused = false; self.debug_step_request = false; + self.inspection_active = false; self.needs_prepare_entry_call = false; } diff --git a/crates/console/prometeu-system/src/virtual_machine_runtime/tick.rs b/crates/console/prometeu-system/src/virtual_machine_runtime/tick.rs index 40c4f951..9168fae8 100644 --- a/crates/console/prometeu-system/src/virtual_machine_runtime/tick.rs +++ b/crates/console/prometeu-system/src/virtual_machine_runtime/tick.rs @@ -4,6 +4,7 @@ use prometeu_hal::asset::BankType; use prometeu_hal::log::{LogLevel, LogSource}; use prometeu_hal::{HardwareBridge, HostContext, InputSignals}; use prometeu_vm::LogicalFrameEndingReason; +use std::sync::atomic::Ordering; impl VirtualMachineRuntime { pub fn debug_step_instruction( @@ -128,8 +129,27 @@ impl VirtualMachineRuntime { || run.reason == LogicalFrameEndingReason::EndOfRom { hw.gfx_mut().render_all(); - self.telemetry_current.host_cpu_time_us = - start.elapsed().as_micros() as u64; + + // 1. Snapshot full telemetry at logical frame end (O(1) with atomic counters) + let gfx_stats = hw.assets().bank_info(BankType::GLYPH); + self.telemetry_current.gfx_used_bytes = gfx_stats.used_bytes; + self.telemetry_current.gfx_inflight_bytes = gfx_stats.inflight_bytes; + self.telemetry_current.gfx_slots_occupied = gfx_stats.slots_occupied as u32; + + let audio_stats = hw.assets().bank_info(BankType::SOUNDS); + self.telemetry_current.audio_used_bytes = audio_stats.used_bytes; + self.telemetry_current.audio_inflight_bytes = audio_stats.inflight_bytes; + self.telemetry_current.audio_slots_occupied = + audio_stats.slots_occupied as u32; + + self.telemetry_current.heap_used_bytes = + vm.heap().used_bytes.load(Ordering::Relaxed); + self.telemetry_current.heap_max_bytes = 0; // Not yet capped + + self.telemetry_current.logs_count = self.log_service.logs_count; + self.log_service.reset_count(); + + self.telemetry_current.host_cpu_time_us = start.elapsed().as_micros() as u64; let ts_ms = self.boot_time.elapsed().as_millis() as u64; self.telemetry_current.violations = self.certifier.evaluate( @@ -166,15 +186,21 @@ impl VirtualMachineRuntime { self.last_frame_cpu_time_us = start.elapsed().as_micros() as u64; - let gfx_stats = hw.assets().bank_info(BankType::GLYPH); - self.telemetry_current.gfx_used_bytes = gfx_stats.used_bytes; - self.telemetry_current.gfx_inflight_bytes = gfx_stats.inflight_bytes; - self.telemetry_current.gfx_slots_occupied = gfx_stats.slots_occupied as u32; + // 2. High-frequency telemetry update (only if inspection is active) + if self.inspection_active { + let gfx_stats = hw.assets().bank_info(BankType::GLYPH); + self.telemetry_current.gfx_used_bytes = gfx_stats.used_bytes; + self.telemetry_current.gfx_inflight_bytes = gfx_stats.inflight_bytes; + self.telemetry_current.gfx_slots_occupied = gfx_stats.slots_occupied as u32; - let audio_stats = hw.assets().bank_info(BankType::SOUNDS); - self.telemetry_current.audio_used_bytes = audio_stats.used_bytes; - self.telemetry_current.audio_inflight_bytes = audio_stats.inflight_bytes; - self.telemetry_current.audio_slots_occupied = audio_stats.slots_occupied as u32; + let audio_stats = hw.assets().bank_info(BankType::SOUNDS); + self.telemetry_current.audio_used_bytes = audio_stats.used_bytes; + self.telemetry_current.audio_inflight_bytes = audio_stats.inflight_bytes; + self.telemetry_current.audio_slots_occupied = audio_stats.slots_occupied as u32; + + self.telemetry_current.heap_used_bytes = vm.heap().used_bytes.load(Ordering::Relaxed); + self.telemetry_current.logs_count = self.log_service.logs_count; + } if !self.logical_frame_active && self.telemetry_last.frame_index == self.logical_frame_index.wrapping_sub(1) @@ -187,6 +213,9 @@ impl VirtualMachineRuntime { self.telemetry_last.audio_used_bytes = self.telemetry_current.audio_used_bytes; self.telemetry_last.audio_inflight_bytes = self.telemetry_current.audio_inflight_bytes; self.telemetry_last.audio_slots_occupied = self.telemetry_current.audio_slots_occupied; + self.telemetry_last.heap_used_bytes = self.telemetry_current.heap_used_bytes; + self.telemetry_last.heap_max_bytes = self.telemetry_current.heap_max_bytes; + self.telemetry_last.logs_count = self.telemetry_current.logs_count; } None diff --git a/crates/console/prometeu-vm/src/heap.rs b/crates/console/prometeu-vm/src/heap.rs index 1476e0e3..bcd4f2dd 100644 --- a/crates/console/prometeu-vm/src/heap.rs +++ b/crates/console/prometeu-vm/src/heap.rs @@ -2,6 +2,9 @@ use crate::call_frame::CallFrame; use crate::object::{ObjectHeader, ObjectKind}; use prometeu_bytecode::{HeapRef, Value}; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::Arc; + /// Internal stored object: header plus opaque payload bytes. #[derive(Debug, Clone)] pub struct StoredObject { @@ -22,6 +25,30 @@ pub struct StoredObject { pub coroutine: Option, } +impl StoredObject { + /// Returns the approximate memory footprint of this object in bytes. + pub fn bytes(&self) -> usize { + let mut total = std::mem::size_of::(); + total += self.payload.capacity(); + + if let Some(elems) = &self.array_elems { + total += std::mem::size_of::>(); + total += elems.capacity() * std::mem::size_of::(); + } + if let Some(env) = &self.closure_env { + total += std::mem::size_of::>(); + total += env.capacity() * std::mem::size_of::(); + } + if let Some(coro) = &self.coroutine { + total += std::mem::size_of::(); + total += coro.stack.capacity() * std::mem::size_of::(); + total += coro.frames.capacity() * std::mem::size_of::(); + } + + total + } +} + /// Execution state of a coroutine. #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum CoroutineState { @@ -49,14 +76,22 @@ pub struct Heap { objects: Vec>, // Reclaimed slots available for deterministic reuse (LIFO). free_list: Vec, + + /// Total bytes currently used by all objects in the heap. + pub used_bytes: Arc, } impl Heap { pub fn new() -> Self { - Self { objects: Vec::new(), free_list: Vec::new() } + Self { + objects: Vec::new(), + free_list: Vec::new(), + used_bytes: Arc::new(AtomicUsize::new(0)), + } } fn insert_object(&mut self, obj: StoredObject) -> HeapRef { + self.used_bytes.fetch_add(obj.bytes(), Ordering::Relaxed); if let Some(idx) = self.free_list.pop() { debug_assert!(self.objects.get(idx).is_some_and(|slot| slot.is_none())); self.objects[idx] = Some(obj); @@ -363,6 +398,7 @@ impl Heap { obj.header.set_marked(false); } else { // Unreachable: reclaim by dropping and turning into tombstone. + self.used_bytes.fetch_sub(obj.bytes(), Ordering::Relaxed); *slot = None; self.free_list.push(idx); } diff --git a/crates/console/prometeu-vm/src/virtual_machine.rs b/crates/console/prometeu-vm/src/virtual_machine.rs index cc8538da..52bbb659 100644 --- a/crates/console/prometeu-vm/src/virtual_machine.rs +++ b/crates/console/prometeu-vm/src/virtual_machine.rs @@ -140,6 +140,11 @@ impl VirtualMachine { self.operand_stack[start..].iter().rev().cloned().collect() } + /// Returns a reference to the VM's heap. + pub fn heap(&self) -> &Heap { + &self.heap + } + /// Returns true if the VM has executed a HALT and is not currently running. pub fn is_halted(&self) -> bool { self.halted diff --git a/crates/host/prometeu-host-desktop-winit/src/runner.rs b/crates/host/prometeu-host-desktop-winit/src/runner.rs index 643976ee..81f86527 100644 --- a/crates/host/prometeu-host-desktop-winit/src/runner.rs +++ b/crates/host/prometeu-host-desktop-winit/src/runner.rs @@ -129,7 +129,7 @@ impl HostRunner { let color_bg = Color::INDIGO; // Dark blue to stand out let color_warn = Color::RED; - self.hardware.gfx.fill_rect(5, 5, 175, 100, color_bg); + self.hardware.gfx.fill_rect(5, 5, 175, 130, color_bg); self.hardware.gfx.draw_text( 10, 10, @@ -187,8 +187,16 @@ impl HostRunner { ); } + self.hardware.gfx.draw_text( + 10, + 82, + &format!("RAM: {}KB", tel.heap_used_bytes / 1024), + color_text, + ); + self.hardware.gfx.draw_text(10, 90, &format!("LOGS: {}", tel.logs_count), color_text); + let cert_color = if tel.violations > 0 { color_warn } else { color_text }; - self.hardware.gfx.draw_text(10, 82, &format!("CERT LAST: {}", tel.violations), cert_color); + self.hardware.gfx.draw_text(10, 98, &format!("CERT LAST: {}", tel.violations), cert_color); if tel.violations > 0 && let Some(event) = self @@ -204,7 +212,7 @@ impl HostRunner { if msg.len() > 30 { msg.truncate(30); } - self.hardware.gfx.draw_text(10, 90, &msg, color_warn); + self.hardware.gfx.draw_text(10, 106, &msg, color_warn); } if let Some(report) = self.firmware.os.last_crash_report.as_ref() { @@ -212,7 +220,7 @@ impl HostRunner { if msg.len() > 30 { msg.truncate(30); } - self.hardware.gfx.draw_text(10, 98, &msg, color_warn); + self.hardware.gfx.draw_text(10, 114, &msg, color_warn); } } } @@ -311,6 +319,9 @@ 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. + 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). if let Some(root) = &self.fs_root { use prometeu_system::fs::FsState; diff --git a/discussion/index.ndjson b/discussion/index.ndjson index cc0aa485..36feb88d 100644 --- a/discussion/index.ndjson +++ b/discussion/index.ndjson @@ -1,4 +1,4 @@ -{"type":"meta","next_id":{"DSC":23,"AGD":21,"DEC":7,"PLN":6,"LSN":26,"CLSN":1}} +{"type":"meta","next_id":{"DSC":23,"AGD":21,"DEC":7,"PLN":6,"LSN":27,"CLSN":1}} {"type":"discussion","id":"DSC-0020","status":"done","ticket":"jenkins-gitea-integration","title":"Jenkins Gitea Integration and Relocation","created_at":"2026-04-07","updated_at":"2026-04-07","tags":["ci","jenkins","gitea"],"agendas":[{"id":"AGD-0018","file":"workflow/agendas/AGD-0018-jenkins-gitea-integration-and-relocation.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}],"decisions":[{"id":"DEC-0003","file":"workflow/decisions/DEC-0003-jenkins-gitea-strategy.md","status":"accepted","created_at":"2026-04-07","updated_at":"2026-04-07"}],"plans":[{"id":"PLN-0003","file":"workflow/plans/PLN-0003-jenkins-gitea-execution.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}],"lessons":[{"id":"LSN-0021","file":"lessons/DSC-0020-jenkins-gitea-integration/LSN-0021-jenkins-gitea-integration.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}]} {"type":"discussion","id":"DSC-0021","status":"done","ticket":"asset-entry-codec-enum-with-metadata","title":"Asset Entry Codec Enum Contract","created_at":"2026-04-09","updated_at":"2026-04-09","tags":["asset","runtime","codec","metadata"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0024","file":"lessons/DSC-0021-asset-entry-codec-enum-contract/LSN-0024-string-on-the-wire-enum-in-runtime.md","status":"done","created_at":"2026-04-09","updated_at":"2026-04-09"}]} {"type":"discussion","id":"DSC-0022","status":"done","ticket":"tile-bank-vs-glyph-bank-domain-naming","title":"Glyph Bank Domain Naming Contract","created_at":"2026-04-09","updated_at":"2026-04-10","tags":["gfx","runtime","naming","domain-model"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0025","file":"lessons/DSC-0022-glyph-bank-domain-naming/LSN-0025-rename-artifact-by-meaning-not-by-token.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}]} @@ -9,7 +9,7 @@ {"type":"discussion","id":"DSC-0005","status":"open","ticket":"system-fault-semantics-and-control-surface","title":"Agenda - System Fault Semantics and Control Surface","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0004","file":"workflow/agendas/AGD-0004-system-fault-semantics-and-control-surface.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]} {"type":"discussion","id":"DSC-0006","status":"open","ticket":"vm-owned-random-service","title":"Agenda - VM-Owned Random Service","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0005","file":"workflow/agendas/AGD-0005-vm-owned-random-service.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]} {"type":"discussion","id":"DSC-0007","status":"open","ticket":"app-home-filesystem-surface-and-semantics","title":"Agenda - App Home Filesystem Surface and Semantics","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0006","file":"workflow/agendas/AGD-0006-app-home-filesystem-surface-and-semantics.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]} -{"type":"discussion","id":"DSC-0008","status":"open","ticket":"perf-runtime-telemetry-hot-path","title":"Agenda - [PERF] Runtime Telemetry Hot Path","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0007","file":"workflow/agendas/AGD-0007-perf-runtime-telemetry-hot-path.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]} +{"type":"discussion","id":"DSC-0008","status":"done","ticket":"perf-runtime-telemetry-hot-path","title":"Agenda - [PERF] Runtime Telemetry Hot Path","created_at":"2026-03-27","updated_at":"2026-04-10","tags":[],"agendas":[{"id":"AGD-0007","file":"workflow/agendas/AGD-0007-perf-runtime-telemetry-hot-path.md","status":"done","created_at":"2026-03-27","updated_at":"2026-04-10"}],"decisions":[{"id":"DEC-0005","file":"workflow/decisions/DEC-0005-perf-push-based-telemetry-model.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10"}],"plans":[{"id":"PLN-0005","file":"workflow/plans/PLN-0005-perf-push-based-telemetry-implementation.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}],"lessons":[{"id":"LSN-0026","file":"lessons/DSC-0008-perf-runtime-telemetry-hot-path/LSN-0026-push-based-telemetry-model.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}]} {"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":[]} diff --git a/discussion/lessons/DSC-0008-perf-runtime-telemetry-hot-path/LSN-0026-push-based-telemetry-model.md b/discussion/lessons/DSC-0008-perf-runtime-telemetry-hot-path/LSN-0026-push-based-telemetry-model.md new file mode 100644 index 00000000..e7abfec2 --- /dev/null +++ b/discussion/lessons/DSC-0008-perf-runtime-telemetry-hot-path/LSN-0026-push-based-telemetry-model.md @@ -0,0 +1,27 @@ +--- +id: LSN-0026 +ticket: perf-runtime-telemetry-hot-path +title: Modelo de Telemetria Push-based +created: 2026-04-10 +tags: [performance, telemetry, atomics] +--- +# Modelo de Telemetria Push-based +O sistema de telemetria do PROMETEU evoluiu de um modelo de varredura sob demanda (pull) para um modelo de contadores incrementais (push), visando minimizar o impacto no *hot path* do runtime. +## O Problema Original +Anteriormente, a cada *host tick*, o runtime solicitava informações de uso de memória dos bancos de assets. Isso resultava em: +- Varreduras $O(n)$ sobre mapas de recursos. +- Múltiplas aquisições de *locks* de leitura em cada tick. +- Overhead desnecessário em hardwares handheld, onde cada microssegundo conta. +## A Solução: Modelo Push com Atômicos +A solução implementada utiliza `AtomicUsize` nos drivers e na VM para manter o estado do sistema em tempo real com custo $O(1)$ de leitura e escrita: +1. **Drivers (Assets):** Contadores atômicos em cada `BankPolicy` são atualizados durante `load`, `commit` e `cancel`. +2. **VM (Heap):** Um contador `used_bytes` na struct `Heap` rastreia alocações e liberações (sweep). +3. **System (Logs):** O `LogService` rastreia a pressão de logs emitida em cada frame. +## Dois Níveis de Observabilidade +Para equilibrar performance e depuração, a coleta foi dividida: +- **Snapshot de Frame (Sempre):** Captura automática no fim de cada frame lógico. Custo irrelevante ($O(1)$). Serve ao `Certifier` e ao log histórico. +- **Tick de Host (Sob Demanda):** A coleta detalhada em cada tick só ocorre se `inspection_active` estiver habilitado (ex: Overlay F1 ligado). +## Lições Aprendidas +- **Desacoplamento de Gatilhos:** Não devemos usar o estado do `Certifier` para habilitar funcionalidades de depuração visual (como o overlay), pois eles têm propósitos e custos diferentes. +- **Consistência Eventual é Suficiente:** Para métricas de telemetria, não é necessário travar o sistema para obter um valor exato a cada nanossegundo. A leitura relaxada de atômicos é suficiente e muito mais performática. +- **Isolamento de Custo:** Mover a lógica de agregação para o driver simplifica o runtime e garante que o custo de telemetria seja pago apenas durante mutações de estado, e não repetidamente durante a execução estável. diff --git a/discussion/workflow/agendas/AGD-0007-perf-runtime-telemetry-hot-path.md b/discussion/workflow/agendas/AGD-0007-perf-runtime-telemetry-hot-path.md index 98a9edaf..d620c2bb 100644 --- a/discussion/workflow/agendas/AGD-0007-perf-runtime-telemetry-hot-path.md +++ b/discussion/workflow/agendas/AGD-0007-perf-runtime-telemetry-hot-path.md @@ -59,8 +59,30 @@ Remover varredura e agregacao lock-heavy do hot path do tick sem perder observab ## Open Questions de Arquitetura 1. O certifier realmente precisa de snapshot de bank a cada tick? + Não. O certifier aceita dados do fim do frame anterior, pois violações de limites de bank costumam ser persistentes entre frames. 2. O overlay pode ler uma versao resumida da telemetria em vez de recalcular tudo? + Sim. O `AssetManager` passará a prover uma struct `BankStats` pré-calculada via contadores atômicos. 3. Vale manter caminho "preciso" so para testes/debug e caminho "barato" para runtime normal? + Sim, mas a "precisão" será definida como "atualizado no último evento de mutação", o que já é suficiente para ambos os casos. +4. Como detectar o modo de depuração/inspeção de forma correta e desacoplada? + Através de um novo campo `inspection_active: bool` no `VirtualMachineRuntime`, controlado explicitamente pelo Host (ex: quando o Overlay F1 ou o Debugger remoto estão ativos). O `certifier` não deve ser usado para este propósito. + +## Sugestao / Recomendacao + +1. **Modelo de Métrica (Push-based):** + - Migrar de snapshot total $O(n)$ para contadores incrementais $O(1)$ no `AssetManager`. + - Utilizar `AtomicUsize` ou campos protegidos por Mutex simples para `used_bytes`, `inflight_bytes` e `slots_occupied`. + - Atualizar esses contadores apenas em eventos de mutação (`load`, `commit`, `cancel`). + +2. **Frequência de Coleta (Dois Níveis):** + - **Básica (Sempre):** O Runtime deve atualizar `telemetry_current` no fechamento de cada logical frame (`FrameSync` ou `EndOfRom`). Isso garante dados para o `certifier` com custo $O(1)$. + - **Alta Frequência (Sob Demanda):** Manter atualização em todo host tick apenas se `inspection_active` for `true` (Overlay F1 visível ou Debugger conectado). + +3. **Responsabilidade da Agregação (Centralizada):** + - O `AssetManager` é o dono da "verdade incremental". O Runtime consome um snapshot barato (struct POD) sem varredura de locks. + +4. **Garantia de Consistência (Eventual):** + - Aceitar defasagem de até 1 frame lógico para métricas de asset bank. ## Dependencias diff --git a/discussion/workflow/agendas/AGD-0012-perf-host-debug-overlay-isolation.md b/discussion/workflow/agendas/AGD-0012-perf-host-debug-overlay-isolation.md index 747c5bec..8bb4ab88 100644 --- a/discussion/workflow/agendas/AGD-0012-perf-host-debug-overlay-isolation.md +++ b/discussion/workflow/agendas/AGD-0012-perf-host-debug-overlay-isolation.md @@ -52,19 +52,22 @@ Isolar o overlay de debug do custo medido do console sem perder utilidade para d ## Open Questions de Arquitetura 1. O overlay precisa ser representativo do hardware final ou apenas ferramenta de desktop? + Não, como é HUD técnico, pode e deve ser renderizado pelo Host nativo para melhor legibilidade. 2. Vale um modo "perf puro" onde overlay nunca toca no framebuffer do console? + Sim. O isolamento garante que o `gfx` emulado esteja 100% livre para o jogo durante a medição. 3. O host deve oferecer toggles separados para stats, logs e overlay visual? + Sim. O `HostRunner` deve expor controles granulares via `inspection_active`. +4. Como melhorar a legibilidade e estética (Glyphs/Transparência)? + Migrar a renderização do HUD para o Host Nativo (Winit/Pixels), permitindo o uso de fontes TrueType (monospaced) nítidas e Alpha Blending real para transparência no fundo do painel. ## Dependencias - `../specs/10-debug-inspection-and-profiling.md` - `../specs/11-portability-and-cross-platform-execution.md` -## Criterio de Saida Desta Agenda +## Sugestao / Recomendacao -Pode virar PR quando houver decisao escrita sobre: - -- onde o overlay e composto; -- politica de cache de texto/glyphs; -- como o custo do overlay aparece na telemetria; -- overhead maximo aceitavel em modo debug. +1. **Migração para Camada Host Nativa:** Renderizar o HUD de debug em uma surface separada ou via pipeline nativo do Host (depois do upscaling do framebuffer do console). +2. **Fontes TrueType (Mono):** Substituir os glyphs bitmapped rudimentares por uma fonte nativa de alta qualidade e nítida. +3. **Composição Alpha:** Permitir fundo semi-transparente para o overlay para não bloquear a visão do jogo. +4. **Acionamento Explícito:** Host deve gerenciar `inspection_active: true` no runtime apenas quando o HUD ou Debugger estiverem ativos. diff --git a/discussion/workflow/decisions/DEC-0005-perf-push-based-telemetry-model.md b/discussion/workflow/decisions/DEC-0005-perf-push-based-telemetry-model.md new file mode 100644 index 00000000..4399573e --- /dev/null +++ b/discussion/workflow/decisions/DEC-0005-perf-push-based-telemetry-model.md @@ -0,0 +1,57 @@ +--- +id: DEC-0005 +title: "Decisão - [PERF] Modelo de Telemetria Push-based" +status: closed +created: 2026-03-27 +resolved: 2026-03-27 +agenda: AGD-0007 +tags: [] +--- + +# Decisão - [PERF] Modelo de Telemetria Push-based + +## Status + +**Fechada (Closed)** - Consensus reached. Implementation approved. + +## Contexto + +O runtime atual (`VirtualMachineRuntime::tick()`) realiza a coleta de telemetria de asset banks (`gfx` e `audio`) em todos os *host ticks*. Essa coleta envolve a chamada de `bank_info()` no `AssetManager`, que executa uma varredura $O(n)$ sobre mapas de recursos e adquire múltiplos locks de leitura. Em hardwares limitados (handhelds), esse custo repetitivo no caminho quente degrada a performance desnecessariamente, mesmo quando o sistema está estável ou em pausa. + +## Decisão + +1. **Modelo de Contadores no Driver e Runtime:** O `AssetManager`, a `Heap` da VM e o `LogService` devem substituir a varredura/contagem total por **contadores atômicos** (`used_bytes`, `inflight_bytes`, `slots_occupied`, `logs_count`) para cada subsistema. Esses contadores serão atualizados incrementalmente ($O(1)$) em cada mutação (load, commit, alloc, free, log). +2. **Snapshot Obrigatório de Fim de Frame:** O runtime capturará o estado desses contadores (Banks, Heap e Logs) **apenas uma vez** por fechamento de frame lógico (`FrameSync` ou `EndOfRom`). Este snapshot será usado para alimentar a `telemetry_last` e o `certifier`. +3. **Coleta sob Demanda (Inspection Mode):** A coleta em cada *host tick* será reativada **somente** se o novo sinalizador `inspection_active: bool` do runtime for verdadeiro. +4. **Desacoplamento do Certifier:** A ativação do `certifier` não habilitará mais a telemetria detalhada em cada tick. O certifier será servido exclusivamente pelos snapshots de fim de frame lógico. + +## Rationale + +* **Performance:** Reduz o custo do tick do runtime de $O(n)$ com locks para $O(1)$ sem locks no modo normal. +* **Observabilidade:** Mantém dados precisos para o overlay (via modo inspeção) e dados válidos para o certifier (via snapshot de frame). +* **Modularidade:** Desacopla as necessidades de depuração (Overlay F1) das necessidades de validação normativa (Certifier). + +## Invariantes / Contrato + +* O `AssetManager` é a única fonte da verdade para o uso de memória de assets; o runtime não deve tentar calcular esses valores manualmente. +* Contadores atômicos garantem que o runtime possa ler estatísticas de bancos sem travar mutações em andamento (consistência eventual). +* A defasagem de até 1 frame lógico é aceitável para métricas de assets bank no modo de operação normal. + +## Impactos + +* **Drivers:** Necessidade de adicionar e gerenciar contadores no `AssetManager`. +* **Virtual Machine:** Adicionar contador atômico de `used_bytes` na `Heap`. +* **Log Service:** Adicionar contador incremental de logs emitidos no frame no `LogService`. +* **Runtime:** Modificação no `VirtualMachineRuntime` para incluir o campo `inspection_active` e lógica condicional no `tick()`. +* **Host:** O host (ex: desktop-winit) deve agora sinalizar quando o overlay de depuração está ativo via `inspection_active`. + +## Referências + +* Agenda [AGD-0007-perf-runtime-telemetry-hot-path.md](../agendas/AGD-0007-perf-runtime-telemetry-hot-path.md) +* Spec `10-debug-inspection-and-profiling.md` + +## Propagação Necessária + +* Atualizar o `VirtualMachineRuntime` para expor o campo `inspection_active`. +* Atualizar o `HostRunner` para sinalizar `inspection_active` quando o overlay F1 for alternado. +* Atualizar a struct `TelemetryFrame` para incluir campos de Heap Memory e Log count. diff --git a/discussion/workflow/plans/PLN-0005-perf-push-based-telemetry-implementation.md b/discussion/workflow/plans/PLN-0005-perf-push-based-telemetry-implementation.md new file mode 100644 index 00000000..8cf7b3e2 --- /dev/null +++ b/discussion/workflow/plans/PLN-0005-perf-push-based-telemetry-implementation.md @@ -0,0 +1,81 @@ +--- +id: PLN-0005 +title: "Plano - [PERF] Implementação de Telemetria Push-based" +status: open +created: 2026-03-27 +origin_decisions: + - DEC-0005 +tags: [] +--- + +# Plano - [PERF] Implementação de Telemetria Push-based + +## Briefing + +Este plano detalha as alterações técnicas para migrar o sistema de telemetria de um modelo de varredura $O(n)$ com locks para um modelo push-based com contadores atômicos $O(1)$. O objetivo é reduzir o overhead do hot path do runtime e adicionar visibilidade sobre a memória RAM (Heap) e volume de logs. + +## Decisions de Origem + +* [DEC-0005 - [PERF] Modelo de Telemetria Push-based](../decisions/DEC-0005-perf-push-based-telemetry-model.md) + +## Alvo + +* `prometeu-drivers` (AssetManager) +* `prometeu-vm` (Heap) +* `prometeu-hal` (TelemetryFrame) +* `prometeu-system` (LogService, VirtualMachineRuntime) +* `prometeu-host-desktop-winit` (HostRunner) + +## Escopo + +1. **Contadores Atômicos:** Implementação de `AtomicUsize` nos subsistemas de assets, heap e logs. +2. **Telemetry Frame:** Expansão da struct para incluir `heap_used`, `heap_max` e `logs_count`. +3. **Lógica de Tick:** Refatoração do `tick.rs` para usar `inspection_active` e snapshots de fim de frame. +4. **Sinalização do Host:** Integração do campo `inspection_active` com o acionamento do overlay F1. + +## Fora de Escopo + +* Migração da renderização do overlay para o host nativo (será tratado em plano derivado da AGD-0012). +* Telemetria de FileSystem IO ou Corrotinas. + +## Plano de Execucao + +### Fase 1: Drivers e VM (Modelo Push) +1. **`prometeu-drivers/src/asset.rs`:** + - Adicionar contadores em `BankPolicy`. + - Atualizar contadores em `load_internal`, `commit` e `cancel`. + - Refatorar `bank_info()` para retornar os valores atômicos sem varredura. +2. **`prometeu-vm/src/heap.rs`:** + - Adicionar contador `used_bytes` na struct `Heap`. + - Atualizar contador em `alloc()` e `free()`. +3. **`prometeu-system/src/log.rs`:** + - Adicionar contador `logs_count` (resetável por frame) no `LogService`. + +### Fase 2: HAL e Runtime (Contrato e Lógica) +1. **`prometeu-hal/src/telemetry.rs`:** + - Adicionar `heap_used_bytes`, `heap_max_bytes` e `logs_count` na `TelemetryFrame`. +2. **`prometeu-system/src/virtual_machine_runtime.rs`:** + - Adicionar campo `public inspection_active: bool`. +3. **`prometeu-system/src/virtual_machine_runtime/tick.rs`:** + - Modificar `tick()` para coletar `bank_info` detalhado apenas se `inspection_active == true`. + - Implementar a captura do snapshot consolidado no fechamento do logical frame. + +### Fase 3: Host e Integração +1. **`prometeu-host-desktop-winit/src/runner.rs`:** + - Sincronizar o estado do campo `overlay_enabled` com `firmware.os.inspection_active`. + +## Criterios de Aceite + +* O sistema compila sem avisos de tipos inexistentes. +* A telemetria de assets (`gfx`/`audio`) continua funcional no overlay F1. +* Novos campos de Heap e Logs aparecem no log de performance do console. +* O custo de telemetria no `tick` deve cair drasticamente quando o overlay estiver desligado (verificável via profiling). + +## Tests / Validacao + +* **Teste Unitário:** Criar teste em `asset.rs` para garantir que contadores batem com a realidade após sequências de carga e cancelamento. +* **Teste de Regressão:** Garantir que o `certifier` continua detectando violações de bank no fim do frame. + +## Riscos + +* **Consistência Eventual:** Como os contadores são atômicos e não travam o sistema, pode haver uma defasagem momentânea durante um `commit` pesado; isto é aceitável conforme DEC-0005. diff --git a/test-cartridges/stress-console/program.pbx b/test-cartridges/stress-console/program.pbx index f2013a8363793bef2b4dcc9b46590ddf95716b28..83b01e1922b659ed64b42e6d32f4ebc2b04ceb63 100644 GIT binary patch delta 89 zcmZ3>zK(r@hNS@m0|O%v&j4Z(AZ7yMH9(vXWH1A1X&?a-X8~e;AX@-PvjVaG#6(3w YAs{0+v$!}jFFigzr?Mbp<0NTj0LnWJDgXcg delta 88 zcmZ3-zLtH0hNV6O0|O%vPX}TVAZ7yM)j*sNWH1A1DIft7X8~e8AX@-PvjVZ*#6(2_ XK_DYHv$!}jFFn2_GbeT9WNBsqyEY81 From 698a076632be772acb75522ac5616a9f0ee2df7c Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Fri, 10 Apr 2026 09:02:04 +0100 Subject: [PATCH 02/10] [PERF] Runtime Telemetry Hot Path --- .../LSN-0026-push-based-telemetry-model.md | 52 ++++++---- ...GD-0007-perf-runtime-telemetry-hot-path.md | 99 ------------------- ...EC-0005-perf-push-based-telemetry-model.md | 57 ----------- ...erf-push-based-telemetry-implementation.md | 81 --------------- 4 files changed, 31 insertions(+), 258 deletions(-) delete mode 100644 discussion/workflow/agendas/AGD-0007-perf-runtime-telemetry-hot-path.md delete mode 100644 discussion/workflow/decisions/DEC-0005-perf-push-based-telemetry-model.md delete mode 100644 discussion/workflow/plans/PLN-0005-perf-push-based-telemetry-implementation.md diff --git a/discussion/lessons/DSC-0008-perf-runtime-telemetry-hot-path/LSN-0026-push-based-telemetry-model.md b/discussion/lessons/DSC-0008-perf-runtime-telemetry-hot-path/LSN-0026-push-based-telemetry-model.md index e7abfec2..f5aae37a 100644 --- a/discussion/lessons/DSC-0008-perf-runtime-telemetry-hot-path/LSN-0026-push-based-telemetry-model.md +++ b/discussion/lessons/DSC-0008-perf-runtime-telemetry-hot-path/LSN-0026-push-based-telemetry-model.md @@ -1,27 +1,37 @@ --- id: LSN-0026 ticket: perf-runtime-telemetry-hot-path -title: Modelo de Telemetria Push-based +title: Push-based Telemetry Model created: 2026-04-10 tags: [performance, telemetry, atomics] --- -# Modelo de Telemetria Push-based -O sistema de telemetria do PROMETEU evoluiu de um modelo de varredura sob demanda (pull) para um modelo de contadores incrementais (push), visando minimizar o impacto no *hot path* do runtime. -## O Problema Original -Anteriormente, a cada *host tick*, o runtime solicitava informações de uso de memória dos bancos de assets. Isso resultava em: -- Varreduras $O(n)$ sobre mapas de recursos. -- Múltiplas aquisições de *locks* de leitura em cada tick. -- Overhead desnecessário em hardwares handheld, onde cada microssegundo conta. -## A Solução: Modelo Push com Atômicos -A solução implementada utiliza `AtomicUsize` nos drivers e na VM para manter o estado do sistema em tempo real com custo $O(1)$ de leitura e escrita: -1. **Drivers (Assets):** Contadores atômicos em cada `BankPolicy` são atualizados durante `load`, `commit` e `cancel`. -2. **VM (Heap):** Um contador `used_bytes` na struct `Heap` rastreia alocações e liberações (sweep). -3. **System (Logs):** O `LogService` rastreia a pressão de logs emitida em cada frame. -## Dois Níveis de Observabilidade -Para equilibrar performance e depuração, a coleta foi dividida: -- **Snapshot de Frame (Sempre):** Captura automática no fim de cada frame lógico. Custo irrelevante ($O(1)$). Serve ao `Certifier` e ao log histórico. -- **Tick de Host (Sob Demanda):** A coleta detalhada em cada tick só ocorre se `inspection_active` estiver habilitado (ex: Overlay F1 ligado). -## Lições Aprendidas -- **Desacoplamento de Gatilhos:** Não devemos usar o estado do `Certifier` para habilitar funcionalidades de depuração visual (como o overlay), pois eles têm propósitos e custos diferentes. -- **Consistência Eventual é Suficiente:** Para métricas de telemetria, não é necessário travar o sistema para obter um valor exato a cada nanossegundo. A leitura relaxada de atômicos é suficiente e muito mais performática. -- **Isolamento de Custo:** Mover a lógica de agregação para o driver simplifica o runtime e garante que o custo de telemetria seja pago apenas durante mutações de estado, e não repetidamente durante a execução estável. + +# Push-based Telemetry Model + +The PROMETEU telemetry system evolved from an on-demand scan model (pull) to an incremental counter model (push), aiming to minimize the impact on the runtime's hot path. + +## The Original Problem + +Previously, at every host tick, the runtime requested memory usage information from the asset banks. This resulted in: +- $O(n)$ scans over resource maps. +- Multiple read lock acquisitions in every tick. +- Unnecessary overhead on handheld hardware, where every microsecond counts. + +## The Solution: Push Model with Atomics + +The implemented solution uses `AtomicUsize` in drivers and the VM to maintain the system state in real-time with $O(1)$ read and write cost: +1. **Drivers (Assets):** Atomic counters in each `BankPolicy` are updated during `load`, `commit`, and `cancel`. +2. **VM (Heap):** A `used_bytes` counter in the `Heap` struct tracks allocations and deallocations (sweep). +3. **System (Logs):** The `LogService` tracks log pressure emitted in each frame. + +## Two Levels of Observability + +To balance performance and debugging, the collection was divided: +- **Frame Snapshot (Always):** Automatic capture at the end of each logical frame. Irrelevant cost ($O(1)$). Serves the `Certifier` and historical logs. +- **Host Tick (On-Demand):** Detailed collection in every tick only occurs if `inspection_active` is enabled (e.g., F1 Overlay on). + +## Lessons Learned + +- **Trigger Decoupling:** We should not use the `Certifier` state to enable visual debugging features (like the overlay), as they have different purposes and costs. +- **Eventual Consistency is Sufficient:** For telemetry metrics, it is not necessary to lock the system to obtain an exact value every nanosecond. Relaxed atomic reading is sufficient and much more performant. +- **Cost Isolation:** Moving the aggregation logic to the driver simplifies the runtime and ensures that the telemetry cost is paid only during state mutations, rather than repeatedly during stable execution. diff --git a/discussion/workflow/agendas/AGD-0007-perf-runtime-telemetry-hot-path.md b/discussion/workflow/agendas/AGD-0007-perf-runtime-telemetry-hot-path.md deleted file mode 100644 index d620c2bb..00000000 --- a/discussion/workflow/agendas/AGD-0007-perf-runtime-telemetry-hot-path.md +++ /dev/null @@ -1,99 +0,0 @@ ---- -id: AGD-0007 -ticket: perf-runtime-telemetry-hot-path -title: Agenda - [PERF] Runtime Telemetry Hot Path -status: open -created: 2026-03-27 -resolved: -decision: -tags: [] ---- - -# Agenda - [PERF] Runtime Telemetry Hot Path - -## Problema - -O runtime cobra telemetria de asset bank no caminho quente de todo host tick. - -Hoje, `tick()` consulta `bank_info()` para `gfx` e `audio` mesmo quando nenhum logical frame foi fechado. O custo de observabilidade acaba sendo pago continuamente pela execucao normal. - -## Dor - -- CPU e locks sao gastos em todos os ticks, nao apenas quando a metrica muda. -- hardware barato sofre mais com trabalho pequeno e repetitivo do que com picos raros. -- overlay, stats e certifier acabam puxando custo estrutural para o core do runtime. - -## Hotspots Atuais - -- [tick.rs](/Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/crates/console/prometeu-system/src/virtual_machine_runtime/tick.rs#L167) -- [asset.rs](/Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/crates/console/prometeu-drivers/src/asset.rs#L618) - -## Alvo da Discussao - -Remover varredura e agregacao lock-heavy do hot path do tick sem perder observabilidade util. - -## O Que Precisa Ser Definido - -1. Modelo de metrica. - Decidir o que passa a ser contador incremental e o que continua sendo snapshot sob demanda. - -2. Frequencia de coleta. - Decidir se atualizacao acontece: - - no fechamento do logical frame; - - apenas com overlay/debug ativo; - - por amostragem periodica; - - por evento de mutacao (`load`, `commit`, `cancel`). - -3. Responsabilidade da agregacao. - Delimitar se a verdade dos bytes/slots fica: - - no `AssetManager`; - - no runtime; - - em uma camada propria de telemetry cache. - -4. Garantia de consistencia. - Decidir qual grau de defasagem e aceitavel para handheld barato: - - exato em tempo real; - - eventual por frame; - - eventual por tick de debug. - -## Open Questions de Arquitetura - -1. O certifier realmente precisa de snapshot de bank a cada tick? - Não. O certifier aceita dados do fim do frame anterior, pois violações de limites de bank costumam ser persistentes entre frames. -2. O overlay pode ler uma versao resumida da telemetria em vez de recalcular tudo? - Sim. O `AssetManager` passará a prover uma struct `BankStats` pré-calculada via contadores atômicos. -3. Vale manter caminho "preciso" so para testes/debug e caminho "barato" para runtime normal? - Sim, mas a "precisão" será definida como "atualizado no último evento de mutação", o que já é suficiente para ambos os casos. -4. Como detectar o modo de depuração/inspeção de forma correta e desacoplada? - Através de um novo campo `inspection_active: bool` no `VirtualMachineRuntime`, controlado explicitamente pelo Host (ex: quando o Overlay F1 ou o Debugger remoto estão ativos). O `certifier` não deve ser usado para este propósito. - -## Sugestao / Recomendacao - -1. **Modelo de Métrica (Push-based):** - - Migrar de snapshot total $O(n)$ para contadores incrementais $O(1)$ no `AssetManager`. - - Utilizar `AtomicUsize` ou campos protegidos por Mutex simples para `used_bytes`, `inflight_bytes` e `slots_occupied`. - - Atualizar esses contadores apenas em eventos de mutação (`load`, `commit`, `cancel`). - -2. **Frequência de Coleta (Dois Níveis):** - - **Básica (Sempre):** O Runtime deve atualizar `telemetry_current` no fechamento de cada logical frame (`FrameSync` ou `EndOfRom`). Isso garante dados para o `certifier` com custo $O(1)$. - - **Alta Frequência (Sob Demanda):** Manter atualização em todo host tick apenas se `inspection_active` for `true` (Overlay F1 visível ou Debugger conectado). - -3. **Responsabilidade da Agregação (Centralizada):** - - O `AssetManager` é o dono da "verdade incremental". O Runtime consome um snapshot barato (struct POD) sem varredura de locks. - -4. **Garantia de Consistência (Eventual):** - - Aceitar defasagem de até 1 frame lógico para métricas de asset bank. - -## Dependencias - -- `../specs/10-debug-inspection-and-profiling.md` -- `../specs/16a-syscall-policies.md` - -## Criterio de Saida Desta Agenda - -Pode virar PR quando houver decisao escrita sobre: - -- metrica incremental vs snapshot; -- ponto canonico de atualizacao da telemetria; -- custo maximo aceitavel no hot path do tick; -- comportamento de overlay/certifier sobre dados defasados. diff --git a/discussion/workflow/decisions/DEC-0005-perf-push-based-telemetry-model.md b/discussion/workflow/decisions/DEC-0005-perf-push-based-telemetry-model.md deleted file mode 100644 index 4399573e..00000000 --- a/discussion/workflow/decisions/DEC-0005-perf-push-based-telemetry-model.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -id: DEC-0005 -title: "Decisão - [PERF] Modelo de Telemetria Push-based" -status: closed -created: 2026-03-27 -resolved: 2026-03-27 -agenda: AGD-0007 -tags: [] ---- - -# Decisão - [PERF] Modelo de Telemetria Push-based - -## Status - -**Fechada (Closed)** - Consensus reached. Implementation approved. - -## Contexto - -O runtime atual (`VirtualMachineRuntime::tick()`) realiza a coleta de telemetria de asset banks (`gfx` e `audio`) em todos os *host ticks*. Essa coleta envolve a chamada de `bank_info()` no `AssetManager`, que executa uma varredura $O(n)$ sobre mapas de recursos e adquire múltiplos locks de leitura. Em hardwares limitados (handhelds), esse custo repetitivo no caminho quente degrada a performance desnecessariamente, mesmo quando o sistema está estável ou em pausa. - -## Decisão - -1. **Modelo de Contadores no Driver e Runtime:** O `AssetManager`, a `Heap` da VM e o `LogService` devem substituir a varredura/contagem total por **contadores atômicos** (`used_bytes`, `inflight_bytes`, `slots_occupied`, `logs_count`) para cada subsistema. Esses contadores serão atualizados incrementalmente ($O(1)$) em cada mutação (load, commit, alloc, free, log). -2. **Snapshot Obrigatório de Fim de Frame:** O runtime capturará o estado desses contadores (Banks, Heap e Logs) **apenas uma vez** por fechamento de frame lógico (`FrameSync` ou `EndOfRom`). Este snapshot será usado para alimentar a `telemetry_last` e o `certifier`. -3. **Coleta sob Demanda (Inspection Mode):** A coleta em cada *host tick* será reativada **somente** se o novo sinalizador `inspection_active: bool` do runtime for verdadeiro. -4. **Desacoplamento do Certifier:** A ativação do `certifier` não habilitará mais a telemetria detalhada em cada tick. O certifier será servido exclusivamente pelos snapshots de fim de frame lógico. - -## Rationale - -* **Performance:** Reduz o custo do tick do runtime de $O(n)$ com locks para $O(1)$ sem locks no modo normal. -* **Observabilidade:** Mantém dados precisos para o overlay (via modo inspeção) e dados válidos para o certifier (via snapshot de frame). -* **Modularidade:** Desacopla as necessidades de depuração (Overlay F1) das necessidades de validação normativa (Certifier). - -## Invariantes / Contrato - -* O `AssetManager` é a única fonte da verdade para o uso de memória de assets; o runtime não deve tentar calcular esses valores manualmente. -* Contadores atômicos garantem que o runtime possa ler estatísticas de bancos sem travar mutações em andamento (consistência eventual). -* A defasagem de até 1 frame lógico é aceitável para métricas de assets bank no modo de operação normal. - -## Impactos - -* **Drivers:** Necessidade de adicionar e gerenciar contadores no `AssetManager`. -* **Virtual Machine:** Adicionar contador atômico de `used_bytes` na `Heap`. -* **Log Service:** Adicionar contador incremental de logs emitidos no frame no `LogService`. -* **Runtime:** Modificação no `VirtualMachineRuntime` para incluir o campo `inspection_active` e lógica condicional no `tick()`. -* **Host:** O host (ex: desktop-winit) deve agora sinalizar quando o overlay de depuração está ativo via `inspection_active`. - -## Referências - -* Agenda [AGD-0007-perf-runtime-telemetry-hot-path.md](../agendas/AGD-0007-perf-runtime-telemetry-hot-path.md) -* Spec `10-debug-inspection-and-profiling.md` - -## Propagação Necessária - -* Atualizar o `VirtualMachineRuntime` para expor o campo `inspection_active`. -* Atualizar o `HostRunner` para sinalizar `inspection_active` quando o overlay F1 for alternado. -* Atualizar a struct `TelemetryFrame` para incluir campos de Heap Memory e Log count. diff --git a/discussion/workflow/plans/PLN-0005-perf-push-based-telemetry-implementation.md b/discussion/workflow/plans/PLN-0005-perf-push-based-telemetry-implementation.md deleted file mode 100644 index 8cf7b3e2..00000000 --- a/discussion/workflow/plans/PLN-0005-perf-push-based-telemetry-implementation.md +++ /dev/null @@ -1,81 +0,0 @@ ---- -id: PLN-0005 -title: "Plano - [PERF] Implementação de Telemetria Push-based" -status: open -created: 2026-03-27 -origin_decisions: - - DEC-0005 -tags: [] ---- - -# Plano - [PERF] Implementação de Telemetria Push-based - -## Briefing - -Este plano detalha as alterações técnicas para migrar o sistema de telemetria de um modelo de varredura $O(n)$ com locks para um modelo push-based com contadores atômicos $O(1)$. O objetivo é reduzir o overhead do hot path do runtime e adicionar visibilidade sobre a memória RAM (Heap) e volume de logs. - -## Decisions de Origem - -* [DEC-0005 - [PERF] Modelo de Telemetria Push-based](../decisions/DEC-0005-perf-push-based-telemetry-model.md) - -## Alvo - -* `prometeu-drivers` (AssetManager) -* `prometeu-vm` (Heap) -* `prometeu-hal` (TelemetryFrame) -* `prometeu-system` (LogService, VirtualMachineRuntime) -* `prometeu-host-desktop-winit` (HostRunner) - -## Escopo - -1. **Contadores Atômicos:** Implementação de `AtomicUsize` nos subsistemas de assets, heap e logs. -2. **Telemetry Frame:** Expansão da struct para incluir `heap_used`, `heap_max` e `logs_count`. -3. **Lógica de Tick:** Refatoração do `tick.rs` para usar `inspection_active` e snapshots de fim de frame. -4. **Sinalização do Host:** Integração do campo `inspection_active` com o acionamento do overlay F1. - -## Fora de Escopo - -* Migração da renderização do overlay para o host nativo (será tratado em plano derivado da AGD-0012). -* Telemetria de FileSystem IO ou Corrotinas. - -## Plano de Execucao - -### Fase 1: Drivers e VM (Modelo Push) -1. **`prometeu-drivers/src/asset.rs`:** - - Adicionar contadores em `BankPolicy`. - - Atualizar contadores em `load_internal`, `commit` e `cancel`. - - Refatorar `bank_info()` para retornar os valores atômicos sem varredura. -2. **`prometeu-vm/src/heap.rs`:** - - Adicionar contador `used_bytes` na struct `Heap`. - - Atualizar contador em `alloc()` e `free()`. -3. **`prometeu-system/src/log.rs`:** - - Adicionar contador `logs_count` (resetável por frame) no `LogService`. - -### Fase 2: HAL e Runtime (Contrato e Lógica) -1. **`prometeu-hal/src/telemetry.rs`:** - - Adicionar `heap_used_bytes`, `heap_max_bytes` e `logs_count` na `TelemetryFrame`. -2. **`prometeu-system/src/virtual_machine_runtime.rs`:** - - Adicionar campo `public inspection_active: bool`. -3. **`prometeu-system/src/virtual_machine_runtime/tick.rs`:** - - Modificar `tick()` para coletar `bank_info` detalhado apenas se `inspection_active == true`. - - Implementar a captura do snapshot consolidado no fechamento do logical frame. - -### Fase 3: Host e Integração -1. **`prometeu-host-desktop-winit/src/runner.rs`:** - - Sincronizar o estado do campo `overlay_enabled` com `firmware.os.inspection_active`. - -## Criterios de Aceite - -* O sistema compila sem avisos de tipos inexistentes. -* A telemetria de assets (`gfx`/`audio`) continua funcional no overlay F1. -* Novos campos de Heap e Logs aparecem no log de performance do console. -* O custo de telemetria no `tick` deve cair drasticamente quando o overlay estiver desligado (verificável via profiling). - -## Tests / Validacao - -* **Teste Unitário:** Criar teste em `asset.rs` para garantir que contadores batem com a realidade após sequências de carga e cancelamento. -* **Teste de Regressão:** Garantir que o `certifier` continua detectando violações de bank no fim do frame. - -## Riscos - -* **Consistência Eventual:** Como os contadores são atômicos e não travam o sistema, pode haver uma defasagem momentânea durante um `commit` pesado; isto é aceitável conforme DEC-0005. From e672e4d6b625100f6b8a92c422b94b9aea5b93f4 Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Fri, 10 Apr 2026 09:20:07 +0100 Subject: [PATCH 03/10] [PERF] Runtime Telemetry Hot Path --- crates/console/prometeu-drivers/src/asset.rs | 32 +++++++++++++------ .../prometeu-hal/src/log/log_service.rs | 7 +--- crates/console/prometeu-hal/src/telemetry.rs | 5 +-- .../src/virtual_machine_runtime/tick.rs | 3 +- crates/console/prometeu-vm/src/heap.rs | 2 +- 5 files changed, 28 insertions(+), 21 deletions(-) diff --git a/crates/console/prometeu-drivers/src/asset.rs b/crates/console/prometeu-drivers/src/asset.rs index ac0f1169..b7f6b2bb 100644 --- a/crates/console/prometeu-drivers/src/asset.rs +++ b/crates/console/prometeu-drivers/src/asset.rs @@ -17,10 +17,17 @@ use std::sync::{Arc, Mutex, RwLock}; use std::thread; use std::time::Instant; +type ResidentMap = HashMap>; +type StagedValue = (Arc, usize); +type StagingMap = HashMap>; + +type AssetTable = HashMap; +type HandleTable = HashMap; + const GLYPH_BANK_PALETTE_COUNT_V1: usize = 64; const GLYPH_BANK_COLORS_PER_PALETTE: usize = 16; const GLYPH_BANK_PALETTE_BYTES_V1: usize = - GLYPH_BANK_PALETTE_COUNT_V1 * GLYPH_BANK_COLORS_PER_PALETTE * std::mem::size_of::(); + GLYPH_BANK_PALETTE_COUNT_V1 * GLYPH_BANK_COLORS_PER_PALETTE * size_of::(); /// Resident metadata for a decoded/materialized asset inside a BankPolicy. #[derive(Debug)] @@ -54,10 +61,10 @@ impl ResidentEntry { /// This is internal to the AssetManager and not visible to peripherals. pub struct BankPolicy { /// Dedup table: asset_id -> resident entry (value + telemetry). - pub resident: Arc>>>, + pub resident: Arc>>, /// Staging area: handle -> value ready to commit. - pub staging: Arc, usize)>>>, + pub staging: Arc>>, /// Total bytes currently in resident storage. pub used_bytes: Arc, @@ -119,7 +126,7 @@ impl BankPolicy { } /// Take staged value (used by commit path). - pub fn take_staging(&self, handle: HandleId) -> Option<(Arc, usize)> { + pub fn take_staging(&self, handle: HandleId) -> Option> { let entry = self.staging.write().unwrap().remove(&handle); if let Some((_, bytes)) = entry.as_ref() { self.inflight_bytes.fetch_sub(*bytes, Ordering::Relaxed); @@ -136,8 +143,8 @@ impl BankPolicy { } pub struct AssetManager { - assets: Arc>>, - handles: Arc>>, + assets: Arc>, + handles: Arc>, next_handle_id: Mutex, assets_data: Arc>, @@ -476,7 +483,11 @@ impl AssetManager { bank_arc, entry_clone.decoded_size as usize, ); - gfx_policy.stage(handle_id, resident_arc, entry_clone.decoded_size as usize); + gfx_policy.stage( + handle_id, + resident_arc, + entry_clone.decoded_size as usize, + ); let mut handles_map = handles.write().unwrap(); if let Some(h) = handles_map.get_mut(&handle_id) { @@ -500,8 +511,11 @@ impl AssetManager { bank_arc, entry_clone.decoded_size as usize, ); - sound_policy - .stage(handle_id, resident_arc, entry_clone.decoded_size as usize); + sound_policy.stage( + handle_id, + resident_arc, + entry_clone.decoded_size as usize, + ); let mut handles_map = handles.write().unwrap(); if let Some(h) = handles_map.get_mut(&handle_id) { diff --git a/crates/console/prometeu-hal/src/log/log_service.rs b/crates/console/prometeu-hal/src/log/log_service.rs index e6d991e6..b5620f65 100644 --- a/crates/console/prometeu-hal/src/log/log_service.rs +++ b/crates/console/prometeu-hal/src/log/log_service.rs @@ -10,12 +10,7 @@ pub struct LogService { impl LogService { pub fn new(capacity: usize) -> Self { - Self { - events: VecDeque::with_capacity(capacity), - capacity, - next_seq: 0, - logs_count: 0, - } + Self { events: VecDeque::with_capacity(capacity), capacity, next_seq: 0, logs_count: 0 } } pub fn log( diff --git a/crates/console/prometeu-hal/src/telemetry.rs b/crates/console/prometeu-hal/src/telemetry.rs index 361cf11c..c061e98d 100644 --- a/crates/console/prometeu-hal/src/telemetry.rs +++ b/crates/console/prometeu-hal/src/telemetry.rs @@ -180,10 +180,7 @@ impl Certifier { LogLevel::Warn, LogSource::Pos, 0xCA07, - format!( - "Cert: Log pressure exceeded limit ({} > {})", - telemetry.logs_count, limit - ), + format!("Cert: Log pressure exceeded limit ({} > {})", telemetry.logs_count, limit), ); violations += 1; } diff --git a/crates/console/prometeu-system/src/virtual_machine_runtime/tick.rs b/crates/console/prometeu-system/src/virtual_machine_runtime/tick.rs index 9168fae8..a879f514 100644 --- a/crates/console/prometeu-system/src/virtual_machine_runtime/tick.rs +++ b/crates/console/prometeu-system/src/virtual_machine_runtime/tick.rs @@ -149,7 +149,8 @@ impl VirtualMachineRuntime { self.telemetry_current.logs_count = self.log_service.logs_count; self.log_service.reset_count(); - self.telemetry_current.host_cpu_time_us = start.elapsed().as_micros() as u64; + self.telemetry_current.host_cpu_time_us = + start.elapsed().as_micros() as u64; let ts_ms = self.boot_time.elapsed().as_millis() as u64; self.telemetry_current.violations = self.certifier.evaluate( diff --git a/crates/console/prometeu-vm/src/heap.rs b/crates/console/prometeu-vm/src/heap.rs index bcd4f2dd..0b3d3463 100644 --- a/crates/console/prometeu-vm/src/heap.rs +++ b/crates/console/prometeu-vm/src/heap.rs @@ -2,8 +2,8 @@ use crate::call_frame::CallFrame; use crate::object::{ObjectHeader, ObjectKind}; use prometeu_bytecode::{HeapRef, Value}; -use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; +use std::sync::atomic::{AtomicUsize, Ordering}; /// Internal stored object: header plus opaque payload bytes. #[derive(Debug, Clone)] From f5580ae0ce6aa5bbbf6ee02d053d5e590467d198 Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Fri, 10 Apr 2026 09:25:05 +0100 Subject: [PATCH 04/10] SyscallRegistryEntry rewrite to use builder pattern --- crates/console/prometeu-hal/src/syscalls.rs | 56 +++++ .../src/syscalls/domains/asset.rs | 74 ++---- .../src/syscalls/domains/audio.rs | 37 +-- .../prometeu-hal/src/syscalls/domains/bank.rs | 35 +-- .../prometeu-hal/src/syscalls/domains/fs.rs | 212 ++++++------------ .../prometeu-hal/src/syscalls/domains/gfx.rs | 148 ++++-------- .../prometeu-hal/src/syscalls/domains/log.rs | 37 +-- .../prometeu-hal/src/syscalls/domains/mod.rs | 31 +-- .../src/syscalls/domains/system.rs | 34 +-- crates/console/prometeu-vm/src/verifier.rs | 8 +- 10 files changed, 227 insertions(+), 445 deletions(-) diff --git a/crates/console/prometeu-hal/src/syscalls.rs b/crates/console/prometeu-hal/src/syscalls.rs index 9565b8bb..c4a90381 100644 --- a/crates/console/prometeu-hal/src/syscalls.rs +++ b/crates/console/prometeu-hal/src/syscalls.rs @@ -96,6 +96,62 @@ pub struct SyscallRegistryEntry { pub meta: SyscallMeta, } +impl SyscallRegistryEntry { + /// Starts the builder with mandatory fields and sensible default values. + pub const fn builder(syscall: Syscall, module: &'static str, name: &'static str) -> Self { + Self { + syscall, + meta: SyscallMeta { + id: syscall as u32, + module, + name, + version: 1, // Default for new syscalls + arg_slots: 0, + ret_slots: 0, + caps: 0, + determinism: Determinism::Deterministic, + may_allocate: false, + cost_hint: 1, + }, + } + } + + pub const fn version(mut self, n: u16) -> Self { + self.meta.version = n; + self + } + + pub const fn args(mut self, n: u8) -> Self { + self.meta.arg_slots = n; + self + } + + pub const fn rets(mut self, n: u16) -> Self { + self.meta.ret_slots = n; + self + } + + pub const fn caps(mut self, caps: CapFlags) -> Self { + self.meta.caps = caps; + self + } + + pub const fn non_deterministic(mut self) -> Self { + self.meta.determinism = Determinism::NonDeterministic; + self + } + + pub const fn may_allocate(mut self) -> Self { + self.meta.may_allocate = true; + self + } + + pub const fn cost(mut self, cost: u32) -> Self { + self.meta.cost_hint = cost; + self + } +} + pub fn meta_for(syscall: Syscall) -> &'static SyscallMeta { registry::meta_for(syscall) } diff --git a/crates/console/prometeu-hal/src/syscalls/domains/asset.rs b/crates/console/prometeu-hal/src/syscalls/domains/asset.rs index a57cbba1..26c7ee30 100644 --- a/crates/console/prometeu-hal/src/syscalls/domains/asset.rs +++ b/crates/console/prometeu-hal/src/syscalls/domains/asset.rs @@ -1,53 +1,27 @@ -use super::entry; -use crate::syscalls::{Determinism, Syscall, SyscallRegistryEntry, caps}; +use crate::syscalls::{Syscall, SyscallRegistryEntry, caps}; pub(crate) const ENTRIES: &[SyscallRegistryEntry] = &[ - entry( - Syscall::AssetLoad, - "asset", - "load", - 1, - 2, - 2, - caps::ASSET, - Determinism::NonDeterministic, - false, - 20, - ), - entry( - Syscall::AssetStatus, - "asset", - "status", - 1, - 1, - 1, - caps::ASSET, - Determinism::NonDeterministic, - false, - 1, - ), - entry( - Syscall::AssetCommit, - "asset", - "commit", - 1, - 1, - 1, - caps::ASSET, - Determinism::NonDeterministic, - false, - 20, - ), - entry( - Syscall::AssetCancel, - "asset", - "cancel", - 1, - 1, - 1, - caps::ASSET, - Determinism::NonDeterministic, - false, - 20, - ), + SyscallRegistryEntry::builder(Syscall::AssetLoad, "asset", "load") + .args(2) + .rets(2) + .caps(caps::ASSET) + .non_deterministic() + .cost(20), + SyscallRegistryEntry::builder(Syscall::AssetStatus, "asset", "status") + .args(1) + .rets(1) + .caps(caps::ASSET) + .non_deterministic(), + SyscallRegistryEntry::builder(Syscall::AssetCommit, "asset", "commit") + .args(1) + .rets(1) + .caps(caps::ASSET) + .non_deterministic() + .cost(20), + SyscallRegistryEntry::builder(Syscall::AssetCancel, "asset", "cancel") + .args(1) + .rets(1) + .caps(caps::ASSET) + .non_deterministic() + .cost(20), ]; diff --git a/crates/console/prometeu-hal/src/syscalls/domains/audio.rs b/crates/console/prometeu-hal/src/syscalls/domains/audio.rs index 5fd1abad..f2898dd4 100644 --- a/crates/console/prometeu-hal/src/syscalls/domains/audio.rs +++ b/crates/console/prometeu-hal/src/syscalls/domains/audio.rs @@ -1,29 +1,14 @@ -use super::entry; -use crate::syscalls::{Determinism, Syscall, SyscallRegistryEntry, caps}; +use crate::syscalls::{Syscall, SyscallRegistryEntry, caps}; pub(crate) const ENTRIES: &[SyscallRegistryEntry] = &[ - entry( - Syscall::AudioPlaySample, - "audio", - "play_sample", - 1, - 5, - 1, - caps::AUDIO, - Determinism::Deterministic, - false, - 5, - ), - entry( - Syscall::AudioPlay, - "audio", - "play", - 1, - 7, - 1, - caps::AUDIO, - Determinism::Deterministic, - false, - 5, - ), + SyscallRegistryEntry::builder(Syscall::AudioPlaySample, "audio", "play_sample") + .args(5) + .rets(1) + .caps(caps::AUDIO) + .cost(5), + SyscallRegistryEntry::builder(Syscall::AudioPlay, "audio", "play") + .args(7) + .rets(1) + .caps(caps::AUDIO) + .cost(5), ]; diff --git a/crates/console/prometeu-hal/src/syscalls/domains/bank.rs b/crates/console/prometeu-hal/src/syscalls/domains/bank.rs index b8665707..87a7ef1b 100644 --- a/crates/console/prometeu-hal/src/syscalls/domains/bank.rs +++ b/crates/console/prometeu-hal/src/syscalls/domains/bank.rs @@ -1,29 +1,12 @@ -use super::entry; -use crate::syscalls::{Determinism, Syscall, SyscallRegistryEntry, caps}; +use crate::syscalls::{Syscall, SyscallRegistryEntry, caps}; pub(crate) const ENTRIES: &[SyscallRegistryEntry] = &[ - entry( - Syscall::BankInfo, - "bank", - "info", - 1, - 1, - 1, - caps::BANK, - Determinism::Deterministic, - false, - 1, - ), - entry( - Syscall::BankSlotInfo, - "bank", - "slot_info", - 1, - 2, - 1, - caps::BANK, - Determinism::Deterministic, - false, - 1, - ), + SyscallRegistryEntry::builder(Syscall::BankInfo, "bank", "info") + .args(1) + .rets(1) + .caps(caps::BANK), + SyscallRegistryEntry::builder(Syscall::BankSlotInfo, "bank", "slot_info") + .args(2) + .rets(1) + .caps(caps::BANK), ]; diff --git a/crates/console/prometeu-hal/src/syscalls/domains/fs.rs b/crates/console/prometeu-hal/src/syscalls/domains/fs.rs index 93ea1877..27e458fe 100644 --- a/crates/console/prometeu-hal/src/syscalls/domains/fs.rs +++ b/crates/console/prometeu-hal/src/syscalls/domains/fs.rs @@ -1,150 +1,68 @@ -use super::entry; -use crate::syscalls::{Determinism, Syscall, SyscallRegistryEntry, caps}; +use crate::syscalls::{Syscall, SyscallRegistryEntry, caps}; pub(crate) const ENTRIES: &[SyscallRegistryEntry] = &[ - entry( - Syscall::FsOpen, - "fs", - "open", - 1, - 1, - 1, - caps::FS, - Determinism::NonDeterministic, - false, - 20, - ), - entry( - Syscall::FsRead, - "fs", - "read", - 1, - 1, - 1, - caps::FS, - Determinism::NonDeterministic, - false, - 20, - ), - entry( - Syscall::FsWrite, - "fs", - "write", - 1, - 2, - 1, - caps::FS, - Determinism::NonDeterministic, - false, - 20, - ), - entry(Syscall::FsClose, "fs", "close", 1, 1, 0, caps::FS, Determinism::Deterministic, false, 5), - entry( - Syscall::FsListDir, - "fs", - "list_dir", - 1, - 1, - 1, - caps::FS, - Determinism::NonDeterministic, - false, - 20, - ), - entry( - Syscall::FsExists, - "fs", - "exists", - 1, - 1, - 1, - caps::FS, - Determinism::Deterministic, - false, - 1, - ), - entry( - Syscall::FsDelete, - "fs", - "delete", - 1, - 1, - 0, - caps::FS, - Determinism::NonDeterministic, - false, - 20, - ), - entry( - Syscall::MemSlotCount, - "mem", - "slot_count", - 1, - 0, - 2, - caps::FS, - Determinism::Deterministic, - false, - 1, - ), - entry( - Syscall::MemSlotStat, - "mem", - "slot_stat", - 1, - 1, - 5, - caps::FS, - Determinism::NonDeterministic, - false, - 5, - ), - entry( - Syscall::MemSlotRead, - "mem", - "slot_read", - 1, - 3, - 3, - caps::FS, - Determinism::NonDeterministic, - false, - 20, - ), - entry( - Syscall::MemSlotWrite, - "mem", - "slot_write", - 1, - 3, - 2, - caps::FS, - Determinism::NonDeterministic, - false, - 20, - ), - entry( - Syscall::MemSlotCommit, - "mem", - "slot_commit", - 1, - 1, - 1, - caps::FS, - Determinism::NonDeterministic, - false, - 20, - ), - entry( - Syscall::MemSlotClear, - "mem", - "slot_clear", - 1, - 1, - 1, - caps::FS, - Determinism::NonDeterministic, - false, - 20, - ), + SyscallRegistryEntry::builder(Syscall::FsOpen, "fs", "open") + .args(1) + .rets(1) + .caps(caps::FS) + .non_deterministic() + .cost(20), + SyscallRegistryEntry::builder(Syscall::FsRead, "fs", "read") + .args(1) + .rets(1) + .caps(caps::FS) + .non_deterministic() + .cost(20), + SyscallRegistryEntry::builder(Syscall::FsWrite, "fs", "write") + .args(2) + .rets(1) + .caps(caps::FS) + .non_deterministic() + .cost(20), + SyscallRegistryEntry::builder(Syscall::FsClose, "fs", "close").args(1).caps(caps::FS).cost(5), + SyscallRegistryEntry::builder(Syscall::FsListDir, "fs", "list_dir") + .args(1) + .rets(1) + .caps(caps::FS) + .non_deterministic() + .cost(20), + SyscallRegistryEntry::builder(Syscall::FsExists, "fs", "exists").args(1).rets(1).caps(caps::FS), + SyscallRegistryEntry::builder(Syscall::FsDelete, "fs", "delete") + .args(1) + .caps(caps::FS) + .non_deterministic() + .cost(20), + SyscallRegistryEntry::builder(Syscall::MemSlotCount, "mem", "slot_count") + .rets(2) + .caps(caps::FS), + SyscallRegistryEntry::builder(Syscall::MemSlotStat, "mem", "slot_stat") + .args(1) + .rets(5) + .caps(caps::FS) + .non_deterministic() + .cost(5), + SyscallRegistryEntry::builder(Syscall::MemSlotRead, "mem", "slot_read") + .args(3) + .rets(3) + .caps(caps::FS) + .non_deterministic() + .cost(20), + SyscallRegistryEntry::builder(Syscall::MemSlotWrite, "mem", "slot_write") + .args(3) + .rets(2) + .caps(caps::FS) + .non_deterministic() + .cost(20), + SyscallRegistryEntry::builder(Syscall::MemSlotCommit, "mem", "slot_commit") + .args(1) + .rets(1) + .caps(caps::FS) + .non_deterministic() + .cost(20), + SyscallRegistryEntry::builder(Syscall::MemSlotClear, "mem", "slot_clear") + .args(1) + .rets(1) + .caps(caps::FS) + .non_deterministic() + .cost(20), ]; diff --git a/crates/console/prometeu-hal/src/syscalls/domains/gfx.rs b/crates/console/prometeu-hal/src/syscalls/domains/gfx.rs index bad18763..95998186 100644 --- a/crates/console/prometeu-hal/src/syscalls/domains/gfx.rs +++ b/crates/console/prometeu-hal/src/syscalls/domains/gfx.rs @@ -1,113 +1,41 @@ -use super::entry; -use crate::syscalls::{Determinism, Syscall, SyscallRegistryEntry, caps}; +use crate::syscalls::{Syscall, SyscallRegistryEntry, caps}; pub(crate) const ENTRIES: &[SyscallRegistryEntry] = &[ - entry( - Syscall::GfxClear, - "gfx", - "clear", - 1, - 1, - 0, - caps::GFX, - Determinism::Deterministic, - false, - 20, - ), - entry( - Syscall::GfxFillRect, - "gfx", - "fill_rect", - 1, - 5, - 0, - caps::GFX, - Determinism::Deterministic, - false, - 20, - ), - entry( - Syscall::GfxDrawLine, - "gfx", - "draw_line", - 1, - 5, - 0, - caps::GFX, - Determinism::Deterministic, - false, - 5, - ), - entry( - Syscall::GfxDrawCircle, - "gfx", - "draw_circle", - 1, - 4, - 0, - caps::GFX, - Determinism::Deterministic, - false, - 5, - ), - entry( - Syscall::GfxDrawDisc, - "gfx", - "draw_disc", - 1, - 5, - 0, - caps::GFX, - Determinism::Deterministic, - false, - 5, - ), - entry( - Syscall::GfxDrawSquare, - "gfx", - "draw_square", - 1, - 6, - 0, - caps::GFX, - Determinism::Deterministic, - false, - 5, - ), - entry( - Syscall::GfxSetSprite, - "gfx", - "set_sprite", - 1, - 10, - 1, - caps::GFX, - Determinism::Deterministic, - false, - 5, - ), - entry( - Syscall::GfxDrawText, - "gfx", - "draw_text", - 1, - 4, - 0, - caps::GFX, - Determinism::Deterministic, - false, - 20, - ), - entry( - Syscall::GfxClear565, - "gfx", - "clear_565", - 1, - 1, - 0, - caps::GFX, - Determinism::Deterministic, - false, - 20, - ), + SyscallRegistryEntry::builder(Syscall::GfxClear, "gfx", "clear") + .args(1) + .caps(caps::GFX) + .cost(20), + SyscallRegistryEntry::builder(Syscall::GfxFillRect, "gfx", "fill_rect") + .args(5) + .caps(caps::GFX) + .cost(20), + SyscallRegistryEntry::builder(Syscall::GfxDrawLine, "gfx", "draw_line") + .args(5) + .caps(caps::GFX) + .cost(5), + SyscallRegistryEntry::builder(Syscall::GfxDrawCircle, "gfx", "draw_circle") + .args(4) + .caps(caps::GFX) + .cost(5), + SyscallRegistryEntry::builder(Syscall::GfxDrawDisc, "gfx", "draw_disc") + .args(5) + .caps(caps::GFX) + .cost(5), + SyscallRegistryEntry::builder(Syscall::GfxDrawSquare, "gfx", "draw_square") + .args(6) + .caps(caps::GFX) + .cost(5), + SyscallRegistryEntry::builder(Syscall::GfxSetSprite, "gfx", "set_sprite") + .args(10) + .rets(1) + .caps(caps::GFX) + .cost(5), + SyscallRegistryEntry::builder(Syscall::GfxDrawText, "gfx", "draw_text") + .args(4) + .caps(caps::GFX) + .cost(20), + SyscallRegistryEntry::builder(Syscall::GfxClear565, "gfx", "clear_565") + .args(1) + .caps(caps::GFX) + .cost(20), ]; diff --git a/crates/console/prometeu-hal/src/syscalls/domains/log.rs b/crates/console/prometeu-hal/src/syscalls/domains/log.rs index 8b03af1f..e4f15400 100644 --- a/crates/console/prometeu-hal/src/syscalls/domains/log.rs +++ b/crates/console/prometeu-hal/src/syscalls/domains/log.rs @@ -1,29 +1,14 @@ -use super::entry; -use crate::syscalls::{Determinism, Syscall, SyscallRegistryEntry, caps}; +use crate::syscalls::{Syscall, SyscallRegistryEntry, caps}; pub(crate) const ENTRIES: &[SyscallRegistryEntry] = &[ - entry( - Syscall::LogWrite, - "log", - "write", - 1, - 2, - 0, - caps::LOG, - Determinism::NonDeterministic, - false, - 5, - ), - entry( - Syscall::LogWriteTag, - "log", - "write_tag", - 1, - 3, - 0, - caps::LOG, - Determinism::NonDeterministic, - false, - 5, - ), + SyscallRegistryEntry::builder(Syscall::LogWrite, "log", "write") + .args(2) + .caps(caps::LOG) + .non_deterministic() + .cost(5), + SyscallRegistryEntry::builder(Syscall::LogWriteTag, "log", "write_tag") + .args(3) + .caps(caps::LOG) + .non_deterministic() + .cost(5), ]; diff --git a/crates/console/prometeu-hal/src/syscalls/domains/mod.rs b/crates/console/prometeu-hal/src/syscalls/domains/mod.rs index 975d5a03..3b4d3b34 100644 --- a/crates/console/prometeu-hal/src/syscalls/domains/mod.rs +++ b/crates/console/prometeu-hal/src/syscalls/domains/mod.rs @@ -6,36 +6,7 @@ mod gfx; mod log; mod system; -use super::{CapFlags, Determinism, Syscall, SyscallMeta, SyscallRegistryEntry}; - -pub(crate) const fn entry( - syscall: Syscall, - module: &'static str, - name: &'static str, - version: u16, - arg_slots: u8, - ret_slots: u16, - caps: CapFlags, - determinism: Determinism, - may_allocate: bool, - cost_hint: u32, -) -> SyscallRegistryEntry { - SyscallRegistryEntry { - syscall, - meta: SyscallMeta { - id: syscall as u32, - module, - name, - version, - arg_slots, - ret_slots, - caps, - determinism, - may_allocate, - cost_hint, - }, - } -} +use super::SyscallRegistryEntry; pub(crate) fn all_entries() -> impl Iterator { system::ENTRIES diff --git a/crates/console/prometeu-hal/src/syscalls/domains/system.rs b/crates/console/prometeu-hal/src/syscalls/domains/system.rs index 2f5259a7..3d8b247a 100644 --- a/crates/console/prometeu-hal/src/syscalls/domains/system.rs +++ b/crates/console/prometeu-hal/src/syscalls/domains/system.rs @@ -1,29 +1,11 @@ -use super::entry; -use crate::syscalls::{Determinism, Syscall, SyscallRegistryEntry, caps}; +use crate::syscalls::{Syscall, SyscallRegistryEntry, caps}; pub(crate) const ENTRIES: &[SyscallRegistryEntry] = &[ - entry( - Syscall::SystemHasCart, - "system", - "has_cart", - 1, - 0, - 1, - caps::SYSTEM, - Determinism::Deterministic, - false, - 1, - ), - entry( - Syscall::SystemRunCart, - "system", - "run_cart", - 1, - 0, - 0, - caps::SYSTEM, - Determinism::NonDeterministic, - false, - 50, - ), + SyscallRegistryEntry::builder(Syscall::SystemHasCart, "system", "has_cart") + .rets(1) + .caps(caps::SYSTEM), + SyscallRegistryEntry::builder(Syscall::SystemRunCart, "system", "run_cart") + .caps(caps::SYSTEM) + .non_deterministic() + .cost(50), ]; diff --git a/crates/console/prometeu-vm/src/verifier.rs b/crates/console/prometeu-vm/src/verifier.rs index b389f735..a689abc6 100644 --- a/crates/console/prometeu-vm/src/verifier.rs +++ b/crates/console/prometeu-vm/src/verifier.rs @@ -146,10 +146,10 @@ impl Verifier { let func_code = &code[func_start..func_end]; - // Funções vazias (sem qualquer byte de código) são consideradas válidas no verificador. - // Elas não consomem nem produzem valores na pilha e não possuem fluxo interno. - // Observação: se uma função vazia for chamada em tempo de execução e retorno/efeitos - // forem esperados, caberá ao gerador de código/linker impedir tal situação. + // Empty functions (no code bytes) are considered valid in the verifier. + // They do not consume or produce values on the stack and have no internal flow. + // Note: if an empty function is called at runtime and return/effects + // are expected, it is the responsibility of the code generator/linker to prevent this situation. if func_code.is_empty() { return Ok(0); } From 47ab6642802aef96065b6405da50da150f631393 Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Fri, 10 Apr 2026 10:55:15 +0100 Subject: [PATCH 05/10] [PERF] Host Debug Overlay Isolation --- .../prometeu-hal/src/log/log_service.rs | 15 ++- crates/console/prometeu-hal/src/telemetry.rs | 67 +++++++++++++- .../src/virtual_machine_runtime.rs | 6 +- .../src/virtual_machine_runtime/dispatch.rs | 3 +- .../src/virtual_machine_runtime/lifecycle.rs | 9 +- .../src/virtual_machine_runtime/tests.rs | 18 ++-- .../src/virtual_machine_runtime/tick.rs | 89 ++++++++---------- .../src/debugger.rs | 4 +- .../prometeu-host-desktop-winit/src/runner.rs | 20 ++-- .../prometeu-host-desktop-winit/src/stats.rs | 2 +- discussion/index.ndjson | 5 +- .../LSN-0027-host-debug-overlay-isolation.md | 39 ++++++++ ...rging-to-single-atomic-telemetry-source.md | 23 +++++ ...-0012-perf-host-debug-overlay-isolation.md | 92 +++++++------------ ...0021-full-migration-to-atomic-telemetry.md | 31 +++++++ ...-0007-perf-host-debug-overlay-isolation.md | 53 +++++++++++ ...0008-full-migration-to-atomic-telemetry.md | 35 +++++++ ...-0006-perf-host-debug-overlay-isolation.md | 64 +++++++++++++ ...0007-full-migration-to-atomic-telemetry.md | 52 +++++++++++ .../10-debug-inspection-and-profiling.md | 29 +++++- ...ortability-and-cross-platform-execution.md | 24 ++++- 21 files changed, 526 insertions(+), 154 deletions(-) create mode 100644 discussion/lessons/DSC-0013-perf-host-debug-overlay-isolation/LSN-0027-host-debug-overlay-isolation.md create mode 100644 discussion/lessons/DSC-0023-perf-full-migration-to-atomic-telemetry/LSN-0028-converging-to-single-atomic-telemetry-source.md create mode 100644 discussion/workflow/agendas/AGD-0021-full-migration-to-atomic-telemetry.md create mode 100644 discussion/workflow/decisions/DEC-0007-perf-host-debug-overlay-isolation.md create mode 100644 discussion/workflow/decisions/DEC-0008-full-migration-to-atomic-telemetry.md create mode 100644 discussion/workflow/plans/PLN-0006-perf-host-debug-overlay-isolation.md create mode 100644 discussion/workflow/plans/PLN-0007-full-migration-to-atomic-telemetry.md diff --git a/crates/console/prometeu-hal/src/log/log_service.rs b/crates/console/prometeu-hal/src/log/log_service.rs index b5620f65..c796b63a 100644 --- a/crates/console/prometeu-hal/src/log/log_service.rs +++ b/crates/console/prometeu-hal/src/log/log_service.rs @@ -1,16 +1,23 @@ use crate::log::{LogEvent, LogLevel, LogSource}; use std::collections::VecDeque; +use std::sync::atomic::{AtomicU32, Ordering}; +use std::sync::Arc; pub struct LogService { events: VecDeque, capacity: usize, next_seq: u64, - pub logs_count: u32, + pub logs_count: Arc, } impl LogService { pub fn new(capacity: usize) -> Self { - Self { events: VecDeque::with_capacity(capacity), capacity, next_seq: 0, logs_count: 0 } + Self { + events: VecDeque::with_capacity(capacity), + capacity, + next_seq: 0, + logs_count: Arc::new(AtomicU32::new(0)), + } } pub fn log( @@ -35,11 +42,11 @@ impl LogService { msg, }); self.next_seq += 1; - self.logs_count += 1; + self.logs_count.fetch_add(1, Ordering::Relaxed); } pub fn reset_count(&mut self) { - self.logs_count = 0; + self.logs_count.store(0, Ordering::Relaxed); } pub fn get_recent(&self, n: usize) -> Vec { diff --git a/crates/console/prometeu-hal/src/telemetry.rs b/crates/console/prometeu-hal/src/telemetry.rs index c061e98d..08d025bb 100644 --- a/crates/console/prometeu-hal/src/telemetry.rs +++ b/crates/console/prometeu-hal/src/telemetry.rs @@ -1,4 +1,6 @@ use crate::log::{LogLevel, LogService, LogSource}; +use std::sync::atomic::{AtomicU32, AtomicU64, AtomicUsize, Ordering}; +use std::sync::Arc; #[derive(Debug, Clone, Copy, Default)] pub struct TelemetryFrame { @@ -29,6 +31,69 @@ pub struct TelemetryFrame { pub logs_count: u32, } +/// Thread-safe, atomic telemetry storage for real-time monitoring by the host. +/// This follows the push-based model from DEC-0005 to avoid expensive scans or locks. +#[derive(Debug, Default)] +pub struct AtomicTelemetry { + pub frame_index: AtomicU64, + pub cycles_used: AtomicU64, + pub cycles_budget: AtomicU64, + pub syscalls: AtomicU32, + pub host_cpu_time_us: AtomicU64, + pub vm_steps: AtomicU32, + pub completed_logical_frames: AtomicU32, + pub violations: AtomicU32, + + // GFX Banks + pub gfx_used_bytes: AtomicUsize, + pub gfx_inflight_bytes: AtomicUsize, + pub gfx_slots_occupied: AtomicU32, + + // Audio Banks + pub audio_used_bytes: AtomicUsize, + pub audio_inflight_bytes: AtomicUsize, + pub audio_slots_occupied: AtomicU32, + + // RAM (Heap) + pub heap_used_bytes: AtomicUsize, + pub heap_max_bytes: AtomicUsize, + + // Log Pressure + pub logs_count: Arc, +} + +impl AtomicTelemetry { + pub fn new(logs_count: Arc) -> Self { + Self { + logs_count, + ..Default::default() + } + } + + /// Snapshots the current atomic state into a TelemetryFrame. + pub fn snapshot(&self) -> TelemetryFrame { + TelemetryFrame { + frame_index: self.frame_index.load(Ordering::Relaxed), + cycles_used: self.cycles_used.load(Ordering::Relaxed), + cycles_budget: self.cycles_budget.load(Ordering::Relaxed), + syscalls: self.syscalls.load(Ordering::Relaxed), + host_cpu_time_us: self.host_cpu_time_us.load(Ordering::Relaxed), + completed_logical_frames: self.completed_logical_frames.load(Ordering::Relaxed), + violations: self.violations.load(Ordering::Relaxed), + gfx_used_bytes: self.gfx_used_bytes.load(Ordering::Relaxed), + gfx_inflight_bytes: self.gfx_inflight_bytes.load(Ordering::Relaxed), + gfx_slots_occupied: self.gfx_slots_occupied.load(Ordering::Relaxed), + audio_used_bytes: self.audio_used_bytes.load(Ordering::Relaxed), + audio_inflight_bytes: self.audio_inflight_bytes.load(Ordering::Relaxed), + audio_slots_occupied: self.audio_slots_occupied.load(Ordering::Relaxed), + heap_used_bytes: self.heap_used_bytes.load(Ordering::Relaxed), + heap_max_bytes: self.heap_max_bytes.load(Ordering::Relaxed), + logs_count: self.logs_count.load(Ordering::Relaxed), + vm_steps: self.vm_steps.load(Ordering::Relaxed), + } + } +} + #[derive(Debug, Clone, Copy, Default)] pub struct CertificationConfig { pub enabled: bool, @@ -196,6 +261,7 @@ mod tests { #[test] fn test_certifier_violations() { + let mut ls = LogService::new(10); let config = CertificationConfig { enabled: true, cycles_budget_per_frame: Some(100), @@ -205,7 +271,6 @@ mod tests { ..Default::default() }; let cert = Certifier::new(config); - let mut ls = LogService::new(10); let mut tel = TelemetryFrame::default(); tel.cycles_used = 150; diff --git a/crates/console/prometeu-system/src/virtual_machine_runtime.rs b/crates/console/prometeu-system/src/virtual_machine_runtime.rs index a4bfa41e..1e5edac4 100644 --- a/crates/console/prometeu-system/src/virtual_machine_runtime.rs +++ b/crates/console/prometeu-system/src/virtual_machine_runtime.rs @@ -9,9 +9,10 @@ use crate::fs::{FsState, VirtualFS}; use crate::services::memcard::MemcardService; use prometeu_hal::cartridge::AppMode; use prometeu_hal::log::LogService; -use prometeu_hal::telemetry::{CertificationConfig, Certifier, TelemetryFrame}; +use prometeu_hal::telemetry::{AtomicTelemetry, CertificationConfig, Certifier}; use prometeu_vm::VirtualMachine; use std::collections::HashMap; +use std::sync::Arc; use std::time::Instant; pub struct VirtualMachineRuntime { @@ -31,8 +32,7 @@ pub struct VirtualMachineRuntime { pub current_cartridge_app_version: String, pub current_cartridge_app_mode: AppMode, pub logs_written_this_frame: HashMap, - pub telemetry_current: TelemetryFrame, - pub telemetry_last: TelemetryFrame, + pub atomic_telemetry: Arc, pub last_crash_report: Option, pub certifier: Certifier, pub paused: bool, diff --git a/crates/console/prometeu-system/src/virtual_machine_runtime/dispatch.rs b/crates/console/prometeu-system/src/virtual_machine_runtime/dispatch.rs index 2504f0b1..bc32d456 100644 --- a/crates/console/prometeu-system/src/virtual_machine_runtime/dispatch.rs +++ b/crates/console/prometeu-system/src/virtual_machine_runtime/dispatch.rs @@ -9,6 +9,7 @@ use prometeu_hal::log::{LogLevel, LogSource}; use prometeu_hal::sprite::Sprite; use prometeu_hal::syscalls::Syscall; use prometeu_hal::vm_fault::VmFault; +use std::sync::atomic::Ordering; use prometeu_hal::{ AudioOpStatus, GfxOpStatus, HostContext, HostReturn, NativeInterface, SyscallId, expect_bool, expect_int, @@ -65,7 +66,7 @@ impl NativeInterface for VirtualMachineRuntime { ret: &mut HostReturn, ctx: &mut HostContext, ) -> Result<(), VmFault> { - self.telemetry_current.syscalls += 1; + self.atomic_telemetry.syscalls.fetch_add(1, Ordering::Relaxed); let syscall = Syscall::from_u32(id).ok_or_else(|| { VmFault::Trap(TRAP_INVALID_SYSCALL, format!("Unknown syscall: 0x{:08X}", id)) })?; diff --git a/crates/console/prometeu-system/src/virtual_machine_runtime/lifecycle.rs b/crates/console/prometeu-system/src/virtual_machine_runtime/lifecycle.rs index 69184441..b8bef76b 100644 --- a/crates/console/prometeu-system/src/virtual_machine_runtime/lifecycle.rs +++ b/crates/console/prometeu-system/src/virtual_machine_runtime/lifecycle.rs @@ -7,6 +7,8 @@ use prometeu_hal::log::{LogLevel, LogSource}; impl VirtualMachineRuntime { pub fn new(cap_config: Option) -> Self { let boot_time = Instant::now(); + let log_service = LogService::new(4096); + let atomic_telemetry = Arc::new(AtomicTelemetry::new(Arc::clone(&log_service.logs_count))); let mut os = Self { tick_index: 0, logical_frame_index: 0, @@ -18,14 +20,13 @@ impl VirtualMachineRuntime { memcard: MemcardService::new(), open_files: HashMap::new(), next_handle: 1, - log_service: LogService::new(4096), + log_service, current_app_id: 0, current_cartridge_title: String::new(), current_cartridge_app_version: String::new(), current_cartridge_app_mode: AppMode::Game, logs_written_this_frame: HashMap::new(), - telemetry_current: TelemetryFrame::default(), - telemetry_last: TelemetryFrame::default(), + atomic_telemetry, last_crash_report: None, certifier: Certifier::new(cap_config.unwrap_or_default()), paused: false, @@ -99,8 +100,6 @@ impl VirtualMachineRuntime { self.current_cartridge_app_mode = AppMode::Game; self.logs_written_this_frame.clear(); - self.telemetry_current = TelemetryFrame::default(); - self.telemetry_last = TelemetryFrame::default(); self.last_crash_report = None; self.paused = false; diff --git a/crates/console/prometeu-system/src/virtual_machine_runtime/tests.rs b/crates/console/prometeu-system/src/virtual_machine_runtime/tests.rs index 293a35a9..59e9d0c4 100644 --- a/crates/console/prometeu-system/src/virtual_machine_runtime/tests.rs +++ b/crates/console/prometeu-system/src/virtual_machine_runtime/tests.rs @@ -273,10 +273,9 @@ fn reset_clears_cartridge_scoped_runtime_state() { runtime.current_cartridge_app_version = "1.2.3".into(); runtime.current_cartridge_app_mode = AppMode::System; runtime.logs_written_this_frame.insert(42, 3); - runtime.telemetry_current.frame_index = 8; - runtime.telemetry_current.cycles_used = 99; - runtime.telemetry_last.frame_index = 7; - runtime.telemetry_last.completed_logical_frames = 2; + runtime.atomic_telemetry.frame_index.store(8, Ordering::Relaxed); + runtime.atomic_telemetry.cycles_used.store(99, Ordering::Relaxed); + runtime.atomic_telemetry.completed_logical_frames.store(2, Ordering::Relaxed); runtime.last_crash_report = Some(CrashReport::VmPanic { message: "stale".into(), pc: Some(55) }); runtime.paused = true; @@ -298,10 +297,9 @@ fn reset_clears_cartridge_scoped_runtime_state() { assert!(runtime.current_cartridge_app_version.is_empty()); assert_eq!(runtime.current_cartridge_app_mode, AppMode::Game); assert!(runtime.logs_written_this_frame.is_empty()); - assert_eq!(runtime.telemetry_current.frame_index, 0); - assert_eq!(runtime.telemetry_current.cycles_used, 0); - assert_eq!(runtime.telemetry_last.frame_index, 0); - assert_eq!(runtime.telemetry_last.completed_logical_frames, 0); + assert_eq!(runtime.atomic_telemetry.frame_index.load(Ordering::Relaxed), 0); + assert_eq!(runtime.atomic_telemetry.cycles_used.load(Ordering::Relaxed), 0); + assert_eq!(runtime.atomic_telemetry.completed_logical_frames.load(Ordering::Relaxed), 0); assert!(runtime.last_crash_report.is_none()); assert!(!runtime.paused); assert!(!runtime.debug_step_request); @@ -331,7 +329,7 @@ fn initialize_vm_failure_clears_previous_identity_and_handles() { runtime.next_handle = 6; runtime.paused = true; runtime.debug_step_request = true; - runtime.telemetry_current.cycles_used = 123; + runtime.atomic_telemetry.cycles_used.store(123, Ordering::Relaxed); let bad_program = serialized_single_function_module( assemble("PUSH_I32 0\nHOSTCALL 0\nHALT").expect("assemble"), @@ -356,7 +354,7 @@ fn initialize_vm_failure_clears_previous_identity_and_handles() { assert_eq!(runtime.next_handle, 1); assert!(!runtime.paused); assert!(!runtime.debug_step_request); - assert_eq!(runtime.telemetry_current.cycles_used, 0); + assert_eq!(runtime.atomic_telemetry.cycles_used.load(Ordering::Relaxed), 0); assert!(matches!(runtime.last_crash_report, Some(CrashReport::VmInit { .. }))); } diff --git a/crates/console/prometeu-system/src/virtual_machine_runtime/tick.rs b/crates/console/prometeu-system/src/virtual_machine_runtime/tick.rs index a879f514..2ded2022 100644 --- a/crates/console/prometeu-system/src/virtual_machine_runtime/tick.rs +++ b/crates/console/prometeu-system/src/virtual_machine_runtime/tick.rs @@ -63,15 +63,17 @@ impl VirtualMachineRuntime { self.needs_prepare_entry_call = false; } - self.telemetry_current = TelemetryFrame { - frame_index: self.logical_frame_index, - cycles_budget: self - .certifier + self.atomic_telemetry.frame_index.store(self.logical_frame_index, Ordering::Relaxed); + self.atomic_telemetry.cycles_budget.store( + self.certifier .config .cycles_budget_per_frame .unwrap_or(Self::CYCLES_PER_LOGICAL_FRAME), - ..Default::default() - }; + Ordering::Relaxed, + ); + self.atomic_telemetry.cycles_used.store(0, Ordering::Relaxed); + self.atomic_telemetry.syscalls.store(0, Ordering::Relaxed); + self.atomic_telemetry.vm_steps.store(0, Ordering::Relaxed); } let budget = std::cmp::min(Self::SLICE_PER_TICK, self.logical_frame_remaining_cycles); @@ -86,8 +88,9 @@ impl VirtualMachineRuntime { Ok(run) => { self.logical_frame_remaining_cycles = self.logical_frame_remaining_cycles.saturating_sub(run.cycles_used); - self.telemetry_current.cycles_used += run.cycles_used; - self.telemetry_current.vm_steps += run.steps_executed; + + self.atomic_telemetry.cycles_used.fetch_add(run.cycles_used, Ordering::Relaxed); + self.atomic_telemetry.vm_steps.fetch_add(run.steps_executed, Ordering::Relaxed); if run.reason == LogicalFrameEndingReason::Breakpoint { self.paused = true; @@ -130,37 +133,33 @@ impl VirtualMachineRuntime { { hw.gfx_mut().render_all(); - // 1. Snapshot full telemetry at logical frame end (O(1) with atomic counters) + // 1. Snapshot full telemetry at logical frame end let gfx_stats = hw.assets().bank_info(BankType::GLYPH); - self.telemetry_current.gfx_used_bytes = gfx_stats.used_bytes; - self.telemetry_current.gfx_inflight_bytes = gfx_stats.inflight_bytes; - self.telemetry_current.gfx_slots_occupied = gfx_stats.slots_occupied as u32; + self.atomic_telemetry.gfx_used_bytes.store(gfx_stats.used_bytes, Ordering::Relaxed); + self.atomic_telemetry.gfx_inflight_bytes.store(gfx_stats.inflight_bytes, Ordering::Relaxed); + self.atomic_telemetry.gfx_slots_occupied.store(gfx_stats.slots_occupied as u32, Ordering::Relaxed); let audio_stats = hw.assets().bank_info(BankType::SOUNDS); - self.telemetry_current.audio_used_bytes = audio_stats.used_bytes; - self.telemetry_current.audio_inflight_bytes = audio_stats.inflight_bytes; - self.telemetry_current.audio_slots_occupied = - audio_stats.slots_occupied as u32; + self.atomic_telemetry.audio_used_bytes.store(audio_stats.used_bytes, Ordering::Relaxed); + self.atomic_telemetry.audio_inflight_bytes.store(audio_stats.inflight_bytes, Ordering::Relaxed); + self.atomic_telemetry.audio_slots_occupied.store(audio_stats.slots_occupied as u32, Ordering::Relaxed); - self.telemetry_current.heap_used_bytes = - vm.heap().used_bytes.load(Ordering::Relaxed); - self.telemetry_current.heap_max_bytes = 0; // Not yet capped - - self.telemetry_current.logs_count = self.log_service.logs_count; - self.log_service.reset_count(); - - self.telemetry_current.host_cpu_time_us = - start.elapsed().as_micros() as u64; + self.atomic_telemetry.heap_used_bytes.store(vm.heap().used_bytes.load(Ordering::Relaxed), Ordering::Relaxed); + self.atomic_telemetry.host_cpu_time_us.store(start.elapsed().as_micros() as u64, Ordering::Relaxed); let ts_ms = self.boot_time.elapsed().as_millis() as u64; - self.telemetry_current.violations = self.certifier.evaluate( - &self.telemetry_current, + let telemetry_snapshot = self.atomic_telemetry.snapshot(); + + let violations = self.certifier.evaluate( + &telemetry_snapshot, &mut self.log_service, ts_ms, ) as u32; - self.telemetry_current.completed_logical_frames += 1; - self.telemetry_last = self.telemetry_current; + self.atomic_telemetry.violations.store(violations, Ordering::Relaxed); + self.atomic_telemetry.completed_logical_frames.fetch_add(1, Ordering::Relaxed); + + self.log_service.reset_count(); self.logical_frame_index += 1; self.logical_frame_active = false; @@ -190,33 +189,19 @@ impl VirtualMachineRuntime { // 2. High-frequency telemetry update (only if inspection is active) if self.inspection_active { let gfx_stats = hw.assets().bank_info(BankType::GLYPH); - self.telemetry_current.gfx_used_bytes = gfx_stats.used_bytes; - self.telemetry_current.gfx_inflight_bytes = gfx_stats.inflight_bytes; - self.telemetry_current.gfx_slots_occupied = gfx_stats.slots_occupied as u32; + self.atomic_telemetry.gfx_used_bytes.store(gfx_stats.used_bytes, Ordering::Relaxed); + self.atomic_telemetry.gfx_inflight_bytes.store(gfx_stats.inflight_bytes, Ordering::Relaxed); + self.atomic_telemetry.gfx_slots_occupied.store(gfx_stats.slots_occupied as u32, Ordering::Relaxed); let audio_stats = hw.assets().bank_info(BankType::SOUNDS); - self.telemetry_current.audio_used_bytes = audio_stats.used_bytes; - self.telemetry_current.audio_inflight_bytes = audio_stats.inflight_bytes; - self.telemetry_current.audio_slots_occupied = audio_stats.slots_occupied as u32; + self.atomic_telemetry.audio_used_bytes.store(audio_stats.used_bytes, Ordering::Relaxed); + self.atomic_telemetry.audio_inflight_bytes.store(audio_stats.inflight_bytes, Ordering::Relaxed); + self.atomic_telemetry.audio_slots_occupied.store(audio_stats.slots_occupied as u32, Ordering::Relaxed); - self.telemetry_current.heap_used_bytes = vm.heap().used_bytes.load(Ordering::Relaxed); - self.telemetry_current.logs_count = self.log_service.logs_count; - } + self.atomic_telemetry.heap_used_bytes.store(vm.heap().used_bytes.load(Ordering::Relaxed), Ordering::Relaxed); - if !self.logical_frame_active - && self.telemetry_last.frame_index == self.logical_frame_index.wrapping_sub(1) - { - self.telemetry_last.host_cpu_time_us = self.last_frame_cpu_time_us; - self.telemetry_last.cycles_budget = self.telemetry_current.cycles_budget; - self.telemetry_last.gfx_used_bytes = self.telemetry_current.gfx_used_bytes; - self.telemetry_last.gfx_inflight_bytes = self.telemetry_current.gfx_inflight_bytes; - self.telemetry_last.gfx_slots_occupied = self.telemetry_current.gfx_slots_occupied; - self.telemetry_last.audio_used_bytes = self.telemetry_current.audio_used_bytes; - self.telemetry_last.audio_inflight_bytes = self.telemetry_current.audio_inflight_bytes; - self.telemetry_last.audio_slots_occupied = self.telemetry_current.audio_slots_occupied; - self.telemetry_last.heap_used_bytes = self.telemetry_current.heap_used_bytes; - self.telemetry_last.heap_max_bytes = self.telemetry_current.heap_max_bytes; - self.telemetry_last.logs_count = self.telemetry_current.logs_count; + self.atomic_telemetry.frame_index.store(self.logical_frame_index, Ordering::Relaxed); + self.atomic_telemetry.host_cpu_time_us.store(start.elapsed().as_micros() as u64, Ordering::Relaxed); } None diff --git a/crates/host/prometeu-host-desktop-winit/src/debugger.rs b/crates/host/prometeu-host-desktop-winit/src/debugger.rs index 0f3bb1c2..abf0cdaf 100644 --- a/crates/host/prometeu-host-desktop-winit/src/debugger.rs +++ b/crates/host/prometeu-host-desktop-winit/src/debugger.rs @@ -253,7 +253,7 @@ impl HostDebugger { // Map Certification tags (0xCA01-0xCA03) to 'Cert' protocol events. if event.tag >= 0xCA01 && event.tag <= 0xCA03 { - let tel = &firmware.os.telemetry_last; + let tel = firmware.os.atomic_telemetry.snapshot(); let cert_config = &firmware.os.certifier.config; let (rule, used, limit) = match event.tag { @@ -293,7 +293,7 @@ impl HostDebugger { // 2. Send telemetry snapshots at the completion of every frame. let current_frame = firmware.os.logical_frame_index; if current_frame > self.last_telemetry_frame { - let tel = &firmware.os.telemetry_last; + let tel = firmware.os.atomic_telemetry.snapshot(); self.send_event(DebugEvent::Telemetry { frame_index: tel.frame_index, vm_steps: tel.vm_steps, diff --git a/crates/host/prometeu-host-desktop-winit/src/runner.rs b/crates/host/prometeu-host-desktop-winit/src/runner.rs index 81f86527..b5bbdf6c 100644 --- a/crates/host/prometeu-host-desktop-winit/src/runner.rs +++ b/crates/host/prometeu-host-desktop-winit/src/runner.rs @@ -124,7 +124,7 @@ impl HostRunner { } fn display_dbg_overlay(&mut self) { - let tel = &self.firmware.os.telemetry_last; + let tel = self.firmware.os.atomic_telemetry.snapshot(); let color_text = Color::WHITE; let color_bg = Color::INDIGO; // Dark blue to stand out let color_warn = Color::RED; @@ -195,18 +195,18 @@ impl HostRunner { ); self.hardware.gfx.draw_text(10, 90, &format!("LOGS: {}", tel.logs_count), color_text); - let cert_color = if tel.violations > 0 { color_warn } else { color_text }; - self.hardware.gfx.draw_text(10, 98, &format!("CERT LAST: {}", tel.violations), cert_color); + // Snapshot does not include violations, as they are part of certification (logical end of frame) + // But for visual debug, we can check if there are recent CA tags in logs + let recent_logs = self.firmware.os.log_service.get_recent(10); + let violations_count = recent_logs.iter().filter(|e| e.tag >= 0xCA01 && e.tag <= 0xCA07).count(); + let cert_color = if violations_count > 0 { color_warn } else { color_text }; + self.hardware.gfx.draw_text(10, 98, &format!("CERT RECENT: {}", violations_count), cert_color); - if tel.violations > 0 - && let Some(event) = self - .firmware - .os - .log_service - .get_recent(10) + if violations_count > 0 + && let Some(event) = recent_logs .into_iter() .rev() - .find(|e| e.tag >= 0xCA01 && e.tag <= 0xCA03) + .find(|e| e.tag >= 0xCA01 && e.tag <= 0xCA07) { let mut msg = event.msg.clone(); if msg.len() > 30 { diff --git a/crates/host/prometeu-host-desktop-winit/src/stats.rs b/crates/host/prometeu-host-desktop-winit/src/stats.rs index 03685a10..3c723442 100644 --- a/crates/host/prometeu-host-desktop-winit/src/stats.rs +++ b/crates/host/prometeu-host-desktop-winit/src/stats.rs @@ -68,7 +68,7 @@ impl HostStats { cpu_load_audio, firmware.os.tick_index, firmware.os.logical_frame_index, - firmware.os.telemetry_last.completed_logical_frames, + firmware.os.atomic_telemetry.completed_logical_frames.load(std::sync::atomic::Ordering::Relaxed), ); window.set_title(&title); } diff --git a/discussion/index.ndjson b/discussion/index.ndjson index 36feb88d..d4c853ef 100644 --- a/discussion/index.ndjson +++ b/discussion/index.ndjson @@ -1,4 +1,5 @@ -{"type":"meta","next_id":{"DSC":23,"AGD":21,"DEC":7,"PLN":6,"LSN":27,"CLSN":1}} +{"type":"meta","next_id":{"DSC":24,"AGD":22,"DEC":9,"PLN":8,"LSN":29,"CLSN":1}} +{"type":"discussion","id":"DSC-0023","status":"done","ticket":"perf-full-migration-to-atomic-telemetry","title":"Agenda - [PERF] Full Migration to Atomic Telemetry","created_at":"2026-04-10","updated_at":"2026-04-10","tags":["perf","runtime","telemetry"],"agendas":[{"id":"AGD-0021","file":"workflow/agendas/AGD-0021-full-migration-to-atomic-telemetry.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}],"decisions":[{"id":"DEC-0008","file":"workflow/decisions/DEC-0008-full-migration-to-atomic-telemetry.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10"}],"plans":[{"id":"PLN-0007","file":"workflow/plans/PLN-0007-full-migration-to-atomic-telemetry.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}],"lessons":[{"id":"LSN-0028","file":"lessons/DSC-0023-perf-full-migration-to-atomic-telemetry/LSN-0028-converging-to-single-atomic-telemetry-source.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}]} {"type":"discussion","id":"DSC-0020","status":"done","ticket":"jenkins-gitea-integration","title":"Jenkins Gitea Integration and Relocation","created_at":"2026-04-07","updated_at":"2026-04-07","tags":["ci","jenkins","gitea"],"agendas":[{"id":"AGD-0018","file":"workflow/agendas/AGD-0018-jenkins-gitea-integration-and-relocation.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}],"decisions":[{"id":"DEC-0003","file":"workflow/decisions/DEC-0003-jenkins-gitea-strategy.md","status":"accepted","created_at":"2026-04-07","updated_at":"2026-04-07"}],"plans":[{"id":"PLN-0003","file":"workflow/plans/PLN-0003-jenkins-gitea-execution.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}],"lessons":[{"id":"LSN-0021","file":"lessons/DSC-0020-jenkins-gitea-integration/LSN-0021-jenkins-gitea-integration.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}]} {"type":"discussion","id":"DSC-0021","status":"done","ticket":"asset-entry-codec-enum-with-metadata","title":"Asset Entry Codec Enum Contract","created_at":"2026-04-09","updated_at":"2026-04-09","tags":["asset","runtime","codec","metadata"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0024","file":"lessons/DSC-0021-asset-entry-codec-enum-contract/LSN-0024-string-on-the-wire-enum-in-runtime.md","status":"done","created_at":"2026-04-09","updated_at":"2026-04-09"}]} {"type":"discussion","id":"DSC-0022","status":"done","ticket":"tile-bank-vs-glyph-bank-domain-naming","title":"Glyph Bank Domain Naming Contract","created_at":"2026-04-09","updated_at":"2026-04-10","tags":["gfx","runtime","naming","domain-model"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0025","file":"lessons/DSC-0022-glyph-bank-domain-naming/LSN-0025-rename-artifact-by-meaning-not-by-token.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}]} @@ -14,7 +15,7 @@ {"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":"open","ticket":"perf-runtime-introspection-syscalls","title":"Agenda - [PERF] Runtime Introspection Syscalls","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0011","file":"workflow/agendas/AGD-0011-perf-runtime-introspection-syscalls.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]} -{"type":"discussion","id":"DSC-0013","status":"open","ticket":"perf-host-debug-overlay-isolation","title":"Agenda - [PERF] Host Debug Overlay Isolation","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0012","file":"workflow/agendas/AGD-0012-perf-host-debug-overlay-isolation.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"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":[{"id":"AGD-0012","file":"workflow/agendas/AGD-0012-perf-host-debug-overlay-isolation.md","status":"done","created_at":"2026-03-27","updated_at":"2026-04-10"}],"decisions":[{"id":"DEC-0007","file":"workflow/decisions/DEC-0007-perf-host-debug-overlay-isolation.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10"}],"plans":[{"id":"PLN-0006","file":"workflow/plans/PLN-0006-perf-host-debug-overlay-isolation.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}],"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-0014","status":"open","ticket":"perf-vm-allocation-and-copy-pressure","title":"Agenda - [PERF] VM Allocation and Copy Pressure","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0013","file":"workflow/agendas/AGD-0013-perf-vm-allocation-and-copy-pressure.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]} {"type":"discussion","id":"DSC-0015","status":"open","ticket":"perf-cartridge-boot-and-program-ownership","title":"Agenda - [PERF] Cartridge Boot and Program Ownership","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0014","file":"workflow/agendas/AGD-0014-perf-cartridge-boot-and-program-ownership.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]} {"type":"discussion","id":"DSC-0016","status":"done","ticket":"tilemap-empty-cell-vs-tile-id-zero","title":"Tilemap Empty Cell vs Tile ID Zero","created_at":"2026-03-27","updated_at":"2026-04-09","tags":[],"agendas":[{"id":"AGD-0015","file":"workflow/agendas/AGD-0015-tilemap-empty-cell-vs-tile-id-zero.md","status":"done","created_at":"2026-03-27","updated_at":"2026-04-09"}],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0022","file":"lessons/DSC-0016-tilemap-empty-cell-semantics/LSN-0022-tilemap-empty-cell-convergence.md","status":"done","created_at":"2026-04-09","updated_at":"2026-04-09"}]} diff --git a/discussion/lessons/DSC-0013-perf-host-debug-overlay-isolation/LSN-0027-host-debug-overlay-isolation.md b/discussion/lessons/DSC-0013-perf-host-debug-overlay-isolation/LSN-0027-host-debug-overlay-isolation.md new file mode 100644 index 00000000..7efa01fc --- /dev/null +++ b/discussion/lessons/DSC-0013-perf-host-debug-overlay-isolation/LSN-0027-host-debug-overlay-isolation.md @@ -0,0 +1,39 @@ +--- +id: LSN-0027 +ticket: perf-host-debug-overlay-isolation +title: Host Debug Overlay Isolation +created: 2026-04-10 +tags: [performance, host, gfx, telemetry] +--- + +# Host Debug Overlay Isolation + +The PROMETEU debug overlay (HUD) was decoupled from the emulated machine pipeline and moved to the Host layer to ensure measurement purity and architectural separation. + +## The Original Problem + +The debug overlay used to be rendered by injecting pixels directly into the emulated GFX pipeline during the logical frame execution. This caused several issues: +- **Performance Distortion:** Cycle measurements for certification included the overhead of formatting technical strings and performing extra draw calls. +- **Leaky Abstraction:** The emulated machine became aware of Host-only inspection needs. +- **GFX Coupling:** The HUD was "burned" into the emulated framebuffer, making it impossible to capture raw game frames without the overlay while technical debugging was active. + +## The Solution: Host-Side Rendering with Atomic Telemetry + +The implemented solution follows a strictly non-intrusive approach: + +1. **Atomic Telemetry (Push-based):** A new `AtomicTelemetry` structure was added to the HAL. It uses `AtomicU64`, `AtomicU32`, and `AtomicUsize` to track metrics (Cycles, Memory, Logs) in real-time. +2. **Runtime Decoupling:** The `VirtualMachineRuntime` updates these atomic counters during its `tick` loop only if `inspection_active` is enabled. It does not perform any rendering or string formatting. +3. **Host-Side HUD:** The `HostRunner` (in `prometeu-host-desktop-winit`) now takes a `snapshot()` of the atomic telemetry and renders the HUD as a native layer after the emulated machine has finished its work for the tick. + +## Impact and Benefits + +- **Zero Machine Overhead:** Rendering the HUD consumes Host CPU/GPU cycles but does not affect the emulated machine's cycle counter or logical behavior. +- **Fidelity:** The emulated framebuffer remains pure, containing only game pixels. +- **Responsive Telemetry:** By using atomics, the Host can read the most recent metrics at any time without waiting for frame boundaries or acquiring heavy read-locks on the runtime state. +- **Platform Agnosticism:** Non-desktop hosts (which do not need the overlay) do not pay any implementation cost or performance penalty for the HUD's existence. + +## Lessons Learned + +- **Decouple Data from View:** Even for internal debugging tools, keeping the data collection (Runtime) separate from the visualization (Host) is crucial for accurate profiling. +- **Atomic Snapshots are Sufficient:** For high-frequency HUD updates, eventual consistency via relaxed atomic loads is more than enough and significantly more performant than synchronizing via Mutexes or logical frame boundaries. +- **Late Composition:** Composition of technical layers should always happen at the latest possible stage of the display pipeline to avoid polluting the core simulation state. diff --git a/discussion/lessons/DSC-0023-perf-full-migration-to-atomic-telemetry/LSN-0028-converging-to-single-atomic-telemetry-source.md b/discussion/lessons/DSC-0023-perf-full-migration-to-atomic-telemetry/LSN-0028-converging-to-single-atomic-telemetry-source.md new file mode 100644 index 00000000..0f7f8438 --- /dev/null +++ b/discussion/lessons/DSC-0023-perf-full-migration-to-atomic-telemetry/LSN-0028-converging-to-single-atomic-telemetry-source.md @@ -0,0 +1,23 @@ +# LSN-0028: Converging to a Single Atomic Telemetry Source + +## Context +Initial implementation of the Host Debug Overlay (DEC-0007) maintained legacy fields (`telemetry_current`, `telemetry_last`) alongside the new `AtomicTelemetry` for safety and backward compatibility. This resulted in redundant code and synchronization complexity in the core VM loop. + +## Problem +Maintaining two sources of truth for telemetry (frame-based and atomic-based) is a form of technical debt. It requires updating both systems, increases memory footprint in the `VirtualMachineRuntime`, and creates ambiguity about which data is more "accurate" or "current." + +## Lesson +1. **Atomics are sufficient:** A well-designed atomic structure with a `snapshot()` method can fulfill all needs, from real-time high-frequency inspection to deterministic frame-end certification. +2. **Push-based over Pull-based:** By having the VM "push" updates to atomic counters, the Host can consume them at its own pace without ever locking the execution thread. +3. **Purity through snapshots:** For processes that require a stable view of a frame (like Certification), capturing an atomic snapshot at the exact logical end of the frame is as precise as maintaining a separate buffered structure. + +## Impact +- **Simpler Code:** Removal of legacy fields reduced the complexity of `tick.rs` and `lifecycle.rs`. +- **Better Performance:** Avoids redundant data copies and struct initializations per frame. +- **Architectural Clarity:** All diagnostic tools (HUD, Debugger, CLI, Certifier) now converge on the same data source. + +## References +- DSC-0023 ([PERF] Full Migration to Atomic Telemetry) +- DEC-0008 (Full Migration Decision) +- PLN-0007 (Migration Plan) +- DEC-0007 (Overlay Isolation) diff --git a/discussion/workflow/agendas/AGD-0012-perf-host-debug-overlay-isolation.md b/discussion/workflow/agendas/AGD-0012-perf-host-debug-overlay-isolation.md index 8bb4ab88..31d9739d 100644 --- a/discussion/workflow/agendas/AGD-0012-perf-host-debug-overlay-isolation.md +++ b/discussion/workflow/agendas/AGD-0012-perf-host-debug-overlay-isolation.md @@ -2,72 +2,48 @@ id: AGD-0012 ticket: perf-host-debug-overlay-isolation title: Agenda - [PERF] Host Debug Overlay Isolation -status: open +status: done created: 2026-03-27 -resolved: -decision: -tags: [] +resolved: 2026-04-10 +decision: DEC-0007 +tags: [performance, host, gfx] --- # Agenda - [PERF] Host Debug Overlay Isolation +## Contexto +O overlay de debug é uma ferramenta exclusiva para o ambiente **Desktop** (`prometeu-host-desktop-winit`). Ele visa fornecer telemetria em tempo real para desenvolvedores sem impactar a fidelidade da emulação ou o desempenho medido do hardware final (handhelds/consoles de baixo custo), onde este overlay não existirá. + ## Problema +Atualmente, o overlay de debug está indevidamente acoplado ao pipeline de `gfx` emulado e ao processamento do runtime. +- **Distorção de Performance:** O custo de renderizar o HUD técnico (formatação de strings e draw calls extras) é contabilizado como custo do jogo. +- **Acoplamento de Pipeline:** O pipeline de `gfx` precisa processar elementos que não pertencem à lógica da máquina virtual. +- **Hotspots:** `runner.rs` realiza `present()` extra e manipulação de texto no loop principal. -O overlay de debug ainda usa o pipeline emulado de `gfx` e injeta custo visual no caminho normal do host. +## Pontos Críticos +- **Fato:** O overlay é uma necessidade de desenvolvimento Desktop, não uma funcionalidade da máquina `prometeu`. +- **Risco:** Qualquer processamento de overlay dentro do runtime `prometeu` invalida a pureza dos ciclos medidos para certificação. +- **Tradeoff:** Mover o overlay para o Host exige acesso assíncrono ou passivo aos dados de telemetria. +- **Hipótese:** Um overlay 100% nativo no Host (Winit/Pixels) usando fontes TrueType terá custo desprezível e legibilidade superior. -Hoje o host formata strings por frame, desenha texto via `gfx` e faz `present()` extra para sobrepor telemetria. +## Opções +- **Opção A (Recomendada):** Camada Host Nativa. O `HostRunner` renderiza o HUD em uma surface separada ou faz um *compositing* nativo após o upscaling do framebuffer do console. +- **Opção B:** Overlay via IPC/Sidecar. Ferramenta externa de inspeção (descartada por complexidade visual). +- **Opção C:** Manter no `gfx` emulado com otimização (descartada por não resolver o acoplamento). -## Dor +## Sugestão / Recomendação +1. **Agnosticismo de GFX:** O overlay deve ser tratado como uma "película" transparente aplicada pelo Host Desktop sobre o resultado final da renderização. +2. **Isolamento de Processamento:** Nenhuma instrução de desenho ou formatação de strings do overlay deve ocorrer dentro do runtime. +3. **Acesso via API:** O Host acessará os dados de telemetria através de uma API dedicada (baseada no modelo push-based da `DEC-0005`). +4. **Interface de Controle:** O acionamento permanece via tecla **F1** como um *toggle*, gerenciado pela camada de Host. +5. **Composição via Host:** Utilizar bibliotecas nativas do Host para renderizar o HUD com fontes TrueType nítidas e Alpha Blending real. -- debug ligado altera custo do render path que deveria estar sendo medido. -- overlay de desenvolvimento distorce a leitura de performance do console. -- handheld barato nao deveria pagar composicao de HUD tecnico no mesmo pipeline do jogo. +## Perguntas em Aberto +- Nenhuma. As questões sobre acesso via API e acionamento via F1 foram resolvidas durante a discussão. -## Hotspots Atuais - -- [runner.rs](/Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/crates/host/prometeu-host-desktop-winit/src/runner.rs#L126) -- [runner.rs](/Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/crates/host/prometeu-host-desktop-winit/src/runner.rs#L381) - -## Alvo da Discussao - -Isolar o overlay de debug do custo medido do console sem perder utilidade para desenvolvimento. - -## O Que Precisa Ser Definido - -1. Lugar de composicao. - Decidir se o overlay: - - continua no `gfx` emulado; - - sobe para camada host nativa; - - vira surface separada de debug. - -2. Politica de strings/glyphs. - Definir se texto e reconstruido por frame ou cacheado. - -3. Custo em modo debug. - Delimitar qual overhead e aceitavel quando overlay estiver ativo. - -4. Efeito na telemetria. - Fechar se a telemetria deve incluir ou excluir explicitamente o custo do overlay. - -## Open Questions de Arquitetura - -1. O overlay precisa ser representativo do hardware final ou apenas ferramenta de desktop? - Não, como é HUD técnico, pode e deve ser renderizado pelo Host nativo para melhor legibilidade. -2. Vale um modo "perf puro" onde overlay nunca toca no framebuffer do console? - Sim. O isolamento garante que o `gfx` emulado esteja 100% livre para o jogo durante a medição. -3. O host deve oferecer toggles separados para stats, logs e overlay visual? - Sim. O `HostRunner` deve expor controles granulares via `inspection_active`. -4. Como melhorar a legibilidade e estética (Glyphs/Transparência)? - Migrar a renderização do HUD para o Host Nativo (Winit/Pixels), permitindo o uso de fontes TrueType (monospaced) nítidas e Alpha Blending real para transparência no fundo do painel. - -## Dependencias - -- `../specs/10-debug-inspection-and-profiling.md` -- `../specs/11-portability-and-cross-platform-execution.md` - -## Sugestao / Recomendacao - -1. **Migração para Camada Host Nativa:** Renderizar o HUD de debug em uma surface separada ou via pipeline nativo do Host (depois do upscaling do framebuffer do console). -2. **Fontes TrueType (Mono):** Substituir os glyphs bitmapped rudimentares por uma fonte nativa de alta qualidade e nítida. -3. **Composição Alpha:** Permitir fundo semi-transparente para o overlay para não bloquear a visão do jogo. -4. **Acionamento Explícito:** Host deve gerenciar `inspection_active: true` no runtime apenas quando o HUD ou Debugger estiverem ativos. +## Critério para Encerrar +A agenda é considerada encerrada quando: +- Houver consenso sobre o isolamento total do pipeline de `gfx`. +- O método de acesso aos dados (API) estiver definido. +- O controle de interface (F1) estiver estabelecido. +*(Critérios atingidos em 2026-04-10)* diff --git a/discussion/workflow/agendas/AGD-0021-full-migration-to-atomic-telemetry.md b/discussion/workflow/agendas/AGD-0021-full-migration-to-atomic-telemetry.md new file mode 100644 index 00000000..1f2e2c6a --- /dev/null +++ b/discussion/workflow/agendas/AGD-0021-full-migration-to-atomic-telemetry.md @@ -0,0 +1,31 @@ +# AGD-0021: Full Migration to Atomic Telemetry + +## Contexto +Durante a implementação do isolamento do overlay de debug no Host Desktop (DEC-0007), foi introduzido o `AtomicTelemetry` para permitir acesso assíncrono e sem locks aos dados de performance. Por motivos de cautela inicial, os campos legados `telemetry_current` e `telemetry_last` foram mantidos no `VirtualMachineRuntime` para compatibilidade com processos de certificação e logs internos. + +## Problema +A manutenção de dois sistemas paralelos de telemetria gera redundância de código, aumenta a superfície de erro e consome ciclos de CPU desnecessários para atualizar dados que já estão disponíveis de forma mais eficiente via atômicos. A "compatibilidade" pretendida não justifica o débito técnico de manter estruturas duplicadas. + +## Pontos Críticos +- **Fato:** `AtomicTelemetry` já provê todos os dados necessários (ciclos, memória, logs). +- **Risco:** Remoção de `telemetry_last` pode quebrar ferramentas que dependem de snapshots estáticos por frame se não houver um substituto claro de snapshot via atômicos. +- **Tradeoff:** A migração exige refatorar o `VirtualMachineRuntime` e possivelmente o `LogService` para convergirem em uma única fonte de verdade. +- **Hipótese:** Um snapshot derivado do `AtomicTelemetry` ao final de cada frame é suficiente para substituir o `telemetry_last` legado sem perda de precisão. + +## Opções +- **Opção A (Recomendada):** Migração total e remoção dos campos legados. O `AtomicTelemetry` torna-se a única fonte de verdade. Onde snapshots estáveis são necessários, eles são extraídos via `AtomicTelemetry::snapshot()`. +- **Opção B:** Manter redundância (Descartada pelo usuário). + +## Sugestão / Recomendação +1. Remover `telemetry_current` e `telemetry_last` do `VirtualMachineRuntime`. +2. Refatorar o loop de execução para atualizar exclusivamente o `AtomicTelemetry`. +3. Garantir que o `LogService` e outras auditorias consumam dados do novo modelo. +4. Atualizar as especificações para refletir o modelo único. + +## Perguntas em Aberto +1. Existe algum uso específico de `telemetry_last` em ferramentas externas (não mapeadas) que dependem do layout de memória antigo? (Assumimos que não para este escopo). + +## Criterio para Encerrar +- Remoção completa dos campos no código. +- Compilação e execução bem-sucedida do Host Desktop com o novo modelo único. +- Atualização da documentação normativa. diff --git a/discussion/workflow/decisions/DEC-0007-perf-host-debug-overlay-isolation.md b/discussion/workflow/decisions/DEC-0007-perf-host-debug-overlay-isolation.md new file mode 100644 index 00000000..9906c13b --- /dev/null +++ b/discussion/workflow/decisions/DEC-0007-perf-host-debug-overlay-isolation.md @@ -0,0 +1,53 @@ +--- +id: DEC-0007 +ticket: perf-host-debug-overlay-isolation +title: Decision - [PERF] Host Debug Overlay Isolation +status: accepted +created: 2026-04-10 +updated: 2026-04-10 +agenda: AGD-0012 +tags: [performance, host, gfx] +--- + +# Decision - [PERF] Host Debug Overlay Isolation + +## Status +**Accepted** + +## Contexto +O overlay de debug do PROMETEU, utilizado para exibir telemetria, logs e métricas em tempo real, está atualmente acoplado ao pipeline de renderização emulado (`gfx`) e ao processamento do runtime. Isso resulta em: +1. **Distorção de Performance:** O custo de formatar strings e realizar chamadas de desenho para o HUD técnico é contabilizado como ciclos do jogo, invalidando medições de certificação. +2. **Acoplamento Indevido:** O pipeline `gfx` emulado processa elementos visuais (HUD) que não existem no hardware original. +3. **Complexidade no Host:** O Host precisa lidar com a injeção dessas informações no buffer de emulação. + +## Decisao +Fica decidido que o overlay de debug será movido integralmente para a camada **Host Desktop** (`prometeu-host-desktop-winit`), operando de forma 100% isolada do runtime e do pipeline de `gfx` emulado. + +1. **Isolamento de Pipeline:** O overlay técnico deve ser tratado como uma "película" (layer) transparente aplicada pelo Host sobre o resultado final da renderização (pós-upscaling). +2. **Isolamento de Processamento:** Nenhuma instrução de desenho ou formatação de strings relacionada ao overlay deve ocorrer dentro do runtime ou durante os ciclos emulados. +3. **Acesso via API:** O Host acessará os dados necessários para o overlay através de uma API de telemetria passiva (baseada em atômicos, conforme definido na `DEC-0005`). +4. **Acionamento via F1:** O controle de visibilidade (toggle) do overlay é de responsabilidade exclusiva do Host, acionado pela tecla **F1**. + +## Rationale +- **Pureza de Medição:** Ao remover o processamento do overlay do runtime, garantimos que 100% dos ciclos medidos pertencem à lógica do cartucho e periféricos emulados. +- **Desempenho no Hardware Alvo:** Como o overlay é exclusivo de Desktop, dispositivos handheld/consoles de baixo custo não devem pagar o preço de implementação ou ramificações lógicas para lidar com HUD técnico. +- **Qualidade Visual:** Utilizar o Host para renderizar o HUD permite o uso de fontes TrueType nítidas e Alpha Blending real, melhorando drasticamente a legibilidade para o desenvolvedor sem afetar a fidelidade da emulação. + +## Invariantes / Contrato +- **Zero Overhead no Runtime:** A presença ou ausência do overlay não deve alterar o contador de ciclos consumidos por frame no runtime. +- **Agnosticismo de GFX:** O framebuffer emulado deve conter apenas os pixels gerados pelo cartucho, sem HUD técnico "queimado" na imagem. +- **Composição Assíncrona/Passiva:** O Host não deve bloquear o runtime para coletar dados para o overlay; a leitura deve ser feita a partir de buffers de telemetria já expostos. + +## Impactos +- **Host (Desktop Winit):** Exige a implementação de uma camada de renderização nativa (ex: `egui` ou composição direta via `pixels`/`winit`) que suporte transparência. +- **Runtime API:** Deve expor campos de telemetria (FPS, Memory Usage, Cycles) via API para consumo pelo Host. +- **Especificações:** Atualização dos capítulos de Portabilidade e Debug para refletir o isolamento. + +## Referencias +- `AGD-0012`: [PERF] Host Debug Overlay Isolation (Agenda de Origem). +- `DEC-0005`: [PERF] Push-based Telemetry Model (Modelo de acesso aos dados). + +## Propagacao Necessaria +1. Remover hotspots de desenho de texto no `runner.rs` do host. +2. Implementar o novo sistema de HUD no host usando bibliotecas nativas. +3. Atualizar a documentação técnica em `docs/specs/runtime/`. diff --git a/discussion/workflow/decisions/DEC-0008-full-migration-to-atomic-telemetry.md b/discussion/workflow/decisions/DEC-0008-full-migration-to-atomic-telemetry.md new file mode 100644 index 00000000..22bdabc1 --- /dev/null +++ b/discussion/workflow/decisions/DEC-0008-full-migration-to-atomic-telemetry.md @@ -0,0 +1,35 @@ +# DEC-0008: Full Migration to Atomic Telemetry + +## Status +Accepted + +## Contexto +Após o sucesso inicial do `AtomicTelemetry` (DEC-0007), identificou-se que a coexistência com os campos legados `telemetry_current` e `telemetry_last` no `VirtualMachineRuntime` é contraproducente. O usuário solicitou a remoção completa da camada de compatibilidade para simplificar o motor e garantir que o modelo atômico seja a única fonte de verdade para performance e inspeção. + +## Decisao +1. **Remoção Total:** Eliminar os campos `telemetry_current` e `telemetry_last` da struct `VirtualMachineRuntime`. +2. **Modelo Único:** O `AtomicTelemetry` passa a ser a única estrutura responsável por rastrear métricas de execução (ciclos, memória, logs). +3. **Snapshot On-Demand:** Qualquer necessidade de telemetria estática (ex: para logs de erro ou final de frame) deve ser atendida pelo método `AtomicTelemetry::snapshot()`, que gera um `TelemetryFrame` imutável a partir do estado atômico atual. +4. **Atualização Condicional:** A atualização dos campos atômicos dentro do loop de `tick` da VM permanece protegida por `inspection_active`, garantindo overhead zero em modo de produção. + +## Rationale +- **Simplicidade:** Reduz o número de campos no `VirtualMachineRuntime`. +- **Performance:** Evita a cópia de dados entre `telemetry_current` e `telemetry_last` ao final de cada frame. +- **Consistência:** Garante que o Host e o Runtime vejam os mesmos dados através da mesma API. + +## Invariantes / Contrato +- O `VirtualMachineRuntime` deve possuir uma instância de `Arc`. +- O `AtomicTelemetry` deve ser thread-safe (já garantido pelo uso de `std::sync::atomic`). + +## Impactos +- **Runtime:** Alteração na struct principal e no loop de tick. +- **HAL:** Possível ajuste no `LogService` se ele dependia diretamente dos campos legados. +- **Docs:** Necessidade de atualizar as especificações de Debug e Portabilidade. + +## Referencias +- AGD-0021 (Migration Agenda) +- DEC-0007 (Overlay Isolation Decision) + +## Propagacao Necessaria +- Refatoração de `VirtualMachineRuntime`. +- Atualização de `prometeu-host-desktop-winit` para garantir que continua funcionando (já migrado no PLN-0006, mas deve ser validado). diff --git a/discussion/workflow/plans/PLN-0006-perf-host-debug-overlay-isolation.md b/discussion/workflow/plans/PLN-0006-perf-host-debug-overlay-isolation.md new file mode 100644 index 00000000..771f7440 --- /dev/null +++ b/discussion/workflow/plans/PLN-0006-perf-host-debug-overlay-isolation.md @@ -0,0 +1,64 @@ +--- +id: PLN-0006 +ticket: perf-host-debug-overlay-isolation +title: PR/Plan - [PERF] Host Debug Overlay Isolation +status: open +created: 2026-04-10 +updated: 2026-04-10 +decisions: [DEC-0007] +tags: [performance, host, gfx] +--- + +# PR/Plan - [PERF] Host Debug Overlay Isolation + +## Briefing +Implementação do isolamento total do overlay de debug no Host Desktop (`prometeu-host-desktop-winit`), removendo o acoplamento com o runtime e o pipeline de `gfx` emulado. + +## Decisions de Origem +- `DEC-0007`: [PERF] Host Debug Overlay Isolation. +- `DEC-0005`: [PERF] Push-based Telemetry Model (base para extração de dados). + +## Alvo +- `crates/host/prometeu-host-desktop-winit`: Implementação da camada nativa de HUD. +- `crates/runtime`: Exposição de campos de telemetria via API. +- `docs/specs/runtime`: Atualização das especificações de debug e portabilidade. + +## Escopo +- **Spec Work:** + - Atualizar `docs/specs/runtime/10-debug-inspection-and-profiling.md` para remover menções ao HUD emulado. + - Atualizar `docs/specs/runtime/11-portability-and-cross-platform-execution.md` para reforçar a separação de responsabilidades (Host-side HUD). +- **Code Work:** + - Expansão da API de telemetria no runtime para incluir todos os dados necessários (Cycles, Memory, Logs). + - Remoção do código de desenho de texto legado no `runner.rs`. + - Integração de biblioteca nativa (ex: `egui` ou composição via `pixels`) para renderização do novo overlay no Host. + - Implementação do toggle via tecla **F1** no `HostRunner`. + +## Fora de Escopo +- Implementação de overlay visual em outros hosts (mobile, handheld). +- Alterações na lógica de emulação central (loop de execução). + +## Plano de Execucao +1. **Fase 1: Especificações (Spec)** + - Revisar e atualizar os arquivos de especificação (`10-debug` e `11-portability`). +2. **Fase 2: Runtime Telemetry API (Code)** + - Garantir que todos os campos de telemetria estejam expostos via atômicos/push-based conforme `DEC-0005`. +3. **Fase 3: Host HUD Implementation (Code)** + - Integrar o novo motor de HUD no `prometeu-host-desktop-winit`. + - Conectar os dados da API de telemetria à visualização do HUD. +4. **Fase 4: Cleanup (Code)** + - Remover hotspots de formatação de strings e draw calls do overlay antigo no Host. + +## Criterios de Aceite +- O overlay de debug é ativado/desativado via tecla **F1**. +- O overlay utiliza fontes TrueType (monospaced) nítidas e fundo semi-transparente. +- O framebuffer emulado não contém pixels do HUD (composição nativa pós-upscaling). +- O custo de ciclos do runtime é idêntico com o overlay ligado ou desligado. + +## Tests / Validacao +- **Verificação Visual:** Confirmar a qualidade das fontes e a transparência do novo HUD. +- **Benchmarking:** Comparar os ciclos consumidos por frame com e sem o HUD ativo para provar isolamento. +- **Teste de Regressão:** Garantir que o F1 toggle não afeta a estabilidade do loop de emulação. + +## Riscos +- **Overhead no Host:** A renderização nativa (ex: `egui`) pode introduzir overhead no Host Desktop em máquinas muito fracas (geralmente aceitável em Desktop). +- **Sincronização de Telemetria:** Pequeno atraso visual entre o frame renderizado e os dados exibidos se a coleta for puramente assíncrona (aceitável para telemetria de debug). diff --git a/discussion/workflow/plans/PLN-0007-full-migration-to-atomic-telemetry.md b/discussion/workflow/plans/PLN-0007-full-migration-to-atomic-telemetry.md new file mode 100644 index 00000000..f9e3228b --- /dev/null +++ b/discussion/workflow/plans/PLN-0007-full-migration-to-atomic-telemetry.md @@ -0,0 +1,52 @@ +# PLN-0007: Full Migration to Atomic Telemetry + +## Briefing +Este plano detalha a remoção técnica dos campos legados de telemetria no `VirtualMachineRuntime` e a migração de todos os consumidores para o modelo de `AtomicTelemetry` introduzido na DEC-0007. + +## Decisions de Origem +- DEC-0008 (Full Migration to Atomic Telemetry) +- DEC-0007 (Host Debug Overlay Isolation) + +## Alvo +- `crates/console/prometeu-system` +- `crates/console/prometeu-hal` +- `crates/host/prometeu-host-desktop-winit` + +## Escopo +- Remoção de `telemetry_current` e `telemetry_last` de `VirtualMachineRuntime`. +- Refatoração do `VirtualMachineRuntime::tick` para remover atualizações redundantes. +- Atualização do `VirtualMachineRuntime::lifecycle` para remover inicialização e reset dos campos legados. +- Refatoração do `LogService` para consumir logs via `AtomicTelemetry`. +- Atualização do `HostRunner` (Desktop) para remover qualquer referência residual aos campos legados. +- Atualização das especificações técnicas em `docs/specs/runtime/`. + +## Fora de Escopo +- Mudanças no formato do `TelemetryFrame` (a menos que estritamente necessário para compatibilidade). +- Otimizações de performance não relacionadas à telemetria. + +## Plano de Execucao +1. **Fase 1: HAL & Telemetry** + - Verificar se `AtomicTelemetry` possui todos os campos necessários. + - Garantir que `LogService` está alinhado com o novo modelo. +2. **Fase 2: Runtime Refactor** + - Remover campos de `VirtualMachineRuntime` em `mod.rs`. + - Limpar inicialização em `lifecycle.rs`. + - Limpar loop de atualização em `tick.rs`. +3. **Fase 3: Host & Integration** + - Corrigir chamadas no `HostRunner` que ainda usem os campos antigos. + - Validar que o snapshot atômico atende às necessidades de inspeção. +4. **Fase 4: Specs & Cleanup** + - Atualizar `10-debug` e `11-portability`. + - Emitir lição aprendida LSN-0028. + +## Criterios de Aceite +- O projeto compila sem warnings relacionados a campos não utilizados. +- O Host Desktop inicia e o overlay (F1) exibe telemetria correta via atômicos. +- Não existem mais os campos `telemetry_current` e `telemetry_last` no código fonte. + +## Tests / Validacao +- `cargo check` em todos os crates afetados. +- Execução manual do host desktop para validar overlay. + +## Riscos +- **Perda de Snapshot de Frame:** Se o `snapshot()` não for chamado no momento certo ao final do frame, o overlay pode mostrar valores parciais de ciclos (resolvido chamando `snapshot()` no Host no momento de renderização do overlay). diff --git a/docs/specs/runtime/10-debug-inspection-and-profiling.md b/docs/specs/runtime/10-debug-inspection-and-profiling.md index 45159504..9137364a 100644 --- a/docs/specs/runtime/10-debug-inspection-and-profiling.md +++ b/docs/specs/runtime/10-debug-inspection-and-profiling.md @@ -18,7 +18,8 @@ It covers: - profiling; - breakpoints and watchpoints; - event and fault visibility; -- certification-facing diagnostics. +- certification-facing diagnostics; +- Host-side debug overlay (HUD) isolation. ## 2 Execution Modes @@ -230,10 +231,34 @@ Each event has: - cost - consequence -## 10 Integration with CAP and Certification +## 10 Host-Side Debug Overlay (HUD) Isolation + +The visual Debug Overlay (HUD) for technical inspection is not part of the emulated machine pipeline. + +### 10.1 Responsibilities + +1. **Runtime:** Only exposes telemetry data via the machine diagnostics surface. It does not perform HUD rendering or string formatting. +2. **Host (Desktop):** Responsible for collecting telemetry from the runtime and rendering the HUD as a native, transparent layer. + +### 10.2 Principles + +- **Zero Pipeline Interference:** HUD rendering must not inject pixels into the emulated framebuffer. It is applied after upscaling or as a separate display surface. +- **Zero Cycle Impact:** HUD-related processing (like formatting technical text) must occur outside the emulated machine cycles. +- **Toggle Control:** The activation of the overlay (typically via **F1**) is managed by the Host layer. + +### 10.3 Atomic Telemetry Model + +To ensure zero-impact synchronization between the VM and the Host Debug Overlay, PROMETEU uses a **push-based atomic model**: + +1. **Atomic Storage:** Metrics such as cycles, syscalls, and memory usage are stored in a dedicated `AtomicTelemetry` structure using thread-safe atomic types (`AtomicU64`, `AtomicU32`, etc.). +2. **Lockless Access:** The Host (Desktop) reads these metrics asynchronously and without locks by taking a `snapshot()` of the atomic state. +3. **Single Source of Truth:** This model is the exclusive source of truth for both real-time inspection and frame-end certification, replacing legacy per-frame buffered fields. + +## 11 Integration with CAP and Certification All debug and profiling data: - feed the certification report - are collected deterministically - do not depend on external tools +- are consistent regardless of whether the Host HUD is active or not. diff --git a/docs/specs/runtime/11-portability-and-cross-platform-execution.md b/docs/specs/runtime/11-portability-and-cross-platform-execution.md index 58383bbc..ccbfae77 100644 --- a/docs/specs/runtime/11-portability-and-cross-platform-execution.md +++ b/docs/specs/runtime/11-portability-and-cross-platform-execution.md @@ -51,6 +51,7 @@ The contract is about logical behavior, not identical physical latency or throug - audio output - physical input collection - access to the sandbox file system +- **technical inspection surfaces (Debug Overlay/HUD)** The host provides realization of machine surfaces. It does not redefine cartridge semantics. @@ -123,8 +124,25 @@ The platform layer: - only displays the framebuffer - does not reinterpret graphics commands +- **may overlay technical HUDs without modifying the logical framebuffer** -## 9 File System and Persistence +## 9 Debug and Inspection Isolation + +To preserve portability and certification purity, technical inspection tools (like the Debug Overlay) are moved to the Host layer. + +- **Host-exclusive:** These tools are only implemented where they are relevant (e.g., Desktop) and do not exist in the logical machine. +- **Non-intrusive:** They must not consume machine cycles or alter memory state. +- **Consistent Results:** A cartridge will produce the same logical results and certification metrics regardless of the Host's inspection capabilities. + +### 9.1 Atomic Telemetry Interface + +Inspection is facilitated by a lockless, push-based atomic interface: + +1. **Host-Independent:** The VM updates atomic counters in every frame. +2. **Asynchronous Observation:** The Host layer reads snapshots of these counters at its own display frequency. +3. **Loop Purity:** This ensures that the VM execution loop remains deterministic and free from synchronization overhead (locks) that could vary across host architectures. + +## 10 File System and Persistence PROMETEU defines a **sandbox logical filesystem**: @@ -140,7 +158,7 @@ The platform maps this filesystem to: Without changing semantics. -## 10 Certification and Portability +## 11 Certification and Portability The **PROMETEU Certification** is valid for all platforms. @@ -154,7 +172,7 @@ It: - will pass on all - will produce the same reports -## 11 What PROMETEU Does Not Guarantee +## 12 What PROMETEU Does Not Guarantee PROMETEU **does not promise**: From b8d33c9e02d2df621c692d9d2d6877b1c67667eb Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Fri, 10 Apr 2026 12:42:15 +0100 Subject: [PATCH 06/10] [PERF] Host Debug Overlay Isolation --- .../prometeu-hal/src/log/log_service.rs | 2 +- crates/console/prometeu-hal/src/telemetry.rs | 24 ++++++-- .../src/virtual_machine_runtime/dispatch.rs | 2 +- .../src/virtual_machine_runtime/lifecycle.rs | 1 + .../src/virtual_machine_runtime/tests.rs | 1 + .../src/virtual_machine_runtime/tick.rs | 60 ++++++++++++++----- .../prometeu-host-desktop-winit/src/runner.rs | 16 +++-- .../prometeu-host-desktop-winit/src/stats.rs | 6 +- 8 files changed, 83 insertions(+), 29 deletions(-) diff --git a/crates/console/prometeu-hal/src/log/log_service.rs b/crates/console/prometeu-hal/src/log/log_service.rs index c796b63a..60022284 100644 --- a/crates/console/prometeu-hal/src/log/log_service.rs +++ b/crates/console/prometeu-hal/src/log/log_service.rs @@ -1,7 +1,7 @@ use crate::log::{LogEvent, LogLevel, LogSource}; use std::collections::VecDeque; -use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::Arc; +use std::sync::atomic::{AtomicU32, Ordering}; pub struct LogService { events: VecDeque, diff --git a/crates/console/prometeu-hal/src/telemetry.rs b/crates/console/prometeu-hal/src/telemetry.rs index 08d025bb..8281cbf3 100644 --- a/crates/console/prometeu-hal/src/telemetry.rs +++ b/crates/console/prometeu-hal/src/telemetry.rs @@ -1,6 +1,6 @@ use crate::log::{LogLevel, LogService, LogSource}; -use std::sync::atomic::{AtomicU32, AtomicU64, AtomicUsize, Ordering}; use std::sync::Arc; +use std::sync::atomic::{AtomicU32, AtomicU64, AtomicUsize, Ordering}; #[derive(Debug, Clone, Copy, Default)] pub struct TelemetryFrame { @@ -64,10 +64,7 @@ pub struct AtomicTelemetry { impl AtomicTelemetry { pub fn new(logs_count: Arc) -> Self { - Self { - logs_count, - ..Default::default() - } + Self { logs_count, ..Default::default() } } /// Snapshots the current atomic state into a TelemetryFrame. @@ -92,6 +89,23 @@ impl AtomicTelemetry { vm_steps: self.vm_steps.load(Ordering::Relaxed), } } + + pub fn reset(&self) { + self.frame_index.store(0, Ordering::Relaxed); + self.cycles_used.store(0, Ordering::Relaxed); + self.syscalls.store(0, Ordering::Relaxed); + self.host_cpu_time_us.store(0, Ordering::Relaxed); + self.completed_logical_frames.store(0, Ordering::Relaxed); + self.violations.store(0, Ordering::Relaxed); + self.gfx_used_bytes.store(0, Ordering::Relaxed); + self.gfx_inflight_bytes.store(0, Ordering::Relaxed); + self.gfx_slots_occupied.store(0, Ordering::Relaxed); + self.audio_used_bytes.store(0, Ordering::Relaxed); + self.audio_inflight_bytes.store(0, Ordering::Relaxed); + self.audio_slots_occupied.store(0, Ordering::Relaxed); + self.heap_used_bytes.store(0, Ordering::Relaxed); + self.vm_steps.store(0, Ordering::Relaxed); + } } #[derive(Debug, Clone, Copy, Default)] diff --git a/crates/console/prometeu-system/src/virtual_machine_runtime/dispatch.rs b/crates/console/prometeu-system/src/virtual_machine_runtime/dispatch.rs index bc32d456..ee988aed 100644 --- a/crates/console/prometeu-system/src/virtual_machine_runtime/dispatch.rs +++ b/crates/console/prometeu-system/src/virtual_machine_runtime/dispatch.rs @@ -9,11 +9,11 @@ use prometeu_hal::log::{LogLevel, LogSource}; use prometeu_hal::sprite::Sprite; use prometeu_hal::syscalls::Syscall; use prometeu_hal::vm_fault::VmFault; -use std::sync::atomic::Ordering; use prometeu_hal::{ AudioOpStatus, GfxOpStatus, HostContext, HostReturn, NativeInterface, SyscallId, expect_bool, expect_int, }; +use std::sync::atomic::Ordering; impl VirtualMachineRuntime { fn syscall_log_write(&mut self, level_val: i64, tag: u16, msg: String) -> Result<(), VmFault> { diff --git a/crates/console/prometeu-system/src/virtual_machine_runtime/lifecycle.rs b/crates/console/prometeu-system/src/virtual_machine_runtime/lifecycle.rs index b8bef76b..0e022748 100644 --- a/crates/console/prometeu-system/src/virtual_machine_runtime/lifecycle.rs +++ b/crates/console/prometeu-system/src/virtual_machine_runtime/lifecycle.rs @@ -89,6 +89,7 @@ impl VirtualMachineRuntime { self.logical_frame_active = false; self.logical_frame_remaining_cycles = 0; self.last_frame_cpu_time_us = 0; + self.atomic_telemetry.reset(); self.open_files.clear(); self.next_handle = 1; diff --git a/crates/console/prometeu-system/src/virtual_machine_runtime/tests.rs b/crates/console/prometeu-system/src/virtual_machine_runtime/tests.rs index 59e9d0c4..938cb5fa 100644 --- a/crates/console/prometeu-system/src/virtual_machine_runtime/tests.rs +++ b/crates/console/prometeu-system/src/virtual_machine_runtime/tests.rs @@ -16,6 +16,7 @@ use prometeu_hal::glyph_bank::GLYPH_BANK_PALETTE_COUNT_V1; use prometeu_hal::syscalls::caps; use prometeu_vm::VmInitError; use std::collections::HashMap; +use std::sync::atomic::Ordering; #[derive(Default)] struct MemFsBackend { diff --git a/crates/console/prometeu-system/src/virtual_machine_runtime/tick.rs b/crates/console/prometeu-system/src/virtual_machine_runtime/tick.rs index 2ded2022..82422f0d 100644 --- a/crates/console/prometeu-system/src/virtual_machine_runtime/tick.rs +++ b/crates/console/prometeu-system/src/virtual_machine_runtime/tick.rs @@ -135,17 +135,33 @@ impl VirtualMachineRuntime { // 1. Snapshot full telemetry at logical frame end let gfx_stats = hw.assets().bank_info(BankType::GLYPH); - self.atomic_telemetry.gfx_used_bytes.store(gfx_stats.used_bytes, Ordering::Relaxed); - self.atomic_telemetry.gfx_inflight_bytes.store(gfx_stats.inflight_bytes, Ordering::Relaxed); - self.atomic_telemetry.gfx_slots_occupied.store(gfx_stats.slots_occupied as u32, Ordering::Relaxed); + self.atomic_telemetry + .gfx_used_bytes + .store(gfx_stats.used_bytes, Ordering::Relaxed); + self.atomic_telemetry + .gfx_inflight_bytes + .store(gfx_stats.inflight_bytes, Ordering::Relaxed); + self.atomic_telemetry + .gfx_slots_occupied + .store(gfx_stats.slots_occupied as u32, Ordering::Relaxed); let audio_stats = hw.assets().bank_info(BankType::SOUNDS); - self.atomic_telemetry.audio_used_bytes.store(audio_stats.used_bytes, Ordering::Relaxed); - self.atomic_telemetry.audio_inflight_bytes.store(audio_stats.inflight_bytes, Ordering::Relaxed); - self.atomic_telemetry.audio_slots_occupied.store(audio_stats.slots_occupied as u32, Ordering::Relaxed); + self.atomic_telemetry + .audio_used_bytes + .store(audio_stats.used_bytes, Ordering::Relaxed); + self.atomic_telemetry + .audio_inflight_bytes + .store(audio_stats.inflight_bytes, Ordering::Relaxed); + self.atomic_telemetry + .audio_slots_occupied + .store(audio_stats.slots_occupied as u32, Ordering::Relaxed); - self.atomic_telemetry.heap_used_bytes.store(vm.heap().used_bytes.load(Ordering::Relaxed), Ordering::Relaxed); - self.atomic_telemetry.host_cpu_time_us.store(start.elapsed().as_micros() as u64, Ordering::Relaxed); + self.atomic_telemetry + .heap_used_bytes + .store(vm.heap().used_bytes.load(Ordering::Relaxed), Ordering::Relaxed); + self.atomic_telemetry + .host_cpu_time_us + .store(start.elapsed().as_micros() as u64, Ordering::Relaxed); let ts_ms = self.boot_time.elapsed().as_millis() as u64; let telemetry_snapshot = self.atomic_telemetry.snapshot(); @@ -157,7 +173,9 @@ impl VirtualMachineRuntime { ) as u32; self.atomic_telemetry.violations.store(violations, Ordering::Relaxed); - self.atomic_telemetry.completed_logical_frames.fetch_add(1, Ordering::Relaxed); + self.atomic_telemetry + .completed_logical_frames + .fetch_add(1, Ordering::Relaxed); self.log_service.reset_count(); @@ -190,18 +208,30 @@ impl VirtualMachineRuntime { if self.inspection_active { let gfx_stats = hw.assets().bank_info(BankType::GLYPH); self.atomic_telemetry.gfx_used_bytes.store(gfx_stats.used_bytes, Ordering::Relaxed); - self.atomic_telemetry.gfx_inflight_bytes.store(gfx_stats.inflight_bytes, Ordering::Relaxed); - self.atomic_telemetry.gfx_slots_occupied.store(gfx_stats.slots_occupied as u32, Ordering::Relaxed); + self.atomic_telemetry + .gfx_inflight_bytes + .store(gfx_stats.inflight_bytes, Ordering::Relaxed); + self.atomic_telemetry + .gfx_slots_occupied + .store(gfx_stats.slots_occupied as u32, Ordering::Relaxed); let audio_stats = hw.assets().bank_info(BankType::SOUNDS); self.atomic_telemetry.audio_used_bytes.store(audio_stats.used_bytes, Ordering::Relaxed); - self.atomic_telemetry.audio_inflight_bytes.store(audio_stats.inflight_bytes, Ordering::Relaxed); - self.atomic_telemetry.audio_slots_occupied.store(audio_stats.slots_occupied as u32, Ordering::Relaxed); + self.atomic_telemetry + .audio_inflight_bytes + .store(audio_stats.inflight_bytes, Ordering::Relaxed); + self.atomic_telemetry + .audio_slots_occupied + .store(audio_stats.slots_occupied as u32, Ordering::Relaxed); - self.atomic_telemetry.heap_used_bytes.store(vm.heap().used_bytes.load(Ordering::Relaxed), Ordering::Relaxed); + self.atomic_telemetry + .heap_used_bytes + .store(vm.heap().used_bytes.load(Ordering::Relaxed), Ordering::Relaxed); self.atomic_telemetry.frame_index.store(self.logical_frame_index, Ordering::Relaxed); - self.atomic_telemetry.host_cpu_time_us.store(start.elapsed().as_micros() as u64, Ordering::Relaxed); + self.atomic_telemetry + .host_cpu_time_us + .store(start.elapsed().as_micros() as u64, Ordering::Relaxed); } None diff --git a/crates/host/prometeu-host-desktop-winit/src/runner.rs b/crates/host/prometeu-host-desktop-winit/src/runner.rs index b5bbdf6c..d7445caa 100644 --- a/crates/host/prometeu-host-desktop-winit/src/runner.rs +++ b/crates/host/prometeu-host-desktop-winit/src/runner.rs @@ -198,15 +198,19 @@ impl HostRunner { // Snapshot does not include violations, as they are part of certification (logical end of frame) // But for visual debug, we can check if there are recent CA tags in logs let recent_logs = self.firmware.os.log_service.get_recent(10); - let violations_count = recent_logs.iter().filter(|e| e.tag >= 0xCA01 && e.tag <= 0xCA07).count(); + let violations_count = + recent_logs.iter().filter(|e| e.tag >= 0xCA01 && e.tag <= 0xCA07).count(); let cert_color = if violations_count > 0 { color_warn } else { color_text }; - self.hardware.gfx.draw_text(10, 98, &format!("CERT RECENT: {}", violations_count), cert_color); + self.hardware.gfx.draw_text( + 10, + 98, + &format!("CERT RECENT: {}", violations_count), + cert_color, + ); if violations_count > 0 - && let Some(event) = recent_logs - .into_iter() - .rev() - .find(|e| e.tag >= 0xCA01 && e.tag <= 0xCA07) + && let Some(event) = + recent_logs.into_iter().rev().find(|e| e.tag >= 0xCA01 && e.tag <= 0xCA07) { let mut msg = event.msg.clone(); if msg.len() > 30 { diff --git a/crates/host/prometeu-host-desktop-winit/src/stats.rs b/crates/host/prometeu-host-desktop-winit/src/stats.rs index 3c723442..1c0bec2a 100644 --- a/crates/host/prometeu-host-desktop-winit/src/stats.rs +++ b/crates/host/prometeu-host-desktop-winit/src/stats.rs @@ -68,7 +68,11 @@ impl HostStats { cpu_load_audio, firmware.os.tick_index, firmware.os.logical_frame_index, - firmware.os.atomic_telemetry.completed_logical_frames.load(std::sync::atomic::Ordering::Relaxed), + firmware + .os + .atomic_telemetry + .completed_logical_frames + .load(std::sync::atomic::Ordering::Relaxed), ); window.set_title(&title); } From 58a5f9a3a65320f239095d2d64a545faec43c63c Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Fri, 10 Apr 2026 14:10:20 +0100 Subject: [PATCH 07/10] [PERF] Host Debug Overlay Isolation --- crates/console/prometeu-hal/src/telemetry.rs | 26 +- .../src/virtual_machine_runtime/tests.rs | 4 + .../src/virtual_machine_runtime/tick.rs | 4 + .../prometeu-host-desktop-winit/src/lib.rs | 1 + .../src/overlay.rs | 460 ++++++++++++++++++ .../prometeu-host-desktop-winit/src/runner.rs | 125 +---- .../prometeu-host-desktop-winit/src/stats.rs | 24 +- discussion/index.ndjson | 5 +- ...-host-overlay-tooling-boundary-revision.md | 96 ++++ .../AGD-0023-overlay-log-metric-last-frame.md | 53 ++ ...-0024-generic-memory-bank-slot-contract.md | 147 ++++++ .../DEC-0009-host-overlay-tooling-boundary.md | 61 +++ .../DEC-0010-overlay-log-metric-last-frame.md | 50 ++ ...et-manager-bank-telemetry-slot-contract.md | 78 +++ ...st-overlay-native-composition-alignment.md | 144 ++++++ ...et-manager-bank-telemetry-slot-contract.md | 153 ++++++ .../10-debug-inspection-and-profiling.md | 5 + ...ortability-and-cross-platform-execution.md | 2 + 18 files changed, 1315 insertions(+), 123 deletions(-) create mode 100644 crates/host/prometeu-host-desktop-winit/src/overlay.rs create mode 100644 discussion/workflow/agendas/AGD-0022-host-overlay-tooling-boundary-revision.md create mode 100644 discussion/workflow/agendas/AGD-0023-overlay-log-metric-last-frame.md create mode 100644 discussion/workflow/agendas/AGD-0024-generic-memory-bank-slot-contract.md create mode 100644 discussion/workflow/decisions/DEC-0009-host-overlay-tooling-boundary.md create mode 100644 discussion/workflow/decisions/DEC-0010-overlay-log-metric-last-frame.md create mode 100644 discussion/workflow/decisions/DEC-0012-asset-manager-bank-telemetry-slot-contract.md create mode 100644 discussion/workflow/plans/PLN-0008-host-overlay-native-composition-alignment.md create mode 100644 discussion/workflow/plans/PLN-0010-asset-manager-bank-telemetry-slot-contract.md diff --git a/crates/console/prometeu-hal/src/telemetry.rs b/crates/console/prometeu-hal/src/telemetry.rs index 8281cbf3..2ac52fab 100644 --- a/crates/console/prometeu-hal/src/telemetry.rs +++ b/crates/console/prometeu-hal/src/telemetry.rs @@ -27,7 +27,7 @@ pub struct TelemetryFrame { pub heap_used_bytes: usize, pub heap_max_bytes: usize, - // Log Pressure + // Log Pressure from the last completed logical frame pub logs_count: u32, } @@ -58,13 +58,15 @@ pub struct AtomicTelemetry { pub heap_used_bytes: AtomicUsize, pub heap_max_bytes: AtomicUsize, - // Log Pressure - pub logs_count: Arc, + // Transient in-flight log counter for the current logical frame + pub current_logs_count: Arc, + // Persisted log count from the last completed logical frame + pub logs_count: AtomicU32, } impl AtomicTelemetry { - pub fn new(logs_count: Arc) -> Self { - Self { logs_count, ..Default::default() } + pub fn new(current_logs_count: Arc) -> Self { + Self { current_logs_count, ..Default::default() } } /// Snapshots the current atomic state into a TelemetryFrame. @@ -105,6 +107,8 @@ impl AtomicTelemetry { self.audio_slots_occupied.store(0, Ordering::Relaxed); self.heap_used_bytes.store(0, Ordering::Relaxed); self.vm_steps.store(0, Ordering::Relaxed); + self.logs_count.store(0, Ordering::Relaxed); + self.current_logs_count.store(0, Ordering::Relaxed); } } @@ -301,4 +305,16 @@ mod tests { assert!(logs[1].msg.contains("syscalls")); assert!(logs[2].msg.contains("GFX bank")); } + + #[test] + fn snapshot_uses_persisted_last_frame_logs() { + let current = Arc::new(AtomicU32::new(7)); + let tel = AtomicTelemetry::new(Arc::clone(¤t)); + tel.logs_count.store(3, Ordering::Relaxed); + + let snapshot = tel.snapshot(); + + assert_eq!(snapshot.logs_count, 3); + assert_eq!(current.load(Ordering::Relaxed), 7); + } } diff --git a/crates/console/prometeu-system/src/virtual_machine_runtime/tests.rs b/crates/console/prometeu-system/src/virtual_machine_runtime/tests.rs index 938cb5fa..4c0c3b38 100644 --- a/crates/console/prometeu-system/src/virtual_machine_runtime/tests.rs +++ b/crates/console/prometeu-system/src/virtual_machine_runtime/tests.rs @@ -274,6 +274,8 @@ fn reset_clears_cartridge_scoped_runtime_state() { runtime.current_cartridge_app_version = "1.2.3".into(); runtime.current_cartridge_app_mode = AppMode::System; runtime.logs_written_this_frame.insert(42, 3); + runtime.atomic_telemetry.logs_count.store(5, Ordering::Relaxed); + runtime.atomic_telemetry.current_logs_count.store(8, Ordering::Relaxed); runtime.atomic_telemetry.frame_index.store(8, Ordering::Relaxed); runtime.atomic_telemetry.cycles_used.store(99, Ordering::Relaxed); runtime.atomic_telemetry.completed_logical_frames.store(2, Ordering::Relaxed); @@ -298,6 +300,8 @@ fn reset_clears_cartridge_scoped_runtime_state() { assert!(runtime.current_cartridge_app_version.is_empty()); assert_eq!(runtime.current_cartridge_app_mode, AppMode::Game); assert!(runtime.logs_written_this_frame.is_empty()); + assert_eq!(runtime.atomic_telemetry.logs_count.load(Ordering::Relaxed), 0); + assert_eq!(runtime.atomic_telemetry.current_logs_count.load(Ordering::Relaxed), 0); assert_eq!(runtime.atomic_telemetry.frame_index.load(Ordering::Relaxed), 0); assert_eq!(runtime.atomic_telemetry.cycles_used.load(Ordering::Relaxed), 0); assert_eq!(runtime.atomic_telemetry.completed_logical_frames.load(Ordering::Relaxed), 0); diff --git a/crates/console/prometeu-system/src/virtual_machine_runtime/tick.rs b/crates/console/prometeu-system/src/virtual_machine_runtime/tick.rs index 82422f0d..14245287 100644 --- a/crates/console/prometeu-system/src/virtual_machine_runtime/tick.rs +++ b/crates/console/prometeu-system/src/virtual_machine_runtime/tick.rs @@ -163,6 +163,10 @@ impl VirtualMachineRuntime { .host_cpu_time_us .store(start.elapsed().as_micros() as u64, Ordering::Relaxed); + let current_frame_logs = + self.atomic_telemetry.current_logs_count.load(Ordering::Relaxed); + self.atomic_telemetry.logs_count.store(current_frame_logs, Ordering::Relaxed); + let ts_ms = self.boot_time.elapsed().as_millis() as u64; let telemetry_snapshot = self.atomic_telemetry.snapshot(); diff --git a/crates/host/prometeu-host-desktop-winit/src/lib.rs b/crates/host/prometeu-host-desktop-winit/src/lib.rs index a91c231f..be84a693 100644 --- a/crates/host/prometeu-host-desktop-winit/src/lib.rs +++ b/crates/host/prometeu-host-desktop-winit/src/lib.rs @@ -4,6 +4,7 @@ pub mod debugger; pub mod fs_backend; pub mod input; pub mod log_sink; +pub mod overlay; pub mod runner; pub mod stats; pub mod utilities; diff --git a/crates/host/prometeu-host-desktop-winit/src/overlay.rs b/crates/host/prometeu-host-desktop-winit/src/overlay.rs new file mode 100644 index 00000000..8a1f458e --- /dev/null +++ b/crates/host/prometeu-host-desktop-winit/src/overlay.rs @@ -0,0 +1,460 @@ +use crate::stats::HostStats; +use prometeu_firmware::Firmware; + +const PANEL_X: usize = 6; +const PANEL_Y: usize = 10; +const PANEL_WIDTH: usize = 170; +const PANEL_PADDING: usize = 8; +const LINE_HEIGHT: usize = 12; +const CHAR_SCALE: usize = 1; +const BAR_WIDTH: usize = PANEL_WIDTH - (PANEL_PADDING * 2); +const BAR_HEIGHT: usize = 6; + +const BG: [u8; 4] = [10, 18, 32, 208]; +const BORDER: [u8; 4] = [90, 126, 170, 255]; +const TEXT: [u8; 4] = [235, 240, 255, 255]; +const DIM: [u8; 4] = [150, 168, 196, 255]; +const WARN: [u8; 4] = [255, 104, 104, 255]; +const BAR_BG: [u8; 4] = [30, 42, 61, 255]; +const BAR_FILL: [u8; 4] = [91, 184, 255, 255]; +const BAR_WARN: [u8; 4] = [255, 150, 102, 255]; +const OVERLAY_HEAP_FALLBACK_BYTES: usize = 8 * 1024 * 1024; + +#[derive(Debug, Clone)] +pub(crate) struct OverlayMetric { + label: &'static str, + value: String, + warn: bool, +} + +#[derive(Debug, Clone)] +pub(crate) struct OverlayBar { + label: &'static str, + value: String, + ratio: f32, + warn: bool, +} + +#[derive(Debug, Clone)] +pub(crate) struct OverlaySnapshot { + rows: Vec<(OverlayMetric, OverlayMetric)>, + bars: Vec, + footer: Vec, +} + +pub(crate) fn capture_snapshot(stats: &HostStats, firmware: &Firmware) -> OverlaySnapshot { + let tel = firmware.os.atomic_telemetry.snapshot(); + let recent_logs = firmware.os.log_service.get_recent(10); + let violations_count = recent_logs.iter().filter(|e| e.tag >= 0xCA01 && e.tag <= 0xCA07).count(); + + let mut footer = Vec::new(); + if violations_count > 0 + && let Some(event) = recent_logs.into_iter().rev().find(|e| e.tag >= 0xCA01 && e.tag <= 0xCA07) + { + footer.push(OverlayMetric { + label: "CERT", + value: truncate_value(&event.msg, 28), + warn: true, + }); + } + + if let Some(report) = firmware.os.last_crash_report.as_ref() { + footer.push(OverlayMetric { + label: "CRASH", + value: truncate_value(&report.summary(), 28), + warn: true, + }); + } + + let cycles_ratio = ratio(tel.cycles_used, tel.cycles_budget); + let heap_total_bytes = firmware + .os + .certifier + .config + .max_heap_bytes + .or(if tel.heap_max_bytes > 0 { Some(tel.heap_max_bytes) } else { None }) + .unwrap_or(OVERLAY_HEAP_FALLBACK_BYTES); + let heap_ratio = ratio(tel.heap_used_bytes as u64, heap_total_bytes as u64); + let gfx_ratio = ratio(tel.gfx_slots_occupied as u64, 16); + let audio_ratio = ratio(tel.audio_slots_occupied as u64, 16); + + OverlaySnapshot { + rows: vec![ + ( + OverlayMetric { + label: "FPS", + value: format!("{:.1}", stats.current_fps), + warn: false, + }, + OverlayMetric { + label: "CERT", + value: violations_count.to_string(), + warn: violations_count > 0, + }, + ), + ( + OverlayMetric { + label: "HOST", + value: format!("{:.2}ms", stats.average_host_cpu_ms()), + warn: false, + }, + OverlayMetric { + label: "STEPS", + value: tel.vm_steps.to_string(), + warn: false, + }, + ), + ( + OverlayMetric { + label: "SYSC", + value: tel.syscalls.to_string(), + warn: false, + }, + OverlayMetric { + label: "LOGS", + value: tel.logs_count.to_string(), + warn: false, + }, + ), + ], + bars: vec![ + OverlayBar { + label: "HEAP", + value: format!( + "{}K{}", + tel.heap_used_bytes.div_ceil(1024), + if heap_total_bytes > 0 { + format!(" / {}K", heap_total_bytes.div_ceil(1024)) + } else { + String::new() + } + ), + ratio: heap_ratio, + warn: tel.heap_used_bytes >= heap_total_bytes, + }, + OverlayBar { + label: "BUDGET", + value: if tel.cycles_budget > 0 { + format!( + "{:.1}K/{:.1}K {:.1}%", + tel.cycles_used as f64 / 1000.0, + tel.cycles_budget as f64 / 1000.0, + cycles_ratio * 100.0 + ) + } else { + "0.0K/0.0K 0.0%".to_string() + }, + ratio: cycles_ratio, + warn: cycles_ratio >= 0.9, + }, + OverlayBar { + label: "GFX", + value: format!( + "{} / 16 slots {}K", + tel.gfx_slots_occupied, + tel.gfx_used_bytes.div_ceil(1024) + ), + ratio: gfx_ratio, + warn: tel.gfx_inflight_bytes > 0, + }, + OverlayBar { + label: "AUD", + value: format!( + "{} / 16 slots {}K", + tel.audio_slots_occupied, + tel.audio_used_bytes.div_ceil(1024) + ), + ratio: audio_ratio, + warn: tel.audio_inflight_bytes > 0, + }, + ], + footer, + } +} + +pub(crate) fn draw_overlay( + frame: &mut [u8], + frame_width: usize, + frame_height: usize, + snapshot: &OverlaySnapshot, +) { + let panel_height = frame_height.saturating_sub(PANEL_Y * 2); + + fill_rect_alpha(frame, frame_width, frame_height, PANEL_X, PANEL_Y, PANEL_WIDTH, panel_height, BG); + stroke_rect(frame, frame_width, frame_height, PANEL_X, PANEL_Y, PANEL_WIDTH, panel_height, BORDER); + + let mut y = PANEL_Y + PANEL_PADDING; + for (left, right) in &snapshot.rows { + draw_metric_pair(frame, frame_width, frame_height, y, left, right); + y += LINE_HEIGHT; + } + + for bar in &snapshot.bars { + let color = if bar.warn { WARN } else { TEXT }; + draw_text(frame, frame_width, frame_height, PANEL_X + PANEL_PADDING, y, bar.label, DIM); + draw_text(frame, frame_width, frame_height, PANEL_X + 48, y, &bar.value, color); + y += LINE_HEIGHT - 2; + + let bar_x = PANEL_X + PANEL_PADDING; + fill_rect(frame, frame_width, frame_height, bar_x, y, BAR_WIDTH, BAR_HEIGHT, BAR_BG); + let fill_width = ((BAR_WIDTH as f32) * bar.ratio.clamp(0.0, 1.0)).round() as usize; + fill_rect( + frame, + frame_width, + frame_height, + bar_x, + y, + fill_width, + BAR_HEIGHT, + if bar.warn { BAR_WARN } else { BAR_FILL }, + ); + stroke_rect(frame, frame_width, frame_height, bar_x, y, BAR_WIDTH, BAR_HEIGHT, BORDER); + y += BAR_HEIGHT + 6; + } + + for line in &snapshot.footer { + let color = if line.warn { WARN } else { TEXT }; + draw_text(frame, frame_width, frame_height, PANEL_X + PANEL_PADDING, y, line.label, DIM); + draw_text(frame, frame_width, frame_height, PANEL_X + 48, y, &line.value, color); + y += LINE_HEIGHT; + } +} + +fn draw_metric_pair( + frame: &mut [u8], + frame_width: usize, + frame_height: usize, + y: usize, + left: &OverlayMetric, + right: &OverlayMetric, +) { + let left_x = PANEL_X + PANEL_PADDING; + let right_x = PANEL_X + 86; + + draw_metric(frame, frame_width, frame_height, left_x, y, left); + draw_metric(frame, frame_width, frame_height, right_x, y, right); +} + +fn draw_metric( + frame: &mut [u8], + frame_width: usize, + frame_height: usize, + x: usize, + y: usize, + metric: &OverlayMetric, +) { + let color = if metric.warn { WARN } else { TEXT }; + draw_text(frame, frame_width, frame_height, x, y, metric.label, DIM); + draw_text(frame, frame_width, frame_height, x + 30, y, &metric.value, color); +} + +fn ratio(value: u64, total: u64) -> f32 { + if total == 0 { 0.0 } else { (value as f32 / total as f32).clamp(0.0, 1.0) } +} + +fn truncate_value(value: &str, max_len: usize) -> String { + let mut upper = value.to_ascii_uppercase(); + if upper.len() > max_len { + upper.truncate(max_len); + } + upper +} + +fn draw_text(frame: &mut [u8], frame_width: usize, frame_height: usize, x: usize, y: usize, text: &str, color: [u8; 4]) { + let mut cursor_x = x; + for ch in text.chars() { + if ch == '\n' { + continue; + } + draw_char(frame, frame_width, frame_height, cursor_x, y, ch.to_ascii_uppercase(), color); + cursor_x += 6 * CHAR_SCALE; + } +} + +fn draw_char(frame: &mut [u8], frame_width: usize, frame_height: usize, x: usize, y: usize, ch: char, color: [u8; 4]) { + let glyph = glyph_bits(ch); + for (row, bits) in glyph.iter().enumerate() { + for col in 0..5 { + if bits & (1 << (4 - col)) != 0 { + fill_rect(frame, frame_width, frame_height, x + col * CHAR_SCALE, y + row * CHAR_SCALE, CHAR_SCALE, CHAR_SCALE, color); + } + } + } +} + +fn fill_rect_alpha( + frame: &mut [u8], + frame_width: usize, + frame_height: usize, + x: usize, + y: usize, + width: usize, + height: usize, + color: [u8; 4], +) { + let max_x = (x + width).min(frame_width); + let max_y = (y + height).min(frame_height); + for py in y..max_y { + for px in x..max_x { + blend_pixel(frame, frame_width, px, py, color); + } + } +} + +fn fill_rect( + frame: &mut [u8], + frame_width: usize, + frame_height: usize, + x: usize, + y: usize, + width: usize, + height: usize, + color: [u8; 4], +) { + let max_x = (x + width).min(frame_width); + let max_y = (y + height).min(frame_height); + for py in y..max_y { + for px in x..max_x { + write_pixel(frame, frame_width, px, py, color); + } + } +} + +fn stroke_rect( + frame: &mut [u8], + frame_width: usize, + frame_height: usize, + x: usize, + y: usize, + width: usize, + height: usize, + color: [u8; 4], +) { + if width == 0 || height == 0 { + return; + } + fill_rect(frame, frame_width, frame_height, x, y, width, 1, color); + fill_rect(frame, frame_width, frame_height, x, y + height.saturating_sub(1), width, 1, color); + fill_rect(frame, frame_width, frame_height, x, y, 1, height, color); + fill_rect(frame, frame_width, frame_height, x + width.saturating_sub(1), y, 1, height, color); +} + +fn blend_pixel(frame: &mut [u8], frame_width: usize, x: usize, y: usize, color: [u8; 4]) { + let idx = (y * frame_width + x) * 4; + let alpha = color[3] as f32 / 255.0; + let inv = 1.0 - alpha; + frame[idx] = (frame[idx] as f32 * inv + color[0] as f32 * alpha).round() as u8; + frame[idx + 1] = (frame[idx + 1] as f32 * inv + color[1] as f32 * alpha).round() as u8; + frame[idx + 2] = (frame[idx + 2] as f32 * inv + color[2] as f32 * alpha).round() as u8; + frame[idx + 3] = 0xFF; +} + +fn write_pixel(frame: &mut [u8], frame_width: usize, x: usize, y: usize, color: [u8; 4]) { + let idx = (y * frame_width + x) * 4; + frame[idx] = color[0]; + frame[idx + 1] = color[1]; + frame[idx + 2] = color[2]; + frame[idx + 3] = color[3]; +} + +fn glyph_bits(ch: char) -> [u8; 7] { + match ch { + 'A' => [0x0E, 0x11, 0x11, 0x1F, 0x11, 0x11, 0x11], + 'B' => [0x1E, 0x11, 0x11, 0x1E, 0x11, 0x11, 0x1E], + 'C' => [0x0E, 0x11, 0x10, 0x10, 0x10, 0x11, 0x0E], + 'D' => [0x1E, 0x12, 0x11, 0x11, 0x11, 0x12, 0x1E], + 'E' => [0x1F, 0x10, 0x10, 0x1E, 0x10, 0x10, 0x1F], + 'F' => [0x1F, 0x10, 0x10, 0x1E, 0x10, 0x10, 0x10], + 'G' => [0x0E, 0x11, 0x10, 0x17, 0x11, 0x11, 0x0E], + 'H' => [0x11, 0x11, 0x11, 0x1F, 0x11, 0x11, 0x11], + 'I' => [0x0E, 0x04, 0x04, 0x04, 0x04, 0x04, 0x0E], + 'J' => [0x01, 0x01, 0x01, 0x01, 0x11, 0x11, 0x0E], + 'K' => [0x11, 0x12, 0x14, 0x18, 0x14, 0x12, 0x11], + 'L' => [0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x1F], + 'M' => [0x11, 0x1B, 0x15, 0x15, 0x11, 0x11, 0x11], + 'N' => [0x11, 0x11, 0x19, 0x15, 0x13, 0x11, 0x11], + 'O' => [0x0E, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0E], + 'P' => [0x1E, 0x11, 0x11, 0x1E, 0x10, 0x10, 0x10], + 'Q' => [0x0E, 0x11, 0x11, 0x11, 0x15, 0x12, 0x0D], + 'R' => [0x1E, 0x11, 0x11, 0x1E, 0x14, 0x12, 0x11], + 'S' => [0x0F, 0x10, 0x10, 0x0E, 0x01, 0x01, 0x1E], + 'T' => [0x1F, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04], + 'U' => [0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0E], + 'V' => [0x11, 0x11, 0x11, 0x11, 0x11, 0x0A, 0x04], + 'W' => [0x11, 0x11, 0x11, 0x15, 0x15, 0x15, 0x0A], + 'X' => [0x11, 0x11, 0x0A, 0x04, 0x0A, 0x11, 0x11], + 'Y' => [0x11, 0x11, 0x0A, 0x04, 0x04, 0x04, 0x04], + 'Z' => [0x1F, 0x01, 0x02, 0x04, 0x08, 0x10, 0x1F], + '0' => [0x0E, 0x11, 0x13, 0x15, 0x19, 0x11, 0x0E], + '1' => [0x04, 0x0C, 0x14, 0x04, 0x04, 0x04, 0x1F], + '2' => [0x0E, 0x11, 0x01, 0x02, 0x04, 0x08, 0x1F], + '3' => [0x1E, 0x01, 0x01, 0x0E, 0x01, 0x01, 0x1E], + '4' => [0x02, 0x06, 0x0A, 0x12, 0x1F, 0x02, 0x02], + '5' => [0x1F, 0x10, 0x10, 0x1E, 0x01, 0x01, 0x1E], + '6' => [0x0E, 0x10, 0x10, 0x1E, 0x11, 0x11, 0x0E], + '7' => [0x1F, 0x01, 0x02, 0x04, 0x08, 0x08, 0x08], + '8' => [0x0E, 0x11, 0x11, 0x0E, 0x11, 0x11, 0x0E], + '9' => [0x0E, 0x11, 0x11, 0x0F, 0x01, 0x01, 0x0E], + ':' => [0x00, 0x04, 0x04, 0x00, 0x04, 0x04, 0x00], + '.' => [0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x06], + '/' => [0x01, 0x02, 0x02, 0x04, 0x08, 0x08, 0x10], + '%' => [0x19, 0x19, 0x02, 0x04, 0x08, 0x13, 0x13], + '(' => [0x02, 0x04, 0x08, 0x08, 0x08, 0x04, 0x02], + ')' => [0x08, 0x04, 0x02, 0x02, 0x02, 0x04, 0x08], + '-' => [0x00, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x00], + '_' => [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F], + '+' => [0x00, 0x04, 0x04, 0x1F, 0x04, 0x04, 0x00], + ' ' => [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], + '?' => [0x0E, 0x11, 0x01, 0x02, 0x04, 0x00, 0x04], + _ => [0x0E, 0x11, 0x01, 0x02, 0x04, 0x00, 0x04], + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn sample_snapshot() -> OverlaySnapshot { + OverlaySnapshot { + rows: vec![ + ( + OverlayMetric { label: "FPS", value: "60.0".to_string(), warn: false }, + OverlayMetric { label: "CERT", value: "2".to_string(), warn: true }, + ), + ( + OverlayMetric { label: "HOST", value: "1.23ms".to_string(), warn: false }, + OverlayMetric { label: "STEPS", value: "420".to_string(), warn: false }, + ), + ], + bars: vec![OverlayBar { + label: "HEAP", + value: "1024K / 8192K".to_string(), + ratio: 0.125, + warn: false, + }, + OverlayBar { + label: "BUDGET", + value: "9.0K/10.0K 90.0%".to_string(), + ratio: 0.9, + warn: true, + }], + footer: vec![OverlayMetric { + label: "CRASH", + value: "VM PANIC".to_string(), + warn: true, + }], + } + } + + #[test] + fn draw_overlay_writes_to_host_rgba_frame() { + let mut frame = vec![0u8; 320 * 180 * 4]; + draw_overlay(&mut frame, 320, 180, &sample_snapshot()); + assert!(frame.iter().any(|&byte| byte != 0)); + } + + #[test] + fn truncate_value_normalizes_and_caps() { + assert_eq!(truncate_value("panic: lowercase", 12), "PANIC: LOWER"); + } +} diff --git a/crates/host/prometeu-host-desktop-winit/src/runner.rs b/crates/host/prometeu-host-desktop-winit/src/runner.rs index d7445caa..76284f0c 100644 --- a/crates/host/prometeu-host-desktop-winit/src/runner.rs +++ b/crates/host/prometeu-host-desktop-winit/src/runner.rs @@ -3,6 +3,7 @@ use crate::debugger::HostDebugger; use crate::fs_backend::HostDirBackend; use crate::input::HostInputHandler; use crate::log_sink::HostConsoleSink; +use crate::overlay::{capture_snapshot, draw_overlay}; use crate::stats::HostStats; use crate::utilities::draw_rgb565_to_rgba8; use pixels::wgpu::PresentMode; @@ -10,7 +11,6 @@ use pixels::{Pixels, PixelsBuilder, SurfaceTexture}; use prometeu_drivers::AudioCommand; use prometeu_drivers::hardware::Hardware; use prometeu_firmware::{BootTarget, Firmware}; -use prometeu_hal::color::Color; use prometeu_hal::telemetry::CertificationConfig; use std::time::{Duration, Instant}; use winit::application::ApplicationHandler; @@ -123,110 +123,6 @@ impl HostRunner { } } - fn display_dbg_overlay(&mut self) { - let tel = self.firmware.os.atomic_telemetry.snapshot(); - let color_text = Color::WHITE; - let color_bg = Color::INDIGO; // Dark blue to stand out - let color_warn = Color::RED; - - self.hardware.gfx.fill_rect(5, 5, 175, 130, color_bg); - self.hardware.gfx.draw_text( - 10, - 10, - &format!("FPS: {:.1}", self.stats.current_fps), - color_text, - ); - self.hardware.gfx.draw_text( - 10, - 18, - &format!("HOST: {:.2}MS", tel.host_cpu_time_us as f64 / 1000.0), - color_text, - ); - self.hardware.gfx.draw_text(10, 26, &format!("STEPS: {}", tel.vm_steps), color_text); - self.hardware.gfx.draw_text(10, 34, &format!("SYSC: {}", tel.syscalls), color_text); - - let cycles_pct = if tel.cycles_budget > 0 { - (tel.cycles_used as f64 / tel.cycles_budget as f64) * 100.0 - } else { - 0.0 - }; - self.hardware.gfx.draw_text( - 10, - 42, - &format!("CYC: {}/{} ({:.1}%)", tel.cycles_used, tel.cycles_budget, cycles_pct), - color_text, - ); - - self.hardware.gfx.draw_text( - 10, - 50, - &format!("GFX: {}K/16M ({}S)", tel.gfx_used_bytes / 1024, tel.gfx_slots_occupied), - color_text, - ); - if tel.gfx_inflight_bytes > 0 { - self.hardware.gfx.draw_text( - 10, - 58, - &format!("LOAD GFX: {}KB", tel.gfx_inflight_bytes / 1024), - color_warn, - ); - } - - self.hardware.gfx.draw_text( - 10, - 66, - &format!("AUD: {}K/32M ({}S)", tel.audio_used_bytes / 1024, tel.audio_slots_occupied), - color_text, - ); - if tel.audio_inflight_bytes > 0 { - self.hardware.gfx.draw_text( - 10, - 74, - &format!("LOAD AUD: {}KB", tel.audio_inflight_bytes / 1024), - color_warn, - ); - } - - self.hardware.gfx.draw_text( - 10, - 82, - &format!("RAM: {}KB", tel.heap_used_bytes / 1024), - color_text, - ); - self.hardware.gfx.draw_text(10, 90, &format!("LOGS: {}", tel.logs_count), color_text); - - // Snapshot does not include violations, as they are part of certification (logical end of frame) - // But for visual debug, we can check if there are recent CA tags in logs - let recent_logs = self.firmware.os.log_service.get_recent(10); - let violations_count = - recent_logs.iter().filter(|e| e.tag >= 0xCA01 && e.tag <= 0xCA07).count(); - let cert_color = if violations_count > 0 { color_warn } else { color_text }; - self.hardware.gfx.draw_text( - 10, - 98, - &format!("CERT RECENT: {}", violations_count), - cert_color, - ); - - if violations_count > 0 - && let Some(event) = - recent_logs.into_iter().rev().find(|e| e.tag >= 0xCA01 && e.tag <= 0xCA07) - { - let mut msg = event.msg.clone(); - if msg.len() > 30 { - msg.truncate(30); - } - self.hardware.gfx.draw_text(10, 106, &msg, color_warn); - } - - if let Some(report) = self.firmware.os.last_crash_report.as_ref() { - let mut msg = report.summary(); - if msg.len() > 30 { - msg.truncate(30); - } - self.hardware.gfx.draw_text(10, 114, &msg, color_warn); - } - } } impl ApplicationHandler for HostRunner { @@ -280,6 +176,9 @@ impl ApplicationHandler for HostRunner { } WindowEvent::RedrawRequested => { + let overlay_snapshot = + self.overlay_enabled.then(|| capture_snapshot(&self.stats, &self.firmware)); + // Get Pixels directly from the field (not via helper that gets the entire &mut self) let pixels = self.pixels.as_mut().expect("pixels not initialized"); @@ -291,6 +190,10 @@ impl ApplicationHandler for HostRunner { let src = self.hardware.gfx.front_buffer(); draw_rgb565_to_rgba8(src, frame); + + if let Some(snapshot) = overlay_snapshot.as_ref() { + draw_overlay(frame, Hardware::W, Hardware::H, snapshot); + } } // <- frame borrow ends here if pixels.render().is_err() { @@ -358,6 +261,7 @@ impl ApplicationHandler for HostRunner { // Unless the debugger is waiting for a 'start' command, advance the system. if !self.debugger.waiting_for_start { self.firmware.tick(&self.input.signals, &mut self.hardware); + self.stats.record_host_cpu_time(self.firmware.os.last_frame_cpu_time_us); } // Sync pause state with audio. @@ -393,15 +297,8 @@ impl ApplicationHandler for HostRunner { }; self.log_sink.process_events(new_events); - // 5. Rendering the Telemetry Overlay (if enabled). - if self.overlay_enabled { - // We temporarily swap buffers to draw over the current image. - self.hardware.gfx.present(); - self.display_dbg_overlay(); - self.hardware.gfx.present(); - } - - // Finally, request a window redraw to present the new pixels. + // 5. Request redraw so the host surface can present the latest machine frame + // and, when enabled, compose the overlay in the host-only RGBA surface. self.request_redraw(); } } diff --git a/crates/host/prometeu-host-desktop-winit/src/stats.rs b/crates/host/prometeu-host-desktop-winit/src/stats.rs index 1c0bec2a..bdcc4275 100644 --- a/crates/host/prometeu-host-desktop-winit/src/stats.rs +++ b/crates/host/prometeu-host-desktop-winit/src/stats.rs @@ -9,6 +9,9 @@ pub struct HostStats { pub current_fps: f64, pub audio_load_accum_us: u64, pub audio_load_samples: u64, + recent_host_cpu_us: [u64; 5], + recent_host_cpu_count: usize, + recent_host_cpu_cursor: usize, } impl Default for HostStats { @@ -25,6 +28,9 @@ impl HostStats { current_fps: 0.0, audio_load_accum_us: 0, audio_load_samples: 0, + recent_host_cpu_us: [0; 5], + recent_host_cpu_count: 0, + recent_host_cpu_cursor: 0, } } @@ -37,6 +43,21 @@ impl HostStats { self.audio_load_samples += 1; } + pub fn record_host_cpu_time(&mut self, us: u64) { + self.recent_host_cpu_us[self.recent_host_cpu_cursor] = us; + self.recent_host_cpu_cursor = (self.recent_host_cpu_cursor + 1) % self.recent_host_cpu_us.len(); + self.recent_host_cpu_count = self.recent_host_cpu_count.saturating_add(1).min(self.recent_host_cpu_us.len()); + } + + pub fn average_host_cpu_ms(&self) -> f64 { + if self.recent_host_cpu_count == 0 { + 0.0 + } else { + let sum: u64 = self.recent_host_cpu_us.iter().take(self.recent_host_cpu_count).sum(); + (sum as f64 / self.recent_host_cpu_count as f64) / 1000.0 + } + } + pub fn update( &mut self, now: Instant, @@ -51,8 +72,7 @@ impl HostStats { if let Some(window) = window { // Fixed comparison always against 60Hz, keep even when doing CPU stress tests let frame_budget_us = 16666.0; - let cpu_load_core = - (firmware.os.last_frame_cpu_time_us as f64 / frame_budget_us) * 100.0; + let cpu_load_core = (self.average_host_cpu_ms() * 1000.0 / frame_budget_us) * 100.0; let cpu_load_audio = if self.audio_load_samples > 0 { (self.audio_load_accum_us as f64 / stats_elapsed.as_micros() as f64) * 100.0 diff --git a/discussion/index.ndjson b/discussion/index.ndjson index d4c853ef..e4fb3881 100644 --- a/discussion/index.ndjson +++ b/discussion/index.ndjson @@ -1,4 +1,4 @@ -{"type":"meta","next_id":{"DSC":24,"AGD":22,"DEC":9,"PLN":8,"LSN":29,"CLSN":1}} +{"type":"meta","next_id":{"DSC":25,"AGD":25,"DEC":13,"PLN":11,"LSN":29,"CLSN":1}} {"type":"discussion","id":"DSC-0023","status":"done","ticket":"perf-full-migration-to-atomic-telemetry","title":"Agenda - [PERF] Full Migration to Atomic Telemetry","created_at":"2026-04-10","updated_at":"2026-04-10","tags":["perf","runtime","telemetry"],"agendas":[{"id":"AGD-0021","file":"workflow/agendas/AGD-0021-full-migration-to-atomic-telemetry.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}],"decisions":[{"id":"DEC-0008","file":"workflow/decisions/DEC-0008-full-migration-to-atomic-telemetry.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10"}],"plans":[{"id":"PLN-0007","file":"workflow/plans/PLN-0007-full-migration-to-atomic-telemetry.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}],"lessons":[{"id":"LSN-0028","file":"lessons/DSC-0023-perf-full-migration-to-atomic-telemetry/LSN-0028-converging-to-single-atomic-telemetry-source.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}]} {"type":"discussion","id":"DSC-0020","status":"done","ticket":"jenkins-gitea-integration","title":"Jenkins Gitea Integration and Relocation","created_at":"2026-04-07","updated_at":"2026-04-07","tags":["ci","jenkins","gitea"],"agendas":[{"id":"AGD-0018","file":"workflow/agendas/AGD-0018-jenkins-gitea-integration-and-relocation.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}],"decisions":[{"id":"DEC-0003","file":"workflow/decisions/DEC-0003-jenkins-gitea-strategy.md","status":"accepted","created_at":"2026-04-07","updated_at":"2026-04-07"}],"plans":[{"id":"PLN-0003","file":"workflow/plans/PLN-0003-jenkins-gitea-execution.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}],"lessons":[{"id":"LSN-0021","file":"lessons/DSC-0020-jenkins-gitea-integration/LSN-0021-jenkins-gitea-integration.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}]} {"type":"discussion","id":"DSC-0021","status":"done","ticket":"asset-entry-codec-enum-with-metadata","title":"Asset Entry Codec Enum Contract","created_at":"2026-04-09","updated_at":"2026-04-09","tags":["asset","runtime","codec","metadata"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0024","file":"lessons/DSC-0021-asset-entry-codec-enum-contract/LSN-0024-string-on-the-wire-enum-in-runtime.md","status":"done","created_at":"2026-04-09","updated_at":"2026-04-09"}]} @@ -15,7 +15,8 @@ {"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":"open","ticket":"perf-runtime-introspection-syscalls","title":"Agenda - [PERF] Runtime Introspection Syscalls","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0011","file":"workflow/agendas/AGD-0011-perf-runtime-introspection-syscalls.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"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":[{"id":"AGD-0012","file":"workflow/agendas/AGD-0012-perf-host-debug-overlay-isolation.md","status":"done","created_at":"2026-03-27","updated_at":"2026-04-10"}],"decisions":[{"id":"DEC-0007","file":"workflow/decisions/DEC-0007-perf-host-debug-overlay-isolation.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10"}],"plans":[{"id":"PLN-0006","file":"workflow/plans/PLN-0006-perf-host-debug-overlay-isolation.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}],"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-0013","status":"open","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":[{"id":"AGD-0012","file":"workflow/agendas/AGD-0012-perf-host-debug-overlay-isolation.md","status":"done","created_at":"2026-03-27","updated_at":"2026-04-10"},{"id":"AGD-0022","file":"AGD-0022-host-overlay-tooling-boundary-revision.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10"},{"id":"AGD-0023","file":"AGD-0023-overlay-log-metric-last-frame.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10"}],"decisions":[{"id":"DEC-0007","file":"workflow/decisions/DEC-0007-perf-host-debug-overlay-isolation.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10"},{"id":"DEC-0009","file":"DEC-0009-host-overlay-tooling-boundary.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10","ref_agenda":"AGD-0022"},{"id":"DEC-0010","file":"DEC-0010-overlay-log-metric-last-frame.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10","ref_agenda":"AGD-0023"}],"plans":[{"id":"PLN-0006","file":"workflow/plans/PLN-0006-perf-host-debug-overlay-isolation.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"},{"id":"PLN-0008","file":"PLN-0008-host-overlay-native-composition-alignment.md","status":"in_progress","created_at":"2026-04-10","updated_at":"2026-04-10","ref_decisions":["DEC-0007","DEC-0008","DEC-0009","DEC-0010"]}],"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":"open","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":[{"id":"AGD-0024","file":"AGD-0024-generic-memory-bank-slot-contract.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10"}],"decisions":[{"id":"DEC-0012","file":"DEC-0012-asset-manager-bank-telemetry-slot-contract.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10","ref_agenda":"AGD-0024"}],"plans":[{"id":"PLN-0010","file":"PLN-0010-asset-manager-bank-telemetry-slot-contract.md","status":"open","created_at":"2026-04-10","updated_at":"2026-04-10","ref_decisions":["DEC-0012"]}],"lessons":[]} {"type":"discussion","id":"DSC-0014","status":"open","ticket":"perf-vm-allocation-and-copy-pressure","title":"Agenda - [PERF] VM Allocation and Copy Pressure","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0013","file":"workflow/agendas/AGD-0013-perf-vm-allocation-and-copy-pressure.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]} {"type":"discussion","id":"DSC-0015","status":"open","ticket":"perf-cartridge-boot-and-program-ownership","title":"Agenda - [PERF] Cartridge Boot and Program Ownership","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0014","file":"workflow/agendas/AGD-0014-perf-cartridge-boot-and-program-ownership.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]} {"type":"discussion","id":"DSC-0016","status":"done","ticket":"tilemap-empty-cell-vs-tile-id-zero","title":"Tilemap Empty Cell vs Tile ID Zero","created_at":"2026-03-27","updated_at":"2026-04-09","tags":[],"agendas":[{"id":"AGD-0015","file":"workflow/agendas/AGD-0015-tilemap-empty-cell-vs-tile-id-zero.md","status":"done","created_at":"2026-03-27","updated_at":"2026-04-09"}],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0022","file":"lessons/DSC-0016-tilemap-empty-cell-semantics/LSN-0022-tilemap-empty-cell-convergence.md","status":"done","created_at":"2026-04-09","updated_at":"2026-04-09"}]} diff --git a/discussion/workflow/agendas/AGD-0022-host-overlay-tooling-boundary-revision.md b/discussion/workflow/agendas/AGD-0022-host-overlay-tooling-boundary-revision.md new file mode 100644 index 00000000..3770ffb2 --- /dev/null +++ b/discussion/workflow/agendas/AGD-0022-host-overlay-tooling-boundary-revision.md @@ -0,0 +1,96 @@ +--- +id: AGD-0022 +ticket: perf-host-debug-overlay-isolation +title: Agenda - [PERF] Host Overlay Tooling Boundary Revision +status: accepted +created: 2026-04-10 +resolved: 2026-04-10 +decision: DEC-0009 +tags: [performance, host, gfx, overlay] +--- + +# Agenda - [PERF] Host Overlay Tooling Boundary Revision + +## Contexto + +`DEC-0007` fechou o movimento do overlay de debug para o `Host Desktop`, fora do runtime e fora do pipeline de `gfx` emulado. Durante a execução, a formulação acabou ficando forte demais em um ponto: passou a sugerir que `fill_rect` e `draw_text` deveriam deixar de existir no contrato atual, quando na verdade o usuário quer preservar essas ferramentas como parte válida do contrato de syscall atual. + +O ajuste pedido agora é mais específico: +- `fill_rect` e `draw_text` permanecem como parte do contrato atual da máquina e continuam disponíveis para uso legítimo do software emulado; +- o overlay técnico de host não pode depender desse caminho; +- o overlay deve possuir ferramentas próprias para compor o que for necessário no host, fora do `hardware.gfx`. + +## Problema + +Sem registrar essa revisão, a execução fica ambígua em dois pontos: +- podemos remover ou enfraquecer APIs gráficas que não deveriam ser tocadas; +- podemos continuar interpretando o overlay host como “qualquer desenho fora do runtime”, mesmo se ele ainda reutilizar primitives do hardware emulado. + +O resultado é ruído entre contrato da máquina, contrato do host, e ferramentas de inspeção. + +## Pontos Criticos + +- **Fato:** O overlay continua sendo exclusivo do host desktop. +- **Fato:** `fill_rect` e `draw_text` fazem parte do contrato atual de syscall e não devem ser removidos por causa do overlay. +- **Risco:** Se o host overlay continuar apoiado em `hardware.gfx`, ele continua contaminando o framebuffer emulado. +- **Risco:** Se criarmos “tools próprias” sem delimitar bem a fronteira, podemos introduzir um mini-subsistema paralelo sem contrato claro. +- **Tradeoff:** Preservar o contrato atual da máquina simplifica compatibilidade, mas exige um caminho de composição separado no host para não misturar domínio emulado com inspeção técnica. + +## Opcoes + +- **Opção A (Recomendada):** Preservar `fill_rect`/`draw_text` como contrato do hardware emulado e criar um conjunto de ferramentas nativas do host para o overlay. + - O overlay passa a usar renderer/layout/composição próprios do host. + - O runtime continua apenas expondo dados. + - O `hardware.gfx` fica reservado ao conteúdo da máquina emulada. + +- **Opção B:** Generalizar `fill_rect`/`draw_text` para servirem também ao host overlay. + - Reaproveita código existente. + - Mantém o acoplamento semântico entre overlay técnico e hardware emulado. + - Dificulta provar pureza do framebuffer. + +- **Opção C:** Criar novas syscalls específicas de overlay no runtime. + - Formaliza ferramentas novas, mas empurra responsabilidade de inspeção para dentro da máquina. + - Viola a direção de host-only já aceita e reabre debate maior do que o necessário. + +## Sugestao / Recomendacao + +Seguir com a **Opção A** e revisar a decisão operacional nestes termos: + +1. `fill_rect` e `draw_text` permanecem intactos como parte do contrato atual de syscall e do hardware emulado. +2. O overlay técnico do desktop host não pode chamar nem depender dessas primitives do `hardware.gfx`. +3. O host deve ter ferramentas próprias de overlay, com responsabilidades explícitas: + - montagem dos dados exibidos; + - layout do painel; + - rasterização/composição nativa; + - controle visual de transparência e tipografia. +4. O runtime continua responsável apenas por telemetria e sinais de inspeção, nunca por desenho do overlay. +5. A validação precisa provar dois limites: + - o contrato gráfico emulado continua disponível; + - o overlay técnico não escreve no framebuffer emulado. + +Para este ciclo, o overlay deve permanecer deliberadamente simples: +- painel semitransparente; +- texto para métricas e estados; +- barras básicas para uso relativo de orçamento/capacidade quando isso melhorar leitura; +- sem badges decorativos, widgets complexos, docking, janelas móveis, ou mini-framework de UI. + +Essa direção preserva o objetivo principal do ticket, que é a fronteira arquitetural correta, sem transformar o host overlay em um projeto próprio. + +## Perguntas em Aberto + +- A implementação deve ficar embutida em `runner.rs` inicialmente ou nascer como módulo dedicado (`overlay.rs`) desde o começo? +- A composição será feita diretamente no buffer final do `pixels` ou via camada/render pass separada dentro do host? +Nenhuma. Em 2026-04-10 foi acordado: +- módulo dedicado para o overlay; +- camada separada no host; +- sem blit do overlay no framebuffer emulado; +- o framebuffer emulado pode ser transportado para a surface/camada de overlay do host, nunca o contrário. + +## Criterio para Encerrar + +Esta agenda pode ser encerrada quando houver consenso explícito sobre: +- preservação de `fill_rect` e `draw_text` no contrato atual; +- proibição de uso dessas primitives pelo overlay técnico; +- conjunto mínimo de ferramentas próprias do overlay no host; +- ponto exato de composição no pipeline do desktop host. +*(Critérios atingidos em 2026-04-10)* diff --git a/discussion/workflow/agendas/AGD-0023-overlay-log-metric-last-frame.md b/discussion/workflow/agendas/AGD-0023-overlay-log-metric-last-frame.md new file mode 100644 index 00000000..bf5a358a --- /dev/null +++ b/discussion/workflow/agendas/AGD-0023-overlay-log-metric-last-frame.md @@ -0,0 +1,53 @@ +--- +id: AGD-0023 +ticket: perf-host-debug-overlay-isolation +title: Agenda - [PERF] Overlay Log Metric Must Show Last Frame +status: accepted +created: 2026-04-10 +resolved: 2026-04-10 +decision: DEC-0010 +tags: [performance, host, telemetry, logs] +--- + +# Agenda - [PERF] Overlay Log Metric Must Show Last Frame + +## Contexto + +Após mover o overlay para a camada de Host, o campo visual `LOGS` passou a expor um contador transitório compartilhado com o `LogService`. Esse contador é incrementado durante o frame e zerado no fechamento do frame lógico. Na prática, quando o Host lê a telemetria para desenhar o overlay, o valor visual tende a aparecer como `0`, mesmo em cenários com alta emissão de logs. + +## Problema + +O campo `LOGS` no overlay não está representando a informação útil para inspeção visual. Um valor transitório intra-frame é adequado para coleta interna, mas não para HUD técnico renderizado assíncronamente pelo Host. + +## Pontos Criticos + +- **Fato:** O Host lê a telemetria fora do momento exato em que o contador transitório atinge seu pico. +- **Fato:** A certificação precisa continuar vendo a pressão de logs do frame completo. +- **Risco:** Manter a semântica atual torna o campo visual enganoso e inutilizável. +- **Tradeoff:** Precisamos preservar o contador transitório para coleta interna, mas expor ao overlay apenas um valor persistido do último frame fechado. + +## Opcoes + +- **Opção A (Recomendada):** Persistir `logs from last completed frame` no `AtomicTelemetry` e fazer o overlay ler apenas esse valor. +- **Opção B:** Manter contador ao vivo e tentar amostrar em outro ponto do loop. +- **Opção C:** Mostrar ambos os valores no overlay. + +## Sugestao / Recomendacao + +Seguir com a **Opção A**: + +1. O contador transitório de logs continua existindo apenas para coleta interna durante o frame. +2. No fechamento do frame lógico, o runtime persiste em `AtomicTelemetry` o total de logs do frame recém-concluído. +3. O campo `LOGS` visível no overlay deve representar exclusivamente `logs from last completed frame`. +4. A certificação deve continuar operando sobre esse mesmo valor persistido do frame concluído. + +## Perguntas em Aberto + +- Nenhuma. Em 2026-04-10 foi decidido que o overlay deve mostrar apenas logs do último frame concluído. + +## Criterio para Encerrar + +- semântica visual de `LOGS` definida como `last completed frame`; +- certificação preservada; +- implementação alinhada no HAL, runtime, overlay e specs. +*(Critérios atingidos em 2026-04-10)* diff --git a/discussion/workflow/agendas/AGD-0024-generic-memory-bank-slot-contract.md b/discussion/workflow/agendas/AGD-0024-generic-memory-bank-slot-contract.md new file mode 100644 index 00000000..de6d764b --- /dev/null +++ b/discussion/workflow/agendas/AGD-0024-generic-memory-bank-slot-contract.md @@ -0,0 +1,147 @@ +--- +id: AGD-0024 +ticket: generic-memory-bank-slot-contract +title: Agenda - Generic Memory Bank Slot Contract +status: accepted +created: 2026-04-10 +resolved: 2026-04-10 +decision: DEC-0012 +tags: [runtime, asset, memory-bank, slots, host] +--- + +# Agenda - Generic Memory Bank Slot Contract + +## Contexto + +Hoje o runtime e o host expõem bancos de memória principalmente pela ótica de bytes totais/usados, enquanto a organização real do hardware já é fortemente orientada a slots. No estado atual: + +- `GFX` e `AUD` aparecem como casos especiais espalhados entre `AssetManager`, `MemoryBanks`, `AssetBridge`, telemetria e overlay; +- os pools concretos são específicos (`glyph_bank_pool`, `sound_bank_pool`); +- a visualização do overlay precisou reinterpretar um modelo em bytes para algo que, operacionalmente, é mais bem entendido como ocupação de slots. + +O pedido do usuário é explicitamente mudar essa ênfase: manter bytes como detalhe secundário quando necessário, mas estruturar o modelo principal como **memory banks by slots**, com possibilidade de contrato específico no domínio de `MemoryBanks` para gerir esses valores. + +Na continuação da discussão, o usuário endureceu a direção: + +- `BankPolicy` deve ser removido por completo; +- `BankStats` deve ser removido por completo; +- o contrato exposto deve trabalhar somente com ocupação por slots, por exemplo: + - `glyph_slots_occupied / glyph_slots.len()` + - `sound_slots_occupied / sound_slots.len()` + +## Problema + +O modelo atual mistura dois níveis de abstração: + +1. **Contrato de capacidade em bytes**, útil para certificação e budgets; +2. **Contrato operacional por slots**, que é o que o host e o programador realmente percebem ao lidar com banks. + +Isso gera acoplamento e duplicação: + +- o domínio conhece `GFX` e `AUD` como exceções, em vez de bancos genéricos com propriedades próprias; +- `BankStats` privilegia bytes e trata slots como apêndice; +- `MemoryBanks` não oferece um contrato consolidado para estatísticas e ocupação por slots; +- o overlay precisa montar sua própria leitura mais útil em cima de um modelo que não foi desenhado para isso. + +Com a direção nova do usuário, há um problema adicional: não basta rebaixar bytes a papel secundário no contrato exposto. As estruturas atuais que orbitam essa semântica (`BankPolicy` e `BankStats`) passam a ser vistas como parte do problema e não como base aceitável para a solução. + +## Pontos Criticos + +- **Fato:** O hardware já é organizado por slots para glyph e sound banks. +- **Fato:** O overlay quer mostrar ocupação por slots, não capacidade em bytes. +- **Fato:** O usuário quer que o contrato exposto use apenas `used_slots` e `total_slots`. +- **Fato:** `BankPolicy` e `BankStats` não devem permanecer como contrato nem como modelagem principal deste domínio. +- **Risco:** Remover essas estruturas sem separar claramente contrato exposto e necessidades internas pode quebrar telemetria e certificação existentes. +- **Risco:** Se bytes continuarem aparecendo no contrato público, a refatoração perde seu objetivo. +- **Tradeoff:** Um contrato mínimo de slots simplifica host/overlay e o domínio exposto, mas exige reposicionar qualquer necessidade residual baseada em bytes. + +## Opcoes + +- **Opção A (Recomendada):** Introduzir um contrato genérico de memory bank orientado exclusivamente a slots no contrato exposto. + - `MemoryBanks` passa a oferecer um contrato explícito para: + - quantidade de slots; + - slots ocupados; + - consulta de slots; + - enumeração genérica de banks. + - `GLYPH` e `SOUND` tornam-se instâncias desse modelo. + - `BankPolicy` e `BankStats` deixam de existir como superfícies do contrato. + +- **Opção B:** Manter `BankPolicy` e `BankStats` internamente, escondendo-os só no host. + - Menor custo imediato. + - Não atende a direção explícita da discussão atual. + +- **Opção C:** Preservar um contrato híbrido com slots e bytes lado a lado. + - Dá continuidade incremental. + - Mantém exatamente a ambiguidade que o usuário quer remover. + +## Sugestao / Recomendacao + +Seguir com a **Opção A**, com os seguintes princípios: + +1. O domínio de memory banks deve ser **slot-first**. +2. O contrato exposto deve usar somente `used_slots` e `total_slots`. +3. `BankPolicy` e `BankStats` devem ser removidos por completo. +4. `MemoryBanks` deve possuir um contrato específico e explícito para ocupação por slots. +5. O host overlay deve consumir esse modelo genérico sem saber detalhes especiais de `GLYPH` vs `SOUND`, além de rótulo e contagem. +6. A revisão deve evitar abstração vazia: o contrato genérico precisa mapear diretamente para `GlyphBank` e `SoundBank`. + +Nomenclatura canônica acordada para a camada genérica: + +- `GLYPH` +- `SOUND` + +`GFX` e `AUD` não devem ser usados como nomes canônicos do contrato genérico de banks, pois são apelidos de apresentação e não os nomes corretos do domínio. + +## Perguntas em Aberto + +- Nenhuma resposta final ainda sobre a forma exata do artefato, mas em 2026-04-10 o usuário fechou os seguintes direcionadores: + - a telemetria necessária deve ser gerenciada pelo `AssetManager`, adicionando os banks ao payload; + - a telemetria deve carregar somente o enum do tipo do bank, não uma abstração genérica adicional no contrato; + - o detalhamento operacional deve ocorrer por enum de slot; + - exemplos concretos do formato esperado ainda precisam ser avaliados antes de encerrar a agenda. + +### Respostas consolidadas desta rodada + +1. **Origem da telemetria:** não mover a genericidade principal para `MemoryBanks`; o `AssetManager` deve gerenciar e expor a telemetria necessária dos banks. +2. **Forma da telemetria:** a telemetria exposta deve carregar somente o enum do tipo do bank. +3. **Detalhamento dos slots:** a leitura operacional deve ser feita por enum de slot. +4. **Formato preferido:** seguir com um resumo por bank no formato do exemplo 1 (`bank_type`, `used_slots`, `total_slots`). +5. **Bytes fake:** bytes não devem continuar no contrato novo de telemetria dos banks. +6. **Certificação:** as regras `max_gfx_bytes` e `max_audio_bytes` devem sair e ser substituídas por limites de slots para `GLYPH` e `SOUND`. +7. **Origem do contrato visível:** o `AssetManager` deve expor diretamente esse resumo por bank. + +Exemplo alvo discutido: + +```rust +pub struct BankTelemetry { + pub bank_type: BankType, + pub used_slots: usize, + pub total_slots: usize, +} +``` + +Origem esperada: + +```rust +impl AssetManager { + pub fn bank_telemetry(&self) -> Vec { ... } +} +``` + +Impacto já identificado para certificação: + +- remover `max_gfx_bytes` +- remover `max_audio_bytes` +- substituir por algo como: + - `max_glyph_slots_used` + - `max_sound_slots_used` + +## Criterio para Encerrar + +Esta agenda pode ser encerrada quando houver direção fechada sobre: + +- contrato genérico por slots; +- remoção completa de `BankPolicy` e `BankStats`; +- ponto de residência da abstração (`MemoryBanks`, HAL, ou ambos); +- impacto esperado em overlay, telemetria e `AssetManager`. +*(Critérios atingidos em 2026-04-10)* diff --git a/discussion/workflow/decisions/DEC-0009-host-overlay-tooling-boundary.md b/discussion/workflow/decisions/DEC-0009-host-overlay-tooling-boundary.md new file mode 100644 index 00000000..d2608d99 --- /dev/null +++ b/discussion/workflow/decisions/DEC-0009-host-overlay-tooling-boundary.md @@ -0,0 +1,61 @@ +--- +id: DEC-0009 +ticket: perf-host-debug-overlay-isolation +title: Decision - [PERF] Host Overlay Tooling Boundary +status: accepted +created: 2026-04-10 +updated: 2026-04-10 +agenda: AGD-0022 +tags: [performance, host, gfx, overlay] +--- + +# Decision - [PERF] Host Overlay Tooling Boundary + +## Status +**Accepted** + +## Contexto +`DEC-0007` já determinou que o overlay técnico do desktop deve existir no Host e fora do pipeline de `gfx` emulado. Durante a execução, surgiu uma ambiguidade adicional: a implementação corretiva poderia ser interpretada como remoção ou desvalorização de `fill_rect` e `draw_text`, quando essas primitives continuam fazendo parte legítima do contrato gráfico atual da máquina emulada. + +Também ficou explícito que “estar no host” não é suficiente se o overlay ainda desenhar usando `hardware.gfx`. O limite correto é mais estrito: o overlay deve possuir ferramentas próprias e compor em camada separada no host. + +## Decisao +Fica decidido que o contrato do overlay técnico do desktop host será refinado da seguinte forma: + +1. **Preservação do Contrato Emulado:** `fill_rect` e `draw_text` MUST remain part of the current syscall and emulated hardware contract. They MUST NOT be removed, weakened, or repurposed because of host overlay work. +2. **Separação de Ferramentas:** O overlay técnico do desktop host MUST use dedicated host-side tooling for panel composition, text rendering, simple bars, and alert presentation. +3. **Proibição de Dependência do Hardware Emulado:** O overlay MUST NOT call, depend on, or route through `hardware.gfx`, `fill_rect`, `draw_text`, or any equivalent emulated-framebuffer primitive. +4. **Camada Separada no Host:** O overlay MUST be rendered in a separate host layer/surface after the emulated frame is produced. The emulated framebuffer MAY be transported into the host presentation surface, but overlay pixels MUST NOT be written back into the emulated framebuffer. +5. **Módulo Dedicado:** A dedicated host module SHALL own overlay rendering behavior. The implementation MUST NOT remain embedded as ad hoc overlay drawing logic inside `runner.rs`. +6. **Escopo Visual Mínimo:** Para este ciclo, o overlay SHOULD remain intentionally simple: semitransparent panel, text, and simple bars where useful. It MUST NOT introduce a general UI framework, docking model, or movable window system. +7. **Responsabilidade do Runtime:** O runtime MUST remain responsible only for telemetry exposure and inspection state gating. It MUST NOT own overlay drawing behavior, layout policy, or visual composition. + +## Rationale +- **Clareza de Fronteira:** Preservar o contrato da máquina e mover apenas o overlay evita misturar API emulada com inspeção técnica de host. +- **Pureza do Framebuffer:** Uma camada separada elimina a contaminação visual do framebuffer emulado e torna a validação objetiva. +- **Simplicidade Controlada:** Texto e barras simples resolvem legibilidade sem transformar o host overlay em um projeto paralelo de UI. +- **Manutenibilidade:** Um módulo dedicado reduz o risco de regressão arquitetural em `runner.rs`. + +## Invariantes / Contrato +- `fill_rect` e `draw_text` continuam válidos para o domínio emulado. +- O overlay técnico do host não escreve no framebuffer emulado. +- O overlay técnico do host não depende de primitives do hardware emulado. +- O módulo de overlay é de responsabilidade exclusiva do host desktop. +- O ponto de composição do overlay ocorre depois que o frame emulado já está pronto para apresentação no host. + +## Impactos +- **Host (Desktop Winit):** Exige um módulo próprio de overlay e uma estratégia de composição em camada separada. +- **Runtime API:** Nenhuma mudança conceitual adicional além de continuar expondo telemetria passiva e gating de inspeção. +- **Specs:** As especificações de debug e portabilidade devem distinguir explicitamente o contrato emulado das ferramentas de overlay do host. +- **Plano de Execução:** `PLN-0008` deve ser atualizado para refletir módulo dedicado e camada separada no host. + +## Referencias +- `AGD-0022`: [PERF] Host Overlay Tooling Boundary Revision. +- `DEC-0007`: [PERF] Host Debug Overlay Isolation. +- `DEC-0008`: Full Migration to Atomic Telemetry. + +## Propagacao Necessaria +1. Atualizar `PLN-0008` para refletir o módulo dedicado e a camada separada no host. +2. Implementar o módulo de overlay no `prometeu-host-desktop-winit`. +3. Remover o uso de `hardware.gfx` pelo overlay técnico. +4. Atualizar as specs para preservar o contrato gráfico emulado e separar a composição do overlay host. diff --git a/discussion/workflow/decisions/DEC-0010-overlay-log-metric-last-frame.md b/discussion/workflow/decisions/DEC-0010-overlay-log-metric-last-frame.md new file mode 100644 index 00000000..ded4b613 --- /dev/null +++ b/discussion/workflow/decisions/DEC-0010-overlay-log-metric-last-frame.md @@ -0,0 +1,50 @@ +--- +id: DEC-0010 +ticket: perf-host-debug-overlay-isolation +title: Decision - [PERF] Overlay Log Metric Uses Last Completed Frame +status: accepted +created: 2026-04-10 +updated: 2026-04-10 +agenda: AGD-0023 +tags: [performance, host, telemetry, logs] +--- + +# Decision - [PERF] Overlay Log Metric Uses Last Completed Frame + +## Status +**Accepted** + +## Contexto +O campo `LOGS` do overlay técnico do desktop host estava lendo um contador transitório incrementado durante o frame e zerado no fechamento do frame lógico. Como o Host compõe o overlay de forma assíncrona em relação ao loop interno da máquina, esse valor visual tende a aparecer como `0` mesmo sob stress com alta emissão de logs. + +## Decisao +1. O valor visual `LOGS` exposto ao overlay MUST represent the number of logs emitted during the last completed logical frame. +2. O contador transitório intra-frame MAY continue to exist for internal collection and frame-end aggregation, but it MUST NOT be exposed diretamente como métrica visual do overlay. +3. O `AtomicTelemetry::snapshot()` MUST expose the persisted last-frame value for `logs_count`. +4. A certificação MUST evaluate log pressure against the persisted last completed frame value, not against a host-timing-dependent transient value. + +## Rationale +- O overlay precisa de um valor estável e observável pelo Host. +- O frame fechado é a unidade correta para comparação com outras métricas como ciclos e syscalls. +- A mesma semântica serve tanto para overlay quanto para certificação, evitando ambiguidade. + +## Invariantes / Contrato +- `logs_count` em `TelemetryFrame` significa `logs from last completed logical frame`. +- O reset do contador transitório não pode apagar o valor persistido exposto ao Host. +- O overlay não deve depender de timing fino entre renderização do Host e fechamento do frame lógico. + +## Impactos +- **HAL:** `AtomicTelemetry` precisa persistir o valor de logs do frame concluído. +- **Runtime:** no fechamento do frame, o valor de logs do frame deve ser copiado antes do reset do contador transitório. +- **Host overlay:** nenhuma mudança conceitual adicional além de passar a receber um valor útil e estável. +- **Specs:** capítulo de debug deve deixar explícita a semântica de `logs_count`. + +## Referencias +- `AGD-0023`: [PERF] Overlay Log Metric Must Show Last Frame. +- `DEC-0008`: Full Migration to Atomic Telemetry. +- `DEC-0009`: Host Overlay Tooling Boundary. + +## Propagacao Necessaria +1. Atualizar `PLN-0008` para incluir a semântica last-frame de `LOGS`. +2. Ajustar HAL e runtime para persistir `logs_count` ao final do frame. +3. Atualizar a especificação de debug. diff --git a/discussion/workflow/decisions/DEC-0012-asset-manager-bank-telemetry-slot-contract.md b/discussion/workflow/decisions/DEC-0012-asset-manager-bank-telemetry-slot-contract.md new file mode 100644 index 00000000..737ef993 --- /dev/null +++ b/discussion/workflow/decisions/DEC-0012-asset-manager-bank-telemetry-slot-contract.md @@ -0,0 +1,78 @@ +--- +id: DEC-0012 +ticket: generic-memory-bank-slot-contract +title: Decision - Asset Manager Bank Telemetry Slot Contract +status: accepted +created: 2026-04-10 +updated: 2026-04-10 +agenda: AGD-0024 +tags: [runtime, asset, memory-bank, slots, host, telemetry] +--- + +# Decision - Asset Manager Bank Telemetry Slot Contract + +## Status +**Accepted** + +## Contexto +O modelo atual de banks ainda carrega forte semântica orientada a bytes e estruturas antigas como `BankPolicy` e `BankStats`, enquanto o consumo real desejado pelo host e pela inspeção técnica é orientado a slots. Além disso, os bytes historicamente usados nesse caminho foram considerados inadequados para sustentar o novo contrato de telemetria de banks. + +Durante a discussão, ficou explícito que: + +- o resumo visível de banks deve ser exposto diretamente pelo `AssetManager`; +- a telemetria de banks deve usar somente o enum do tipo do bank; +- a semântica operacional deve ser `used_slots / total_slots`; +- `GFX` e `AUD` não são nomes canônicos do domínio; os nomes corretos são `GLYPH` e `SOUND`; +- a certificação de banks deve deixar de usar limites em bytes e passar a usar limites por slots. + +## Decisao +1. O contrato visível de telemetria de banks MUST be exposed by `AssetManager`. +2. O resumo de bank MUST use the following structure shape: + +```rust +pub struct BankTelemetry { + pub bank_type: BankType, + pub used_slots: usize, + pub total_slots: usize, +} +``` + +3. O `bank_type` MUST use canonical domain names. For the current banks, the canonical names are `GLYPH` and `SOUND`. +4. O contrato de telemetria de banks MUST be slot-first and MUST NOT depend on byte counts. +5. `BankPolicy` e `BankStats` MUST be removed completely from the bank telemetry contract path. +6. O host overlay MUST consume `AssetManager` bank telemetry instead of hardcoded bank-specific logic. +7. As regras de certificação `max_gfx_bytes` e `max_audio_bytes` MUST be removed. +8. A certificação de banks MUST migrate to slot-based limits, using canonical bank-specific slot limits such as: + - `max_glyph_slots_used` + - `max_sound_slots_used` +9. Any remaining byte-based accounting MAY survive only as internal implementation detail if strictly necessary, but it MUST NOT remain part of the exposed bank telemetry contract. + +## Rationale +- Slots são a unidade operacional correta para entender ocupação de banks. +- O `AssetManager` já é o lugar que conhece carregamento, commit e ocupação prática dos banks. +- Remover bytes do contrato elimina uma fonte de telemetria considerada enganosa. +- `GLYPH` e `SOUND` preservam a linguagem correta do domínio e evitam apelidos frágeis na interface. +- Certificação por slots mantém coerência entre contrato exposto, overlay e limites técnicos. + +## Invariantes / Contrato +- A telemetria de banks exposta ao restante do sistema sai do `AssetManager`. +- Cada entrada de bank telemetria informa somente tipo do bank, slots usados e slots totais. +- O contrato canônico usa `GLYPH` e `SOUND`. +- O overlay não depende de bytes para mostrar ocupação de banks. +- A certificação de banks não depende de bytes. + +## Impactos +- **AssetManager:** precisa expor `Vec` ou equivalente direto. +- **HAL / Bridges:** precisam alinhar interfaces consumidoras ao contrato slot-first. +- **Overlay:** deve iterar a telemetria de banks do `AssetManager`. +- **Certifier:** precisa trocar limites de bytes por limites de slots para banks. +- **Legado:** `BankPolicy`, `BankStats`, e caminhos ancorados em `max_gfx_bytes` / `max_audio_bytes` precisam ser removidos ou recolocados fora do contrato exposto. + +## Referencias +- `AGD-0024`: Generic Memory Bank Slot Contract. +- `DEC-0010`: Overlay Log Metric Uses Last Completed Frame. + +## Propagacao Necessaria +1. Criar plano de execução para refatorar `AssetManager`, HAL, overlay e certificação. +2. Remover o caminho de telemetria de banks baseado em bytes. +3. Migrar limites de certificação de banks para slots. diff --git a/discussion/workflow/plans/PLN-0008-host-overlay-native-composition-alignment.md b/discussion/workflow/plans/PLN-0008-host-overlay-native-composition-alignment.md new file mode 100644 index 00000000..b4a404f6 --- /dev/null +++ b/discussion/workflow/plans/PLN-0008-host-overlay-native-composition-alignment.md @@ -0,0 +1,144 @@ +--- +id: PLN-0008 +ticket: perf-host-debug-overlay-isolation +title: PR/Plan - Host Overlay Native Composition Alignment +status: in_progress +created: 2026-04-10 +completed: +tags: [performance, host, gfx, telemetry] +--- + +## Objective + +Bring the desktop debug overlay implementation back into compliance with `DEC-0007`, `DEC-0009`, and `DEC-0010` by keeping the overlay in the host layer while rendering it in a dedicated host module and separate host layer, outside the emulated hardware framebuffer and outside `hardware.gfx`. + +## Background + +`DEC-0007` requires the debug overlay to be a host-only layer composed after the emulated frame is complete. `DEC-0009` further clarifies that `fill_rect` and `draw_text` remain part of the emulated machine contract, but the host overlay must not depend on them and must live in a dedicated host module and separate host layer. `DEC-0010` fixes the semantics of the visible `LOGS` metric so the Host overlay reads the last completed frame value instead of a transient intra-frame counter. + +The current `HostRunner` still calls `hardware.gfx.fill_rect()` and `hardware.gfx.draw_text()` for the overlay, which means the HUD is still being injected into the emulated graphics path even though its data source already comes from `AtomicTelemetry`. + +`PLN-0007` completed the telemetry migration, so the remaining gap is no longer data access. The remaining gap is presentation architecture in the desktop host. + +## Scope + +### Included +- Remove all overlay rendering calls that write through `hardware.gfx`. +- Introduce a desktop-host-native overlay composition path in `prometeu-host-desktop-winit`. +- Implement the overlay in a dedicated host module instead of ad hoc logic inside `runner.rs`. +- Keep overlay visibility and lifecycle fully controlled by the desktop host. +- Continue reading inspection data through `AtomicTelemetry::snapshot()` and host-side log access. +- Expose `LOGS` to the overlay as the last completed logical frame value. +- Update runtime specifications so they explicitly state that the overlay is host-native and not part of the emulated machine framebuffer. +- Add verification that enabling the overlay does not mutate emulated pixels. + +### Excluded +- Adding overlays to non-desktop hosts. +- Reopening the telemetry model or changing `TelemetryFrame` semantics. +- Reworking unrelated presentation, frame pacing, debugger transport, or certification behavior. + +## Execution Steps + +### Step 1 - Extract overlay responsibilities into a dedicated host module + +**What:** +Move overlay formatting, layout, and drawing responsibilities out of `runner.rs` and into a dedicated host module. + +**How:** +Create a dedicated module such as `overlay.rs` that owns host overlay data preparation, panel layout, simple bars, and text rendering. `runner.rs` should orchestrate visibility and presentation only. The new module must never call emulated graphics primitives. + +**File(s):** +- `crates/host/prometeu-host-desktop-winit/src/runner.rs` +- `crates/host/prometeu-host-desktop-winit/src/stats.rs` +- `crates/host/prometeu-host-desktop-winit/src/overlay.rs` + +### Step 2 - Compose the overlay in a separate host layer + +**What:** +Ensure the overlay is rendered in a separate host layer/surface and never blitted into the emulated framebuffer. + +**How:** +Preserve the `F1` toggle in `HostRunner`, keep telemetry reads on the host side, and present the overlay after the emulated frame is already ready for display. The implementation may transport the emulated framebuffer into the host presentation surface, but the overlay must remain a separate host composition step and must not write back into `hardware.gfx`. + +**File(s):** +- `crates/host/prometeu-host-desktop-winit/src/runner.rs` +- `crates/host/prometeu-host-desktop-winit/src/overlay.rs` + +### Step 3 - Add regression coverage for framebuffer purity + +**What:** +Add tests or deterministic checks that prove the overlay no longer modifies emulated graphics output. + +**How:** +Cover the overlay toggle and composition boundary with focused host tests where practical. At minimum, add a test seam or helper that lets the host render path be exercised without requiring overlay drawing through `hardware.gfx`. If a full automated framebuffer assertion is not practical yet, add a narrow unit test around the new composition entry point plus manual verification instructions that compare raw emulated output with overlay enabled and disabled. + +**File(s):** +- `crates/host/prometeu-host-desktop-winit/src/runner.rs` +- `crates/host/prometeu-host-desktop-winit/src/overlay.rs` +- `crates/host/prometeu-host-desktop-winit/src/tests.rs` or existing test module location + +### Step 4 - Align log metric semantics with frame-end snapshots + +**What:** +Ensure the visible `LOGS` metric represents the last completed frame instead of a transient in-flight counter. + +**How:** +Persist the completed-frame log count into `AtomicTelemetry` before resetting the transient `LogService` counter. Keep certification evaluation and host overlay consumption aligned on that persisted frame-end value. + +**File(s):** +- `crates/console/prometeu-hal/src/log/log_service.rs` +- `crates/console/prometeu-hal/src/telemetry.rs` +- `crates/console/prometeu-system/src/virtual_machine_runtime/tick.rs` +- `crates/host/prometeu-host-desktop-winit/src/overlay.rs` + +### Step 5 - Update normative documentation + +**What:** +Align specs and workflow artifacts with the corrected execution path. + +**How:** +Update the runtime debug and portability specifications to explicitly say the overlay is desktop-host-native, post-upscaling, and outside the emulated framebuffer. Keep the discussion workflow aligned by referencing this corrective plan during execution and closure. + +**File(s):** +- `docs/specs/runtime/10-debug-inspection-and-profiling.md` +- `docs/specs/runtime/11-portability-and-cross-platform-execution.md` + +## Test Requirements + +### Unit Tests +- Host overlay toggle logic remains host-owned and does not require `hardware.gfx` text drawing. +- New host-native overlay composition entry point can format and present telemetry without mutating the emulated framebuffer abstraction. +- Simple bars and text layout are owned by the dedicated host overlay module. + +### Integration Tests +- `cargo check` for `prometeu-host-desktop-winit`, `prometeu-system`, and any touched supporting crate. +- If automated host rendering tests are feasible, verify an emulated frame buffer dump is identical with overlay off and overlay on before the host-only composition stage. + +### Manual Verification +- Run the desktop host, toggle `F1`, and confirm the overlay appears and disappears correctly. +- Capture or inspect the emulated framebuffer path and confirm no HUD pixels are written into machine output. +- Confirm telemetry values still update through `AtomicTelemetry`. + +## Acceptance Criteria + +- [ ] No desktop overlay code writes through `hardware.gfx.fill_rect`, `hardware.gfx.draw_text`, or equivalent emulated-machine drawing APIs. +- [ ] `fill_rect` and `draw_text` remain intact as part of the emulated graphics contract and are not repurposed for host overlay rendering. +- [ ] The desktop overlay remains controlled by `HostRunner` and uses host-side telemetry snapshots. +- [ ] The visible overlay is composed in a separate host layer after the emulated frame and is not burned into the emulated framebuffer. +- [ ] Runtime telemetry remains gated by inspection state and continues to function for overlay consumption. +- [ ] The visible `LOGS` metric represents the last completed logical frame. +- [ ] Specs describe the overlay as host-native and outside the emulated hardware contract. + +## Dependencies + +- `DEC-0007` accepted and unchanged. +- `DEC-0008` accepted and unchanged. +- `DEC-0009` accepted and unchanged. +- `DEC-0010` accepted and unchanged. +- Existing `AtomicTelemetry` host snapshot path remains available. + +## Risks + +- Desktop-native text and alpha composition may require extra host rendering plumbing that is more complex than the previous `hardware.gfx` path. +- Without an explicit test seam, it is easy to accidentally reintroduce overlay writes into the emulated graphics path during future host refactors. +- Host composition cost may rise slightly, but that cost is acceptable as long as it remains outside runtime cycle accounting. diff --git a/discussion/workflow/plans/PLN-0010-asset-manager-bank-telemetry-slot-contract.md b/discussion/workflow/plans/PLN-0010-asset-manager-bank-telemetry-slot-contract.md new file mode 100644 index 00000000..c3e23e9d --- /dev/null +++ b/discussion/workflow/plans/PLN-0010-asset-manager-bank-telemetry-slot-contract.md @@ -0,0 +1,153 @@ +--- +id: PLN-0010 +ticket: generic-memory-bank-slot-contract +title: PR/Plan - Asset Manager Bank Telemetry Slot Contract +status: open +created: 2026-04-10 +completed: +tags: [runtime, asset, memory-bank, slots, host, telemetry] +--- + +## Objective + +Implement the `DEC-0012` bank telemetry contract so `AssetManager` exposes slot-based bank summaries using canonical bank names `GLYPH` and `SOUND`, while host overlay and certification consume slot-based limits instead of byte-based bank metrics. + +## Background + +`DEC-0012` locked the bank telemetry contract around a simple slot-first structure: + +```rust +pub struct BankTelemetry { + pub bank_type: BankType, + pub used_slots: usize, + pub total_slots: usize, +} +``` + +This replaces the old bank telemetry path that relied on byte-oriented structures and presentation aliases. The implementation must remove `BankPolicy` and `BankStats` from the exposed bank telemetry contract path, move visible bank summaries to `AssetManager`, and migrate certification rules from byte-based thresholds to slot-based thresholds. + +## Scope + +### Included +- Add a visible slot-based `BankTelemetry` contract owned by `AssetManager`. +- Remove `BankStats` from the public bank telemetry contract path. +- Remove `BankPolicy` from the bank telemetry contract path and refactor internal code accordingly. +- Update bridge/consumer APIs so bank summaries come from `AssetManager` slot telemetry. +- Migrate certification from `max_gfx_bytes` / `max_audio_bytes` to slot-based limits for `GLYPH` and `SOUND`. +- Update host overlay to iterate `AssetManager` bank telemetry using canonical `GLYPH` and `SOUND` labels. +- Update specs/docs affected by the contract change. + +### Excluded +- Adding new bank kinds beyond the currently supported `GLYPH` and `SOUND`. +- Reworking unrelated asset load/commit semantics. +- Redesigning generic slot-detail payloads beyond the accepted summary shape unless implementation requires a narrowly scoped helper. + +## Execution Steps + +### Step 1 - Introduce `BankTelemetry` in the exposed asset contract + +**What:** +Define the new slot-based summary type and make it the canonical visible representation of bank telemetry. + +**How:** +Add `BankTelemetry` to the HAL asset domain and ensure canonical naming uses `GLYPH` and `SOUND`. Remove or deprecate exposed `BankStats` paths that conflict with the new contract. + +**File(s):** +- `crates/console/prometeu-hal/src/asset.rs` +- `crates/console/prometeu-hal/src/asset_bridge.rs` + +### Step 2 - Make `AssetManager` expose bank telemetry directly + +**What:** +Implement `AssetManager` support for returning `Vec` from live slot occupancy. + +**How:** +Compute `used_slots` from current slot occupancy and `total_slots` from the fixed slot arrays for `GLYPH` and `SOUND`. Keep implementation simple and derive the summary directly from the installed slots. Refactor any existing public bank-info consumers away from byte-oriented APIs. + +**File(s):** +- `crates/console/prometeu-drivers/src/asset.rs` +- `crates/console/prometeu-drivers/src/memory_banks.rs` + +### Step 3 - Remove byte-based bank certification rules + +**What:** +Replace byte-based certification limits for banks with slot-based limits. + +**How:** +Remove `max_gfx_bytes` and `max_audio_bytes` from certification config and certifier checks. Introduce slot-based limits such as `max_glyph_slots_used` and `max_sound_slots_used`, and evaluate them against `BankTelemetry` summaries or equivalent slot-based data available at frame-end. + +**File(s):** +- `crates/console/prometeu-hal/src/telemetry.rs` +- `crates/console/prometeu-system/src/virtual_machine_runtime/tick.rs` + +### Step 4 - Update host overlay to consume slot telemetry + +**What:** +Replace hardcoded bank presentation with iteration over `AssetManager` bank telemetry. + +**How:** +Render one bar per bank using `bank_type`, `used_slots`, and `total_slots`. Use canonical labels `GLYPH` and `SOUND`, and remove any byte-based bank presentation from the overlay. + +**File(s):** +- `crates/host/prometeu-host-desktop-winit/src/overlay.rs` +- Any host-side adapter/helper used to access asset telemetry + +### Step 5 - Remove obsolete structures from the contract path + +**What:** +Eliminate `BankPolicy` and `BankStats` from the exposed bank telemetry path. + +**How:** +Refactor code so the new slot-based path is the only public contract. If internal implementation helpers must remain temporarily during migration, keep them private and make sure no consumer depends on them as contract. + +**File(s):** +- `crates/console/prometeu-drivers/src/asset.rs` +- `crates/console/prometeu-hal/src/asset.rs` +- Related callers revealed during implementation + +### Step 6 - Update normative documentation + +**What:** +Align specifications with the new slot-based bank telemetry contract. + +**How:** +Update relevant runtime documentation to describe bank occupancy in terms of `GLYPH` and `SOUND` slot usage, and remove outdated references to byte-based bank telemetry where they were treated as visible contract. + +**File(s):** +- `docs/specs/runtime/10-debug-inspection-and-profiling.md` +- `docs/specs/runtime/15-asset-management.md` +- Any additional touched runtime spec chapter + +## Test Requirements + +### Unit Tests +- `AssetManager` reports correct `BankTelemetry` for empty and occupied `GLYPH` / `SOUND` slots. +- Certification slot limits trigger correctly for `GLYPH` and `SOUND`. +- Overlay rendering accepts generic bank telemetry iteration. + +### Integration Tests +- `cargo check` for affected HAL, drivers, system, and host crates. +- Focused tests for asset bank telemetry and certification migration. + +### Manual Verification +- Run the desktop host and confirm banks appear as `GLYPH` and `SOUND`. +- Confirm bank bars track occupied slots instead of bytes. +- Confirm certification behavior still reports bank pressure using slot limits. + +## Acceptance Criteria + +- [ ] `AssetManager` exposes a visible `BankTelemetry` summary with `bank_type`, `used_slots`, and `total_slots`. +- [ ] Canonical bank names in the new contract are `GLYPH` and `SOUND`. +- [ ] Byte-based bank certification limits are removed and replaced by slot-based limits. +- [ ] Host overlay renders bank occupancy from generic slot telemetry rather than byte-oriented hardcoded bank logic. +- [ ] `BankPolicy` and `BankStats` are no longer part of the exposed bank telemetry contract path. + +## Dependencies + +- `DEC-0012` accepted and unchanged. + +## Risks + +- Removing byte-based bank paths may have wider ripple effects than expected if old APIs are reused outside the overlay. +- The line between “removed from contract path” and “removed completely from implementation” must stay explicit during refactor to avoid accidental partial migration. +- Certification migration must stay synchronized with telemetry migration or the host and certifier will diverge semantically. diff --git a/docs/specs/runtime/10-debug-inspection-and-profiling.md b/docs/specs/runtime/10-debug-inspection-and-profiling.md index 9137364a..c5160a4b 100644 --- a/docs/specs/runtime/10-debug-inspection-and-profiling.md +++ b/docs/specs/runtime/10-debug-inspection-and-profiling.md @@ -238,6 +238,10 @@ The visual Debug Overlay (HUD) for technical inspection is not part of the emula ### 10.1 Responsibilities 1. **Runtime:** Only exposes telemetry data via the machine diagnostics surface. It does not perform HUD rendering or string formatting. +2. **Emulated graphics contract:** Machine graphics primitives such as `fill_rect` and `draw_text` remain valid parts of the emulated graphics/syscall contract. They are not host overlay APIs. +3. **Host overlay module:** The Desktop Host owns a dedicated overlay module that performs host-side text, panel, and simple bar composition. +4. **Composition boundary:** The overlay is composed on the Host presentation surface after the emulated frame is ready. Overlay pixels must not be written back into the emulated framebuffer. +5. **Host control:** Overlay visibility and presentation policy remain under Host control. 2. **Host (Desktop):** Responsible for collecting telemetry from the runtime and rendering the HUD as a native, transparent layer. ### 10.2 Principles @@ -253,6 +257,7 @@ To ensure zero-impact synchronization between the VM and the Host Debug Overlay, 1. **Atomic Storage:** Metrics such as cycles, syscalls, and memory usage are stored in a dedicated `AtomicTelemetry` structure using thread-safe atomic types (`AtomicU64`, `AtomicU32`, etc.). 2. **Lockless Access:** The Host (Desktop) reads these metrics asynchronously and without locks by taking a `snapshot()` of the atomic state. 3. **Single Source of Truth:** This model is the exclusive source of truth for both real-time inspection and frame-end certification, replacing legacy per-frame buffered fields. +4. **Frame-Closed Log Metric:** `logs_count` in the snapshot represents the number of logs emitted in the last completed logical frame, not a transient in-flight counter. ## 11 Integration with CAP and Certification diff --git a/docs/specs/runtime/11-portability-and-cross-platform-execution.md b/docs/specs/runtime/11-portability-and-cross-platform-execution.md index ccbfae77..e745eef9 100644 --- a/docs/specs/runtime/11-portability-and-cross-platform-execution.md +++ b/docs/specs/runtime/11-portability-and-cross-platform-execution.md @@ -125,6 +125,7 @@ The platform layer: - only displays the framebuffer - does not reinterpret graphics commands - **may overlay technical HUDs without modifying the logical framebuffer** +- may transport the logical framebuffer into a host presentation surface where a host-only overlay layer is composed ## 9 Debug and Inspection Isolation @@ -133,6 +134,7 @@ To preserve portability and certification purity, technical inspection tools (li - **Host-exclusive:** These tools are only implemented where they are relevant (e.g., Desktop) and do not exist in the logical machine. - **Non-intrusive:** They must not consume machine cycles or alter memory state. - **Consistent Results:** A cartridge will produce the same logical results and certification metrics regardless of the Host's inspection capabilities. +- **Contract Boundary:** Emulated graphics primitives such as `fill_rect` and `draw_text` remain part of the machine contract, but Host overlays must not depend on them. ### 9.1 Atomic Telemetry Interface From 4cdfaefda7f02b40569187e2f60bc999c4d7d969 Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Fri, 10 Apr 2026 19:16:44 +0100 Subject: [PATCH 08/10] [PERF] Host Debug Overlay Isolation --- crates/console/prometeu-drivers/src/asset.rs | 91 ++----- crates/console/prometeu-hal/src/asset.rs | 11 +- .../console/prometeu-hal/src/asset_bridge.rs | 4 +- .../prometeu-hal/src/debugger_protocol.rs | 20 +- crates/console/prometeu-hal/src/telemetry.rs | 78 +++--- .../src/virtual_machine_runtime/dispatch.rs | 13 +- .../src/virtual_machine_runtime/tick.rs | 68 ++--- .../src/debugger.rs | 20 +- .../src/overlay.rs | 248 ++++++++++-------- .../prometeu-host-desktop-winit/src/runner.rs | 1 - .../prometeu-host-desktop-winit/src/stats.rs | 6 +- discussion/index.ndjson | 2 +- ...-0024-generic-memory-bank-slot-contract.md | 4 +- ...et-manager-bank-telemetry-slot-contract.md | 76 +++--- ...et-manager-bank-telemetry-slot-contract.md | 199 ++++++++------ .../10-debug-inspection-and-profiling.md | 28 ++ docs/specs/runtime/15-asset-management.md | 23 +- 17 files changed, 490 insertions(+), 402 deletions(-) diff --git a/crates/console/prometeu-drivers/src/asset.rs b/crates/console/prometeu-drivers/src/asset.rs index b7f6b2bb..cbb6c38f 100644 --- a/crates/console/prometeu-drivers/src/asset.rs +++ b/crates/console/prometeu-drivers/src/asset.rs @@ -2,8 +2,8 @@ use crate::memory_banks::{GlyphBankPoolInstaller, SoundBankPoolInstaller}; use prometeu_hal::AssetBridge; use prometeu_hal::asset::{ - AssetCodec, AssetEntry, AssetId, AssetLoadError, AssetOpStatus, BankStats, BankType, HandleId, - LoadStatus, PreloadEntry, SlotRef, SlotStats, + AssetCodec, AssetEntry, AssetId, AssetLoadError, AssetOpStatus, BankTelemetry, BankType, + HandleId, LoadStatus, PreloadEntry, SlotRef, SlotStats, }; use prometeu_hal::cartridge::AssetsPayloadSource; use prometeu_hal::color::Color; @@ -161,11 +161,6 @@ pub struct AssetManager { /// Residency policy for sound banks. sound_policy: BankPolicy, - /// Count of occupied slots for GFX. - gfx_slots_occupied: AtomicUsize, - /// Count of occupied slots for sounds. - sound_slots_occupied: AtomicUsize, - // Commits that are ready to be applied at the next frame boundary. pending_commits: Mutex>, } @@ -206,8 +201,8 @@ impl AssetBridge for AssetManager { fn apply_commits(&self) { self.apply_commits() } - fn bank_info(&self, kind: BankType) -> BankStats { - self.bank_info(kind) + fn bank_telemetry(&self) -> Vec { + self.bank_telemetry() } fn slot_info(&self, slot: SlotRef) -> SlotStats { self.slot_info(slot) @@ -302,8 +297,6 @@ impl AssetManager { sound_slots: Arc::new(RwLock::new(std::array::from_fn(|_| None))), gfx_policy: BankPolicy::new(), sound_policy: BankPolicy::new(), - gfx_slots_occupied: AtomicUsize::new(0), - sound_slots_occupied: AtomicUsize::new(0), handles: Arc::new(RwLock::new(HashMap::new())), next_handle_id: Mutex::new(1), assets_data: Arc::new(RwLock::new(assets_data)), @@ -732,9 +725,6 @@ impl AssetManager { self.gfx_installer.install_glyph_bank(h.slot.index, bank); let mut slots = self.gfx_slots.write().unwrap(); if h.slot.index < slots.len() { - if slots[h.slot.index].is_none() { - self.gfx_slots_occupied.fetch_add(1, Ordering::Relaxed); - } slots[h.slot.index] = Some(h._asset_id); } h.status = LoadStatus::COMMITTED; @@ -745,9 +735,6 @@ impl AssetManager { self.sound_installer.install_sound_bank(h.slot.index, bank); let mut slots = self.sound_slots.write().unwrap(); if h.slot.index < slots.len() { - if slots[h.slot.index].is_none() { - self.sound_slots_occupied.fetch_add(1, Ordering::Relaxed); - } slots[h.slot.index] = Some(h._asset_id); } h.status = LoadStatus::COMMITTED; @@ -759,37 +746,21 @@ impl AssetManager { } } - pub fn bank_info(&self, kind: BankType) -> BankStats { - match kind { - BankType::GLYPH => { - let used_bytes = self.gfx_policy.used_bytes.load(Ordering::Relaxed); - let inflight_bytes = self.gfx_policy.inflight_bytes.load(Ordering::Relaxed); - let slots_occupied = self.gfx_slots_occupied.load(Ordering::Relaxed); + pub fn bank_telemetry(&self) -> Vec { + vec![self.bank_telemetry_for(BankType::GLYPH), self.bank_telemetry_for(BankType::SOUNDS)] + } - BankStats { - total_bytes: 16 * 1024 * 1024, - used_bytes, - free_bytes: (16usize * 1024 * 1024).saturating_sub(used_bytes), - inflight_bytes, - slot_count: 16, - slots_occupied, - } + fn bank_telemetry_for(&self, kind: BankType) -> BankTelemetry { + let used_slots = match kind { + BankType::GLYPH => { + self.gfx_slots.read().unwrap().iter().filter(|slot| slot.is_some()).count() } BankType::SOUNDS => { - let used_bytes = self.sound_policy.used_bytes.load(Ordering::Relaxed); - let inflight_bytes = self.sound_policy.inflight_bytes.load(Ordering::Relaxed); - let slots_occupied = self.sound_slots_occupied.load(Ordering::Relaxed); - - BankStats { - total_bytes: 32 * 1024 * 1024, - used_bytes, - free_bytes: (32usize * 1024 * 1024).saturating_sub(used_bytes), - inflight_bytes, - slot_count: 16, - slots_occupied, - } + self.sound_slots.read().unwrap().iter().filter(|slot| slot.is_some()).count() } - } + }; + + BankTelemetry { bank_type: kind, used_slots, total_slots: 16 } } pub fn slot_info(&self, slot: SlotRef) -> SlotStats { @@ -842,8 +813,6 @@ impl AssetManager { pub fn shutdown(&self) { self.gfx_policy.clear(); self.sound_policy.clear(); - self.gfx_slots_occupied.store(0, Ordering::Relaxed); - self.sound_slots_occupied.store(0, Ordering::Relaxed); self.handles.write().unwrap().clear(); self.pending_commits.lock().unwrap().clear(); self.gfx_slots.write().unwrap().fill(None); @@ -1196,7 +1165,6 @@ mod tests { let width = 16; let height = 16; - let decoded_bytes = expected_glyph_decoded_size(width, height); let data = test_glyph_asset_data(); let am = AssetManager::new( @@ -1207,10 +1175,10 @@ mod tests { ); // Initially zero - let info = am.bank_info(BankType::GLYPH); - assert_eq!(info.used_bytes, 0); - assert_eq!(info.inflight_bytes, 0); - assert_eq!(info.slots_occupied, 0); + let info = am.bank_telemetry(); + assert_eq!(info[0].bank_type, BankType::GLYPH); + assert_eq!(info[0].used_slots, 0); + assert_eq!(info[0].total_slots, 16); // Loading let handle = am.load(0, 0).expect("load must allocate handle"); @@ -1221,26 +1189,21 @@ mod tests { thread::sleep(std::time::Duration::from_millis(10)); } - let info = am.bank_info(BankType::GLYPH); - // Note: put_resident happens in worker thread, then stage happens. - assert_eq!(info.used_bytes, decoded_bytes); - assert_eq!(info.inflight_bytes, decoded_bytes); - assert_eq!(info.slots_occupied, 0); + let info = am.bank_telemetry(); + assert_eq!(info[0].used_slots, 0); // Commit am.commit(handle); am.apply_commits(); - let info = am.bank_info(BankType::GLYPH); - assert_eq!(info.used_bytes, decoded_bytes); - assert_eq!(info.inflight_bytes, 0); - assert_eq!(info.slots_occupied, 1); + let info = am.bank_telemetry(); + assert_eq!(info[0].used_slots, 1); + assert_eq!(info[1].bank_type, BankType::SOUNDS); + assert_eq!(info[1].used_slots, 0); // Shutdown resets am.shutdown(); - let info = am.bank_info(BankType::GLYPH); - assert_eq!(info.used_bytes, 0); - assert_eq!(info.inflight_bytes, 0); - assert_eq!(info.slots_occupied, 0); + let info = am.bank_telemetry(); + assert_eq!(info[0].used_slots, 0); } } diff --git a/crates/console/prometeu-hal/src/asset.rs b/crates/console/prometeu-hal/src/asset.rs index e67b49c0..24a2d447 100644 --- a/crates/console/prometeu-hal/src/asset.rs +++ b/crates/console/prometeu-hal/src/asset.rs @@ -105,13 +105,10 @@ pub enum AssetOpStatus { } #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct BankStats { - pub total_bytes: usize, - pub used_bytes: usize, - pub free_bytes: usize, - pub inflight_bytes: usize, - pub slot_count: usize, - pub slots_occupied: usize, +pub struct BankTelemetry { + pub bank_type: BankType, + pub used_slots: usize, + pub total_slots: usize, } #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/crates/console/prometeu-hal/src/asset_bridge.rs b/crates/console/prometeu-hal/src/asset_bridge.rs index 7de6aade..5c80e3fe 100644 --- a/crates/console/prometeu-hal/src/asset_bridge.rs +++ b/crates/console/prometeu-hal/src/asset_bridge.rs @@ -1,5 +1,5 @@ use crate::asset::{ - AssetEntry, AssetId, AssetLoadError, AssetOpStatus, BankStats, BankType, HandleId, LoadStatus, + AssetEntry, AssetId, AssetLoadError, AssetOpStatus, BankTelemetry, HandleId, LoadStatus, PreloadEntry, SlotRef, SlotStats, }; use crate::cartridge::AssetsPayloadSource; @@ -16,7 +16,7 @@ pub trait AssetBridge { fn commit(&self, handle: HandleId) -> AssetOpStatus; fn cancel(&self, handle: HandleId) -> AssetOpStatus; fn apply_commits(&self); - fn bank_info(&self, kind: BankType) -> BankStats; + fn bank_telemetry(&self) -> Vec; fn slot_info(&self, slot: SlotRef) -> SlotStats; fn shutdown(&self); } diff --git a/crates/console/prometeu-hal/src/debugger_protocol.rs b/crates/console/prometeu-hal/src/debugger_protocol.rs index 8c6d9999..033c0cc2 100644 --- a/crates/console/prometeu-hal/src/debugger_protocol.rs +++ b/crates/console/prometeu-hal/src/debugger_protocol.rs @@ -64,12 +64,10 @@ pub enum DebugEvent { cycles_budget: u64, host_cpu_time_us: u64, violations: u32, - gfx_used_bytes: usize, - gfx_inflight_bytes: usize, - gfx_slots_occupied: u32, - audio_used_bytes: usize, - audio_inflight_bytes: usize, - audio_slots_occupied: u32, + glyph_slots_used: u32, + glyph_slots_total: u32, + sound_slots_used: u32, + sound_slots_total: u32, }, #[serde(rename = "fault")] Fault { @@ -97,12 +95,10 @@ mod tests { cycles_budget: 10000, host_cpu_time_us: 1200, violations: 0, - gfx_used_bytes: 1024, - gfx_inflight_bytes: 0, - gfx_slots_occupied: 1, - audio_used_bytes: 2048, - audio_inflight_bytes: 0, - audio_slots_occupied: 2, + glyph_slots_used: 1, + glyph_slots_total: 16, + sound_slots_used: 2, + sound_slots_total: 16, }; let json = serde_json::to_string(&event).unwrap(); diff --git a/crates/console/prometeu-hal/src/telemetry.rs b/crates/console/prometeu-hal/src/telemetry.rs index 2ac52fab..0965a95d 100644 --- a/crates/console/prometeu-hal/src/telemetry.rs +++ b/crates/console/prometeu-hal/src/telemetry.rs @@ -13,15 +13,11 @@ pub struct TelemetryFrame { pub completed_logical_frames: u32, pub violations: u32, - // GFX Banks - pub gfx_used_bytes: usize, - pub gfx_inflight_bytes: usize, - pub gfx_slots_occupied: u32, - - // Audio Banks - pub audio_used_bytes: usize, - pub audio_inflight_bytes: usize, - pub audio_slots_occupied: u32, + // Bank telemetry + pub glyph_slots_used: u32, + pub glyph_slots_total: u32, + pub sound_slots_used: u32, + pub sound_slots_total: u32, // RAM (Heap) pub heap_used_bytes: usize, @@ -44,15 +40,11 @@ pub struct AtomicTelemetry { pub completed_logical_frames: AtomicU32, pub violations: AtomicU32, - // GFX Banks - pub gfx_used_bytes: AtomicUsize, - pub gfx_inflight_bytes: AtomicUsize, - pub gfx_slots_occupied: AtomicU32, - - // Audio Banks - pub audio_used_bytes: AtomicUsize, - pub audio_inflight_bytes: AtomicUsize, - pub audio_slots_occupied: AtomicU32, + // Bank telemetry + pub glyph_slots_used: AtomicU32, + pub glyph_slots_total: AtomicU32, + pub sound_slots_used: AtomicU32, + pub sound_slots_total: AtomicU32, // RAM (Heap) pub heap_used_bytes: AtomicUsize, @@ -79,12 +71,10 @@ impl AtomicTelemetry { host_cpu_time_us: self.host_cpu_time_us.load(Ordering::Relaxed), completed_logical_frames: self.completed_logical_frames.load(Ordering::Relaxed), violations: self.violations.load(Ordering::Relaxed), - gfx_used_bytes: self.gfx_used_bytes.load(Ordering::Relaxed), - gfx_inflight_bytes: self.gfx_inflight_bytes.load(Ordering::Relaxed), - gfx_slots_occupied: self.gfx_slots_occupied.load(Ordering::Relaxed), - audio_used_bytes: self.audio_used_bytes.load(Ordering::Relaxed), - audio_inflight_bytes: self.audio_inflight_bytes.load(Ordering::Relaxed), - audio_slots_occupied: self.audio_slots_occupied.load(Ordering::Relaxed), + glyph_slots_used: self.glyph_slots_used.load(Ordering::Relaxed), + glyph_slots_total: self.glyph_slots_total.load(Ordering::Relaxed), + sound_slots_used: self.sound_slots_used.load(Ordering::Relaxed), + sound_slots_total: self.sound_slots_total.load(Ordering::Relaxed), heap_used_bytes: self.heap_used_bytes.load(Ordering::Relaxed), heap_max_bytes: self.heap_max_bytes.load(Ordering::Relaxed), logs_count: self.logs_count.load(Ordering::Relaxed), @@ -99,12 +89,10 @@ impl AtomicTelemetry { self.host_cpu_time_us.store(0, Ordering::Relaxed); self.completed_logical_frames.store(0, Ordering::Relaxed); self.violations.store(0, Ordering::Relaxed); - self.gfx_used_bytes.store(0, Ordering::Relaxed); - self.gfx_inflight_bytes.store(0, Ordering::Relaxed); - self.gfx_slots_occupied.store(0, Ordering::Relaxed); - self.audio_used_bytes.store(0, Ordering::Relaxed); - self.audio_inflight_bytes.store(0, Ordering::Relaxed); - self.audio_slots_occupied.store(0, Ordering::Relaxed); + self.glyph_slots_used.store(0, Ordering::Relaxed); + self.glyph_slots_total.store(0, Ordering::Relaxed); + self.sound_slots_used.store(0, Ordering::Relaxed); + self.sound_slots_total.store(0, Ordering::Relaxed); self.heap_used_bytes.store(0, Ordering::Relaxed); self.vm_steps.store(0, Ordering::Relaxed); self.logs_count.store(0, Ordering::Relaxed); @@ -118,8 +106,8 @@ pub struct CertificationConfig { pub cycles_budget_per_frame: Option, pub max_syscalls_per_frame: Option, pub max_host_cpu_us_per_frame: Option, - pub max_gfx_bytes: Option, - pub max_audio_bytes: Option, + pub max_glyph_slots_used: Option, + pub max_sound_slots_used: Option, pub max_heap_bytes: Option, pub max_logs_per_frame: Option, } @@ -199,9 +187,9 @@ impl Certifier { violations += 1; } - // 4. GFX Memory - if let Some(limit) = self.config.max_gfx_bytes - && telemetry.gfx_used_bytes > limit + // 4. GLYPH bank slots + if let Some(limit) = self.config.max_glyph_slots_used + && telemetry.glyph_slots_used > limit { log_service.log( ts_ms, @@ -210,16 +198,16 @@ impl Certifier { LogSource::Pos, 0xCA04, format!( - "Cert: GFX bank exceeded memory limit ({} > {})", - telemetry.gfx_used_bytes, limit + "Cert: GLYPH bank exceeded slot limit ({} > {})", + telemetry.glyph_slots_used, limit ), ); violations += 1; } - // 5. Audio Memory - if let Some(limit) = self.config.max_audio_bytes - && telemetry.audio_used_bytes > limit + // 5. SOUNDS bank slots + if let Some(limit) = self.config.max_sound_slots_used + && telemetry.sound_slots_used > limit { log_service.log( ts_ms, @@ -228,8 +216,8 @@ impl Certifier { LogSource::Pos, 0xCA05, format!( - "Cert: Audio bank exceeded memory limit ({} > {})", - telemetry.audio_used_bytes, limit + "Cert: SOUNDS bank exceeded slot limit ({} > {})", + telemetry.sound_slots_used, limit ), ); violations += 1; @@ -285,7 +273,7 @@ mod tests { cycles_budget_per_frame: Some(100), max_syscalls_per_frame: Some(5), max_host_cpu_us_per_frame: Some(1000), - max_gfx_bytes: Some(1024), + max_glyph_slots_used: Some(1), ..Default::default() }; let cert = Certifier::new(config); @@ -294,7 +282,7 @@ mod tests { tel.cycles_used = 150; tel.syscalls = 10; tel.host_cpu_time_us = 500; - tel.gfx_used_bytes = 2048; + tel.glyph_slots_used = 2; let violations = cert.evaluate(&tel, &mut ls, 1000); assert_eq!(violations, 3); @@ -303,7 +291,7 @@ mod tests { assert_eq!(logs.len(), 3); assert!(logs[0].msg.contains("cycles_used")); assert!(logs[1].msg.contains("syscalls")); - assert!(logs[2].msg.contains("GFX bank")); + assert!(logs[2].msg.contains("GLYPH bank")); } #[test] diff --git a/crates/console/prometeu-system/src/virtual_machine_runtime/dispatch.rs b/crates/console/prometeu-system/src/virtual_machine_runtime/dispatch.rs index ee988aed..e6037679 100644 --- a/crates/console/prometeu-system/src/virtual_machine_runtime/dispatch.rs +++ b/crates/console/prometeu-system/src/virtual_machine_runtime/dispatch.rs @@ -486,8 +486,17 @@ impl NativeInterface for VirtualMachineRuntime { 1 => BankType::SOUNDS, _ => return Err(VmFault::Trap(TRAP_TYPE, "Invalid asset type".to_string())), }; - let json = - serde_json::to_string(&hw.assets().bank_info(asset_type)).unwrap_or_default(); + let telemetry = hw + .assets() + .bank_telemetry() + .into_iter() + .find(|entry| entry.bank_type == asset_type) + .unwrap_or(prometeu_hal::asset::BankTelemetry { + bank_type: asset_type, + used_slots: 0, + total_slots: 0, + }); + let json = serde_json::to_string(&telemetry).unwrap_or_default(); ret.push_string(json); Ok(()) } diff --git a/crates/console/prometeu-system/src/virtual_machine_runtime/tick.rs b/crates/console/prometeu-system/src/virtual_machine_runtime/tick.rs index 14245287..ba58892b 100644 --- a/crates/console/prometeu-system/src/virtual_machine_runtime/tick.rs +++ b/crates/console/prometeu-system/src/virtual_machine_runtime/tick.rs @@ -1,12 +1,26 @@ use super::*; use crate::CrashReport; -use prometeu_hal::asset::BankType; +use prometeu_hal::asset::{BankTelemetry, BankType}; use prometeu_hal::log::{LogLevel, LogSource}; use prometeu_hal::{HardwareBridge, HostContext, InputSignals}; use prometeu_vm::LogicalFrameEndingReason; use std::sync::atomic::Ordering; impl VirtualMachineRuntime { + fn bank_telemetry_summary(hw: &dyn HardwareBridge) -> (BankTelemetry, BankTelemetry) { + let telemetry = hw.assets().bank_telemetry(); + let glyph = + telemetry.iter().find(|entry| entry.bank_type == BankType::GLYPH).cloned().unwrap_or( + BankTelemetry { bank_type: BankType::GLYPH, used_slots: 0, total_slots: 0 }, + ); + let sounds = + telemetry.iter().find(|entry| entry.bank_type == BankType::SOUNDS).cloned().unwrap_or( + BankTelemetry { bank_type: BankType::SOUNDS, used_slots: 0, total_slots: 0 }, + ); + + (glyph, sounds) + } + pub fn debug_step_instruction( &mut self, vm: &mut VirtualMachine, @@ -134,27 +148,19 @@ impl VirtualMachineRuntime { hw.gfx_mut().render_all(); // 1. Snapshot full telemetry at logical frame end - let gfx_stats = hw.assets().bank_info(BankType::GLYPH); + let (glyph_bank, sound_bank) = Self::bank_telemetry_summary(hw); self.atomic_telemetry - .gfx_used_bytes - .store(gfx_stats.used_bytes, Ordering::Relaxed); + .glyph_slots_used + .store(glyph_bank.used_slots as u32, Ordering::Relaxed); self.atomic_telemetry - .gfx_inflight_bytes - .store(gfx_stats.inflight_bytes, Ordering::Relaxed); + .glyph_slots_total + .store(glyph_bank.total_slots as u32, Ordering::Relaxed); self.atomic_telemetry - .gfx_slots_occupied - .store(gfx_stats.slots_occupied as u32, Ordering::Relaxed); - - let audio_stats = hw.assets().bank_info(BankType::SOUNDS); + .sound_slots_used + .store(sound_bank.used_slots as u32, Ordering::Relaxed); self.atomic_telemetry - .audio_used_bytes - .store(audio_stats.used_bytes, Ordering::Relaxed); - self.atomic_telemetry - .audio_inflight_bytes - .store(audio_stats.inflight_bytes, Ordering::Relaxed); - self.atomic_telemetry - .audio_slots_occupied - .store(audio_stats.slots_occupied as u32, Ordering::Relaxed); + .sound_slots_total + .store(sound_bank.total_slots as u32, Ordering::Relaxed); self.atomic_telemetry .heap_used_bytes @@ -165,7 +171,9 @@ impl VirtualMachineRuntime { let current_frame_logs = self.atomic_telemetry.current_logs_count.load(Ordering::Relaxed); - self.atomic_telemetry.logs_count.store(current_frame_logs, Ordering::Relaxed); + self.atomic_telemetry + .logs_count + .store(current_frame_logs, Ordering::Relaxed); let ts_ms = self.boot_time.elapsed().as_millis() as u64; let telemetry_snapshot = self.atomic_telemetry.snapshot(); @@ -210,23 +218,19 @@ impl VirtualMachineRuntime { // 2. High-frequency telemetry update (only if inspection is active) if self.inspection_active { - let gfx_stats = hw.assets().bank_info(BankType::GLYPH); - self.atomic_telemetry.gfx_used_bytes.store(gfx_stats.used_bytes, Ordering::Relaxed); + let (glyph_bank, sound_bank) = Self::bank_telemetry_summary(hw); self.atomic_telemetry - .gfx_inflight_bytes - .store(gfx_stats.inflight_bytes, Ordering::Relaxed); + .glyph_slots_used + .store(glyph_bank.used_slots as u32, Ordering::Relaxed); self.atomic_telemetry - .gfx_slots_occupied - .store(gfx_stats.slots_occupied as u32, Ordering::Relaxed); - - let audio_stats = hw.assets().bank_info(BankType::SOUNDS); - self.atomic_telemetry.audio_used_bytes.store(audio_stats.used_bytes, Ordering::Relaxed); + .glyph_slots_total + .store(glyph_bank.total_slots as u32, Ordering::Relaxed); self.atomic_telemetry - .audio_inflight_bytes - .store(audio_stats.inflight_bytes, Ordering::Relaxed); + .sound_slots_used + .store(sound_bank.used_slots as u32, Ordering::Relaxed); self.atomic_telemetry - .audio_slots_occupied - .store(audio_stats.slots_occupied as u32, Ordering::Relaxed); + .sound_slots_total + .store(sound_bank.total_slots as u32, Ordering::Relaxed); self.atomic_telemetry .heap_used_bytes diff --git a/crates/host/prometeu-host-desktop-winit/src/debugger.rs b/crates/host/prometeu-host-desktop-winit/src/debugger.rs index abf0cdaf..2127459b 100644 --- a/crates/host/prometeu-host-desktop-winit/src/debugger.rs +++ b/crates/host/prometeu-host-desktop-winit/src/debugger.rs @@ -272,6 +272,16 @@ impl HostDebugger { 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), }; @@ -302,12 +312,10 @@ impl HostDebugger { cycles_budget: tel.cycles_budget, host_cpu_time_us: tel.host_cpu_time_us, violations: tel.violations, - gfx_used_bytes: tel.gfx_used_bytes, - gfx_inflight_bytes: tel.gfx_inflight_bytes, - gfx_slots_occupied: tel.gfx_slots_occupied, - audio_used_bytes: tel.audio_used_bytes, - audio_inflight_bytes: tel.audio_inflight_bytes, - audio_slots_occupied: tel.audio_slots_occupied, + 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.last_telemetry_frame = current_frame; } diff --git a/crates/host/prometeu-host-desktop-winit/src/overlay.rs b/crates/host/prometeu-host-desktop-winit/src/overlay.rs index 8a1f458e..b6c3e64c 100644 --- a/crates/host/prometeu-host-desktop-winit/src/overlay.rs +++ b/crates/host/prometeu-host-desktop-winit/src/overlay.rs @@ -42,14 +42,30 @@ pub(crate) struct OverlaySnapshot { footer: Vec, } +#[derive(Clone, Copy)] +struct Rect { + x: usize, + y: usize, + width: usize, + height: usize, +} + +struct FrameCanvas<'a> { + frame: &'a mut [u8], + width: usize, + height: usize, +} + pub(crate) fn capture_snapshot(stats: &HostStats, firmware: &Firmware) -> OverlaySnapshot { let tel = firmware.os.atomic_telemetry.snapshot(); let recent_logs = firmware.os.log_service.get_recent(10); - let violations_count = recent_logs.iter().filter(|e| e.tag >= 0xCA01 && e.tag <= 0xCA07).count(); + let violations_count = + recent_logs.iter().filter(|e| e.tag >= 0xCA01 && e.tag <= 0xCA07).count(); let mut footer = Vec::new(); if violations_count > 0 - && let Some(event) = recent_logs.into_iter().rev().find(|e| e.tag >= 0xCA01 && e.tag <= 0xCA07) + && let Some(event) = + recent_logs.into_iter().rev().find(|e| e.tag >= 0xCA01 && e.tag <= 0xCA07) { footer.push(OverlayMetric { label: "CERT", @@ -75,8 +91,8 @@ pub(crate) fn capture_snapshot(stats: &HostStats, firmware: &Firmware) -> Overla .or(if tel.heap_max_bytes > 0 { Some(tel.heap_max_bytes) } else { None }) .unwrap_or(OVERLAY_HEAP_FALLBACK_BYTES); let heap_ratio = ratio(tel.heap_used_bytes as u64, heap_total_bytes as u64); - let gfx_ratio = ratio(tel.gfx_slots_occupied as u64, 16); - let audio_ratio = ratio(tel.audio_slots_occupied as u64, 16); + let glyph_ratio = ratio(tel.glyph_slots_used as u64, tel.glyph_slots_total as u64); + let sound_ratio = ratio(tel.sound_slots_used as u64, tel.sound_slots_total as u64); OverlaySnapshot { rows: vec![ @@ -98,23 +114,11 @@ pub(crate) fn capture_snapshot(stats: &HostStats, firmware: &Firmware) -> Overla value: format!("{:.2}ms", stats.average_host_cpu_ms()), warn: false, }, - OverlayMetric { - label: "STEPS", - value: tel.vm_steps.to_string(), - warn: false, - }, + OverlayMetric { label: "STEPS", value: tel.vm_steps.to_string(), warn: false }, ), ( - OverlayMetric { - label: "SYSC", - value: tel.syscalls.to_string(), - warn: false, - }, - OverlayMetric { - label: "LOGS", - value: tel.logs_count.to_string(), - warn: false, - }, + OverlayMetric { label: "SYSC", value: tel.syscalls.to_string(), warn: false }, + OverlayMetric { label: "LOGS", value: tel.logs_count.to_string(), warn: false }, ), ], bars: vec![ @@ -136,36 +140,27 @@ pub(crate) fn capture_snapshot(stats: &HostStats, firmware: &Firmware) -> Overla label: "BUDGET", value: if tel.cycles_budget > 0 { format!( - "{:.1}K/{:.1}K {:.1}%", + "{:.1}K/{:.1}K", tel.cycles_used as f64 / 1000.0, - tel.cycles_budget as f64 / 1000.0, - cycles_ratio * 100.0 + tel.cycles_budget as f64 / 1000.0 ) } else { - "0.0K/0.0K 0.0%".to_string() + "0.0K/0.0K".to_string() }, ratio: cycles_ratio, warn: cycles_ratio >= 0.9, }, OverlayBar { - label: "GFX", - value: format!( - "{} / 16 slots {}K", - tel.gfx_slots_occupied, - tel.gfx_used_bytes.div_ceil(1024) - ), - ratio: gfx_ratio, - warn: tel.gfx_inflight_bytes > 0, + label: "GLYPH", + value: format!("{} / {} slots", tel.glyph_slots_used, tel.glyph_slots_total), + ratio: glyph_ratio, + warn: tel.glyph_slots_total > 0 && tel.glyph_slots_used >= tel.glyph_slots_total, }, OverlayBar { - label: "AUD", - value: format!( - "{} / 16 slots {}K", - tel.audio_slots_occupied, - tel.audio_used_bytes.div_ceil(1024) - ), - ratio: audio_ratio, - warn: tel.audio_inflight_bytes > 0, + label: "SOUNDS", + value: format!("{} / {} slots", tel.sound_slots_used, tel.sound_slots_total), + ratio: sound_ratio, + warn: tel.sound_slots_total > 0 && tel.sound_slots_used >= tel.sound_slots_total, }, ], footer, @@ -178,44 +173,58 @@ pub(crate) fn draw_overlay( frame_height: usize, snapshot: &OverlaySnapshot, ) { + let mut canvas = FrameCanvas { frame, width: frame_width, height: frame_height }; let panel_height = frame_height.saturating_sub(PANEL_Y * 2); + let panel_rect = Rect { x: PANEL_X, y: PANEL_Y, width: PANEL_WIDTH, height: panel_height }; - fill_rect_alpha(frame, frame_width, frame_height, PANEL_X, PANEL_Y, PANEL_WIDTH, panel_height, BG); - stroke_rect(frame, frame_width, frame_height, PANEL_X, PANEL_Y, PANEL_WIDTH, panel_height, BORDER); + fill_rect_alpha(&mut canvas, panel_rect, BG); + stroke_rect(&mut canvas, panel_rect, BORDER); let mut y = PANEL_Y + PANEL_PADDING; for (left, right) in &snapshot.rows { - draw_metric_pair(frame, frame_width, frame_height, y, left, right); + draw_metric_pair(canvas.frame, canvas.width, canvas.height, y, left, right); y += LINE_HEIGHT; } for bar in &snapshot.bars { let color = if bar.warn { WARN } else { TEXT }; - draw_text(frame, frame_width, frame_height, PANEL_X + PANEL_PADDING, y, bar.label, DIM); - draw_text(frame, frame_width, frame_height, PANEL_X + 48, y, &bar.value, color); + draw_text( + canvas.frame, + canvas.width, + canvas.height, + PANEL_X + PANEL_PADDING, + y, + bar.label, + DIM, + ); + draw_text(canvas.frame, canvas.width, canvas.height, PANEL_X + 48, y, &bar.value, color); y += LINE_HEIGHT - 2; let bar_x = PANEL_X + PANEL_PADDING; - fill_rect(frame, frame_width, frame_height, bar_x, y, BAR_WIDTH, BAR_HEIGHT, BAR_BG); + let bar_rect = Rect { x: bar_x, y, width: BAR_WIDTH, height: BAR_HEIGHT }; + fill_rect(&mut canvas, bar_rect, BAR_BG); let fill_width = ((BAR_WIDTH as f32) * bar.ratio.clamp(0.0, 1.0)).round() as usize; fill_rect( - frame, - frame_width, - frame_height, - bar_x, - y, - fill_width, - BAR_HEIGHT, + &mut canvas, + Rect { x: bar_x, y, width: fill_width, height: BAR_HEIGHT }, if bar.warn { BAR_WARN } else { BAR_FILL }, ); - stroke_rect(frame, frame_width, frame_height, bar_x, y, BAR_WIDTH, BAR_HEIGHT, BORDER); + stroke_rect(&mut canvas, bar_rect, BORDER); y += BAR_HEIGHT + 6; } for line in &snapshot.footer { let color = if line.warn { WARN } else { TEXT }; - draw_text(frame, frame_width, frame_height, PANEL_X + PANEL_PADDING, y, line.label, DIM); - draw_text(frame, frame_width, frame_height, PANEL_X + 48, y, &line.value, color); + draw_text( + canvas.frame, + canvas.width, + canvas.height, + PANEL_X + PANEL_PADDING, + y, + line.label, + DIM, + ); + draw_text(canvas.frame, canvas.width, canvas.height, PANEL_X + 48, y, &line.value, color); y += LINE_HEIGHT; } } @@ -260,7 +269,15 @@ fn truncate_value(value: &str, max_len: usize) -> String { upper } -fn draw_text(frame: &mut [u8], frame_width: usize, frame_height: usize, x: usize, y: usize, text: &str, color: [u8; 4]) { +fn draw_text( + frame: &mut [u8], + frame_width: usize, + frame_height: usize, + x: usize, + y: usize, + text: &str, + color: [u8; 4], +) { let mut cursor_x = x; for ch in text.chars() { if ch == '\n' { @@ -271,72 +288,71 @@ fn draw_text(frame: &mut [u8], frame_width: usize, frame_height: usize, x: usize } } -fn draw_char(frame: &mut [u8], frame_width: usize, frame_height: usize, x: usize, y: usize, ch: char, color: [u8; 4]) { +fn draw_char( + frame: &mut [u8], + frame_width: usize, + frame_height: usize, + x: usize, + y: usize, + ch: char, + color: [u8; 4], +) { let glyph = glyph_bits(ch); for (row, bits) in glyph.iter().enumerate() { for col in 0..5 { if bits & (1 << (4 - col)) != 0 { - fill_rect(frame, frame_width, frame_height, x + col * CHAR_SCALE, y + row * CHAR_SCALE, CHAR_SCALE, CHAR_SCALE, color); + let mut canvas = FrameCanvas { frame, width: frame_width, height: frame_height }; + fill_rect( + &mut canvas, + Rect { + x: x + col * CHAR_SCALE, + y: y + row * CHAR_SCALE, + width: CHAR_SCALE, + height: CHAR_SCALE, + }, + color, + ); } } } } -fn fill_rect_alpha( - frame: &mut [u8], - frame_width: usize, - frame_height: usize, - x: usize, - y: usize, - width: usize, - height: usize, - color: [u8; 4], -) { - let max_x = (x + width).min(frame_width); - let max_y = (y + height).min(frame_height); - for py in y..max_y { - for px in x..max_x { - blend_pixel(frame, frame_width, px, py, color); +fn fill_rect_alpha(canvas: &mut FrameCanvas<'_>, rect: Rect, color: [u8; 4]) { + let max_x = (rect.x + rect.width).min(canvas.width); + let max_y = (rect.y + rect.height).min(canvas.height); + for py in rect.y..max_y { + for px in rect.x..max_x { + blend_pixel(canvas.frame, canvas.width, px, py, color); } } } -fn fill_rect( - frame: &mut [u8], - frame_width: usize, - frame_height: usize, - x: usize, - y: usize, - width: usize, - height: usize, - color: [u8; 4], -) { - let max_x = (x + width).min(frame_width); - let max_y = (y + height).min(frame_height); - for py in y..max_y { - for px in x..max_x { - write_pixel(frame, frame_width, px, py, color); +fn fill_rect(canvas: &mut FrameCanvas<'_>, rect: Rect, color: [u8; 4]) { + let max_x = (rect.x + rect.width).min(canvas.width); + let max_y = (rect.y + rect.height).min(canvas.height); + for py in rect.y..max_y { + for px in rect.x..max_x { + write_pixel(canvas.frame, canvas.width, px, py, color); } } } -fn stroke_rect( - frame: &mut [u8], - frame_width: usize, - frame_height: usize, - x: usize, - y: usize, - width: usize, - height: usize, - color: [u8; 4], -) { - if width == 0 || height == 0 { +fn stroke_rect(canvas: &mut FrameCanvas<'_>, rect: Rect, color: [u8; 4]) { + if rect.width == 0 || rect.height == 0 { return; } - fill_rect(frame, frame_width, frame_height, x, y, width, 1, color); - fill_rect(frame, frame_width, frame_height, x, y + height.saturating_sub(1), width, 1, color); - fill_rect(frame, frame_width, frame_height, x, y, 1, height, color); - fill_rect(frame, frame_width, frame_height, x + width.saturating_sub(1), y, 1, height, color); + fill_rect(canvas, Rect { x: rect.x, y: rect.y, width: rect.width, height: 1 }, color); + fill_rect( + canvas, + Rect { x: rect.x, y: rect.y + rect.height.saturating_sub(1), width: rect.width, height: 1 }, + color, + ); + fill_rect(canvas, Rect { x: rect.x, y: rect.y, width: 1, height: rect.height }, color); + fill_rect( + canvas, + Rect { x: rect.x + rect.width.saturating_sub(1), y: rect.y, width: 1, height: rect.height }, + color, + ); } fn blend_pixel(frame: &mut [u8], frame_width: usize, x: usize, y: usize, color: [u8; 4]) { @@ -426,18 +442,20 @@ mod tests { OverlayMetric { label: "STEPS", value: "420".to_string(), warn: false }, ), ], - bars: vec![OverlayBar { - label: "HEAP", - value: "1024K / 8192K".to_string(), - ratio: 0.125, - warn: false, - }, - OverlayBar { - label: "BUDGET", - value: "9.0K/10.0K 90.0%".to_string(), - ratio: 0.9, - warn: true, - }], + bars: vec![ + OverlayBar { + label: "HEAP", + value: "1024K / 8192K".to_string(), + ratio: 0.125, + warn: false, + }, + OverlayBar { + label: "BUDGET", + value: "9.0K/10.0K".to_string(), + ratio: 0.9, + warn: true, + }, + ], footer: vec![OverlayMetric { label: "CRASH", value: "VM PANIC".to_string(), diff --git a/crates/host/prometeu-host-desktop-winit/src/runner.rs b/crates/host/prometeu-host-desktop-winit/src/runner.rs index 76284f0c..cb1cd9cb 100644 --- a/crates/host/prometeu-host-desktop-winit/src/runner.rs +++ b/crates/host/prometeu-host-desktop-winit/src/runner.rs @@ -122,7 +122,6 @@ impl HostRunner { w.request_redraw(); } } - } impl ApplicationHandler for HostRunner { diff --git a/crates/host/prometeu-host-desktop-winit/src/stats.rs b/crates/host/prometeu-host-desktop-winit/src/stats.rs index bdcc4275..3cbda095 100644 --- a/crates/host/prometeu-host-desktop-winit/src/stats.rs +++ b/crates/host/prometeu-host-desktop-winit/src/stats.rs @@ -45,8 +45,10 @@ impl HostStats { pub fn record_host_cpu_time(&mut self, us: u64) { self.recent_host_cpu_us[self.recent_host_cpu_cursor] = us; - self.recent_host_cpu_cursor = (self.recent_host_cpu_cursor + 1) % self.recent_host_cpu_us.len(); - self.recent_host_cpu_count = self.recent_host_cpu_count.saturating_add(1).min(self.recent_host_cpu_us.len()); + self.recent_host_cpu_cursor = + (self.recent_host_cpu_cursor + 1) % self.recent_host_cpu_us.len(); + self.recent_host_cpu_count = + self.recent_host_cpu_count.saturating_add(1).min(self.recent_host_cpu_us.len()); } pub fn average_host_cpu_ms(&self) -> f64 { diff --git a/discussion/index.ndjson b/discussion/index.ndjson index e4fb3881..0e13da96 100644 --- a/discussion/index.ndjson +++ b/discussion/index.ndjson @@ -16,7 +16,7 @@ {"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":"open","ticket":"perf-runtime-introspection-syscalls","title":"Agenda - [PERF] Runtime Introspection Syscalls","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0011","file":"workflow/agendas/AGD-0011-perf-runtime-introspection-syscalls.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]} {"type":"discussion","id":"DSC-0013","status":"open","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":[{"id":"AGD-0012","file":"workflow/agendas/AGD-0012-perf-host-debug-overlay-isolation.md","status":"done","created_at":"2026-03-27","updated_at":"2026-04-10"},{"id":"AGD-0022","file":"AGD-0022-host-overlay-tooling-boundary-revision.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10"},{"id":"AGD-0023","file":"AGD-0023-overlay-log-metric-last-frame.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10"}],"decisions":[{"id":"DEC-0007","file":"workflow/decisions/DEC-0007-perf-host-debug-overlay-isolation.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10"},{"id":"DEC-0009","file":"DEC-0009-host-overlay-tooling-boundary.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10","ref_agenda":"AGD-0022"},{"id":"DEC-0010","file":"DEC-0010-overlay-log-metric-last-frame.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10","ref_agenda":"AGD-0023"}],"plans":[{"id":"PLN-0006","file":"workflow/plans/PLN-0006-perf-host-debug-overlay-isolation.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"},{"id":"PLN-0008","file":"PLN-0008-host-overlay-native-composition-alignment.md","status":"in_progress","created_at":"2026-04-10","updated_at":"2026-04-10","ref_decisions":["DEC-0007","DEC-0008","DEC-0009","DEC-0010"]}],"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":"open","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":[{"id":"AGD-0024","file":"AGD-0024-generic-memory-bank-slot-contract.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10"}],"decisions":[{"id":"DEC-0012","file":"DEC-0012-asset-manager-bank-telemetry-slot-contract.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10","ref_agenda":"AGD-0024"}],"plans":[{"id":"PLN-0010","file":"PLN-0010-asset-manager-bank-telemetry-slot-contract.md","status":"open","created_at":"2026-04-10","updated_at":"2026-04-10","ref_decisions":["DEC-0012"]}],"lessons":[]} +{"type":"discussion","id":"DSC-0024","status":"open","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":[{"id":"AGD-0024","file":"AGD-0024-generic-memory-bank-slot-contract.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10"}],"decisions":[{"id":"DEC-0012","file":"DEC-0012-asset-manager-bank-telemetry-slot-contract.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10","ref_agenda":"AGD-0024"}],"plans":[{"id":"PLN-0010","file":"PLN-0010-asset-manager-bank-telemetry-slot-contract.md","status":"in_progress","created_at":"2026-04-10","updated_at":"2026-04-10","ref_decisions":["DEC-0012"]}],"lessons":[]} {"type":"discussion","id":"DSC-0014","status":"open","ticket":"perf-vm-allocation-and-copy-pressure","title":"Agenda - [PERF] VM Allocation and Copy Pressure","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0013","file":"workflow/agendas/AGD-0013-perf-vm-allocation-and-copy-pressure.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]} {"type":"discussion","id":"DSC-0015","status":"open","ticket":"perf-cartridge-boot-and-program-ownership","title":"Agenda - [PERF] Cartridge Boot and Program Ownership","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0014","file":"workflow/agendas/AGD-0014-perf-cartridge-boot-and-program-ownership.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]} {"type":"discussion","id":"DSC-0016","status":"done","ticket":"tilemap-empty-cell-vs-tile-id-zero","title":"Tilemap Empty Cell vs Tile ID Zero","created_at":"2026-03-27","updated_at":"2026-04-09","tags":[],"agendas":[{"id":"AGD-0015","file":"workflow/agendas/AGD-0015-tilemap-empty-cell-vs-tile-id-zero.md","status":"done","created_at":"2026-03-27","updated_at":"2026-04-09"}],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0022","file":"lessons/DSC-0016-tilemap-empty-cell-semantics/LSN-0022-tilemap-empty-cell-convergence.md","status":"done","created_at":"2026-04-09","updated_at":"2026-04-09"}]} diff --git a/discussion/workflow/agendas/AGD-0024-generic-memory-bank-slot-contract.md b/discussion/workflow/agendas/AGD-0024-generic-memory-bank-slot-contract.md index de6d764b..6dc203cb 100644 --- a/discussion/workflow/agendas/AGD-0024-generic-memory-bank-slot-contract.md +++ b/discussion/workflow/agendas/AGD-0024-generic-memory-bank-slot-contract.md @@ -82,13 +82,13 @@ Seguir com a **Opção A**, com os seguintes princípios: 2. O contrato exposto deve usar somente `used_slots` e `total_slots`. 3. `BankPolicy` e `BankStats` devem ser removidos por completo. 4. `MemoryBanks` deve possuir um contrato específico e explícito para ocupação por slots. -5. O host overlay deve consumir esse modelo genérico sem saber detalhes especiais de `GLYPH` vs `SOUND`, além de rótulo e contagem. +5. O host overlay deve consumir esse modelo genérico sem saber detalhes especiais de `GLYPH` vs `SOUNDS`, além de rótulo e contagem. 6. A revisão deve evitar abstração vazia: o contrato genérico precisa mapear diretamente para `GlyphBank` e `SoundBank`. Nomenclatura canônica acordada para a camada genérica: - `GLYPH` -- `SOUND` +- `SOUNDS` `GFX` e `AUD` não devem ser usados como nomes canônicos do contrato genérico de banks, pois são apelidos de apresentação e não os nomes corretos do domínio. diff --git a/discussion/workflow/decisions/DEC-0012-asset-manager-bank-telemetry-slot-contract.md b/discussion/workflow/decisions/DEC-0012-asset-manager-bank-telemetry-slot-contract.md index 737ef993..03089200 100644 --- a/discussion/workflow/decisions/DEC-0012-asset-manager-bank-telemetry-slot-contract.md +++ b/discussion/workflow/decisions/DEC-0012-asset-manager-bank-telemetry-slot-contract.md @@ -15,19 +15,22 @@ tags: [runtime, asset, memory-bank, slots, host, telemetry] **Accepted** ## Contexto -O modelo atual de banks ainda carrega forte semântica orientada a bytes e estruturas antigas como `BankPolicy` e `BankStats`, enquanto o consumo real desejado pelo host e pela inspeção técnica é orientado a slots. Além disso, os bytes historicamente usados nesse caminho foram considerados inadequados para sustentar o novo contrato de telemetria de banks. +AGD-0024 fechou a mudança do contrato de banks de um modelo orientado a bytes para um modelo orientado a slots. A agenda também fechou que `BankPolicy` e `BankStats` não são base aceitável para a nova superfície visível e que a telemetria operacional consumida por host e overlay deve sair diretamente do `AssetManager`. -Durante a discussão, ficou explícito que: +Os pontos que a agenda resolveu e que esta decisão precisa cristalizar sem ambiguidade são: -- o resumo visível de banks deve ser exposto diretamente pelo `AssetManager`; -- a telemetria de banks deve usar somente o enum do tipo do bank; -- a semântica operacional deve ser `used_slots / total_slots`; -- `GFX` e `AUD` não são nomes canônicos do domínio; os nomes corretos são `GLYPH` e `SOUND`; -- a certificação de banks deve deixar de usar limites em bytes e passar a usar limites por slots. +- o contrato visível é `slot-first`; +- o resumo visível por bank usa apenas `bank_type`, `used_slots` e `total_slots`; +- o detalhamento operacional ocorre por enum de slot; +- `GLYPH` e `SOUNDS` são os nomes canônicos do domínio; +- `GFX` e `AUD` não são nomes canônicos do contrato; +- a certificação deixa de usar bytes e passa a usar limites por slots; +- a genericidade principal do contrato visível não reside em `MemoryBanks`. ## Decisao -1. O contrato visível de telemetria de banks MUST be exposed by `AssetManager`. -2. O resumo de bank MUST use the following structure shape: +1. O contrato visível de telemetria de banks MUST ser exposto diretamente por `AssetManager`. +2. O contrato visível de telemetria de banks MUST NOT ter `MemoryBanks` como superfície principal de exposição. `MemoryBanks` MAY manter detalhes internos de suporte, mas o ponto canônico de consumo visível do sistema SHALL ser `AssetManager`. +3. O resumo canônico por bank MUST usar somente os campos `bank_type`, `used_slots` e `total_slots`, no formato: ```rust pub struct BankTelemetry { @@ -37,42 +40,53 @@ pub struct BankTelemetry { } ``` -3. O `bank_type` MUST use canonical domain names. For the current banks, the canonical names are `GLYPH` and `SOUND`. -4. O contrato de telemetria de banks MUST be slot-first and MUST NOT depend on byte counts. -5. `BankPolicy` e `BankStats` MUST be removed completely from the bank telemetry contract path. -6. O host overlay MUST consume `AssetManager` bank telemetry instead of hardcoded bank-specific logic. -7. As regras de certificação `max_gfx_bytes` e `max_audio_bytes` MUST be removed. -8. A certificação de banks MUST migrate to slot-based limits, using canonical bank-specific slot limits such as: +4. O contrato visível de telemetria de banks MUST ser `slot-first` e MUST NOT depender de contagens de bytes. +5. `bank_type` MUST usar nomes canônicos do domínio. Para os banks atuais, os nomes canônicos são `GLYPH` e `SOUNDS`. +6. `GFX` e `AUD` MUST NOT aparecer como nomes canônicos do contrato genérico de banks. Eles MAY existir apenas como rótulos de apresentação, quando estritamente necessário fora do contrato canônico. +7. `BankPolicy` e `BankStats` MUST ser removidos completamente do caminho do contrato visível de telemetria de banks e MUST NOT permanecer como modelagem principal desse domínio exposto. +8. O overlay do host MUST consumir a telemetria exposta por `AssetManager` e MUST NOT manter lógica hardcoded dependente de bancos específicos para calcular ocupação principal. +9. O detalhamento operacional por slot MUST ser definido por enum de slot, e qualquer inspeção detalhada de ocupação SHALL seguir esse modelo, não um detalhamento baseado em bytes. +10. As regras de certificação `max_gfx_bytes` e `max_audio_bytes` MUST ser removidas. +11. A certificação de banks MUST migrar para limites por slots, usando nomes canônicos de bank, tais como: - `max_glyph_slots_used` - `max_sound_slots_used` -9. Any remaining byte-based accounting MAY survive only as internal implementation detail if strictly necessary, but it MUST NOT remain part of the exposed bank telemetry contract. +12. Qualquer contabilização residual em bytes MAY sobreviver apenas como detalhe interno de implementação, quando estritamente necessária, e MUST NOT permanecer no contrato visível de telemetria de banks. ## Rationale -- Slots são a unidade operacional correta para entender ocupação de banks. -- O `AssetManager` já é o lugar que conhece carregamento, commit e ocupação prática dos banks. -- Remover bytes do contrato elimina uma fonte de telemetria considerada enganosa. -- `GLYPH` e `SOUND` preservam a linguagem correta do domínio e evitam apelidos frágeis na interface. -- Certificação por slots mantém coerência entre contrato exposto, overlay e limites técnicos. +- Slots são a unidade operacional real percebida por host, overlay e depuração de banks. +- `AssetManager` é o ponto que já conhece a ocupação efetiva dos banks e, portanto, é o lugar correto para expor o resumo visível. +- Remover bytes do contrato visível evita uma telemetria enganosa e elimina a ambiguidade que a agenda abriu para resolver. +- Fixar `GLYPH` e `SOUNDS` como nomes canônicos evita contaminar o contrato com apelidos de apresentação. +- Fixar enum de slot como detalhamento operacional evita reabrir a discussão sob outra forma durante a implementação. +- Migrar certificação para slots mantém o contrato exposto, o overlay e os limites técnicos usando a mesma semântica. ## Invariantes / Contrato -- A telemetria de banks exposta ao restante do sistema sai do `AssetManager`. -- Cada entrada de bank telemetria informa somente tipo do bank, slots usados e slots totais. -- O contrato canônico usa `GLYPH` e `SOUND`. +- A telemetria de banks exposta ao restante do sistema sai de `AssetManager`. +- O contrato visível principal não reside em `MemoryBanks`. +- Cada entrada de telemetria de bank informa somente `bank_type`, `used_slots` e `total_slots`. +- O contrato canônico usa `GLYPH` e `SOUNDS`. +- O detalhamento operacional de slots usa enum de slot. - O overlay não depende de bytes para mostrar ocupação de banks. - A certificação de banks não depende de bytes. ## Impactos -- **AssetManager:** precisa expor `Vec` ou equivalente direto. -- **HAL / Bridges:** precisam alinhar interfaces consumidoras ao contrato slot-first. -- **Overlay:** deve iterar a telemetria de banks do `AssetManager`. +- **AssetManager:** deve expor `Vec` ou equivalente direto como superfície canônica visível. +- **MemoryBanks:** pode continuar como suporte interno, mas não deve concentrar o contrato visível principal de telemetria. +- **HAL / Bridges:** precisam alinhar qualquer interface consumidora ao contrato `slot-first`. +- **Overlay:** deve iterar a telemetria de banks do `AssetManager` e parar de recomputar a ocupação principal a partir de bytes. - **Certifier:** precisa trocar limites de bytes por limites de slots para banks. -- **Legado:** `BankPolicy`, `BankStats`, e caminhos ancorados em `max_gfx_bytes` / `max_audio_bytes` precisam ser removidos ou recolocados fora do contrato exposto. +- **Legado:** `BankPolicy`, `BankStats`, `max_gfx_bytes` e `max_audio_bytes` precisam ser removidos do caminho canônico dessa capacidade. ## Referencias - `AGD-0024`: Generic Memory Bank Slot Contract. - `DEC-0010`: Overlay Log Metric Uses Last Completed Frame. ## Propagacao Necessaria -1. Criar plano de execução para refatorar `AssetManager`, HAL, overlay e certificação. -2. Remover o caminho de telemetria de banks baseado em bytes. -3. Migrar limites de certificação de banks para slots. +1. O plano de execução deve refatorar `AssetManager`, `MemoryBanks`, HAL/bridges, overlay e certificação sem reabrir a arquitetura base. +2. O caminho de telemetria de banks baseado em bytes deve ser removido do contrato visível. +3. O detalhamento operacional por enum de slot deve ser propagado para os pontos que inspecionam ocupação detalhada. +4. A certificação de banks deve migrar de limites em bytes para limites em slots. + +## Revision Log +- 2026-04-10: Initial decision emitted from `AGD-0024`. +- 2026-04-10: Decision tightened to align explicitly with agenda closure on `AssetManager` ownership, slot-enum operational detail, and `MemoryBanks` non-canonical residency. diff --git a/discussion/workflow/plans/PLN-0010-asset-manager-bank-telemetry-slot-contract.md b/discussion/workflow/plans/PLN-0010-asset-manager-bank-telemetry-slot-contract.md index c3e23e9d..3b17cf2f 100644 --- a/discussion/workflow/plans/PLN-0010-asset-manager-bank-telemetry-slot-contract.md +++ b/discussion/workflow/plans/PLN-0010-asset-manager-bank-telemetry-slot-contract.md @@ -2,152 +2,193 @@ id: PLN-0010 ticket: generic-memory-bank-slot-contract title: PR/Plan - Asset Manager Bank Telemetry Slot Contract -status: open +status: in_progress created: 2026-04-10 completed: -tags: [runtime, asset, memory-bank, slots, host, telemetry] +tags: [runtime, asset, memory-bank, slots, host, telemetry, certification] --- -## Objective +## Briefing -Implement the `DEC-0012` bank telemetry contract so `AssetManager` exposes slot-based bank summaries using canonical bank names `GLYPH` and `SOUND`, while host overlay and certification consume slot-based limits instead of byte-based bank metrics. +Implement `DEC-0012` by replacing the current byte-oriented bank telemetry path with a slot-first contract exposed directly by `AssetManager`. The execution MUST remove `BankPolicy` and `BankStats` from the visible telemetry path, keep canonical bank names aligned with `BankType` (`GLYPH`, `SOUNDS`), make the host overlay consume the new `AssetManager` summary, and migrate bank certification from byte limits to slot limits. -## Background +## Decisions de Origem -`DEC-0012` locked the bank telemetry contract around a simple slot-first structure: +- `DEC-0012` - Asset Manager Bank Telemetry Slot Contract -```rust -pub struct BankTelemetry { - pub bank_type: BankType, - pub used_slots: usize, - pub total_slots: usize, -} -``` +## Alvo -This replaces the old bank telemetry path that relied on byte-oriented structures and presentation aliases. The implementation must remove `BankPolicy` and `BankStats` from the exposed bank telemetry contract path, move visible bank summaries to `AssetManager`, and migrate certification rules from byte-based thresholds to slot-based thresholds. +Deliver a canonical bank telemetry path with these properties: -## Scope +- `AssetManager` exposes the visible per-bank summary. +- The visible summary shape is slot-first and limited to `bank_type`, `used_slots`, and `total_slots`. +- Detailed inspection remains slot-based through `SlotRef` / slot enums, not byte-based accounting. +- Host overlay and certification consume the new model. +- Legacy byte-oriented bank telemetry fields and limits are removed from the canonical path. + +## Escopo ### Included -- Add a visible slot-based `BankTelemetry` contract owned by `AssetManager`. -- Remove `BankStats` from the public bank telemetry contract path. -- Remove `BankPolicy` from the bank telemetry contract path and refactor internal code accordingly. -- Update bridge/consumer APIs so bank summaries come from `AssetManager` slot telemetry. -- Migrate certification from `max_gfx_bytes` / `max_audio_bytes` to slot-based limits for `GLYPH` and `SOUND`. -- Update host overlay to iterate `AssetManager` bank telemetry using canonical `GLYPH` and `SOUND` labels. -- Update specs/docs affected by the contract change. -### Excluded -- Adding new bank kinds beyond the currently supported `GLYPH` and `SOUND`. -- Reworking unrelated asset load/commit semantics. -- Redesigning generic slot-detail payloads beyond the accepted summary shape unless implementation requires a narrowly scoped helper. +- Add a canonical `BankTelemetry` type to the shared HAL asset contract. +- Add an `AssetBridge` method for bank telemetry summaries exposed by `AssetManager`. +- Refactor `AssetManager` in `crates/console/prometeu-drivers` to compute slot-first bank telemetry. +- Remove `BankStats` from the visible bank telemetry path and stop using it for overlay and certification. +- Remove `BankPolicy` as the main modeling surface used to describe visible bank telemetry. +- Keep detailed slot inspection on explicit slot references instead of bank byte summaries. +- Update runtime telemetry / certification to track `max_glyph_slots_used` and `max_sound_slots_used`. +- Update host overlay consumers to render bank occupancy from the new `AssetManager` summary. +- Update runtime specs so the published contract describes slot-first bank telemetry and slot-based certification. -## Execution Steps +## Fora de Escopo -### Step 1 - Introduce `BankTelemetry` in the exposed asset contract +- Reopening the base architectural decision about whether the visible summary belongs in `AssetManager`. +- Introducing new bank categories beyond the current `GLYPH` and `SOUNDS`. +- Reworking unrelated asset loading semantics, cartridge payload layout, or host UI design beyond adapting bank telemetry consumption. +- Removing all internal byte accounting if some private implementation detail still needs it temporarily. + +## Plano de Execucao + +### Step 1 - Lock the shared contract in HAL **What:** -Define the new slot-based summary type and make it the canonical visible representation of bank telemetry. +Define the canonical shared types and trait surface for slot-first bank telemetry. **How:** -Add `BankTelemetry` to the HAL asset domain and ensure canonical naming uses `GLYPH` and `SOUND`. Remove or deprecate exposed `BankStats` paths that conflict with the new contract. +Add `BankTelemetry` to `crates/console/prometeu-hal/src/asset.rs` using `bank_type`, `used_slots`, and `total_slots`. Update `crates/console/prometeu-hal/src/asset_bridge.rs` so the visible bridge exposes a bank telemetry summary method from `AssetManager`. Remove or deprecate `BankStats` from the visible bridge path so downstream callers stop depending on byte-oriented bank summaries. **File(s):** - `crates/console/prometeu-hal/src/asset.rs` - `crates/console/prometeu-hal/src/asset_bridge.rs` -### Step 2 - Make `AssetManager` expose bank telemetry directly +**Depends on:** +- None + +### Step 2 - Refactor AssetManager to produce slot-first summaries **What:** -Implement `AssetManager` support for returning `Vec` from live slot occupancy. +Move visible bank summary generation to a canonical `AssetManager` slot telemetry path. **How:** -Compute `used_slots` from current slot occupancy and `total_slots` from the fixed slot arrays for `GLYPH` and `SOUND`. Keep implementation simple and derive the summary directly from the installed slots. Refactor any existing public bank-info consumers away from byte-oriented APIs. +Replace `bank_info(BankType) -> BankStats` with a summary path that reports only slot occupancy for each canonical bank type. Compute `used_slots` from the slot arrays already owned by `AssetManager` (`gfx_slots`, `sound_slots`) or equivalent canonical occupancy state. Keep detailed per-slot inspection in `slot_info(SlotRef)`. Remove `BankPolicy` and its byte counters as the visible modeling surface for bank telemetry; if any internal storage helper remains temporarily, it must not leak into the shared contract. **File(s):** - `crates/console/prometeu-drivers/src/asset.rs` - `crates/console/prometeu-drivers/src/memory_banks.rs` -### Step 3 - Remove byte-based bank certification rules +**Depends on:** +- Step 1 + +### Step 3 - Migrate runtime and host consumers to the new summary **What:** -Replace byte-based certification limits for banks with slot-based limits. +Replace every bank summary consumer that still expects byte-oriented telemetry. **How:** -Remove `max_gfx_bytes` and `max_audio_bytes` from certification config and certifier checks. Introduce slot-based limits such as `max_glyph_slots_used` and `max_sound_slots_used`, and evaluate them against `BankTelemetry` summaries or equivalent slot-based data available at frame-end. +Update runtime and host callers to consume `BankTelemetry` from `AssetManager` instead of `BankStats`. Replace any direct `bank_info(BankType)` callsites with the new canonical summary path. Preserve slot-level inspection through `slot_info(SlotRef)` where detailed occupancy is needed. The host overlay must render bank occupancy from the `AssetManager` summary and must not reconstruct primary bank occupancy from bytes. + +**File(s):** +- `crates/console/prometeu-system/src/virtual_machine_runtime/tick.rs` +- `crates/host/prometeu-host-desktop-winit/src/overlay.rs` +- Any additional callsites found through `rg 'bank_info|BankStats' crates` + +**Depends on:** +- Step 2 + +### Step 4 - Replace certification byte limits with slot limits + +**What:** +Move bank certification from byte ceilings to slot ceilings. + +**How:** +Replace `max_gfx_bytes` and `max_audio_bytes` in telemetry / certification configuration and evaluation with slot-based limits such as `max_glyph_slots_used` and `max_sound_slots_used`. Update runtime accounting to record the maximum occupied slot count per bank during execution. Remove byte-limit evaluation from the bank certification path. **File(s):** - `crates/console/prometeu-hal/src/telemetry.rs` - `crates/console/prometeu-system/src/virtual_machine_runtime/tick.rs` -### Step 4 - Update host overlay to consume slot telemetry +**Depends on:** +- Step 3 + +### Step 5 - Remove residual canonical-path names and semantics that conflict with DEC-0012 **What:** -Replace hardcoded bank presentation with iteration over `AssetManager` bank telemetry. +Eliminate remaining visible-path terminology and structures that preserve the old model. **How:** -Render one bar per bank using `bank_type`, `used_slots`, and `total_slots`. Use canonical labels `GLYPH` and `SOUND`, and remove any byte-based bank presentation from the overlay. +Ensure no visible bank telemetry path still uses `GFX` / `AUD` as canonical names, `BankStats` as the summary type, or byte fields as the public bank occupancy contract. Keep `GLYPH` and `SOUNDS` aligned with the existing `BankType` enum. If bridges, docs, or helper APIs still expose the old path, remove or rename them in the same change set so the canonical path is unambiguous. **File(s):** -- `crates/host/prometeu-host-desktop-winit/src/overlay.rs` -- Any host-side adapter/helper used to access asset telemetry - -### Step 5 - Remove obsolete structures from the contract path - -**What:** -Eliminate `BankPolicy` and `BankStats` from the exposed bank telemetry path. - -**How:** -Refactor code so the new slot-based path is the only public contract. If internal implementation helpers must remain temporarily during migration, keep them private and make sure no consumer depends on them as contract. - -**File(s):** -- `crates/console/prometeu-drivers/src/asset.rs` - `crates/console/prometeu-hal/src/asset.rs` -- Related callers revealed during implementation +- `crates/console/prometeu-hal/src/asset_bridge.rs` +- `crates/console/prometeu-drivers/src/asset.rs` +- `crates/console/prometeu-system/src/virtual_machine_runtime/tick.rs` +- `crates/host/prometeu-host-desktop-winit/src/overlay.rs` -### Step 6 - Update normative documentation +**Depends on:** +- Step 4 + +### Step 6 - Update published specifications **What:** -Align specifications with the new slot-based bank telemetry contract. +Align canonical docs with the implemented slot-first bank contract. **How:** -Update relevant runtime documentation to describe bank occupancy in terms of `GLYPH` and `SOUND` slot usage, and remove outdated references to byte-based bank telemetry where they were treated as visible contract. +Update runtime specs to state that bank telemetry is exposed from `AssetManager`, that the published per-bank summary is slot-first, that detailed occupancy inspection remains slot-based, and that certification uses slot ceilings instead of byte ceilings. Remove residual published references to `max_gfx_bytes`, `max_audio_bytes`, or byte-oriented bank summaries in canonical specs. **File(s):** -- `docs/specs/runtime/10-debug-inspection-and-profiling.md` - `docs/specs/runtime/15-asset-management.md` -- Any additional touched runtime spec chapter +- `docs/specs/runtime/10-debug-inspection-and-profiling.md` +- Any additional runtime spec file that still documents byte-based bank certification or overlay bank summary consumption -## Test Requirements +**Depends on:** +- Step 5 + +## Criterios de Aceite + +- [ ] `AssetManager` is the canonical visible source of bank telemetry. +- [ ] The visible bank summary contract uses only `bank_type`, `used_slots`, and `total_slots`. +- [ ] Canonical bank names in the visible contract are `GLYPH` and `SOUNDS`. +- [ ] `BankStats` is no longer used as the visible bank telemetry summary type. +- [ ] `BankPolicy` is not the main visible modeling surface for bank telemetry. +- [ ] Host overlay bank occupancy uses the new `AssetManager` bank summary. +- [ ] Detailed occupancy inspection remains slot-based via slot references or equivalent slot enums. +- [ ] `max_gfx_bytes` and `max_audio_bytes` are removed from bank certification. +- [ ] Slot-based bank certification limits exist and are enforced. +- [ ] Published runtime specs describe the slot-first contract and slot-based certification model. + +## Tests / Validacao ### Unit Tests -- `AssetManager` reports correct `BankTelemetry` for empty and occupied `GLYPH` / `SOUND` slots. -- Certification slot limits trigger correctly for `GLYPH` and `SOUND`. -- Overlay rendering accepts generic bank telemetry iteration. + +- Add or update tests in `crates/console/prometeu-drivers/src/asset.rs` to verify the bank summary reports correct `used_slots` and `total_slots` for `GLYPH` and `SOUNDS`. +- Add or update tests in `crates/console/prometeu-hal/src/telemetry.rs` to verify slot-based certification thresholds trigger correctly and byte-based limits no longer exist in the bank path. +- Keep slot inspection tests proving `slot_info(SlotRef)` remains the detailed inspection mechanism. ### Integration Tests -- `cargo check` for affected HAL, drivers, system, and host crates. -- Focused tests for asset bank telemetry and certification migration. + +- Run `cargo test -p prometeu-drivers`. +- Run `cargo test -p prometeu-hal`. +- Run `cargo test -p prometeu-system`. +- Run `cargo test -p prometeu-host-desktop-winit` if overlay bank telemetry callsites are changed there. +- Run `cargo check` for the full touched workspace if cross-crate API changes make targeted tests insufficient. ### Manual Verification -- Run the desktop host and confirm banks appear as `GLYPH` and `SOUND`. -- Confirm bank bars track occupied slots instead of bytes. -- Confirm certification behavior still reports bank pressure using slot limits. -## Acceptance Criteria - -- [ ] `AssetManager` exposes a visible `BankTelemetry` summary with `bank_type`, `used_slots`, and `total_slots`. -- [ ] Canonical bank names in the new contract are `GLYPH` and `SOUND`. -- [ ] Byte-based bank certification limits are removed and replaced by slot-based limits. -- [ ] Host overlay renders bank occupancy from generic slot telemetry rather than byte-oriented hardcoded bank logic. -- [ ] `BankPolicy` and `BankStats` are no longer part of the exposed bank telemetry contract path. +- Start the desktop host and confirm the overlay shows per-bank occupancy using slot counts, not bytes. +- Confirm the overlay labels use `GLYPH` and `SOUNDS` or approved presentation labels derived from those canonical bank types. +- Confirm certification output reports slot-limit violations and no longer reports `max_gfx_bytes` / `max_audio_bytes`. +- Confirm detailed slot inspection still works for both glyph and sound slots. ## Dependencies -- `DEC-0012` accepted and unchanged. +- `DEC-0012` remains accepted and unchanged. +- Existing `BankType` enum values remain `GLYPH` and `SOUNDS`. +- `AssetManager` remains the asset-domain owner for slot occupancy summaries. -## Risks +## Riscos -- Removing byte-based bank paths may have wider ripple effects than expected if old APIs are reused outside the overlay. -- The line between “removed from contract path” and “removed completely from implementation” must stay explicit during refactor to avoid accidental partial migration. -- Certification migration must stay synchronized with telemetry migration or the host and certifier will diverge semantically. +- Cross-crate trait changes in `AssetBridge` may create a wider refactor than the bank telemetry callsites initially suggest. +- Removing `BankStats` from the visible path may expose hidden byte-based assumptions in runtime telemetry, overlay formatting, or tests. +- Certification migration may fail partially if slot maxima are recorded in one layer while enforcement still reads old byte fields in another layer. +- If byte accounting is still needed internally during the refactor, the implementation can accidentally leak that detail back into public interfaces unless the change set is kept cohesive. diff --git a/docs/specs/runtime/10-debug-inspection-and-profiling.md b/docs/specs/runtime/10-debug-inspection-and-profiling.md index c5160a4b..50db8446 100644 --- a/docs/specs/runtime/10-debug-inspection-and-profiling.md +++ b/docs/specs/runtime/10-debug-inspection-and-profiling.md @@ -187,6 +187,23 @@ Limit:32KB These data directly feed the certification. +### 7.1 Bank Occupancy Profiling + +Bank occupancy diagnostics are slot-first. + +The visible per-bank telemetry used by host inspection surfaces and certification is: + +- `bank_type` +- `used_slots` +- `total_slots` + +For the current runtime banks, the canonical bank names are: + +- `GLYPH` +- `SOUNDS` + +Byte-oriented bank occupancy is not the canonical visible profiling contract. + ## 8 Breakpoints and Watchpoints ### 8.1 Breakpoints @@ -218,6 +235,17 @@ Execution can pause when: ## 9 Event and Fault Debugging +## 10 Certification Diagnostics + +Certification diagnostics may enforce bank occupancy ceilings. + +For bank residency, certification uses slot-based limits, such as: + +- `max_glyph_slots_used` +- `max_sound_slots_used` + +Bank certification MUST NOT depend on `max_gfx_bytes` or `max_audio_bytes`. + PROMETEU allows observing: - event queue diff --git a/docs/specs/runtime/15-asset-management.md b/docs/specs/runtime/15-asset-management.md index 3feb76eb..90c56683 100644 --- a/docs/specs/runtime/15-asset-management.md +++ b/docs/specs/runtime/15-asset-management.md @@ -169,7 +169,7 @@ For `TILES` v1: The current runtime exposes bank types: -- `TILES` +- `GLYPH` - `SOUNDS` Assets are loaded into explicit slots identified by slot index at the public ABI boundary. @@ -184,6 +184,27 @@ SlotRef { bank_type, index } That internal representation is derived from the resolved `AssetEntry`, not supplied by the caller. +### 5.1 Canonical Bank Telemetry + +The canonical visible bank telemetry contract is exposed by `AssetManager`. + +The per-bank summary is slot-first and uses this shape: + +```text +BankTelemetry { + bank_type + used_slots + total_slots +} +``` + +Rules: + +- the visible contract MUST NOT expose byte-oriented bank occupancy as the canonical summary; +- canonical bank names are `GLYPH` and `SOUNDS`; +- detailed occupancy inspection remains slot-based through slot references, not bank byte totals; +- any residual byte accounting MAY exist internally, but it is not part of the visible bank telemetry contract. + ## 6 Load Lifecycle The runtime asset manager exposes a staged lifecycle: From 5436c54e6d7c2f0d5c9ef0d194ad10577c08351c Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Fri, 10 Apr 2026 19:19:08 +0100 Subject: [PATCH 09/10] [PERF] Host Debug Overlay Isolation --- .../prometeu-host-desktop-winit/src/overlay.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/crates/host/prometeu-host-desktop-winit/src/overlay.rs b/crates/host/prometeu-host-desktop-winit/src/overlay.rs index b6c3e64c..b6725909 100644 --- a/crates/host/prometeu-host-desktop-winit/src/overlay.rs +++ b/crates/host/prometeu-host-desktop-winit/src/overlay.rs @@ -4,10 +4,11 @@ use prometeu_firmware::Firmware; const PANEL_X: usize = 6; const PANEL_Y: usize = 10; const PANEL_WIDTH: usize = 170; -const PANEL_PADDING: usize = 8; +const PANEL_PADDING_X: usize = 8; +const PANEL_PADDING_Y: usize = 3; const LINE_HEIGHT: usize = 12; const CHAR_SCALE: usize = 1; -const BAR_WIDTH: usize = PANEL_WIDTH - (PANEL_PADDING * 2); +const BAR_WIDTH: usize = PANEL_WIDTH - (PANEL_PADDING_X * 2); const BAR_HEIGHT: usize = 6; const BG: [u8; 4] = [10, 18, 32, 208]; @@ -180,7 +181,7 @@ pub(crate) fn draw_overlay( fill_rect_alpha(&mut canvas, panel_rect, BG); stroke_rect(&mut canvas, panel_rect, BORDER); - let mut y = PANEL_Y + PANEL_PADDING; + let mut y = PANEL_Y + PANEL_PADDING_Y; for (left, right) in &snapshot.rows { draw_metric_pair(canvas.frame, canvas.width, canvas.height, y, left, right); y += LINE_HEIGHT; @@ -192,7 +193,7 @@ pub(crate) fn draw_overlay( canvas.frame, canvas.width, canvas.height, - PANEL_X + PANEL_PADDING, + PANEL_X + PANEL_PADDING_X, y, bar.label, DIM, @@ -200,7 +201,7 @@ pub(crate) fn draw_overlay( draw_text(canvas.frame, canvas.width, canvas.height, PANEL_X + 48, y, &bar.value, color); y += LINE_HEIGHT - 2; - let bar_x = PANEL_X + PANEL_PADDING; + let bar_x = PANEL_X + PANEL_PADDING_X; let bar_rect = Rect { x: bar_x, y, width: BAR_WIDTH, height: BAR_HEIGHT }; fill_rect(&mut canvas, bar_rect, BAR_BG); let fill_width = ((BAR_WIDTH as f32) * bar.ratio.clamp(0.0, 1.0)).round() as usize; @@ -219,7 +220,7 @@ pub(crate) fn draw_overlay( canvas.frame, canvas.width, canvas.height, - PANEL_X + PANEL_PADDING, + PANEL_X + PANEL_PADDING_X, y, line.label, DIM, @@ -237,7 +238,7 @@ fn draw_metric_pair( left: &OverlayMetric, right: &OverlayMetric, ) { - let left_x = PANEL_X + PANEL_PADDING; + let left_x = PANEL_X + PANEL_PADDING_X; let right_x = PANEL_X + 86; draw_metric(frame, frame_width, frame_height, left_x, y, left); From 7a696623b5ae8113f108962e9a1967784e910e36 Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Fri, 10 Apr 2026 19:22:52 +0100 Subject: [PATCH 10/10] [PERF] Host Debug Overlay Isolation --- .../src/overlay.rs | 2 +- .../.backups/index.ndjson.20260410-192035.bak | 25 +++ discussion/index.ndjson | 4 +- ...bank-telemetry-belongs-in-asset-manager.md | 70 +++++++ ...-0024-generic-memory-bank-slot-contract.md | 147 ------------- ...et-manager-bank-telemetry-slot-contract.md | 92 --------- ...et-manager-bank-telemetry-slot-contract.md | 194 ------------------ 7 files changed, 98 insertions(+), 436 deletions(-) create mode 100644 discussion/.backups/index.ndjson.20260410-192035.bak create mode 100644 discussion/lessons/DSC-0024-generic-memory-bank-slot-contract/LSN-0029-slot-first-bank-telemetry-belongs-in-asset-manager.md delete mode 100644 discussion/workflow/agendas/AGD-0024-generic-memory-bank-slot-contract.md delete mode 100644 discussion/workflow/decisions/DEC-0012-asset-manager-bank-telemetry-slot-contract.md delete mode 100644 discussion/workflow/plans/PLN-0010-asset-manager-bank-telemetry-slot-contract.md diff --git a/crates/host/prometeu-host-desktop-winit/src/overlay.rs b/crates/host/prometeu-host-desktop-winit/src/overlay.rs index b6725909..f1d707a1 100644 --- a/crates/host/prometeu-host-desktop-winit/src/overlay.rs +++ b/crates/host/prometeu-host-desktop-winit/src/overlay.rs @@ -2,7 +2,7 @@ use crate::stats::HostStats; use prometeu_firmware::Firmware; const PANEL_X: usize = 6; -const PANEL_Y: usize = 10; +const PANEL_Y: usize = 3; const PANEL_WIDTH: usize = 170; const PANEL_PADDING_X: usize = 8; const PANEL_PADDING_Y: usize = 3; diff --git a/discussion/.backups/index.ndjson.20260410-192035.bak b/discussion/.backups/index.ndjson.20260410-192035.bak new file mode 100644 index 00000000..0e13da96 --- /dev/null +++ b/discussion/.backups/index.ndjson.20260410-192035.bak @@ -0,0 +1,25 @@ +{"type":"meta","next_id":{"DSC":25,"AGD":25,"DEC":13,"PLN":11,"LSN":29,"CLSN":1}} +{"type":"discussion","id":"DSC-0023","status":"done","ticket":"perf-full-migration-to-atomic-telemetry","title":"Agenda - [PERF] Full Migration to Atomic Telemetry","created_at":"2026-04-10","updated_at":"2026-04-10","tags":["perf","runtime","telemetry"],"agendas":[{"id":"AGD-0021","file":"workflow/agendas/AGD-0021-full-migration-to-atomic-telemetry.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}],"decisions":[{"id":"DEC-0008","file":"workflow/decisions/DEC-0008-full-migration-to-atomic-telemetry.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10"}],"plans":[{"id":"PLN-0007","file":"workflow/plans/PLN-0007-full-migration-to-atomic-telemetry.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}],"lessons":[{"id":"LSN-0028","file":"lessons/DSC-0023-perf-full-migration-to-atomic-telemetry/LSN-0028-converging-to-single-atomic-telemetry-source.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}]} +{"type":"discussion","id":"DSC-0020","status":"done","ticket":"jenkins-gitea-integration","title":"Jenkins Gitea Integration and Relocation","created_at":"2026-04-07","updated_at":"2026-04-07","tags":["ci","jenkins","gitea"],"agendas":[{"id":"AGD-0018","file":"workflow/agendas/AGD-0018-jenkins-gitea-integration-and-relocation.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}],"decisions":[{"id":"DEC-0003","file":"workflow/decisions/DEC-0003-jenkins-gitea-strategy.md","status":"accepted","created_at":"2026-04-07","updated_at":"2026-04-07"}],"plans":[{"id":"PLN-0003","file":"workflow/plans/PLN-0003-jenkins-gitea-execution.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}],"lessons":[{"id":"LSN-0021","file":"lessons/DSC-0020-jenkins-gitea-integration/LSN-0021-jenkins-gitea-integration.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}]} +{"type":"discussion","id":"DSC-0021","status":"done","ticket":"asset-entry-codec-enum-with-metadata","title":"Asset Entry Codec Enum Contract","created_at":"2026-04-09","updated_at":"2026-04-09","tags":["asset","runtime","codec","metadata"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0024","file":"lessons/DSC-0021-asset-entry-codec-enum-contract/LSN-0024-string-on-the-wire-enum-in-runtime.md","status":"done","created_at":"2026-04-09","updated_at":"2026-04-09"}]} +{"type":"discussion","id":"DSC-0022","status":"done","ticket":"tile-bank-vs-glyph-bank-domain-naming","title":"Glyph Bank Domain Naming Contract","created_at":"2026-04-09","updated_at":"2026-04-10","tags":["gfx","runtime","naming","domain-model"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0025","file":"lessons/DSC-0022-glyph-bank-domain-naming/LSN-0025-rename-artifact-by-meaning-not-by-token.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}]} +{"type":"discussion","id":"DSC-0001","status":"done","ticket":"legacy-runtime-learn-import","title":"Import legacy runtime learn into discussion lessons","created_at":"2026-03-27","updated_at":"2026-03-27","tags":["migration","tech-debt"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0001","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0001-prometeu-learn-index.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0002","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0002-historical-asset-status-first-fault-and-return-contract.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0003","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0003-historical-audio-status-first-fault-and-return-contract.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0004","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0004-historical-cartridge-boot-protocol-and-manifest-authority.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0005","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0005-historical-game-memcard-slots-surface-and-semantics.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0006","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0006-historical-gfx-status-first-fault-and-return-contract.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0007","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0007-historical-retired-fault-and-input-decisions.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0008","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0008-historical-vm-core-and-assets.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0009","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0009-mental-model-asset-management.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0010","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0010-mental-model-audio.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0011","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0011-mental-model-gfx.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0012","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0012-mental-model-input.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0013","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0013-mental-model-observability-and-debugging.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0014","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0014-mental-model-portability-and-cross-platform.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0015","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0015-mental-model-save-memory-and-memcard.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0016","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0016-mental-model-status-first-and-fault-thinking.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0017","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0017-mental-model-time-and-cycles.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0018","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0018-mental-model-touch.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"}]} +{"type":"discussion","id":"DSC-0002","status":"open","ticket":"runtime-edge-test-plan","title":"Agenda - Runtime Edge Test Plan","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0001","file":"workflow/agendas/AGD-0001-runtime-edge-test-plan.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]} +{"type":"discussion","id":"DSC-0003","status":"open","ticket":"packed-cartridge-loader-pmc","title":"Agenda - Packed Cartridge Loader PMC","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0002","file":"workflow/agendas/AGD-0002-packed-cartridge-loader-pmc.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]} +{"type":"discussion","id":"DSC-0004","status":"open","ticket":"system-run-cart","title":"Agenda - System Run Cart","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0003","file":"workflow/agendas/AGD-0003-system-run-cart.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]} +{"type":"discussion","id":"DSC-0005","status":"open","ticket":"system-fault-semantics-and-control-surface","title":"Agenda - System Fault Semantics and Control Surface","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0004","file":"workflow/agendas/AGD-0004-system-fault-semantics-and-control-surface.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]} +{"type":"discussion","id":"DSC-0006","status":"open","ticket":"vm-owned-random-service","title":"Agenda - VM-Owned Random Service","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0005","file":"workflow/agendas/AGD-0005-vm-owned-random-service.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]} +{"type":"discussion","id":"DSC-0007","status":"open","ticket":"app-home-filesystem-surface-and-semantics","title":"Agenda - App Home Filesystem Surface and Semantics","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0006","file":"workflow/agendas/AGD-0006-app-home-filesystem-surface-and-semantics.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]} +{"type":"discussion","id":"DSC-0008","status":"done","ticket":"perf-runtime-telemetry-hot-path","title":"Agenda - [PERF] Runtime Telemetry Hot Path","created_at":"2026-03-27","updated_at":"2026-04-10","tags":[],"agendas":[{"id":"AGD-0007","file":"workflow/agendas/AGD-0007-perf-runtime-telemetry-hot-path.md","status":"done","created_at":"2026-03-27","updated_at":"2026-04-10"}],"decisions":[{"id":"DEC-0005","file":"workflow/decisions/DEC-0005-perf-push-based-telemetry-model.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10"}],"plans":[{"id":"PLN-0005","file":"workflow/plans/PLN-0005-perf-push-based-telemetry-implementation.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}],"lessons":[{"id":"LSN-0026","file":"lessons/DSC-0008-perf-runtime-telemetry-hot-path/LSN-0026-push-based-telemetry-model.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}]} +{"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":"open","ticket":"perf-runtime-introspection-syscalls","title":"Agenda - [PERF] Runtime Introspection Syscalls","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0011","file":"workflow/agendas/AGD-0011-perf-runtime-introspection-syscalls.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]} +{"type":"discussion","id":"DSC-0013","status":"open","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":[{"id":"AGD-0012","file":"workflow/agendas/AGD-0012-perf-host-debug-overlay-isolation.md","status":"done","created_at":"2026-03-27","updated_at":"2026-04-10"},{"id":"AGD-0022","file":"AGD-0022-host-overlay-tooling-boundary-revision.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10"},{"id":"AGD-0023","file":"AGD-0023-overlay-log-metric-last-frame.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10"}],"decisions":[{"id":"DEC-0007","file":"workflow/decisions/DEC-0007-perf-host-debug-overlay-isolation.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10"},{"id":"DEC-0009","file":"DEC-0009-host-overlay-tooling-boundary.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10","ref_agenda":"AGD-0022"},{"id":"DEC-0010","file":"DEC-0010-overlay-log-metric-last-frame.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10","ref_agenda":"AGD-0023"}],"plans":[{"id":"PLN-0006","file":"workflow/plans/PLN-0006-perf-host-debug-overlay-isolation.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"},{"id":"PLN-0008","file":"PLN-0008-host-overlay-native-composition-alignment.md","status":"in_progress","created_at":"2026-04-10","updated_at":"2026-04-10","ref_decisions":["DEC-0007","DEC-0008","DEC-0009","DEC-0010"]}],"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":"open","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":[{"id":"AGD-0024","file":"AGD-0024-generic-memory-bank-slot-contract.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10"}],"decisions":[{"id":"DEC-0012","file":"DEC-0012-asset-manager-bank-telemetry-slot-contract.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10","ref_agenda":"AGD-0024"}],"plans":[{"id":"PLN-0010","file":"PLN-0010-asset-manager-bank-telemetry-slot-contract.md","status":"in_progress","created_at":"2026-04-10","updated_at":"2026-04-10","ref_decisions":["DEC-0012"]}],"lessons":[]} +{"type":"discussion","id":"DSC-0014","status":"open","ticket":"perf-vm-allocation-and-copy-pressure","title":"Agenda - [PERF] VM Allocation and Copy Pressure","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0013","file":"workflow/agendas/AGD-0013-perf-vm-allocation-and-copy-pressure.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]} +{"type":"discussion","id":"DSC-0015","status":"open","ticket":"perf-cartridge-boot-and-program-ownership","title":"Agenda - [PERF] Cartridge Boot and Program Ownership","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0014","file":"workflow/agendas/AGD-0014-perf-cartridge-boot-and-program-ownership.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]} +{"type":"discussion","id":"DSC-0016","status":"done","ticket":"tilemap-empty-cell-vs-tile-id-zero","title":"Tilemap Empty Cell vs Tile ID Zero","created_at":"2026-03-27","updated_at":"2026-04-09","tags":[],"agendas":[{"id":"AGD-0015","file":"workflow/agendas/AGD-0015-tilemap-empty-cell-vs-tile-id-zero.md","status":"done","created_at":"2026-03-27","updated_at":"2026-04-09"}],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0022","file":"lessons/DSC-0016-tilemap-empty-cell-semantics/LSN-0022-tilemap-empty-cell-convergence.md","status":"done","created_at":"2026-04-09","updated_at":"2026-04-09"}]} +{"type":"discussion","id":"DSC-0017","status":"done","ticket":"asset-entry-metadata-normalization-contract","title":"Asset Entry Metadata Normalization Contract","created_at":"2026-03-27","updated_at":"2026-04-09","tags":[],"agendas":[{"id":"AGD-0016","file":"workflow/agendas/AGD-0016-asset-entry-metadata-normalization-contract.md","status":"done","created_at":"2026-03-27","updated_at":"2026-04-09"}],"decisions":[{"id":"DEC-0004","file":"workflow/decisions/DEC-0004-asset-entry-metadata-normalization-contract.md","status":"accepted","created_at":"2026-04-09","updated_at":"2026-04-09"}],"plans":[],"lessons":[{"id":"LSN-0023","file":"lessons/DSC-0017-asset-metadata-normalization/LSN-0023-typed-asset-metadata-helpers.md","status":"done","created_at":"2026-04-09","updated_at":"2026-04-09"}]} +{"type":"discussion","id":"DSC-0018","status":"done","ticket":"asset-load-asset-id-int-contract","title":"Asset Load Asset ID Int Contract","created_at":"2026-03-27","updated_at":"2026-03-27","tags":["asset","runtime","abi"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0019","file":"lessons/DSC-0018-asset-load-asset-id-int-contract/LSN-0019-asset-load-id-abi-convergence.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"}]} +{"type":"discussion","id":"DSC-0019","status":"done","ticket":"jenkinsfile-correction","title":"Jenkinsfile Correction and Relocation","created_at":"2026-04-07","updated_at":"2026-04-07","tags":["ci","jenkins"],"agendas":[{"id":"AGD-0017","file":"workflow/agendas/AGD-0017-jenkinsfile-correction.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}],"decisions":[{"id":"DEC-0002","file":"workflow/decisions/DEC-0002-jenkinsfile-strategy.md","status":"accepted","created_at":"2026-04-07","updated_at":"2026-04-07"}],"plans":[{"id":"PLN-0002","file":"workflow/plans/PLN-0002-jenkinsfile-execution.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}],"lessons":[{"id":"LSN-0020","file":"lessons/DSC-0019-jenkins-ci-standardization/LSN-0020-jenkins-standard-relocation.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}]} diff --git a/discussion/index.ndjson b/discussion/index.ndjson index 0e13da96..a6ed74ff 100644 --- a/discussion/index.ndjson +++ b/discussion/index.ndjson @@ -1,4 +1,4 @@ -{"type":"meta","next_id":{"DSC":25,"AGD":25,"DEC":13,"PLN":11,"LSN":29,"CLSN":1}} +{"type":"meta","next_id":{"DSC":25,"AGD":25,"DEC":13,"PLN":11,"LSN":30,"CLSN":1}} {"type":"discussion","id":"DSC-0023","status":"done","ticket":"perf-full-migration-to-atomic-telemetry","title":"Agenda - [PERF] Full Migration to Atomic Telemetry","created_at":"2026-04-10","updated_at":"2026-04-10","tags":["perf","runtime","telemetry"],"agendas":[{"id":"AGD-0021","file":"workflow/agendas/AGD-0021-full-migration-to-atomic-telemetry.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}],"decisions":[{"id":"DEC-0008","file":"workflow/decisions/DEC-0008-full-migration-to-atomic-telemetry.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10"}],"plans":[{"id":"PLN-0007","file":"workflow/plans/PLN-0007-full-migration-to-atomic-telemetry.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}],"lessons":[{"id":"LSN-0028","file":"lessons/DSC-0023-perf-full-migration-to-atomic-telemetry/LSN-0028-converging-to-single-atomic-telemetry-source.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}]} {"type":"discussion","id":"DSC-0020","status":"done","ticket":"jenkins-gitea-integration","title":"Jenkins Gitea Integration and Relocation","created_at":"2026-04-07","updated_at":"2026-04-07","tags":["ci","jenkins","gitea"],"agendas":[{"id":"AGD-0018","file":"workflow/agendas/AGD-0018-jenkins-gitea-integration-and-relocation.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}],"decisions":[{"id":"DEC-0003","file":"workflow/decisions/DEC-0003-jenkins-gitea-strategy.md","status":"accepted","created_at":"2026-04-07","updated_at":"2026-04-07"}],"plans":[{"id":"PLN-0003","file":"workflow/plans/PLN-0003-jenkins-gitea-execution.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}],"lessons":[{"id":"LSN-0021","file":"lessons/DSC-0020-jenkins-gitea-integration/LSN-0021-jenkins-gitea-integration.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}]} {"type":"discussion","id":"DSC-0021","status":"done","ticket":"asset-entry-codec-enum-with-metadata","title":"Asset Entry Codec Enum Contract","created_at":"2026-04-09","updated_at":"2026-04-09","tags":["asset","runtime","codec","metadata"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0024","file":"lessons/DSC-0021-asset-entry-codec-enum-contract/LSN-0024-string-on-the-wire-enum-in-runtime.md","status":"done","created_at":"2026-04-09","updated_at":"2026-04-09"}]} @@ -16,7 +16,7 @@ {"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":"open","ticket":"perf-runtime-introspection-syscalls","title":"Agenda - [PERF] Runtime Introspection Syscalls","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0011","file":"workflow/agendas/AGD-0011-perf-runtime-introspection-syscalls.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]} {"type":"discussion","id":"DSC-0013","status":"open","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":[{"id":"AGD-0012","file":"workflow/agendas/AGD-0012-perf-host-debug-overlay-isolation.md","status":"done","created_at":"2026-03-27","updated_at":"2026-04-10"},{"id":"AGD-0022","file":"AGD-0022-host-overlay-tooling-boundary-revision.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10"},{"id":"AGD-0023","file":"AGD-0023-overlay-log-metric-last-frame.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10"}],"decisions":[{"id":"DEC-0007","file":"workflow/decisions/DEC-0007-perf-host-debug-overlay-isolation.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10"},{"id":"DEC-0009","file":"DEC-0009-host-overlay-tooling-boundary.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10","ref_agenda":"AGD-0022"},{"id":"DEC-0010","file":"DEC-0010-overlay-log-metric-last-frame.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10","ref_agenda":"AGD-0023"}],"plans":[{"id":"PLN-0006","file":"workflow/plans/PLN-0006-perf-host-debug-overlay-isolation.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"},{"id":"PLN-0008","file":"PLN-0008-host-overlay-native-composition-alignment.md","status":"in_progress","created_at":"2026-04-10","updated_at":"2026-04-10","ref_decisions":["DEC-0007","DEC-0008","DEC-0009","DEC-0010"]}],"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":"open","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":[{"id":"AGD-0024","file":"AGD-0024-generic-memory-bank-slot-contract.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10"}],"decisions":[{"id":"DEC-0012","file":"DEC-0012-asset-manager-bank-telemetry-slot-contract.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10","ref_agenda":"AGD-0024"}],"plans":[{"id":"PLN-0010","file":"PLN-0010-asset-manager-bank-telemetry-slot-contract.md","status":"in_progress","created_at":"2026-04-10","updated_at":"2026-04-10","ref_decisions":["DEC-0012"]}],"lessons":[]} +{"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-0014","status":"open","ticket":"perf-vm-allocation-and-copy-pressure","title":"Agenda - [PERF] VM Allocation and Copy Pressure","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0013","file":"workflow/agendas/AGD-0013-perf-vm-allocation-and-copy-pressure.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]} {"type":"discussion","id":"DSC-0015","status":"open","ticket":"perf-cartridge-boot-and-program-ownership","title":"Agenda - [PERF] Cartridge Boot and Program Ownership","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0014","file":"workflow/agendas/AGD-0014-perf-cartridge-boot-and-program-ownership.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]} {"type":"discussion","id":"DSC-0016","status":"done","ticket":"tilemap-empty-cell-vs-tile-id-zero","title":"Tilemap Empty Cell vs Tile ID Zero","created_at":"2026-03-27","updated_at":"2026-04-09","tags":[],"agendas":[{"id":"AGD-0015","file":"workflow/agendas/AGD-0015-tilemap-empty-cell-vs-tile-id-zero.md","status":"done","created_at":"2026-03-27","updated_at":"2026-04-09"}],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0022","file":"lessons/DSC-0016-tilemap-empty-cell-semantics/LSN-0022-tilemap-empty-cell-convergence.md","status":"done","created_at":"2026-04-09","updated_at":"2026-04-09"}]} diff --git a/discussion/lessons/DSC-0024-generic-memory-bank-slot-contract/LSN-0029-slot-first-bank-telemetry-belongs-in-asset-manager.md b/discussion/lessons/DSC-0024-generic-memory-bank-slot-contract/LSN-0029-slot-first-bank-telemetry-belongs-in-asset-manager.md new file mode 100644 index 00000000..41ac47c7 --- /dev/null +++ b/discussion/lessons/DSC-0024-generic-memory-bank-slot-contract/LSN-0029-slot-first-bank-telemetry-belongs-in-asset-manager.md @@ -0,0 +1,70 @@ +--- +id: LSN-0029 +ticket: generic-memory-bank-slot-contract +title: Slot-First Bank Telemetry Belongs in AssetManager +created: 2026-04-10 +tags: [runtime, asset, telemetry, memory-bank, slots] +--- + +## Context + +This discussion started from a mismatch between how memory banks were modeled and how engineers actually reason about them in the runtime. The public-facing bank path still leaned on byte totals and helper structures such as `BankStats`, even though the actual operational model was slot occupancy for `GLYPH` and `SOUNDS`. + +The refactor closed that gap by making the visible bank summary slot-first and moving the canonical summary ownership to `AssetManager`. + +## Key Decisions + +### AssetManager Owns the Visible Bank Summary + +**What:** +The canonical visible bank telemetry is exposed by `AssetManager`, not by `MemoryBanks`, and uses `BankTelemetry { bank_type, used_slots, total_slots }`. + +**Why:** +`AssetManager` is the domain owner that already knows loaded assets, active slot occupancy, and the bank summary that host tooling and certification need. Keeping the visible summary there prevents the abstraction from drifting into a generic but less useful lower-level surface. + +**Trade-offs:** +This keeps the visible contract simpler and more explicit, but it also means low-level byte accounting and residency details cannot be treated as the primary public model anymore. + +### Slot Occupancy Is the Operational Truth + +**What:** +The public bank summary is slot-first. Detailed inspection remains per-slot through slot references and slot enums. + +**Why:** +Banks are operated, debugged, and visualized in terms of occupied slots, not aggregate bytes. Overlay and certification become easier to understand when they read the same unit the runtime actually uses to allocate and inspect banks. + +**Trade-offs:** +Byte totals may still exist internally for implementation needs, but they no longer define the public contract. Any internal byte path must stay subordinate to the slot model. + +### Certification Must Share the Same Unit as Inspection + +**What:** +Certification moved from `max_gfx_bytes` / `max_audio_bytes` to slot-based ceilings such as `max_glyph_slots_used` and `max_sound_slots_used`. + +**Why:** +A certification rule should use the same visible semantics as overlay and diagnostics. If inspection is slot-first but certification is byte-first, engineers end up debugging two different models for the same bank state. + +**Trade-offs:** +The migration removes some old byte-oriented limits, so any future need for byte budgeting must be introduced as a separate internal concern instead of piggybacking on the visible bank contract. + +## Patterns and Algorithms + +- Put the canonical telemetry contract at the domain owner that already knows the operational state, instead of at a lower abstraction that would need extra interpretation. +- Prefer the unit engineers actually use in debugging and operations for the public contract. +- Keep detailed inspection and summary telemetry separate: + summary telemetry answers "how full is each bank?"; + slot inspection answers "what is in this slot?". +- When a public telemetry model changes units, migrate certification and debugger payloads in the same change set. + +## Pitfalls + +- Leaving byte-oriented helper types in the visible path keeps old semantics alive even after the new summary exists. +- Moving the generic contract too low in the stack can create an abstract API that is technically reusable but no longer aligned with the runtime's operational owner. +- Updating overlay without updating certification creates inconsistent diagnostics. +- Renaming presentation labels without aligning canonical bank names causes drift between `BankType`, telemetry payloads, and docs. + +## Takeaways + +- Public bank telemetry should reflect slot occupancy, not leftover byte-accounting structures. +- `AssetManager` is the right place for the canonical visible bank summary because it owns the practical bank state. +- Overlay, debugger, syscalls, and certification should all consume the same bank unit to avoid semantic drift. diff --git a/discussion/workflow/agendas/AGD-0024-generic-memory-bank-slot-contract.md b/discussion/workflow/agendas/AGD-0024-generic-memory-bank-slot-contract.md deleted file mode 100644 index 6dc203cb..00000000 --- a/discussion/workflow/agendas/AGD-0024-generic-memory-bank-slot-contract.md +++ /dev/null @@ -1,147 +0,0 @@ ---- -id: AGD-0024 -ticket: generic-memory-bank-slot-contract -title: Agenda - Generic Memory Bank Slot Contract -status: accepted -created: 2026-04-10 -resolved: 2026-04-10 -decision: DEC-0012 -tags: [runtime, asset, memory-bank, slots, host] ---- - -# Agenda - Generic Memory Bank Slot Contract - -## Contexto - -Hoje o runtime e o host expõem bancos de memória principalmente pela ótica de bytes totais/usados, enquanto a organização real do hardware já é fortemente orientada a slots. No estado atual: - -- `GFX` e `AUD` aparecem como casos especiais espalhados entre `AssetManager`, `MemoryBanks`, `AssetBridge`, telemetria e overlay; -- os pools concretos são específicos (`glyph_bank_pool`, `sound_bank_pool`); -- a visualização do overlay precisou reinterpretar um modelo em bytes para algo que, operacionalmente, é mais bem entendido como ocupação de slots. - -O pedido do usuário é explicitamente mudar essa ênfase: manter bytes como detalhe secundário quando necessário, mas estruturar o modelo principal como **memory banks by slots**, com possibilidade de contrato específico no domínio de `MemoryBanks` para gerir esses valores. - -Na continuação da discussão, o usuário endureceu a direção: - -- `BankPolicy` deve ser removido por completo; -- `BankStats` deve ser removido por completo; -- o contrato exposto deve trabalhar somente com ocupação por slots, por exemplo: - - `glyph_slots_occupied / glyph_slots.len()` - - `sound_slots_occupied / sound_slots.len()` - -## Problema - -O modelo atual mistura dois níveis de abstração: - -1. **Contrato de capacidade em bytes**, útil para certificação e budgets; -2. **Contrato operacional por slots**, que é o que o host e o programador realmente percebem ao lidar com banks. - -Isso gera acoplamento e duplicação: - -- o domínio conhece `GFX` e `AUD` como exceções, em vez de bancos genéricos com propriedades próprias; -- `BankStats` privilegia bytes e trata slots como apêndice; -- `MemoryBanks` não oferece um contrato consolidado para estatísticas e ocupação por slots; -- o overlay precisa montar sua própria leitura mais útil em cima de um modelo que não foi desenhado para isso. - -Com a direção nova do usuário, há um problema adicional: não basta rebaixar bytes a papel secundário no contrato exposto. As estruturas atuais que orbitam essa semântica (`BankPolicy` e `BankStats`) passam a ser vistas como parte do problema e não como base aceitável para a solução. - -## Pontos Criticos - -- **Fato:** O hardware já é organizado por slots para glyph e sound banks. -- **Fato:** O overlay quer mostrar ocupação por slots, não capacidade em bytes. -- **Fato:** O usuário quer que o contrato exposto use apenas `used_slots` e `total_slots`. -- **Fato:** `BankPolicy` e `BankStats` não devem permanecer como contrato nem como modelagem principal deste domínio. -- **Risco:** Remover essas estruturas sem separar claramente contrato exposto e necessidades internas pode quebrar telemetria e certificação existentes. -- **Risco:** Se bytes continuarem aparecendo no contrato público, a refatoração perde seu objetivo. -- **Tradeoff:** Um contrato mínimo de slots simplifica host/overlay e o domínio exposto, mas exige reposicionar qualquer necessidade residual baseada em bytes. - -## Opcoes - -- **Opção A (Recomendada):** Introduzir um contrato genérico de memory bank orientado exclusivamente a slots no contrato exposto. - - `MemoryBanks` passa a oferecer um contrato explícito para: - - quantidade de slots; - - slots ocupados; - - consulta de slots; - - enumeração genérica de banks. - - `GLYPH` e `SOUND` tornam-se instâncias desse modelo. - - `BankPolicy` e `BankStats` deixam de existir como superfícies do contrato. - -- **Opção B:** Manter `BankPolicy` e `BankStats` internamente, escondendo-os só no host. - - Menor custo imediato. - - Não atende a direção explícita da discussão atual. - -- **Opção C:** Preservar um contrato híbrido com slots e bytes lado a lado. - - Dá continuidade incremental. - - Mantém exatamente a ambiguidade que o usuário quer remover. - -## Sugestao / Recomendacao - -Seguir com a **Opção A**, com os seguintes princípios: - -1. O domínio de memory banks deve ser **slot-first**. -2. O contrato exposto deve usar somente `used_slots` e `total_slots`. -3. `BankPolicy` e `BankStats` devem ser removidos por completo. -4. `MemoryBanks` deve possuir um contrato específico e explícito para ocupação por slots. -5. O host overlay deve consumir esse modelo genérico sem saber detalhes especiais de `GLYPH` vs `SOUNDS`, além de rótulo e contagem. -6. A revisão deve evitar abstração vazia: o contrato genérico precisa mapear diretamente para `GlyphBank` e `SoundBank`. - -Nomenclatura canônica acordada para a camada genérica: - -- `GLYPH` -- `SOUNDS` - -`GFX` e `AUD` não devem ser usados como nomes canônicos do contrato genérico de banks, pois são apelidos de apresentação e não os nomes corretos do domínio. - -## Perguntas em Aberto - -- Nenhuma resposta final ainda sobre a forma exata do artefato, mas em 2026-04-10 o usuário fechou os seguintes direcionadores: - - a telemetria necessária deve ser gerenciada pelo `AssetManager`, adicionando os banks ao payload; - - a telemetria deve carregar somente o enum do tipo do bank, não uma abstração genérica adicional no contrato; - - o detalhamento operacional deve ocorrer por enum de slot; - - exemplos concretos do formato esperado ainda precisam ser avaliados antes de encerrar a agenda. - -### Respostas consolidadas desta rodada - -1. **Origem da telemetria:** não mover a genericidade principal para `MemoryBanks`; o `AssetManager` deve gerenciar e expor a telemetria necessária dos banks. -2. **Forma da telemetria:** a telemetria exposta deve carregar somente o enum do tipo do bank. -3. **Detalhamento dos slots:** a leitura operacional deve ser feita por enum de slot. -4. **Formato preferido:** seguir com um resumo por bank no formato do exemplo 1 (`bank_type`, `used_slots`, `total_slots`). -5. **Bytes fake:** bytes não devem continuar no contrato novo de telemetria dos banks. -6. **Certificação:** as regras `max_gfx_bytes` e `max_audio_bytes` devem sair e ser substituídas por limites de slots para `GLYPH` e `SOUND`. -7. **Origem do contrato visível:** o `AssetManager` deve expor diretamente esse resumo por bank. - -Exemplo alvo discutido: - -```rust -pub struct BankTelemetry { - pub bank_type: BankType, - pub used_slots: usize, - pub total_slots: usize, -} -``` - -Origem esperada: - -```rust -impl AssetManager { - pub fn bank_telemetry(&self) -> Vec { ... } -} -``` - -Impacto já identificado para certificação: - -- remover `max_gfx_bytes` -- remover `max_audio_bytes` -- substituir por algo como: - - `max_glyph_slots_used` - - `max_sound_slots_used` - -## Criterio para Encerrar - -Esta agenda pode ser encerrada quando houver direção fechada sobre: - -- contrato genérico por slots; -- remoção completa de `BankPolicy` e `BankStats`; -- ponto de residência da abstração (`MemoryBanks`, HAL, ou ambos); -- impacto esperado em overlay, telemetria e `AssetManager`. -*(Critérios atingidos em 2026-04-10)* diff --git a/discussion/workflow/decisions/DEC-0012-asset-manager-bank-telemetry-slot-contract.md b/discussion/workflow/decisions/DEC-0012-asset-manager-bank-telemetry-slot-contract.md deleted file mode 100644 index 03089200..00000000 --- a/discussion/workflow/decisions/DEC-0012-asset-manager-bank-telemetry-slot-contract.md +++ /dev/null @@ -1,92 +0,0 @@ ---- -id: DEC-0012 -ticket: generic-memory-bank-slot-contract -title: Decision - Asset Manager Bank Telemetry Slot Contract -status: accepted -created: 2026-04-10 -updated: 2026-04-10 -agenda: AGD-0024 -tags: [runtime, asset, memory-bank, slots, host, telemetry] ---- - -# Decision - Asset Manager Bank Telemetry Slot Contract - -## Status -**Accepted** - -## Contexto -AGD-0024 fechou a mudança do contrato de banks de um modelo orientado a bytes para um modelo orientado a slots. A agenda também fechou que `BankPolicy` e `BankStats` não são base aceitável para a nova superfície visível e que a telemetria operacional consumida por host e overlay deve sair diretamente do `AssetManager`. - -Os pontos que a agenda resolveu e que esta decisão precisa cristalizar sem ambiguidade são: - -- o contrato visível é `slot-first`; -- o resumo visível por bank usa apenas `bank_type`, `used_slots` e `total_slots`; -- o detalhamento operacional ocorre por enum de slot; -- `GLYPH` e `SOUNDS` são os nomes canônicos do domínio; -- `GFX` e `AUD` não são nomes canônicos do contrato; -- a certificação deixa de usar bytes e passa a usar limites por slots; -- a genericidade principal do contrato visível não reside em `MemoryBanks`. - -## Decisao -1. O contrato visível de telemetria de banks MUST ser exposto diretamente por `AssetManager`. -2. O contrato visível de telemetria de banks MUST NOT ter `MemoryBanks` como superfície principal de exposição. `MemoryBanks` MAY manter detalhes internos de suporte, mas o ponto canônico de consumo visível do sistema SHALL ser `AssetManager`. -3. O resumo canônico por bank MUST usar somente os campos `bank_type`, `used_slots` e `total_slots`, no formato: - -```rust -pub struct BankTelemetry { - pub bank_type: BankType, - pub used_slots: usize, - pub total_slots: usize, -} -``` - -4. O contrato visível de telemetria de banks MUST ser `slot-first` e MUST NOT depender de contagens de bytes. -5. `bank_type` MUST usar nomes canônicos do domínio. Para os banks atuais, os nomes canônicos são `GLYPH` e `SOUNDS`. -6. `GFX` e `AUD` MUST NOT aparecer como nomes canônicos do contrato genérico de banks. Eles MAY existir apenas como rótulos de apresentação, quando estritamente necessário fora do contrato canônico. -7. `BankPolicy` e `BankStats` MUST ser removidos completamente do caminho do contrato visível de telemetria de banks e MUST NOT permanecer como modelagem principal desse domínio exposto. -8. O overlay do host MUST consumir a telemetria exposta por `AssetManager` e MUST NOT manter lógica hardcoded dependente de bancos específicos para calcular ocupação principal. -9. O detalhamento operacional por slot MUST ser definido por enum de slot, e qualquer inspeção detalhada de ocupação SHALL seguir esse modelo, não um detalhamento baseado em bytes. -10. As regras de certificação `max_gfx_bytes` e `max_audio_bytes` MUST ser removidas. -11. A certificação de banks MUST migrar para limites por slots, usando nomes canônicos de bank, tais como: - - `max_glyph_slots_used` - - `max_sound_slots_used` -12. Qualquer contabilização residual em bytes MAY sobreviver apenas como detalhe interno de implementação, quando estritamente necessária, e MUST NOT permanecer no contrato visível de telemetria de banks. - -## Rationale -- Slots são a unidade operacional real percebida por host, overlay e depuração de banks. -- `AssetManager` é o ponto que já conhece a ocupação efetiva dos banks e, portanto, é o lugar correto para expor o resumo visível. -- Remover bytes do contrato visível evita uma telemetria enganosa e elimina a ambiguidade que a agenda abriu para resolver. -- Fixar `GLYPH` e `SOUNDS` como nomes canônicos evita contaminar o contrato com apelidos de apresentação. -- Fixar enum de slot como detalhamento operacional evita reabrir a discussão sob outra forma durante a implementação. -- Migrar certificação para slots mantém o contrato exposto, o overlay e os limites técnicos usando a mesma semântica. - -## Invariantes / Contrato -- A telemetria de banks exposta ao restante do sistema sai de `AssetManager`. -- O contrato visível principal não reside em `MemoryBanks`. -- Cada entrada de telemetria de bank informa somente `bank_type`, `used_slots` e `total_slots`. -- O contrato canônico usa `GLYPH` e `SOUNDS`. -- O detalhamento operacional de slots usa enum de slot. -- O overlay não depende de bytes para mostrar ocupação de banks. -- A certificação de banks não depende de bytes. - -## Impactos -- **AssetManager:** deve expor `Vec` ou equivalente direto como superfície canônica visível. -- **MemoryBanks:** pode continuar como suporte interno, mas não deve concentrar o contrato visível principal de telemetria. -- **HAL / Bridges:** precisam alinhar qualquer interface consumidora ao contrato `slot-first`. -- **Overlay:** deve iterar a telemetria de banks do `AssetManager` e parar de recomputar a ocupação principal a partir de bytes. -- **Certifier:** precisa trocar limites de bytes por limites de slots para banks. -- **Legado:** `BankPolicy`, `BankStats`, `max_gfx_bytes` e `max_audio_bytes` precisam ser removidos do caminho canônico dessa capacidade. - -## Referencias -- `AGD-0024`: Generic Memory Bank Slot Contract. -- `DEC-0010`: Overlay Log Metric Uses Last Completed Frame. - -## Propagacao Necessaria -1. O plano de execução deve refatorar `AssetManager`, `MemoryBanks`, HAL/bridges, overlay e certificação sem reabrir a arquitetura base. -2. O caminho de telemetria de banks baseado em bytes deve ser removido do contrato visível. -3. O detalhamento operacional por enum de slot deve ser propagado para os pontos que inspecionam ocupação detalhada. -4. A certificação de banks deve migrar de limites em bytes para limites em slots. - -## Revision Log -- 2026-04-10: Initial decision emitted from `AGD-0024`. -- 2026-04-10: Decision tightened to align explicitly with agenda closure on `AssetManager` ownership, slot-enum operational detail, and `MemoryBanks` non-canonical residency. diff --git a/discussion/workflow/plans/PLN-0010-asset-manager-bank-telemetry-slot-contract.md b/discussion/workflow/plans/PLN-0010-asset-manager-bank-telemetry-slot-contract.md deleted file mode 100644 index 3b17cf2f..00000000 --- a/discussion/workflow/plans/PLN-0010-asset-manager-bank-telemetry-slot-contract.md +++ /dev/null @@ -1,194 +0,0 @@ ---- -id: PLN-0010 -ticket: generic-memory-bank-slot-contract -title: PR/Plan - Asset Manager Bank Telemetry Slot Contract -status: in_progress -created: 2026-04-10 -completed: -tags: [runtime, asset, memory-bank, slots, host, telemetry, certification] ---- - -## Briefing - -Implement `DEC-0012` by replacing the current byte-oriented bank telemetry path with a slot-first contract exposed directly by `AssetManager`. The execution MUST remove `BankPolicy` and `BankStats` from the visible telemetry path, keep canonical bank names aligned with `BankType` (`GLYPH`, `SOUNDS`), make the host overlay consume the new `AssetManager` summary, and migrate bank certification from byte limits to slot limits. - -## Decisions de Origem - -- `DEC-0012` - Asset Manager Bank Telemetry Slot Contract - -## Alvo - -Deliver a canonical bank telemetry path with these properties: - -- `AssetManager` exposes the visible per-bank summary. -- The visible summary shape is slot-first and limited to `bank_type`, `used_slots`, and `total_slots`. -- Detailed inspection remains slot-based through `SlotRef` / slot enums, not byte-based accounting. -- Host overlay and certification consume the new model. -- Legacy byte-oriented bank telemetry fields and limits are removed from the canonical path. - -## Escopo - -### Included - -- Add a canonical `BankTelemetry` type to the shared HAL asset contract. -- Add an `AssetBridge` method for bank telemetry summaries exposed by `AssetManager`. -- Refactor `AssetManager` in `crates/console/prometeu-drivers` to compute slot-first bank telemetry. -- Remove `BankStats` from the visible bank telemetry path and stop using it for overlay and certification. -- Remove `BankPolicy` as the main modeling surface used to describe visible bank telemetry. -- Keep detailed slot inspection on explicit slot references instead of bank byte summaries. -- Update runtime telemetry / certification to track `max_glyph_slots_used` and `max_sound_slots_used`. -- Update host overlay consumers to render bank occupancy from the new `AssetManager` summary. -- Update runtime specs so the published contract describes slot-first bank telemetry and slot-based certification. - -## Fora de Escopo - -- Reopening the base architectural decision about whether the visible summary belongs in `AssetManager`. -- Introducing new bank categories beyond the current `GLYPH` and `SOUNDS`. -- Reworking unrelated asset loading semantics, cartridge payload layout, or host UI design beyond adapting bank telemetry consumption. -- Removing all internal byte accounting if some private implementation detail still needs it temporarily. - -## Plano de Execucao - -### Step 1 - Lock the shared contract in HAL - -**What:** -Define the canonical shared types and trait surface for slot-first bank telemetry. - -**How:** -Add `BankTelemetry` to `crates/console/prometeu-hal/src/asset.rs` using `bank_type`, `used_slots`, and `total_slots`. Update `crates/console/prometeu-hal/src/asset_bridge.rs` so the visible bridge exposes a bank telemetry summary method from `AssetManager`. Remove or deprecate `BankStats` from the visible bridge path so downstream callers stop depending on byte-oriented bank summaries. - -**File(s):** -- `crates/console/prometeu-hal/src/asset.rs` -- `crates/console/prometeu-hal/src/asset_bridge.rs` - -**Depends on:** -- None - -### Step 2 - Refactor AssetManager to produce slot-first summaries - -**What:** -Move visible bank summary generation to a canonical `AssetManager` slot telemetry path. - -**How:** -Replace `bank_info(BankType) -> BankStats` with a summary path that reports only slot occupancy for each canonical bank type. Compute `used_slots` from the slot arrays already owned by `AssetManager` (`gfx_slots`, `sound_slots`) or equivalent canonical occupancy state. Keep detailed per-slot inspection in `slot_info(SlotRef)`. Remove `BankPolicy` and its byte counters as the visible modeling surface for bank telemetry; if any internal storage helper remains temporarily, it must not leak into the shared contract. - -**File(s):** -- `crates/console/prometeu-drivers/src/asset.rs` -- `crates/console/prometeu-drivers/src/memory_banks.rs` - -**Depends on:** -- Step 1 - -### Step 3 - Migrate runtime and host consumers to the new summary - -**What:** -Replace every bank summary consumer that still expects byte-oriented telemetry. - -**How:** -Update runtime and host callers to consume `BankTelemetry` from `AssetManager` instead of `BankStats`. Replace any direct `bank_info(BankType)` callsites with the new canonical summary path. Preserve slot-level inspection through `slot_info(SlotRef)` where detailed occupancy is needed. The host overlay must render bank occupancy from the `AssetManager` summary and must not reconstruct primary bank occupancy from bytes. - -**File(s):** -- `crates/console/prometeu-system/src/virtual_machine_runtime/tick.rs` -- `crates/host/prometeu-host-desktop-winit/src/overlay.rs` -- Any additional callsites found through `rg 'bank_info|BankStats' crates` - -**Depends on:** -- Step 2 - -### Step 4 - Replace certification byte limits with slot limits - -**What:** -Move bank certification from byte ceilings to slot ceilings. - -**How:** -Replace `max_gfx_bytes` and `max_audio_bytes` in telemetry / certification configuration and evaluation with slot-based limits such as `max_glyph_slots_used` and `max_sound_slots_used`. Update runtime accounting to record the maximum occupied slot count per bank during execution. Remove byte-limit evaluation from the bank certification path. - -**File(s):** -- `crates/console/prometeu-hal/src/telemetry.rs` -- `crates/console/prometeu-system/src/virtual_machine_runtime/tick.rs` - -**Depends on:** -- Step 3 - -### Step 5 - Remove residual canonical-path names and semantics that conflict with DEC-0012 - -**What:** -Eliminate remaining visible-path terminology and structures that preserve the old model. - -**How:** -Ensure no visible bank telemetry path still uses `GFX` / `AUD` as canonical names, `BankStats` as the summary type, or byte fields as the public bank occupancy contract. Keep `GLYPH` and `SOUNDS` aligned with the existing `BankType` enum. If bridges, docs, or helper APIs still expose the old path, remove or rename them in the same change set so the canonical path is unambiguous. - -**File(s):** -- `crates/console/prometeu-hal/src/asset.rs` -- `crates/console/prometeu-hal/src/asset_bridge.rs` -- `crates/console/prometeu-drivers/src/asset.rs` -- `crates/console/prometeu-system/src/virtual_machine_runtime/tick.rs` -- `crates/host/prometeu-host-desktop-winit/src/overlay.rs` - -**Depends on:** -- Step 4 - -### Step 6 - Update published specifications - -**What:** -Align canonical docs with the implemented slot-first bank contract. - -**How:** -Update runtime specs to state that bank telemetry is exposed from `AssetManager`, that the published per-bank summary is slot-first, that detailed occupancy inspection remains slot-based, and that certification uses slot ceilings instead of byte ceilings. Remove residual published references to `max_gfx_bytes`, `max_audio_bytes`, or byte-oriented bank summaries in canonical specs. - -**File(s):** -- `docs/specs/runtime/15-asset-management.md` -- `docs/specs/runtime/10-debug-inspection-and-profiling.md` -- Any additional runtime spec file that still documents byte-based bank certification or overlay bank summary consumption - -**Depends on:** -- Step 5 - -## Criterios de Aceite - -- [ ] `AssetManager` is the canonical visible source of bank telemetry. -- [ ] The visible bank summary contract uses only `bank_type`, `used_slots`, and `total_slots`. -- [ ] Canonical bank names in the visible contract are `GLYPH` and `SOUNDS`. -- [ ] `BankStats` is no longer used as the visible bank telemetry summary type. -- [ ] `BankPolicy` is not the main visible modeling surface for bank telemetry. -- [ ] Host overlay bank occupancy uses the new `AssetManager` bank summary. -- [ ] Detailed occupancy inspection remains slot-based via slot references or equivalent slot enums. -- [ ] `max_gfx_bytes` and `max_audio_bytes` are removed from bank certification. -- [ ] Slot-based bank certification limits exist and are enforced. -- [ ] Published runtime specs describe the slot-first contract and slot-based certification model. - -## Tests / Validacao - -### Unit Tests - -- Add or update tests in `crates/console/prometeu-drivers/src/asset.rs` to verify the bank summary reports correct `used_slots` and `total_slots` for `GLYPH` and `SOUNDS`. -- Add or update tests in `crates/console/prometeu-hal/src/telemetry.rs` to verify slot-based certification thresholds trigger correctly and byte-based limits no longer exist in the bank path. -- Keep slot inspection tests proving `slot_info(SlotRef)` remains the detailed inspection mechanism. - -### Integration Tests - -- Run `cargo test -p prometeu-drivers`. -- Run `cargo test -p prometeu-hal`. -- Run `cargo test -p prometeu-system`. -- Run `cargo test -p prometeu-host-desktop-winit` if overlay bank telemetry callsites are changed there. -- Run `cargo check` for the full touched workspace if cross-crate API changes make targeted tests insufficient. - -### Manual Verification - -- Start the desktop host and confirm the overlay shows per-bank occupancy using slot counts, not bytes. -- Confirm the overlay labels use `GLYPH` and `SOUNDS` or approved presentation labels derived from those canonical bank types. -- Confirm certification output reports slot-limit violations and no longer reports `max_gfx_bytes` / `max_audio_bytes`. -- Confirm detailed slot inspection still works for both glyph and sound slots. - -## Dependencies - -- `DEC-0012` remains accepted and unchanged. -- Existing `BankType` enum values remain `GLYPH` and `SOUNDS`. -- `AssetManager` remains the asset-domain owner for slot occupancy summaries. - -## Riscos - -- Cross-crate trait changes in `AssetBridge` may create a wider refactor than the bank telemetry callsites initially suggest. -- Removing `BankStats` from the visible path may expose hidden byte-based assumptions in runtime telemetry, overlay formatting, or tests. -- Certification migration may fail partially if slot maxima are recorded in one layer while enforcement still reads old byte fields in another layer. -- If byte accounting is still needed internally during the refactor, the implementation can accidentally leak that detail back into public interfaces unless the change set is kept cohesive.