Merge pull request 'dev/perf-host-debug-overlay-isolation' (#14) from dev/perf-host-debug-overlay-isolation into master
All checks were successful
Intrepid/Prometeu/Runtime/pipeline/head This commit looks good
All checks were successful
Intrepid/Prometeu/Runtime/pipeline/head This commit looks good
Reviewed-on: #14
This commit is contained in:
commit
8f98aee64e
@ -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<SoundBank>,
|
||||
|
||||
/// 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<Vec<HandleId>>,
|
||||
}
|
||||
@ -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<BankTelemetry> {
|
||||
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);
|
||||
|
||||
BankStats {
|
||||
total_bytes: 16 * 1024 * 1024,
|
||||
used_bytes,
|
||||
free_bytes: (16usize * 1024 * 1024).saturating_sub(used_bytes),
|
||||
inflight_bytes,
|
||||
slot_count: 16,
|
||||
slots_occupied,
|
||||
pub fn bank_telemetry(&self) -> Vec<BankTelemetry> {
|
||||
vec![self.bank_telemetry_for(BankType::GLYPH), self.bank_telemetry_for(BankType::SOUNDS)]
|
||||
}
|
||||
|
||||
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);
|
||||
self.sound_slots.read().unwrap().iter().filter(|slot| slot.is_some()).count()
|
||||
}
|
||||
};
|
||||
|
||||
BankStats {
|
||||
total_bytes: 32 * 1024 * 1024,
|
||||
used_bytes,
|
||||
free_bytes: (32usize * 1024 * 1024).saturating_sub(used_bytes),
|
||||
inflight_bytes,
|
||||
slot_count: 16,
|
||||
slots_occupied,
|
||||
}
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)]
|
||||
|
||||
@ -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<BankTelemetry>;
|
||||
fn slot_info(&self, slot: SlotRef) -> SlotStats;
|
||||
fn shutdown(&self);
|
||||
}
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -1,16 +1,23 @@
|
||||
use crate::log::{LogEvent, LogLevel, LogSource};
|
||||
use std::collections::VecDeque;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
|
||||
pub struct LogService {
|
||||
events: VecDeque<LogEvent>,
|
||||
capacity: usize,
|
||||
next_seq: u64,
|
||||
pub logs_count: u32,
|
||||
pub logs_count: Arc<AtomicU32>,
|
||||
}
|
||||
|
||||
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<LogEvent> {
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
use crate::log::{LogLevel, LogService, LogSource};
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicU32, AtomicU64, AtomicUsize, Ordering};
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct TelemetryFrame {
|
||||
@ -11,32 +13,101 @@ 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,
|
||||
pub heap_max_bytes: usize,
|
||||
|
||||
// Log Pressure
|
||||
// Log Pressure from the last completed logical frame
|
||||
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,
|
||||
|
||||
// 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,
|
||||
pub heap_max_bytes: AtomicUsize,
|
||||
|
||||
// Transient in-flight log counter for the current logical frame
|
||||
pub current_logs_count: Arc<AtomicU32>,
|
||||
// Persisted log count from the last completed logical frame
|
||||
pub logs_count: AtomicU32,
|
||||
}
|
||||
|
||||
impl AtomicTelemetry {
|
||||
pub fn new(current_logs_count: Arc<AtomicU32>) -> Self {
|
||||
Self { current_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),
|
||||
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),
|
||||
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.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);
|
||||
self.current_logs_count.store(0, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct CertificationConfig {
|
||||
pub enabled: bool,
|
||||
pub cycles_budget_per_frame: Option<u64>,
|
||||
pub max_syscalls_per_frame: Option<u32>,
|
||||
pub max_host_cpu_us_per_frame: Option<u64>,
|
||||
pub max_gfx_bytes: Option<usize>,
|
||||
pub max_audio_bytes: Option<usize>,
|
||||
pub max_glyph_slots_used: Option<u32>,
|
||||
pub max_sound_slots_used: Option<u32>,
|
||||
pub max_heap_bytes: Option<usize>,
|
||||
pub max_logs_per_frame: Option<u32>,
|
||||
}
|
||||
@ -116,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,
|
||||
@ -127,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,
|
||||
@ -145,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;
|
||||
@ -196,22 +267,22 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_certifier_violations() {
|
||||
let mut ls = LogService::new(10);
|
||||
let config = CertificationConfig {
|
||||
enabled: true,
|
||||
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);
|
||||
let mut ls = LogService::new(10);
|
||||
|
||||
let mut tel = TelemetryFrame::default();
|
||||
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);
|
||||
@ -220,6 +291,18 @@ 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]
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<u32, u32>,
|
||||
pub telemetry_current: TelemetryFrame,
|
||||
pub telemetry_last: TelemetryFrame,
|
||||
pub atomic_telemetry: Arc<AtomicTelemetry>,
|
||||
pub last_crash_report: Option<CrashReport>,
|
||||
pub certifier: Certifier,
|
||||
pub paused: bool,
|
||||
|
||||
@ -13,6 +13,7 @@ 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> {
|
||||
@ -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))
|
||||
})?;
|
||||
@ -485,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(())
|
||||
}
|
||||
|
||||
@ -7,6 +7,8 @@ use prometeu_hal::log::{LogLevel, LogSource};
|
||||
impl VirtualMachineRuntime {
|
||||
pub fn new(cap_config: Option<CertificationConfig>) -> 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,
|
||||
@ -88,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;
|
||||
@ -99,8 +101,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;
|
||||
|
||||
@ -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 {
|
||||
@ -273,10 +274,11 @@ 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.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);
|
||||
runtime.last_crash_report =
|
||||
Some(CrashReport::VmPanic { message: "stale".into(), pc: Some(55) });
|
||||
runtime.paused = true;
|
||||
@ -298,10 +300,11 @@ 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.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);
|
||||
assert!(runtime.last_crash_report.is_none());
|
||||
assert!(!runtime.paused);
|
||||
assert!(!runtime.debug_step_request);
|
||||
@ -331,7 +334,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 +359,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 { .. })));
|
||||
}
|
||||
|
||||
|
||||
@ -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,
|
||||
@ -63,15 +77,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 +102,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 +147,49 @@ impl VirtualMachineRuntime {
|
||||
{
|
||||
hw.gfx_mut().render_all();
|
||||
|
||||
// 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;
|
||||
// 1. Snapshot full telemetry at logical frame end
|
||||
let (glyph_bank, sound_bank) = Self::bank_telemetry_summary(hw);
|
||||
self.atomic_telemetry
|
||||
.glyph_slots_used
|
||||
.store(glyph_bank.used_slots as u32, Ordering::Relaxed);
|
||||
self.atomic_telemetry
|
||||
.glyph_slots_total
|
||||
.store(glyph_bank.total_slots as u32, Ordering::Relaxed);
|
||||
self.atomic_telemetry
|
||||
.sound_slots_used
|
||||
.store(sound_bank.used_slots as u32, Ordering::Relaxed);
|
||||
self.atomic_telemetry
|
||||
.sound_slots_total
|
||||
.store(sound_bank.total_slots 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
|
||||
.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.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 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;
|
||||
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;
|
||||
@ -189,34 +218,28 @@ 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;
|
||||
let (glyph_bank, sound_bank) = Self::bank_telemetry_summary(hw);
|
||||
self.atomic_telemetry
|
||||
.glyph_slots_used
|
||||
.store(glyph_bank.used_slots as u32, Ordering::Relaxed);
|
||||
self.atomic_telemetry
|
||||
.glyph_slots_total
|
||||
.store(glyph_bank.total_slots as u32, Ordering::Relaxed);
|
||||
self.atomic_telemetry
|
||||
.sound_slots_used
|
||||
.store(sound_bank.used_slots as u32, Ordering::Relaxed);
|
||||
self.atomic_telemetry
|
||||
.sound_slots_total
|
||||
.store(sound_bank.total_slots 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
|
||||
.heap_used_bytes
|
||||
.store(vm.heap().used_bytes.load(Ordering::Relaxed), 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;
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@ -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 {
|
||||
@ -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),
|
||||
};
|
||||
|
||||
@ -293,7 +303,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,
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
479
crates/host/prometeu-host-desktop-winit/src/overlay.rs
Normal file
479
crates/host/prometeu-host-desktop-winit/src/overlay.rs
Normal file
@ -0,0 +1,479 @@
|
||||
use crate::stats::HostStats;
|
||||
use prometeu_firmware::Firmware;
|
||||
|
||||
const PANEL_X: usize = 6;
|
||||
const PANEL_Y: usize = 3;
|
||||
const PANEL_WIDTH: usize = 170;
|
||||
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_X * 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<OverlayBar>,
|
||||
footer: Vec<OverlayMetric>,
|
||||
}
|
||||
|
||||
#[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 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 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![
|
||||
(
|
||||
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",
|
||||
tel.cycles_used as f64 / 1000.0,
|
||||
tel.cycles_budget as f64 / 1000.0
|
||||
)
|
||||
} else {
|
||||
"0.0K/0.0K".to_string()
|
||||
},
|
||||
ratio: cycles_ratio,
|
||||
warn: cycles_ratio >= 0.9,
|
||||
},
|
||||
OverlayBar {
|
||||
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: "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,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn draw_overlay(
|
||||
frame: &mut [u8],
|
||||
frame_width: usize,
|
||||
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(&mut canvas, panel_rect, BG);
|
||||
stroke_rect(&mut canvas, panel_rect, BORDER);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
for bar in &snapshot.bars {
|
||||
let color = if bar.warn { WARN } else { TEXT };
|
||||
draw_text(
|
||||
canvas.frame,
|
||||
canvas.width,
|
||||
canvas.height,
|
||||
PANEL_X + PANEL_PADDING_X,
|
||||
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_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;
|
||||
fill_rect(
|
||||
&mut canvas,
|
||||
Rect { x: bar_x, y, width: fill_width, height: BAR_HEIGHT },
|
||||
if bar.warn { BAR_WARN } else { BAR_FILL },
|
||||
);
|
||||
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(
|
||||
canvas.frame,
|
||||
canvas.width,
|
||||
canvas.height,
|
||||
PANEL_X + PANEL_PADDING_X,
|
||||
y,
|
||||
line.label,
|
||||
DIM,
|
||||
);
|
||||
draw_text(canvas.frame, canvas.width, canvas.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_X;
|
||||
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 {
|
||||
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(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(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(canvas: &mut FrameCanvas<'_>, rect: Rect, color: [u8; 4]) {
|
||||
if rect.width == 0 || rect.height == 0 {
|
||||
return;
|
||||
}
|
||||
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]) {
|
||||
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".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");
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
@ -122,107 +122,6 @@ impl HostRunner {
|
||||
w.request_redraw();
|
||||
}
|
||||
}
|
||||
|
||||
fn display_dbg_overlay(&mut self) {
|
||||
let tel = &self.firmware.os.telemetry_last;
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
if tel.violations > 0
|
||||
&& let Some(event) = self
|
||||
.firmware
|
||||
.os
|
||||
.log_service
|
||||
.get_recent(10)
|
||||
.into_iter()
|
||||
.rev()
|
||||
.find(|e| e.tag >= 0xCA01 && e.tag <= 0xCA03)
|
||||
{
|
||||
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 {
|
||||
@ -276,6 +175,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");
|
||||
|
||||
@ -287,6 +189,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() {
|
||||
@ -354,6 +260,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.
|
||||
@ -389,15 +296,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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,23 @@ 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 +74,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
|
||||
@ -68,7 +90,11 @@ 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);
|
||||
}
|
||||
|
||||
25
discussion/.backups/index.ndjson.20260410-192035.bak
Normal file
25
discussion/.backups/index.ndjson.20260410-192035.bak
Normal file
@ -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"}]}
|
||||
@ -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":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"}]}
|
||||
{"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,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":"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":"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":"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"}]}
|
||||
|
||||
@ -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.
|
||||
@ -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)
|
||||
@ -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.
|
||||
@ -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)*
|
||||
|
||||
@ -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.
|
||||
@ -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)*
|
||||
@ -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)*
|
||||
@ -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/`.
|
||||
@ -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<AtomicTelemetry>`.
|
||||
- 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).
|
||||
@ -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.
|
||||
@ -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.
|
||||
@ -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).
|
||||
@ -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).
|
||||
@ -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.
|
||||
@ -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
|
||||
|
||||
@ -186,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
|
||||
@ -217,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
|
||||
@ -230,10 +259,39 @@ 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. **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
|
||||
|
||||
- **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.
|
||||
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
|
||||
|
||||
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.
|
||||
|
||||
@ -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,27 @@ 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 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.
|
||||
- **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
|
||||
|
||||
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 +160,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 +174,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**:
|
||||
|
||||
|
||||
@ -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:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user