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: