[PERF] Host Debug Overlay Isolation

This commit is contained in:
bQUARKz 2026-04-10 14:10:20 +01:00
parent b8d33c9e02
commit 58a5f9a3a6
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
18 changed files with 1315 additions and 123 deletions

View File

@ -27,7 +27,7 @@ pub struct TelemetryFrame {
pub heap_used_bytes: usize, pub heap_used_bytes: usize,
pub heap_max_bytes: usize, pub heap_max_bytes: usize,
// Log Pressure // Log Pressure from the last completed logical frame
pub logs_count: u32, pub logs_count: u32,
} }
@ -58,13 +58,15 @@ pub struct AtomicTelemetry {
pub heap_used_bytes: AtomicUsize, pub heap_used_bytes: AtomicUsize,
pub heap_max_bytes: AtomicUsize, pub heap_max_bytes: AtomicUsize,
// Log Pressure // Transient in-flight log counter for the current logical frame
pub logs_count: Arc<AtomicU32>, pub current_logs_count: Arc<AtomicU32>,
// Persisted log count from the last completed logical frame
pub logs_count: AtomicU32,
} }
impl AtomicTelemetry { impl AtomicTelemetry {
pub fn new(logs_count: Arc<AtomicU32>) -> Self { pub fn new(current_logs_count: Arc<AtomicU32>) -> Self {
Self { logs_count, ..Default::default() } Self { current_logs_count, ..Default::default() }
} }
/// Snapshots the current atomic state into a TelemetryFrame. /// Snapshots the current atomic state into a TelemetryFrame.
@ -105,6 +107,8 @@ impl AtomicTelemetry {
self.audio_slots_occupied.store(0, Ordering::Relaxed); self.audio_slots_occupied.store(0, Ordering::Relaxed);
self.heap_used_bytes.store(0, Ordering::Relaxed); self.heap_used_bytes.store(0, Ordering::Relaxed);
self.vm_steps.store(0, Ordering::Relaxed); self.vm_steps.store(0, Ordering::Relaxed);
self.logs_count.store(0, Ordering::Relaxed);
self.current_logs_count.store(0, Ordering::Relaxed);
} }
} }
@ -301,4 +305,16 @@ mod tests {
assert!(logs[1].msg.contains("syscalls")); assert!(logs[1].msg.contains("syscalls"));
assert!(logs[2].msg.contains("GFX bank")); assert!(logs[2].msg.contains("GFX bank"));
} }
#[test]
fn snapshot_uses_persisted_last_frame_logs() {
let current = Arc::new(AtomicU32::new(7));
let tel = AtomicTelemetry::new(Arc::clone(&current));
tel.logs_count.store(3, Ordering::Relaxed);
let snapshot = tel.snapshot();
assert_eq!(snapshot.logs_count, 3);
assert_eq!(current.load(Ordering::Relaxed), 7);
}
} }

View File

@ -274,6 +274,8 @@ fn reset_clears_cartridge_scoped_runtime_state() {
runtime.current_cartridge_app_version = "1.2.3".into(); runtime.current_cartridge_app_version = "1.2.3".into();
runtime.current_cartridge_app_mode = AppMode::System; runtime.current_cartridge_app_mode = AppMode::System;
runtime.logs_written_this_frame.insert(42, 3); runtime.logs_written_this_frame.insert(42, 3);
runtime.atomic_telemetry.logs_count.store(5, Ordering::Relaxed);
runtime.atomic_telemetry.current_logs_count.store(8, Ordering::Relaxed);
runtime.atomic_telemetry.frame_index.store(8, Ordering::Relaxed); runtime.atomic_telemetry.frame_index.store(8, Ordering::Relaxed);
runtime.atomic_telemetry.cycles_used.store(99, Ordering::Relaxed); runtime.atomic_telemetry.cycles_used.store(99, Ordering::Relaxed);
runtime.atomic_telemetry.completed_logical_frames.store(2, Ordering::Relaxed); runtime.atomic_telemetry.completed_logical_frames.store(2, Ordering::Relaxed);
@ -298,6 +300,8 @@ fn reset_clears_cartridge_scoped_runtime_state() {
assert!(runtime.current_cartridge_app_version.is_empty()); assert!(runtime.current_cartridge_app_version.is_empty());
assert_eq!(runtime.current_cartridge_app_mode, AppMode::Game); assert_eq!(runtime.current_cartridge_app_mode, AppMode::Game);
assert!(runtime.logs_written_this_frame.is_empty()); assert!(runtime.logs_written_this_frame.is_empty());
assert_eq!(runtime.atomic_telemetry.logs_count.load(Ordering::Relaxed), 0);
assert_eq!(runtime.atomic_telemetry.current_logs_count.load(Ordering::Relaxed), 0);
assert_eq!(runtime.atomic_telemetry.frame_index.load(Ordering::Relaxed), 0); assert_eq!(runtime.atomic_telemetry.frame_index.load(Ordering::Relaxed), 0);
assert_eq!(runtime.atomic_telemetry.cycles_used.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_eq!(runtime.atomic_telemetry.completed_logical_frames.load(Ordering::Relaxed), 0);

View File

@ -163,6 +163,10 @@ impl VirtualMachineRuntime {
.host_cpu_time_us .host_cpu_time_us
.store(start.elapsed().as_micros() as u64, Ordering::Relaxed); .store(start.elapsed().as_micros() as u64, Ordering::Relaxed);
let current_frame_logs =
self.atomic_telemetry.current_logs_count.load(Ordering::Relaxed);
self.atomic_telemetry.logs_count.store(current_frame_logs, Ordering::Relaxed);
let ts_ms = self.boot_time.elapsed().as_millis() as u64; let ts_ms = self.boot_time.elapsed().as_millis() as u64;
let telemetry_snapshot = self.atomic_telemetry.snapshot(); let telemetry_snapshot = self.atomic_telemetry.snapshot();

View File

@ -4,6 +4,7 @@ pub mod debugger;
pub mod fs_backend; pub mod fs_backend;
pub mod input; pub mod input;
pub mod log_sink; pub mod log_sink;
pub mod overlay;
pub mod runner; pub mod runner;
pub mod stats; pub mod stats;
pub mod utilities; pub mod utilities;

View File

@ -0,0 +1,460 @@
use crate::stats::HostStats;
use prometeu_firmware::Firmware;
const PANEL_X: usize = 6;
const PANEL_Y: usize = 10;
const PANEL_WIDTH: usize = 170;
const PANEL_PADDING: usize = 8;
const LINE_HEIGHT: usize = 12;
const CHAR_SCALE: usize = 1;
const BAR_WIDTH: usize = PANEL_WIDTH - (PANEL_PADDING * 2);
const BAR_HEIGHT: usize = 6;
const BG: [u8; 4] = [10, 18, 32, 208];
const BORDER: [u8; 4] = [90, 126, 170, 255];
const TEXT: [u8; 4] = [235, 240, 255, 255];
const DIM: [u8; 4] = [150, 168, 196, 255];
const WARN: [u8; 4] = [255, 104, 104, 255];
const BAR_BG: [u8; 4] = [30, 42, 61, 255];
const BAR_FILL: [u8; 4] = [91, 184, 255, 255];
const BAR_WARN: [u8; 4] = [255, 150, 102, 255];
const OVERLAY_HEAP_FALLBACK_BYTES: usize = 8 * 1024 * 1024;
#[derive(Debug, Clone)]
pub(crate) struct OverlayMetric {
label: &'static str,
value: String,
warn: bool,
}
#[derive(Debug, Clone)]
pub(crate) struct OverlayBar {
label: &'static str,
value: String,
ratio: f32,
warn: bool,
}
#[derive(Debug, Clone)]
pub(crate) struct OverlaySnapshot {
rows: Vec<(OverlayMetric, OverlayMetric)>,
bars: Vec<OverlayBar>,
footer: Vec<OverlayMetric>,
}
pub(crate) fn capture_snapshot(stats: &HostStats, firmware: &Firmware) -> OverlaySnapshot {
let tel = firmware.os.atomic_telemetry.snapshot();
let recent_logs = firmware.os.log_service.get_recent(10);
let violations_count = recent_logs.iter().filter(|e| e.tag >= 0xCA01 && e.tag <= 0xCA07).count();
let mut footer = Vec::new();
if violations_count > 0
&& let Some(event) = recent_logs.into_iter().rev().find(|e| e.tag >= 0xCA01 && e.tag <= 0xCA07)
{
footer.push(OverlayMetric {
label: "CERT",
value: truncate_value(&event.msg, 28),
warn: true,
});
}
if let Some(report) = firmware.os.last_crash_report.as_ref() {
footer.push(OverlayMetric {
label: "CRASH",
value: truncate_value(&report.summary(), 28),
warn: true,
});
}
let cycles_ratio = ratio(tel.cycles_used, tel.cycles_budget);
let heap_total_bytes = firmware
.os
.certifier
.config
.max_heap_bytes
.or(if tel.heap_max_bytes > 0 { Some(tel.heap_max_bytes) } else { None })
.unwrap_or(OVERLAY_HEAP_FALLBACK_BYTES);
let heap_ratio = ratio(tel.heap_used_bytes as u64, heap_total_bytes as u64);
let gfx_ratio = ratio(tel.gfx_slots_occupied as u64, 16);
let audio_ratio = ratio(tel.audio_slots_occupied as u64, 16);
OverlaySnapshot {
rows: vec![
(
OverlayMetric {
label: "FPS",
value: format!("{:.1}", stats.current_fps),
warn: false,
},
OverlayMetric {
label: "CERT",
value: violations_count.to_string(),
warn: violations_count > 0,
},
),
(
OverlayMetric {
label: "HOST",
value: format!("{:.2}ms", stats.average_host_cpu_ms()),
warn: false,
},
OverlayMetric {
label: "STEPS",
value: tel.vm_steps.to_string(),
warn: false,
},
),
(
OverlayMetric {
label: "SYSC",
value: tel.syscalls.to_string(),
warn: false,
},
OverlayMetric {
label: "LOGS",
value: tel.logs_count.to_string(),
warn: false,
},
),
],
bars: vec![
OverlayBar {
label: "HEAP",
value: format!(
"{}K{}",
tel.heap_used_bytes.div_ceil(1024),
if heap_total_bytes > 0 {
format!(" / {}K", heap_total_bytes.div_ceil(1024))
} else {
String::new()
}
),
ratio: heap_ratio,
warn: tel.heap_used_bytes >= heap_total_bytes,
},
OverlayBar {
label: "BUDGET",
value: if tel.cycles_budget > 0 {
format!(
"{:.1}K/{:.1}K {:.1}%",
tel.cycles_used as f64 / 1000.0,
tel.cycles_budget as f64 / 1000.0,
cycles_ratio * 100.0
)
} else {
"0.0K/0.0K 0.0%".to_string()
},
ratio: cycles_ratio,
warn: cycles_ratio >= 0.9,
},
OverlayBar {
label: "GFX",
value: format!(
"{} / 16 slots {}K",
tel.gfx_slots_occupied,
tel.gfx_used_bytes.div_ceil(1024)
),
ratio: gfx_ratio,
warn: tel.gfx_inflight_bytes > 0,
},
OverlayBar {
label: "AUD",
value: format!(
"{} / 16 slots {}K",
tel.audio_slots_occupied,
tel.audio_used_bytes.div_ceil(1024)
),
ratio: audio_ratio,
warn: tel.audio_inflight_bytes > 0,
},
],
footer,
}
}
pub(crate) fn draw_overlay(
frame: &mut [u8],
frame_width: usize,
frame_height: usize,
snapshot: &OverlaySnapshot,
) {
let panel_height = frame_height.saturating_sub(PANEL_Y * 2);
fill_rect_alpha(frame, frame_width, frame_height, PANEL_X, PANEL_Y, PANEL_WIDTH, panel_height, BG);
stroke_rect(frame, frame_width, frame_height, PANEL_X, PANEL_Y, PANEL_WIDTH, panel_height, BORDER);
let mut y = PANEL_Y + PANEL_PADDING;
for (left, right) in &snapshot.rows {
draw_metric_pair(frame, frame_width, frame_height, y, left, right);
y += LINE_HEIGHT;
}
for bar in &snapshot.bars {
let color = if bar.warn { WARN } else { TEXT };
draw_text(frame, frame_width, frame_height, PANEL_X + PANEL_PADDING, y, bar.label, DIM);
draw_text(frame, frame_width, frame_height, PANEL_X + 48, y, &bar.value, color);
y += LINE_HEIGHT - 2;
let bar_x = PANEL_X + PANEL_PADDING;
fill_rect(frame, frame_width, frame_height, bar_x, y, BAR_WIDTH, BAR_HEIGHT, BAR_BG);
let fill_width = ((BAR_WIDTH as f32) * bar.ratio.clamp(0.0, 1.0)).round() as usize;
fill_rect(
frame,
frame_width,
frame_height,
bar_x,
y,
fill_width,
BAR_HEIGHT,
if bar.warn { BAR_WARN } else { BAR_FILL },
);
stroke_rect(frame, frame_width, frame_height, bar_x, y, BAR_WIDTH, BAR_HEIGHT, BORDER);
y += BAR_HEIGHT + 6;
}
for line in &snapshot.footer {
let color = if line.warn { WARN } else { TEXT };
draw_text(frame, frame_width, frame_height, PANEL_X + PANEL_PADDING, y, line.label, DIM);
draw_text(frame, frame_width, frame_height, PANEL_X + 48, y, &line.value, color);
y += LINE_HEIGHT;
}
}
fn draw_metric_pair(
frame: &mut [u8],
frame_width: usize,
frame_height: usize,
y: usize,
left: &OverlayMetric,
right: &OverlayMetric,
) {
let left_x = PANEL_X + PANEL_PADDING;
let right_x = PANEL_X + 86;
draw_metric(frame, frame_width, frame_height, left_x, y, left);
draw_metric(frame, frame_width, frame_height, right_x, y, right);
}
fn draw_metric(
frame: &mut [u8],
frame_width: usize,
frame_height: usize,
x: usize,
y: usize,
metric: &OverlayMetric,
) {
let color = if metric.warn { WARN } else { TEXT };
draw_text(frame, frame_width, frame_height, x, y, metric.label, DIM);
draw_text(frame, frame_width, frame_height, x + 30, y, &metric.value, color);
}
fn ratio(value: u64, total: u64) -> f32 {
if total == 0 { 0.0 } else { (value as f32 / total as f32).clamp(0.0, 1.0) }
}
fn truncate_value(value: &str, max_len: usize) -> String {
let mut upper = value.to_ascii_uppercase();
if upper.len() > max_len {
upper.truncate(max_len);
}
upper
}
fn draw_text(frame: &mut [u8], frame_width: usize, frame_height: usize, x: usize, y: usize, text: &str, color: [u8; 4]) {
let mut cursor_x = x;
for ch in text.chars() {
if ch == '\n' {
continue;
}
draw_char(frame, frame_width, frame_height, cursor_x, y, ch.to_ascii_uppercase(), color);
cursor_x += 6 * CHAR_SCALE;
}
}
fn draw_char(frame: &mut [u8], frame_width: usize, frame_height: usize, x: usize, y: usize, ch: char, color: [u8; 4]) {
let glyph = glyph_bits(ch);
for (row, bits) in glyph.iter().enumerate() {
for col in 0..5 {
if bits & (1 << (4 - col)) != 0 {
fill_rect(frame, frame_width, frame_height, x + col * CHAR_SCALE, y + row * CHAR_SCALE, CHAR_SCALE, CHAR_SCALE, color);
}
}
}
}
fn fill_rect_alpha(
frame: &mut [u8],
frame_width: usize,
frame_height: usize,
x: usize,
y: usize,
width: usize,
height: usize,
color: [u8; 4],
) {
let max_x = (x + width).min(frame_width);
let max_y = (y + height).min(frame_height);
for py in y..max_y {
for px in x..max_x {
blend_pixel(frame, frame_width, px, py, color);
}
}
}
fn fill_rect(
frame: &mut [u8],
frame_width: usize,
frame_height: usize,
x: usize,
y: usize,
width: usize,
height: usize,
color: [u8; 4],
) {
let max_x = (x + width).min(frame_width);
let max_y = (y + height).min(frame_height);
for py in y..max_y {
for px in x..max_x {
write_pixel(frame, frame_width, px, py, color);
}
}
}
fn stroke_rect(
frame: &mut [u8],
frame_width: usize,
frame_height: usize,
x: usize,
y: usize,
width: usize,
height: usize,
color: [u8; 4],
) {
if width == 0 || height == 0 {
return;
}
fill_rect(frame, frame_width, frame_height, x, y, width, 1, color);
fill_rect(frame, frame_width, frame_height, x, y + height.saturating_sub(1), width, 1, color);
fill_rect(frame, frame_width, frame_height, x, y, 1, height, color);
fill_rect(frame, frame_width, frame_height, x + width.saturating_sub(1), y, 1, height, color);
}
fn blend_pixel(frame: &mut [u8], frame_width: usize, x: usize, y: usize, color: [u8; 4]) {
let idx = (y * frame_width + x) * 4;
let alpha = color[3] as f32 / 255.0;
let inv = 1.0 - alpha;
frame[idx] = (frame[idx] as f32 * inv + color[0] as f32 * alpha).round() as u8;
frame[idx + 1] = (frame[idx + 1] as f32 * inv + color[1] as f32 * alpha).round() as u8;
frame[idx + 2] = (frame[idx + 2] as f32 * inv + color[2] as f32 * alpha).round() as u8;
frame[idx + 3] = 0xFF;
}
fn write_pixel(frame: &mut [u8], frame_width: usize, x: usize, y: usize, color: [u8; 4]) {
let idx = (y * frame_width + x) * 4;
frame[idx] = color[0];
frame[idx + 1] = color[1];
frame[idx + 2] = color[2];
frame[idx + 3] = color[3];
}
fn glyph_bits(ch: char) -> [u8; 7] {
match ch {
'A' => [0x0E, 0x11, 0x11, 0x1F, 0x11, 0x11, 0x11],
'B' => [0x1E, 0x11, 0x11, 0x1E, 0x11, 0x11, 0x1E],
'C' => [0x0E, 0x11, 0x10, 0x10, 0x10, 0x11, 0x0E],
'D' => [0x1E, 0x12, 0x11, 0x11, 0x11, 0x12, 0x1E],
'E' => [0x1F, 0x10, 0x10, 0x1E, 0x10, 0x10, 0x1F],
'F' => [0x1F, 0x10, 0x10, 0x1E, 0x10, 0x10, 0x10],
'G' => [0x0E, 0x11, 0x10, 0x17, 0x11, 0x11, 0x0E],
'H' => [0x11, 0x11, 0x11, 0x1F, 0x11, 0x11, 0x11],
'I' => [0x0E, 0x04, 0x04, 0x04, 0x04, 0x04, 0x0E],
'J' => [0x01, 0x01, 0x01, 0x01, 0x11, 0x11, 0x0E],
'K' => [0x11, 0x12, 0x14, 0x18, 0x14, 0x12, 0x11],
'L' => [0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x1F],
'M' => [0x11, 0x1B, 0x15, 0x15, 0x11, 0x11, 0x11],
'N' => [0x11, 0x11, 0x19, 0x15, 0x13, 0x11, 0x11],
'O' => [0x0E, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0E],
'P' => [0x1E, 0x11, 0x11, 0x1E, 0x10, 0x10, 0x10],
'Q' => [0x0E, 0x11, 0x11, 0x11, 0x15, 0x12, 0x0D],
'R' => [0x1E, 0x11, 0x11, 0x1E, 0x14, 0x12, 0x11],
'S' => [0x0F, 0x10, 0x10, 0x0E, 0x01, 0x01, 0x1E],
'T' => [0x1F, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04],
'U' => [0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0E],
'V' => [0x11, 0x11, 0x11, 0x11, 0x11, 0x0A, 0x04],
'W' => [0x11, 0x11, 0x11, 0x15, 0x15, 0x15, 0x0A],
'X' => [0x11, 0x11, 0x0A, 0x04, 0x0A, 0x11, 0x11],
'Y' => [0x11, 0x11, 0x0A, 0x04, 0x04, 0x04, 0x04],
'Z' => [0x1F, 0x01, 0x02, 0x04, 0x08, 0x10, 0x1F],
'0' => [0x0E, 0x11, 0x13, 0x15, 0x19, 0x11, 0x0E],
'1' => [0x04, 0x0C, 0x14, 0x04, 0x04, 0x04, 0x1F],
'2' => [0x0E, 0x11, 0x01, 0x02, 0x04, 0x08, 0x1F],
'3' => [0x1E, 0x01, 0x01, 0x0E, 0x01, 0x01, 0x1E],
'4' => [0x02, 0x06, 0x0A, 0x12, 0x1F, 0x02, 0x02],
'5' => [0x1F, 0x10, 0x10, 0x1E, 0x01, 0x01, 0x1E],
'6' => [0x0E, 0x10, 0x10, 0x1E, 0x11, 0x11, 0x0E],
'7' => [0x1F, 0x01, 0x02, 0x04, 0x08, 0x08, 0x08],
'8' => [0x0E, 0x11, 0x11, 0x0E, 0x11, 0x11, 0x0E],
'9' => [0x0E, 0x11, 0x11, 0x0F, 0x01, 0x01, 0x0E],
':' => [0x00, 0x04, 0x04, 0x00, 0x04, 0x04, 0x00],
'.' => [0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x06],
'/' => [0x01, 0x02, 0x02, 0x04, 0x08, 0x08, 0x10],
'%' => [0x19, 0x19, 0x02, 0x04, 0x08, 0x13, 0x13],
'(' => [0x02, 0x04, 0x08, 0x08, 0x08, 0x04, 0x02],
')' => [0x08, 0x04, 0x02, 0x02, 0x02, 0x04, 0x08],
'-' => [0x00, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x00],
'_' => [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F],
'+' => [0x00, 0x04, 0x04, 0x1F, 0x04, 0x04, 0x00],
' ' => [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
'?' => [0x0E, 0x11, 0x01, 0x02, 0x04, 0x00, 0x04],
_ => [0x0E, 0x11, 0x01, 0x02, 0x04, 0x00, 0x04],
}
}
#[cfg(test)]
mod tests {
use super::*;
fn sample_snapshot() -> OverlaySnapshot {
OverlaySnapshot {
rows: vec![
(
OverlayMetric { label: "FPS", value: "60.0".to_string(), warn: false },
OverlayMetric { label: "CERT", value: "2".to_string(), warn: true },
),
(
OverlayMetric { label: "HOST", value: "1.23ms".to_string(), warn: false },
OverlayMetric { label: "STEPS", value: "420".to_string(), warn: false },
),
],
bars: vec![OverlayBar {
label: "HEAP",
value: "1024K / 8192K".to_string(),
ratio: 0.125,
warn: false,
},
OverlayBar {
label: "BUDGET",
value: "9.0K/10.0K 90.0%".to_string(),
ratio: 0.9,
warn: true,
}],
footer: vec![OverlayMetric {
label: "CRASH",
value: "VM PANIC".to_string(),
warn: true,
}],
}
}
#[test]
fn draw_overlay_writes_to_host_rgba_frame() {
let mut frame = vec![0u8; 320 * 180 * 4];
draw_overlay(&mut frame, 320, 180, &sample_snapshot());
assert!(frame.iter().any(|&byte| byte != 0));
}
#[test]
fn truncate_value_normalizes_and_caps() {
assert_eq!(truncate_value("panic: lowercase", 12), "PANIC: LOWER");
}
}

View File

@ -3,6 +3,7 @@ use crate::debugger::HostDebugger;
use crate::fs_backend::HostDirBackend; use crate::fs_backend::HostDirBackend;
use crate::input::HostInputHandler; use crate::input::HostInputHandler;
use crate::log_sink::HostConsoleSink; use crate::log_sink::HostConsoleSink;
use crate::overlay::{capture_snapshot, draw_overlay};
use crate::stats::HostStats; use crate::stats::HostStats;
use crate::utilities::draw_rgb565_to_rgba8; use crate::utilities::draw_rgb565_to_rgba8;
use pixels::wgpu::PresentMode; use pixels::wgpu::PresentMode;
@ -10,7 +11,6 @@ use pixels::{Pixels, PixelsBuilder, SurfaceTexture};
use prometeu_drivers::AudioCommand; use prometeu_drivers::AudioCommand;
use prometeu_drivers::hardware::Hardware; use prometeu_drivers::hardware::Hardware;
use prometeu_firmware::{BootTarget, Firmware}; use prometeu_firmware::{BootTarget, Firmware};
use prometeu_hal::color::Color;
use prometeu_hal::telemetry::CertificationConfig; use prometeu_hal::telemetry::CertificationConfig;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use winit::application::ApplicationHandler; use winit::application::ApplicationHandler;
@ -123,110 +123,6 @@ impl HostRunner {
} }
} }
fn display_dbg_overlay(&mut self) {
let tel = self.firmware.os.atomic_telemetry.snapshot();
let color_text = Color::WHITE;
let color_bg = Color::INDIGO; // Dark blue to stand out
let color_warn = Color::RED;
self.hardware.gfx.fill_rect(5, 5, 175, 130, color_bg);
self.hardware.gfx.draw_text(
10,
10,
&format!("FPS: {:.1}", self.stats.current_fps),
color_text,
);
self.hardware.gfx.draw_text(
10,
18,
&format!("HOST: {:.2}MS", tel.host_cpu_time_us as f64 / 1000.0),
color_text,
);
self.hardware.gfx.draw_text(10, 26, &format!("STEPS: {}", tel.vm_steps), color_text);
self.hardware.gfx.draw_text(10, 34, &format!("SYSC: {}", tel.syscalls), color_text);
let cycles_pct = if tel.cycles_budget > 0 {
(tel.cycles_used as f64 / tel.cycles_budget as f64) * 100.0
} else {
0.0
};
self.hardware.gfx.draw_text(
10,
42,
&format!("CYC: {}/{} ({:.1}%)", tel.cycles_used, tel.cycles_budget, cycles_pct),
color_text,
);
self.hardware.gfx.draw_text(
10,
50,
&format!("GFX: {}K/16M ({}S)", tel.gfx_used_bytes / 1024, tel.gfx_slots_occupied),
color_text,
);
if tel.gfx_inflight_bytes > 0 {
self.hardware.gfx.draw_text(
10,
58,
&format!("LOAD GFX: {}KB", tel.gfx_inflight_bytes / 1024),
color_warn,
);
}
self.hardware.gfx.draw_text(
10,
66,
&format!("AUD: {}K/32M ({}S)", tel.audio_used_bytes / 1024, tel.audio_slots_occupied),
color_text,
);
if tel.audio_inflight_bytes > 0 {
self.hardware.gfx.draw_text(
10,
74,
&format!("LOAD AUD: {}KB", tel.audio_inflight_bytes / 1024),
color_warn,
);
}
self.hardware.gfx.draw_text(
10,
82,
&format!("RAM: {}KB", tel.heap_used_bytes / 1024),
color_text,
);
self.hardware.gfx.draw_text(10, 90, &format!("LOGS: {}", tel.logs_count), color_text);
// Snapshot does not include violations, as they are part of certification (logical end of frame)
// But for visual debug, we can check if there are recent CA tags in logs
let recent_logs = self.firmware.os.log_service.get_recent(10);
let violations_count =
recent_logs.iter().filter(|e| e.tag >= 0xCA01 && e.tag <= 0xCA07).count();
let cert_color = if violations_count > 0 { color_warn } else { color_text };
self.hardware.gfx.draw_text(
10,
98,
&format!("CERT RECENT: {}", violations_count),
cert_color,
);
if violations_count > 0
&& let Some(event) =
recent_logs.into_iter().rev().find(|e| e.tag >= 0xCA01 && e.tag <= 0xCA07)
{
let mut msg = event.msg.clone();
if msg.len() > 30 {
msg.truncate(30);
}
self.hardware.gfx.draw_text(10, 106, &msg, color_warn);
}
if let Some(report) = self.firmware.os.last_crash_report.as_ref() {
let mut msg = report.summary();
if msg.len() > 30 {
msg.truncate(30);
}
self.hardware.gfx.draw_text(10, 114, &msg, color_warn);
}
}
} }
impl ApplicationHandler for HostRunner { impl ApplicationHandler for HostRunner {
@ -280,6 +176,9 @@ impl ApplicationHandler for HostRunner {
} }
WindowEvent::RedrawRequested => { 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) // 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"); let pixels = self.pixels.as_mut().expect("pixels not initialized");
@ -291,6 +190,10 @@ impl ApplicationHandler for HostRunner {
let src = self.hardware.gfx.front_buffer(); let src = self.hardware.gfx.front_buffer();
draw_rgb565_to_rgba8(src, frame); 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 } // <- frame borrow ends here
if pixels.render().is_err() { if pixels.render().is_err() {
@ -358,6 +261,7 @@ impl ApplicationHandler for HostRunner {
// Unless the debugger is waiting for a 'start' command, advance the system. // Unless the debugger is waiting for a 'start' command, advance the system.
if !self.debugger.waiting_for_start { if !self.debugger.waiting_for_start {
self.firmware.tick(&self.input.signals, &mut self.hardware); 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. // Sync pause state with audio.
@ -393,15 +297,8 @@ impl ApplicationHandler for HostRunner {
}; };
self.log_sink.process_events(new_events); self.log_sink.process_events(new_events);
// 5. Rendering the Telemetry Overlay (if enabled). // 5. Request redraw so the host surface can present the latest machine frame
if self.overlay_enabled { // and, when enabled, compose the overlay in the host-only RGBA surface.
// 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.
self.request_redraw(); self.request_redraw();
} }
} }

View File

@ -9,6 +9,9 @@ pub struct HostStats {
pub current_fps: f64, pub current_fps: f64,
pub audio_load_accum_us: u64, pub audio_load_accum_us: u64,
pub audio_load_samples: 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 { impl Default for HostStats {
@ -25,6 +28,9 @@ impl HostStats {
current_fps: 0.0, current_fps: 0.0,
audio_load_accum_us: 0, audio_load_accum_us: 0,
audio_load_samples: 0, audio_load_samples: 0,
recent_host_cpu_us: [0; 5],
recent_host_cpu_count: 0,
recent_host_cpu_cursor: 0,
} }
} }
@ -37,6 +43,21 @@ impl HostStats {
self.audio_load_samples += 1; 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( pub fn update(
&mut self, &mut self,
now: Instant, now: Instant,
@ -51,8 +72,7 @@ impl HostStats {
if let Some(window) = window { if let Some(window) = window {
// Fixed comparison always against 60Hz, keep even when doing CPU stress tests // Fixed comparison always against 60Hz, keep even when doing CPU stress tests
let frame_budget_us = 16666.0; let frame_budget_us = 16666.0;
let cpu_load_core = let cpu_load_core = (self.average_host_cpu_ms() * 1000.0 / frame_budget_us) * 100.0;
(firmware.os.last_frame_cpu_time_us as f64 / frame_budget_us) * 100.0;
let cpu_load_audio = if self.audio_load_samples > 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 (self.audio_load_accum_us as f64 / stats_elapsed.as_micros() as f64) * 100.0

View File

@ -1,4 +1,4 @@
{"type":"meta","next_id":{"DSC":24,"AGD":22,"DEC":9,"PLN":8,"LSN":29,"CLSN":1}} {"type":"meta","next_id":{"DSC":25,"AGD":25,"DEC":13,"PLN":11,"LSN":29,"CLSN":1}}
{"type":"discussion","id":"DSC-0023","status":"done","ticket":"perf-full-migration-to-atomic-telemetry","title":"Agenda - [PERF] Full Migration to Atomic Telemetry","created_at":"2026-04-10","updated_at":"2026-04-10","tags":["perf","runtime","telemetry"],"agendas":[{"id":"AGD-0021","file":"workflow/agendas/AGD-0021-full-migration-to-atomic-telemetry.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}],"decisions":[{"id":"DEC-0008","file":"workflow/decisions/DEC-0008-full-migration-to-atomic-telemetry.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10"}],"plans":[{"id":"PLN-0007","file":"workflow/plans/PLN-0007-full-migration-to-atomic-telemetry.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}],"lessons":[{"id":"LSN-0028","file":"lessons/DSC-0023-perf-full-migration-to-atomic-telemetry/LSN-0028-converging-to-single-atomic-telemetry-source.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}]} {"type":"discussion","id":"DSC-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-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-0021","status":"done","ticket":"asset-entry-codec-enum-with-metadata","title":"Asset Entry Codec Enum Contract","created_at":"2026-04-09","updated_at":"2026-04-09","tags":["asset","runtime","codec","metadata"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0024","file":"lessons/DSC-0021-asset-entry-codec-enum-contract/LSN-0024-string-on-the-wire-enum-in-runtime.md","status":"done","created_at":"2026-04-09","updated_at":"2026-04-09"}]}
@ -15,7 +15,8 @@
{"type":"discussion","id":"DSC-0010","status":"open","ticket":"perf-host-desktop-frame-pacing-and-presentation","title":"Agenda - [PERF] Host Desktop Frame Pacing and Presentation","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0009","file":"workflow/agendas/AGD-0009-perf-host-desktop-frame-pacing-and-presentation.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]} {"type":"discussion","id":"DSC-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-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-0012","status":"open","ticket":"perf-runtime-introspection-syscalls","title":"Agenda - [PERF] Runtime Introspection Syscalls","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0011","file":"workflow/agendas/AGD-0011-perf-runtime-introspection-syscalls.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]}
{"type":"discussion","id":"DSC-0013","status":"done","ticket":"perf-host-debug-overlay-isolation","title":"Agenda - [PERF] Host Debug Overlay Isolation","created_at":"2026-03-27","updated_at":"2026-04-10","tags":[],"agendas":[{"id":"AGD-0012","file":"workflow/agendas/AGD-0012-perf-host-debug-overlay-isolation.md","status":"done","created_at":"2026-03-27","updated_at":"2026-04-10"}],"decisions":[{"id":"DEC-0007","file":"workflow/decisions/DEC-0007-perf-host-debug-overlay-isolation.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10"}],"plans":[{"id":"PLN-0006","file":"workflow/plans/PLN-0006-perf-host-debug-overlay-isolation.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}],"lessons":[{"id":"LSN-0027","file":"lessons/DSC-0013-perf-host-debug-overlay-isolation/LSN-0027-host-debug-overlay-isolation.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}]} {"type":"discussion","id":"DSC-0013","status":"open","ticket":"perf-host-debug-overlay-isolation","title":"Agenda - [PERF] Host Debug Overlay Isolation","created_at":"2026-03-27","updated_at":"2026-04-10","tags":[],"agendas":[{"id":"AGD-0012","file":"workflow/agendas/AGD-0012-perf-host-debug-overlay-isolation.md","status":"done","created_at":"2026-03-27","updated_at":"2026-04-10"},{"id":"AGD-0022","file":"AGD-0022-host-overlay-tooling-boundary-revision.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10"},{"id":"AGD-0023","file":"AGD-0023-overlay-log-metric-last-frame.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10"}],"decisions":[{"id":"DEC-0007","file":"workflow/decisions/DEC-0007-perf-host-debug-overlay-isolation.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10"},{"id":"DEC-0009","file":"DEC-0009-host-overlay-tooling-boundary.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10","ref_agenda":"AGD-0022"},{"id":"DEC-0010","file":"DEC-0010-overlay-log-metric-last-frame.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10","ref_agenda":"AGD-0023"}],"plans":[{"id":"PLN-0006","file":"workflow/plans/PLN-0006-perf-host-debug-overlay-isolation.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"},{"id":"PLN-0008","file":"PLN-0008-host-overlay-native-composition-alignment.md","status":"in_progress","created_at":"2026-04-10","updated_at":"2026-04-10","ref_decisions":["DEC-0007","DEC-0008","DEC-0009","DEC-0010"]}],"lessons":[{"id":"LSN-0027","file":"lessons/DSC-0013-perf-host-debug-overlay-isolation/LSN-0027-host-debug-overlay-isolation.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}]}
{"type":"discussion","id":"DSC-0024","status":"open","ticket":"generic-memory-bank-slot-contract","title":"Agenda - Generic Memory Bank Slot Contract","created_at":"2026-04-10","updated_at":"2026-04-10","tags":["runtime","asset","memory-bank","slots","host"],"agendas":[{"id":"AGD-0024","file":"AGD-0024-generic-memory-bank-slot-contract.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10"}],"decisions":[{"id":"DEC-0012","file":"DEC-0012-asset-manager-bank-telemetry-slot-contract.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10","ref_agenda":"AGD-0024"}],"plans":[{"id":"PLN-0010","file":"PLN-0010-asset-manager-bank-telemetry-slot-contract.md","status":"open","created_at":"2026-04-10","updated_at":"2026-04-10","ref_decisions":["DEC-0012"]}],"lessons":[]}
{"type":"discussion","id":"DSC-0014","status":"open","ticket":"perf-vm-allocation-and-copy-pressure","title":"Agenda - [PERF] VM Allocation and Copy Pressure","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0013","file":"workflow/agendas/AGD-0013-perf-vm-allocation-and-copy-pressure.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]} {"type":"discussion","id":"DSC-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-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-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"}]}

View File

@ -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)*

View File

@ -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)*

View File

@ -0,0 +1,147 @@
---
id: AGD-0024
ticket: generic-memory-bank-slot-contract
title: Agenda - Generic Memory Bank Slot Contract
status: accepted
created: 2026-04-10
resolved: 2026-04-10
decision: DEC-0012
tags: [runtime, asset, memory-bank, slots, host]
---
# Agenda - Generic Memory Bank Slot Contract
## Contexto
Hoje o runtime e o host expõem bancos de memória principalmente pela ótica de bytes totais/usados, enquanto a organização real do hardware já é fortemente orientada a slots. No estado atual:
- `GFX` e `AUD` aparecem como casos especiais espalhados entre `AssetManager`, `MemoryBanks`, `AssetBridge`, telemetria e overlay;
- os pools concretos são específicos (`glyph_bank_pool`, `sound_bank_pool`);
- a visualização do overlay precisou reinterpretar um modelo em bytes para algo que, operacionalmente, é mais bem entendido como ocupação de slots.
O pedido do usuário é explicitamente mudar essa ênfase: manter bytes como detalhe secundário quando necessário, mas estruturar o modelo principal como **memory banks by slots**, com possibilidade de contrato específico no domínio de `MemoryBanks` para gerir esses valores.
Na continuação da discussão, o usuário endureceu a direção:
- `BankPolicy` deve ser removido por completo;
- `BankStats` deve ser removido por completo;
- o contrato exposto deve trabalhar somente com ocupação por slots, por exemplo:
- `glyph_slots_occupied / glyph_slots.len()`
- `sound_slots_occupied / sound_slots.len()`
## Problema
O modelo atual mistura dois níveis de abstração:
1. **Contrato de capacidade em bytes**, útil para certificação e budgets;
2. **Contrato operacional por slots**, que é o que o host e o programador realmente percebem ao lidar com banks.
Isso gera acoplamento e duplicação:
- o domínio conhece `GFX` e `AUD` como exceções, em vez de bancos genéricos com propriedades próprias;
- `BankStats` privilegia bytes e trata slots como apêndice;
- `MemoryBanks` não oferece um contrato consolidado para estatísticas e ocupação por slots;
- o overlay precisa montar sua própria leitura mais útil em cima de um modelo que não foi desenhado para isso.
Com a direção nova do usuário, há um problema adicional: não basta rebaixar bytes a papel secundário no contrato exposto. As estruturas atuais que orbitam essa semântica (`BankPolicy` e `BankStats`) passam a ser vistas como parte do problema e não como base aceitável para a solução.
## Pontos Criticos
- **Fato:** O hardware já é organizado por slots para glyph e sound banks.
- **Fato:** O overlay quer mostrar ocupação por slots, não capacidade em bytes.
- **Fato:** O usuário quer que o contrato exposto use apenas `used_slots` e `total_slots`.
- **Fato:** `BankPolicy` e `BankStats` não devem permanecer como contrato nem como modelagem principal deste domínio.
- **Risco:** Remover essas estruturas sem separar claramente contrato exposto e necessidades internas pode quebrar telemetria e certificação existentes.
- **Risco:** Se bytes continuarem aparecendo no contrato público, a refatoração perde seu objetivo.
- **Tradeoff:** Um contrato mínimo de slots simplifica host/overlay e o domínio exposto, mas exige reposicionar qualquer necessidade residual baseada em bytes.
## Opcoes
- **Opção A (Recomendada):** Introduzir um contrato genérico de memory bank orientado exclusivamente a slots no contrato exposto.
- `MemoryBanks` passa a oferecer um contrato explícito para:
- quantidade de slots;
- slots ocupados;
- consulta de slots;
- enumeração genérica de banks.
- `GLYPH` e `SOUND` tornam-se instâncias desse modelo.
- `BankPolicy` e `BankStats` deixam de existir como superfícies do contrato.
- **Opção B:** Manter `BankPolicy` e `BankStats` internamente, escondendo-os só no host.
- Menor custo imediato.
- Não atende a direção explícita da discussão atual.
- **Opção C:** Preservar um contrato híbrido com slots e bytes lado a lado.
- Dá continuidade incremental.
- Mantém exatamente a ambiguidade que o usuário quer remover.
## Sugestao / Recomendacao
Seguir com a **Opção A**, com os seguintes princípios:
1. O domínio de memory banks deve ser **slot-first**.
2. O contrato exposto deve usar somente `used_slots` e `total_slots`.
3. `BankPolicy` e `BankStats` devem ser removidos por completo.
4. `MemoryBanks` deve possuir um contrato específico e explícito para ocupação por slots.
5. O host overlay deve consumir esse modelo genérico sem saber detalhes especiais de `GLYPH` vs `SOUND`, além de rótulo e contagem.
6. A revisão deve evitar abstração vazia: o contrato genérico precisa mapear diretamente para `GlyphBank` e `SoundBank`.
Nomenclatura canônica acordada para a camada genérica:
- `GLYPH`
- `SOUND`
`GFX` e `AUD` não devem ser usados como nomes canônicos do contrato genérico de banks, pois são apelidos de apresentação e não os nomes corretos do domínio.
## Perguntas em Aberto
- Nenhuma resposta final ainda sobre a forma exata do artefato, mas em 2026-04-10 o usuário fechou os seguintes direcionadores:
- a telemetria necessária deve ser gerenciada pelo `AssetManager`, adicionando os banks ao payload;
- a telemetria deve carregar somente o enum do tipo do bank, não uma abstração genérica adicional no contrato;
- o detalhamento operacional deve ocorrer por enum de slot;
- exemplos concretos do formato esperado ainda precisam ser avaliados antes de encerrar a agenda.
### Respostas consolidadas desta rodada
1. **Origem da telemetria:** não mover a genericidade principal para `MemoryBanks`; o `AssetManager` deve gerenciar e expor a telemetria necessária dos banks.
2. **Forma da telemetria:** a telemetria exposta deve carregar somente o enum do tipo do bank.
3. **Detalhamento dos slots:** a leitura operacional deve ser feita por enum de slot.
4. **Formato preferido:** seguir com um resumo por bank no formato do exemplo 1 (`bank_type`, `used_slots`, `total_slots`).
5. **Bytes fake:** bytes não devem continuar no contrato novo de telemetria dos banks.
6. **Certificação:** as regras `max_gfx_bytes` e `max_audio_bytes` devem sair e ser substituídas por limites de slots para `GLYPH` e `SOUND`.
7. **Origem do contrato visível:** o `AssetManager` deve expor diretamente esse resumo por bank.
Exemplo alvo discutido:
```rust
pub struct BankTelemetry {
pub bank_type: BankType,
pub used_slots: usize,
pub total_slots: usize,
}
```
Origem esperada:
```rust
impl AssetManager {
pub fn bank_telemetry(&self) -> Vec<BankTelemetry> { ... }
}
```
Impacto já identificado para certificação:
- remover `max_gfx_bytes`
- remover `max_audio_bytes`
- substituir por algo como:
- `max_glyph_slots_used`
- `max_sound_slots_used`
## Criterio para Encerrar
Esta agenda pode ser encerrada quando houver direção fechada sobre:
- contrato genérico por slots;
- remoção completa de `BankPolicy` e `BankStats`;
- ponto de residência da abstração (`MemoryBanks`, HAL, ou ambos);
- impacto esperado em overlay, telemetria e `AssetManager`.
*(Critérios atingidos em 2026-04-10)*

View File

@ -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.

View File

@ -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.

View File

@ -0,0 +1,78 @@
---
id: DEC-0012
ticket: generic-memory-bank-slot-contract
title: Decision - Asset Manager Bank Telemetry Slot Contract
status: accepted
created: 2026-04-10
updated: 2026-04-10
agenda: AGD-0024
tags: [runtime, asset, memory-bank, slots, host, telemetry]
---
# Decision - Asset Manager Bank Telemetry Slot Contract
## Status
**Accepted**
## Contexto
O modelo atual de banks ainda carrega forte semântica orientada a bytes e estruturas antigas como `BankPolicy` e `BankStats`, enquanto o consumo real desejado pelo host e pela inspeção técnica é orientado a slots. Além disso, os bytes historicamente usados nesse caminho foram considerados inadequados para sustentar o novo contrato de telemetria de banks.
Durante a discussão, ficou explícito que:
- o resumo visível de banks deve ser exposto diretamente pelo `AssetManager`;
- a telemetria de banks deve usar somente o enum do tipo do bank;
- a semântica operacional deve ser `used_slots / total_slots`;
- `GFX` e `AUD` não são nomes canônicos do domínio; os nomes corretos são `GLYPH` e `SOUND`;
- a certificação de banks deve deixar de usar limites em bytes e passar a usar limites por slots.
## Decisao
1. O contrato visível de telemetria de banks MUST be exposed by `AssetManager`.
2. O resumo de bank MUST use the following structure shape:
```rust
pub struct BankTelemetry {
pub bank_type: BankType,
pub used_slots: usize,
pub total_slots: usize,
}
```
3. O `bank_type` MUST use canonical domain names. For the current banks, the canonical names are `GLYPH` and `SOUND`.
4. O contrato de telemetria de banks MUST be slot-first and MUST NOT depend on byte counts.
5. `BankPolicy` e `BankStats` MUST be removed completely from the bank telemetry contract path.
6. O host overlay MUST consume `AssetManager` bank telemetry instead of hardcoded bank-specific logic.
7. As regras de certificação `max_gfx_bytes` e `max_audio_bytes` MUST be removed.
8. A certificação de banks MUST migrate to slot-based limits, using canonical bank-specific slot limits such as:
- `max_glyph_slots_used`
- `max_sound_slots_used`
9. Any remaining byte-based accounting MAY survive only as internal implementation detail if strictly necessary, but it MUST NOT remain part of the exposed bank telemetry contract.
## Rationale
- Slots são a unidade operacional correta para entender ocupação de banks.
- O `AssetManager` já é o lugar que conhece carregamento, commit e ocupação prática dos banks.
- Remover bytes do contrato elimina uma fonte de telemetria considerada enganosa.
- `GLYPH` e `SOUND` preservam a linguagem correta do domínio e evitam apelidos frágeis na interface.
- Certificação por slots mantém coerência entre contrato exposto, overlay e limites técnicos.
## Invariantes / Contrato
- A telemetria de banks exposta ao restante do sistema sai do `AssetManager`.
- Cada entrada de bank telemetria informa somente tipo do bank, slots usados e slots totais.
- O contrato canônico usa `GLYPH` e `SOUND`.
- O overlay não depende de bytes para mostrar ocupação de banks.
- A certificação de banks não depende de bytes.
## Impactos
- **AssetManager:** precisa expor `Vec<BankTelemetry>` ou equivalente direto.
- **HAL / Bridges:** precisam alinhar interfaces consumidoras ao contrato slot-first.
- **Overlay:** deve iterar a telemetria de banks do `AssetManager`.
- **Certifier:** precisa trocar limites de bytes por limites de slots para banks.
- **Legado:** `BankPolicy`, `BankStats`, e caminhos ancorados em `max_gfx_bytes` / `max_audio_bytes` precisam ser removidos ou recolocados fora do contrato exposto.
## Referencias
- `AGD-0024`: Generic Memory Bank Slot Contract.
- `DEC-0010`: Overlay Log Metric Uses Last Completed Frame.
## Propagacao Necessaria
1. Criar plano de execução para refatorar `AssetManager`, HAL, overlay e certificação.
2. Remover o caminho de telemetria de banks baseado em bytes.
3. Migrar limites de certificação de banks para slots.

View File

@ -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.

View File

@ -0,0 +1,153 @@
---
id: PLN-0010
ticket: generic-memory-bank-slot-contract
title: PR/Plan - Asset Manager Bank Telemetry Slot Contract
status: open
created: 2026-04-10
completed:
tags: [runtime, asset, memory-bank, slots, host, telemetry]
---
## Objective
Implement the `DEC-0012` bank telemetry contract so `AssetManager` exposes slot-based bank summaries using canonical bank names `GLYPH` and `SOUND`, while host overlay and certification consume slot-based limits instead of byte-based bank metrics.
## Background
`DEC-0012` locked the bank telemetry contract around a simple slot-first structure:
```rust
pub struct BankTelemetry {
pub bank_type: BankType,
pub used_slots: usize,
pub total_slots: usize,
}
```
This replaces the old bank telemetry path that relied on byte-oriented structures and presentation aliases. The implementation must remove `BankPolicy` and `BankStats` from the exposed bank telemetry contract path, move visible bank summaries to `AssetManager`, and migrate certification rules from byte-based thresholds to slot-based thresholds.
## Scope
### Included
- Add a visible slot-based `BankTelemetry` contract owned by `AssetManager`.
- Remove `BankStats` from the public bank telemetry contract path.
- Remove `BankPolicy` from the bank telemetry contract path and refactor internal code accordingly.
- Update bridge/consumer APIs so bank summaries come from `AssetManager` slot telemetry.
- Migrate certification from `max_gfx_bytes` / `max_audio_bytes` to slot-based limits for `GLYPH` and `SOUND`.
- Update host overlay to iterate `AssetManager` bank telemetry using canonical `GLYPH` and `SOUND` labels.
- Update specs/docs affected by the contract change.
### Excluded
- Adding new bank kinds beyond the currently supported `GLYPH` and `SOUND`.
- Reworking unrelated asset load/commit semantics.
- Redesigning generic slot-detail payloads beyond the accepted summary shape unless implementation requires a narrowly scoped helper.
## Execution Steps
### Step 1 - Introduce `BankTelemetry` in the exposed asset contract
**What:**
Define the new slot-based summary type and make it the canonical visible representation of bank telemetry.
**How:**
Add `BankTelemetry` to the HAL asset domain and ensure canonical naming uses `GLYPH` and `SOUND`. Remove or deprecate exposed `BankStats` paths that conflict with the new contract.
**File(s):**
- `crates/console/prometeu-hal/src/asset.rs`
- `crates/console/prometeu-hal/src/asset_bridge.rs`
### Step 2 - Make `AssetManager` expose bank telemetry directly
**What:**
Implement `AssetManager` support for returning `Vec<BankTelemetry>` from live slot occupancy.
**How:**
Compute `used_slots` from current slot occupancy and `total_slots` from the fixed slot arrays for `GLYPH` and `SOUND`. Keep implementation simple and derive the summary directly from the installed slots. Refactor any existing public bank-info consumers away from byte-oriented APIs.
**File(s):**
- `crates/console/prometeu-drivers/src/asset.rs`
- `crates/console/prometeu-drivers/src/memory_banks.rs`
### Step 3 - Remove byte-based bank certification rules
**What:**
Replace byte-based certification limits for banks with slot-based limits.
**How:**
Remove `max_gfx_bytes` and `max_audio_bytes` from certification config and certifier checks. Introduce slot-based limits such as `max_glyph_slots_used` and `max_sound_slots_used`, and evaluate them against `BankTelemetry` summaries or equivalent slot-based data available at frame-end.
**File(s):**
- `crates/console/prometeu-hal/src/telemetry.rs`
- `crates/console/prometeu-system/src/virtual_machine_runtime/tick.rs`
### Step 4 - Update host overlay to consume slot telemetry
**What:**
Replace hardcoded bank presentation with iteration over `AssetManager` bank telemetry.
**How:**
Render one bar per bank using `bank_type`, `used_slots`, and `total_slots`. Use canonical labels `GLYPH` and `SOUND`, and remove any byte-based bank presentation from the overlay.
**File(s):**
- `crates/host/prometeu-host-desktop-winit/src/overlay.rs`
- Any host-side adapter/helper used to access asset telemetry
### Step 5 - Remove obsolete structures from the contract path
**What:**
Eliminate `BankPolicy` and `BankStats` from the exposed bank telemetry path.
**How:**
Refactor code so the new slot-based path is the only public contract. If internal implementation helpers must remain temporarily during migration, keep them private and make sure no consumer depends on them as contract.
**File(s):**
- `crates/console/prometeu-drivers/src/asset.rs`
- `crates/console/prometeu-hal/src/asset.rs`
- Related callers revealed during implementation
### Step 6 - Update normative documentation
**What:**
Align specifications with the new slot-based bank telemetry contract.
**How:**
Update relevant runtime documentation to describe bank occupancy in terms of `GLYPH` and `SOUND` slot usage, and remove outdated references to byte-based bank telemetry where they were treated as visible contract.
**File(s):**
- `docs/specs/runtime/10-debug-inspection-and-profiling.md`
- `docs/specs/runtime/15-asset-management.md`
- Any additional touched runtime spec chapter
## Test Requirements
### Unit Tests
- `AssetManager` reports correct `BankTelemetry` for empty and occupied `GLYPH` / `SOUND` slots.
- Certification slot limits trigger correctly for `GLYPH` and `SOUND`.
- Overlay rendering accepts generic bank telemetry iteration.
### Integration Tests
- `cargo check` for affected HAL, drivers, system, and host crates.
- Focused tests for asset bank telemetry and certification migration.
### Manual Verification
- Run the desktop host and confirm banks appear as `GLYPH` and `SOUND`.
- Confirm bank bars track occupied slots instead of bytes.
- Confirm certification behavior still reports bank pressure using slot limits.
## Acceptance Criteria
- [ ] `AssetManager` exposes a visible `BankTelemetry` summary with `bank_type`, `used_slots`, and `total_slots`.
- [ ] Canonical bank names in the new contract are `GLYPH` and `SOUND`.
- [ ] Byte-based bank certification limits are removed and replaced by slot-based limits.
- [ ] Host overlay renders bank occupancy from generic slot telemetry rather than byte-oriented hardcoded bank logic.
- [ ] `BankPolicy` and `BankStats` are no longer part of the exposed bank telemetry contract path.
## Dependencies
- `DEC-0012` accepted and unchanged.
## Risks
- Removing byte-based bank paths may have wider ripple effects than expected if old APIs are reused outside the overlay.
- The line between “removed from contract path” and “removed completely from implementation” must stay explicit during refactor to avoid accidental partial migration.
- Certification migration must stay synchronized with telemetry migration or the host and certifier will diverge semantically.

View File

@ -238,6 +238,10 @@ The visual Debug Overlay (HUD) for technical inspection is not part of the emula
### 10.1 Responsibilities ### 10.1 Responsibilities
1. **Runtime:** Only exposes telemetry data via the machine diagnostics surface. It does not perform HUD rendering or string formatting. 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. 2. **Host (Desktop):** Responsible for collecting telemetry from the runtime and rendering the HUD as a native, transparent layer.
### 10.2 Principles ### 10.2 Principles
@ -253,6 +257,7 @@ To ensure zero-impact synchronization between the VM and the Host Debug Overlay,
1. **Atomic Storage:** Metrics such as cycles, syscalls, and memory usage are stored in a dedicated `AtomicTelemetry` structure using thread-safe atomic types (`AtomicU64`, `AtomicU32`, etc.). 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. 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. 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 ## 11 Integration with CAP and Certification

View File

@ -125,6 +125,7 @@ The platform layer:
- only displays the framebuffer - only displays the framebuffer
- does not reinterpret graphics commands - does not reinterpret graphics commands
- **may overlay technical HUDs without modifying the logical framebuffer** - **may overlay technical HUDs without modifying the logical framebuffer**
- may transport the logical framebuffer into a host presentation surface where a host-only overlay layer is composed
## 9 Debug and Inspection Isolation ## 9 Debug and Inspection Isolation
@ -133,6 +134,7 @@ To preserve portability and certification purity, technical inspection tools (li
- **Host-exclusive:** These tools are only implemented where they are relevant (e.g., Desktop) and do not exist in the logical machine. - **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. - **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. - **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 ### 9.1 Atomic Telemetry Interface