implements PLN-0084 remove host debug overlay

This commit is contained in:
bQUARKz 2026-05-25 14:23:18 +01:00
parent 295666b310
commit d79c11faa8
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
7 changed files with 33 additions and 536 deletions

View File

@ -4,7 +4,6 @@ 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;

View File

@ -1,487 +0,0 @@
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: &mut Firmware) -> OverlaySnapshot {
let tel = firmware.os.vm().telemetry_snapshot();
let recent_logs = firmware.os.recent_logs(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.vm().last_crash_report() {
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
.vm()
.cert_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);
let scene_ratio = ratio(tel.scene_slots_used as u64, tel.scene_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,
},
OverlayBar {
label: "SCENE",
value: format!("{} / {} slots", tel.scene_slots_used, tel.scene_slots_total),
ratio: scene_ratio,
warn: tel.scene_slots_total > 0 && tel.scene_slots_used >= tel.scene_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::*;
use prometeu_drivers::hardware::Hardware;
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; Hardware::W * Hardware::H * 4];
draw_overlay(&mut frame, Hardware::W, Hardware::H, &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,7 +3,6 @@ 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_rgba8888_to_rgba8;
use pixels::wgpu::PresentMode;
@ -128,9 +127,6 @@ pub struct HostRunner {
/// directly instead of depending on guest-visible inspection syscalls.
debugger: HostDebugger,
/// Flag to enable/disable the technical telemetry display.
overlay_enabled: bool,
/// The physical audio driver.
audio: HostAudio,
/// Last known pause state to sync with audio.
@ -170,7 +166,6 @@ impl HostRunner {
stats: HostStats::new(),
debugger: HostDebugger::new(),
overlay_enabled: false,
audio: HostAudio::new(),
last_paused_state: false,
presentation: PresentationState::default(),
@ -272,9 +267,6 @@ impl ApplicationHandler for HostRunner {
}
WindowEvent::RedrawRequested => {
let overlay_snapshot =
self.overlay_enabled.then(|| capture_snapshot(&self.stats, &mut 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");
@ -286,10 +278,6 @@ impl ApplicationHandler for HostRunner {
let src = self.hardware.gfx.front_buffer();
draw_rgba8888_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() {
@ -309,12 +297,6 @@ impl ApplicationHandler for HostRunner {
self.request_redraw_if_needed();
println!("[Debugger] Execution started!");
}
if is_down && code == KeyCode::F1 {
self.overlay_enabled = !self.overlay_enabled;
self.invalidate_host_surface();
self.request_redraw_if_needed();
}
}
}
@ -332,12 +314,8 @@ impl ApplicationHandler for HostRunner {
// 1. Process pending debug commands from the network.
self.debugger.check_commands(&mut self.firmware, &mut self.hardware);
// Sync inspection mode state. This is host-owned overlay/debugger control,
// not a guest-visible debug ABI switch.
self.firmware
.os
.vm()
.set_inspection_active(self.overlay_enabled || self.debugger.stream.is_some());
// Sync debugger inspection state; this is not a guest-visible debug ABI switch.
self.firmware.os.vm().set_inspection_active(self.debugger.stream.is_some());
// 2. Maintain a filesystem connection if it was lost (e.g., directory removed).
if let Some(root) = &self.fs_root

View File

@ -1,5 +1,5 @@
{"type":"meta","next_id":{"DSC":39,"AGD":39,"DEC":31,"PLN":87,"LSN":47,"CLSN":1}}
{"type":"discussion","id":"DSC-0038","status":"in_progress","ticket":"render-frame-packet-boundary","title":"Logical Render Pipelines and Command Packets","created_at":"2026-05-25","updated_at":"2026-05-25","tags":["gfx","renderer","runtime","frame-composer","architecture","ui","pipeline"],"agendas":[{"id":"AGD-0038","file":"AGD-0038-renderframepacket-boundary-for-classic-2d-renderer.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25"}],"decisions":[{"id":"DEC-0030","file":"DEC-0030-logical-render-pipeline-command-boundary.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25","ref_agenda":"AGD-0038"}],"plans":[{"id":"PLN-0073","file":"PLN-0073-render-contract-specs.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0074","file":"PLN-0074-hal-render-submission-types.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0075","file":"PLN-0075-rendermanager-core.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0076","file":"PLN-0076-capabilities-and-abi-domain-split.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0077","file":"PLN-0077-composer-buffer-and-game2d-packet.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0078","file":"PLN-0078-classic2d-game-renderer-consumer.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0079","file":"PLN-0079-gfx2d-primitive-domain.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0080","file":"PLN-0080-shell-ui-packet-and-gfxui-domain.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0081","file":"PLN-0081-shell-hub-migration.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0082","file":"PLN-0082-frame-publication-and-present-boundary.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0083","file":"PLN-0083-fade-removal.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0084","file":"PLN-0084-host-debug-overlay-removal.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0085","file":"PLN-0085-pbs-stdlib-and-syscall-declarations.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0086","file":"PLN-0086-end-to-end-render-boundary-validation.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]}],"lessons":[]}
{"type":"discussion","id":"DSC-0038","status":"in_progress","ticket":"render-frame-packet-boundary","title":"Logical Render Pipelines and Command Packets","created_at":"2026-05-25","updated_at":"2026-05-25","tags":["gfx","renderer","runtime","frame-composer","architecture","ui","pipeline"],"agendas":[{"id":"AGD-0038","file":"AGD-0038-renderframepacket-boundary-for-classic-2d-renderer.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25"}],"decisions":[{"id":"DEC-0030","file":"DEC-0030-logical-render-pipeline-command-boundary.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25","ref_agenda":"AGD-0038"}],"plans":[{"id":"PLN-0073","file":"PLN-0073-render-contract-specs.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0074","file":"PLN-0074-hal-render-submission-types.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0075","file":"PLN-0075-rendermanager-core.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0076","file":"PLN-0076-capabilities-and-abi-domain-split.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0077","file":"PLN-0077-composer-buffer-and-game2d-packet.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0078","file":"PLN-0078-classic2d-game-renderer-consumer.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0079","file":"PLN-0079-gfx2d-primitive-domain.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0080","file":"PLN-0080-shell-ui-packet-and-gfxui-domain.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0081","file":"PLN-0081-shell-hub-migration.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0082","file":"PLN-0082-frame-publication-and-present-boundary.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0083","file":"PLN-0083-fade-removal.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0084","file":"PLN-0084-host-debug-overlay-removal.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0085","file":"PLN-0085-pbs-stdlib-and-syscall-declarations.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0086","file":"PLN-0086-end-to-end-render-boundary-validation.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]}],"lessons":[]}
{"type":"discussion","id":"DSC-0035","status":"done","ticket":"task-owned-shell-windows","title":"Agenda - Task-Owned Shell Windows","created_at":"2026-05-15","updated_at":"2026-05-15","tags":["runtime","os","task","window-manager","shell","lifecycle"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0044","file":"discussion/lessons/DSC-0035-task-owned-shell-windows/LSN-0044-task-window-liveness-belongs-to-the-task.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15"}]}
{"type":"discussion","id":"DSC-0034","status":"done","ticket":"system-os-domain-facades","title":"Agenda - SystemOS Domain Facades","created_at":"2026-05-15","updated_at":"2026-05-15","tags":["runtime","os","services","api-surface","lifecycle","fs"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0043","file":"discussion/lessons/DSC-0034-system-os-domain-facades/LSN-0043-systemos-domain-facades.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15"}]}
{"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":"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":"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":"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":"discussion/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"}]}

View File

@ -2,7 +2,7 @@
id: PLN-0084
ticket: render-frame-packet-boundary
title: Host Debug Overlay Removal
status: open
status: done
created: 2026-05-25
ref_decisions: [DEC-0030]
tags: [gfx, renderer, runtime, frame-composer, architecture, ui, pipeline]

View File

@ -18,7 +18,7 @@ It covers:
- profiling;
- event and fault visibility;
- certification input data;
- host-side debug overlay (HUD) isolation.
- host-side debug UI isolation.
## 2 Execution Modes
@ -131,15 +131,15 @@ The host may:
- observe buffers separately
- identify excessive redraw
Host-owned overlay or inspection surfaces MUST observe the last published logical frame rather than force continuous redraw to probe for changes.
Host-owned inspection surfaces MUST observe the last published logical frame rather than force continuous redraw to probe for changes.
When overlay, debugger, or other host-only diagnostics become visually stable:
When debugger or other host-only diagnostics become stable:
- the host MUST preserve the last valid visible image;
- the host MUST NOT keep recomposing the surface by continuous polling alone;
- additional redraw is only justified by a newly published logical frame or a host-owned invalidation event such as overlay toggle, resize, expose, or explicit debugger-visible state transition.
- additional redraw is only justified by a newly published logical frame or a host-owned invalidation event such as resize, expose, or explicit debugger-visible state transition.
Paused or breakpointed execution does not grant permission to swap logical machine buffers just to sustain a host-only HUD.
Paused or breakpointed execution does not grant permission to swap logical machine buffers just to sustain host-only diagnostics.
## 6 Time Profiling (Cycles)
@ -285,28 +285,32 @@ Each event has:
- cost
- consequence
## 11 Host-Side Debug Overlay (HUD) Isolation
## 11 Host-Side Debug UI Isolation
The visual Debug Overlay (HUD) for technical inspection is not part of the emulated machine pipeline.
The desktop host MUST NOT inject a debug overlay into Game or Shell output. Any
future visual debug UI is a separate product/domain concern and requires its own
decision before it can become part of host presentation.
### 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.
6. **Host (Desktop):** Responsible for collecting telemetry from the runtime and rendering the HUD as a native, transparent layer.
3. **Host diagnostics:** The Desktop Host may consume telemetry for logs,
debugger streams, and non-render diagnostics, but it does not compose debug
pixels into the presentation surface.
4. **Composition boundary:** Published Game and Shell output must be presented
without host-owned debug overlay injection.
### 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.
- **Zero Pipeline Interference:** Host diagnostics must not inject pixels into
the emulated framebuffer or the host presentation surface.
- **Zero Cycle Impact:** Diagnostic processing must occur outside the emulated
machine cycles.
### 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**:
To ensure zero-impact synchronization between the VM and host diagnostics, 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.
@ -320,4 +324,4 @@ All machine diagnostics and profiling data:
- feed the host-owned certification report;
- are collected deterministically;
- do not require a guest-visible debug ABI;
- are consistent regardless of whether the Host HUD is active or not.
- are consistent regardless of whether host diagnostics are active or not.

View File

@ -51,7 +51,8 @@ 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)**
- technical inspection and debugger integrations that do not modify presented
Game or Shell pixels
The host provides realization of machine surfaces. It does not redefine cartridge semantics.
@ -123,8 +124,8 @@ The graphics system:
The platform layer:
- consumes closed render submissions through a render-surface implementation
- **may overlay technical HUDs without modifying the logical framebuffer**
- may transport published RGBA8888 output into a host presentation surface where a host-only overlay layer is composed
- transports published RGBA8888 output into a host presentation surface without
injecting host-owned debug overlay pixels
The host presentation layer MUST treat RGBA8888 as the canonical logical
framebuffer format. RGB565 conversion is not part of the normal host
@ -136,17 +137,19 @@ host-owned invalidation, not by perpetual redraw polling.
In particular:
- a stable published submission MAY remain visible across multiple host wakeups without recomposition;
- a host MAY redraw the same logical frame again when its own surface is invalidated by resize, expose, or host-only overlay/debug changes;
- a host MAY redraw the same logical frame again when its own surface is invalidated by resize, expose, or debugger-visible host changes;
- a host MUST NOT invent intermediate logical frames or require continuous redraw merely to discover whether a new logical frame exists.
## 9 Debug and Inspection Isolation
To preserve portability and certification purity, technical inspection tools (like the Debug Overlay) are moved to the Host layer.
To preserve portability and certification purity, technical inspection tools are host-owned and must not inject pixels into Game or Shell output.
- **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.
- **Contract Boundary:** Emulated graphics primitives such as `fill_rect` and
`draw_text` remain part of the machine contract; host diagnostics must not
depend on them for visual injection.
### 9.1 Atomic Telemetry Interface