[PERF] Host Debug Overlay Isolation
This commit is contained in:
parent
58a5f9a3a6
commit
4cdfaefda7
@ -2,8 +2,8 @@
|
|||||||
use crate::memory_banks::{GlyphBankPoolInstaller, SoundBankPoolInstaller};
|
use crate::memory_banks::{GlyphBankPoolInstaller, SoundBankPoolInstaller};
|
||||||
use prometeu_hal::AssetBridge;
|
use prometeu_hal::AssetBridge;
|
||||||
use prometeu_hal::asset::{
|
use prometeu_hal::asset::{
|
||||||
AssetCodec, AssetEntry, AssetId, AssetLoadError, AssetOpStatus, BankStats, BankType, HandleId,
|
AssetCodec, AssetEntry, AssetId, AssetLoadError, AssetOpStatus, BankTelemetry, BankType,
|
||||||
LoadStatus, PreloadEntry, SlotRef, SlotStats,
|
HandleId, LoadStatus, PreloadEntry, SlotRef, SlotStats,
|
||||||
};
|
};
|
||||||
use prometeu_hal::cartridge::AssetsPayloadSource;
|
use prometeu_hal::cartridge::AssetsPayloadSource;
|
||||||
use prometeu_hal::color::Color;
|
use prometeu_hal::color::Color;
|
||||||
@ -161,11 +161,6 @@ pub struct AssetManager {
|
|||||||
/// Residency policy for sound banks.
|
/// Residency policy for sound banks.
|
||||||
sound_policy: BankPolicy<SoundBank>,
|
sound_policy: BankPolicy<SoundBank>,
|
||||||
|
|
||||||
/// Count of occupied slots for GFX.
|
|
||||||
gfx_slots_occupied: AtomicUsize,
|
|
||||||
/// Count of occupied slots for sounds.
|
|
||||||
sound_slots_occupied: AtomicUsize,
|
|
||||||
|
|
||||||
// Commits that are ready to be applied at the next frame boundary.
|
// Commits that are ready to be applied at the next frame boundary.
|
||||||
pending_commits: Mutex<Vec<HandleId>>,
|
pending_commits: Mutex<Vec<HandleId>>,
|
||||||
}
|
}
|
||||||
@ -206,8 +201,8 @@ impl AssetBridge for AssetManager {
|
|||||||
fn apply_commits(&self) {
|
fn apply_commits(&self) {
|
||||||
self.apply_commits()
|
self.apply_commits()
|
||||||
}
|
}
|
||||||
fn bank_info(&self, kind: BankType) -> BankStats {
|
fn bank_telemetry(&self) -> Vec<BankTelemetry> {
|
||||||
self.bank_info(kind)
|
self.bank_telemetry()
|
||||||
}
|
}
|
||||||
fn slot_info(&self, slot: SlotRef) -> SlotStats {
|
fn slot_info(&self, slot: SlotRef) -> SlotStats {
|
||||||
self.slot_info(slot)
|
self.slot_info(slot)
|
||||||
@ -302,8 +297,6 @@ impl AssetManager {
|
|||||||
sound_slots: Arc::new(RwLock::new(std::array::from_fn(|_| None))),
|
sound_slots: Arc::new(RwLock::new(std::array::from_fn(|_| None))),
|
||||||
gfx_policy: BankPolicy::new(),
|
gfx_policy: BankPolicy::new(),
|
||||||
sound_policy: BankPolicy::new(),
|
sound_policy: BankPolicy::new(),
|
||||||
gfx_slots_occupied: AtomicUsize::new(0),
|
|
||||||
sound_slots_occupied: AtomicUsize::new(0),
|
|
||||||
handles: Arc::new(RwLock::new(HashMap::new())),
|
handles: Arc::new(RwLock::new(HashMap::new())),
|
||||||
next_handle_id: Mutex::new(1),
|
next_handle_id: Mutex::new(1),
|
||||||
assets_data: Arc::new(RwLock::new(assets_data)),
|
assets_data: Arc::new(RwLock::new(assets_data)),
|
||||||
@ -732,9 +725,6 @@ impl AssetManager {
|
|||||||
self.gfx_installer.install_glyph_bank(h.slot.index, bank);
|
self.gfx_installer.install_glyph_bank(h.slot.index, bank);
|
||||||
let mut slots = self.gfx_slots.write().unwrap();
|
let mut slots = self.gfx_slots.write().unwrap();
|
||||||
if h.slot.index < slots.len() {
|
if h.slot.index < slots.len() {
|
||||||
if slots[h.slot.index].is_none() {
|
|
||||||
self.gfx_slots_occupied.fetch_add(1, Ordering::Relaxed);
|
|
||||||
}
|
|
||||||
slots[h.slot.index] = Some(h._asset_id);
|
slots[h.slot.index] = Some(h._asset_id);
|
||||||
}
|
}
|
||||||
h.status = LoadStatus::COMMITTED;
|
h.status = LoadStatus::COMMITTED;
|
||||||
@ -745,9 +735,6 @@ impl AssetManager {
|
|||||||
self.sound_installer.install_sound_bank(h.slot.index, bank);
|
self.sound_installer.install_sound_bank(h.slot.index, bank);
|
||||||
let mut slots = self.sound_slots.write().unwrap();
|
let mut slots = self.sound_slots.write().unwrap();
|
||||||
if h.slot.index < slots.len() {
|
if h.slot.index < slots.len() {
|
||||||
if slots[h.slot.index].is_none() {
|
|
||||||
self.sound_slots_occupied.fetch_add(1, Ordering::Relaxed);
|
|
||||||
}
|
|
||||||
slots[h.slot.index] = Some(h._asset_id);
|
slots[h.slot.index] = Some(h._asset_id);
|
||||||
}
|
}
|
||||||
h.status = LoadStatus::COMMITTED;
|
h.status = LoadStatus::COMMITTED;
|
||||||
@ -759,37 +746,21 @@ impl AssetManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn bank_info(&self, kind: BankType) -> BankStats {
|
pub fn bank_telemetry(&self) -> Vec<BankTelemetry> {
|
||||||
match kind {
|
vec![self.bank_telemetry_for(BankType::GLYPH), self.bank_telemetry_for(BankType::SOUNDS)]
|
||||||
BankType::GLYPH => {
|
|
||||||
let used_bytes = self.gfx_policy.used_bytes.load(Ordering::Relaxed);
|
|
||||||
let inflight_bytes = self.gfx_policy.inflight_bytes.load(Ordering::Relaxed);
|
|
||||||
let slots_occupied = self.gfx_slots_occupied.load(Ordering::Relaxed);
|
|
||||||
|
|
||||||
BankStats {
|
|
||||||
total_bytes: 16 * 1024 * 1024,
|
|
||||||
used_bytes,
|
|
||||||
free_bytes: (16usize * 1024 * 1024).saturating_sub(used_bytes),
|
|
||||||
inflight_bytes,
|
|
||||||
slot_count: 16,
|
|
||||||
slots_occupied,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn bank_telemetry_for(&self, kind: BankType) -> BankTelemetry {
|
||||||
|
let used_slots = match kind {
|
||||||
|
BankType::GLYPH => {
|
||||||
|
self.gfx_slots.read().unwrap().iter().filter(|slot| slot.is_some()).count()
|
||||||
}
|
}
|
||||||
BankType::SOUNDS => {
|
BankType::SOUNDS => {
|
||||||
let used_bytes = self.sound_policy.used_bytes.load(Ordering::Relaxed);
|
self.sound_slots.read().unwrap().iter().filter(|slot| slot.is_some()).count()
|
||||||
let inflight_bytes = self.sound_policy.inflight_bytes.load(Ordering::Relaxed);
|
}
|
||||||
let slots_occupied = self.sound_slots_occupied.load(Ordering::Relaxed);
|
};
|
||||||
|
|
||||||
BankStats {
|
BankTelemetry { bank_type: kind, used_slots, total_slots: 16 }
|
||||||
total_bytes: 32 * 1024 * 1024,
|
|
||||||
used_bytes,
|
|
||||||
free_bytes: (32usize * 1024 * 1024).saturating_sub(used_bytes),
|
|
||||||
inflight_bytes,
|
|
||||||
slot_count: 16,
|
|
||||||
slots_occupied,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn slot_info(&self, slot: SlotRef) -> SlotStats {
|
pub fn slot_info(&self, slot: SlotRef) -> SlotStats {
|
||||||
@ -842,8 +813,6 @@ impl AssetManager {
|
|||||||
pub fn shutdown(&self) {
|
pub fn shutdown(&self) {
|
||||||
self.gfx_policy.clear();
|
self.gfx_policy.clear();
|
||||||
self.sound_policy.clear();
|
self.sound_policy.clear();
|
||||||
self.gfx_slots_occupied.store(0, Ordering::Relaxed);
|
|
||||||
self.sound_slots_occupied.store(0, Ordering::Relaxed);
|
|
||||||
self.handles.write().unwrap().clear();
|
self.handles.write().unwrap().clear();
|
||||||
self.pending_commits.lock().unwrap().clear();
|
self.pending_commits.lock().unwrap().clear();
|
||||||
self.gfx_slots.write().unwrap().fill(None);
|
self.gfx_slots.write().unwrap().fill(None);
|
||||||
@ -1196,7 +1165,6 @@ mod tests {
|
|||||||
|
|
||||||
let width = 16;
|
let width = 16;
|
||||||
let height = 16;
|
let height = 16;
|
||||||
let decoded_bytes = expected_glyph_decoded_size(width, height);
|
|
||||||
let data = test_glyph_asset_data();
|
let data = test_glyph_asset_data();
|
||||||
|
|
||||||
let am = AssetManager::new(
|
let am = AssetManager::new(
|
||||||
@ -1207,10 +1175,10 @@ mod tests {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Initially zero
|
// Initially zero
|
||||||
let info = am.bank_info(BankType::GLYPH);
|
let info = am.bank_telemetry();
|
||||||
assert_eq!(info.used_bytes, 0);
|
assert_eq!(info[0].bank_type, BankType::GLYPH);
|
||||||
assert_eq!(info.inflight_bytes, 0);
|
assert_eq!(info[0].used_slots, 0);
|
||||||
assert_eq!(info.slots_occupied, 0);
|
assert_eq!(info[0].total_slots, 16);
|
||||||
|
|
||||||
// Loading
|
// Loading
|
||||||
let handle = am.load(0, 0).expect("load must allocate handle");
|
let handle = am.load(0, 0).expect("load must allocate handle");
|
||||||
@ -1221,26 +1189,21 @@ mod tests {
|
|||||||
thread::sleep(std::time::Duration::from_millis(10));
|
thread::sleep(std::time::Duration::from_millis(10));
|
||||||
}
|
}
|
||||||
|
|
||||||
let info = am.bank_info(BankType::GLYPH);
|
let info = am.bank_telemetry();
|
||||||
// Note: put_resident happens in worker thread, then stage happens.
|
assert_eq!(info[0].used_slots, 0);
|
||||||
assert_eq!(info.used_bytes, decoded_bytes);
|
|
||||||
assert_eq!(info.inflight_bytes, decoded_bytes);
|
|
||||||
assert_eq!(info.slots_occupied, 0);
|
|
||||||
|
|
||||||
// Commit
|
// Commit
|
||||||
am.commit(handle);
|
am.commit(handle);
|
||||||
am.apply_commits();
|
am.apply_commits();
|
||||||
|
|
||||||
let info = am.bank_info(BankType::GLYPH);
|
let info = am.bank_telemetry();
|
||||||
assert_eq!(info.used_bytes, decoded_bytes);
|
assert_eq!(info[0].used_slots, 1);
|
||||||
assert_eq!(info.inflight_bytes, 0);
|
assert_eq!(info[1].bank_type, BankType::SOUNDS);
|
||||||
assert_eq!(info.slots_occupied, 1);
|
assert_eq!(info[1].used_slots, 0);
|
||||||
|
|
||||||
// Shutdown resets
|
// Shutdown resets
|
||||||
am.shutdown();
|
am.shutdown();
|
||||||
let info = am.bank_info(BankType::GLYPH);
|
let info = am.bank_telemetry();
|
||||||
assert_eq!(info.used_bytes, 0);
|
assert_eq!(info[0].used_slots, 0);
|
||||||
assert_eq!(info.inflight_bytes, 0);
|
|
||||||
assert_eq!(info.slots_occupied, 0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -105,13 +105,10 @@ pub enum AssetOpStatus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct BankStats {
|
pub struct BankTelemetry {
|
||||||
pub total_bytes: usize,
|
pub bank_type: BankType,
|
||||||
pub used_bytes: usize,
|
pub used_slots: usize,
|
||||||
pub free_bytes: usize,
|
pub total_slots: usize,
|
||||||
pub inflight_bytes: usize,
|
|
||||||
pub slot_count: usize,
|
|
||||||
pub slots_occupied: usize,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
use crate::asset::{
|
use crate::asset::{
|
||||||
AssetEntry, AssetId, AssetLoadError, AssetOpStatus, BankStats, BankType, HandleId, LoadStatus,
|
AssetEntry, AssetId, AssetLoadError, AssetOpStatus, BankTelemetry, HandleId, LoadStatus,
|
||||||
PreloadEntry, SlotRef, SlotStats,
|
PreloadEntry, SlotRef, SlotStats,
|
||||||
};
|
};
|
||||||
use crate::cartridge::AssetsPayloadSource;
|
use crate::cartridge::AssetsPayloadSource;
|
||||||
@ -16,7 +16,7 @@ pub trait AssetBridge {
|
|||||||
fn commit(&self, handle: HandleId) -> AssetOpStatus;
|
fn commit(&self, handle: HandleId) -> AssetOpStatus;
|
||||||
fn cancel(&self, handle: HandleId) -> AssetOpStatus;
|
fn cancel(&self, handle: HandleId) -> AssetOpStatus;
|
||||||
fn apply_commits(&self);
|
fn apply_commits(&self);
|
||||||
fn bank_info(&self, kind: BankType) -> BankStats;
|
fn bank_telemetry(&self) -> Vec<BankTelemetry>;
|
||||||
fn slot_info(&self, slot: SlotRef) -> SlotStats;
|
fn slot_info(&self, slot: SlotRef) -> SlotStats;
|
||||||
fn shutdown(&self);
|
fn shutdown(&self);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -64,12 +64,10 @@ pub enum DebugEvent {
|
|||||||
cycles_budget: u64,
|
cycles_budget: u64,
|
||||||
host_cpu_time_us: u64,
|
host_cpu_time_us: u64,
|
||||||
violations: u32,
|
violations: u32,
|
||||||
gfx_used_bytes: usize,
|
glyph_slots_used: u32,
|
||||||
gfx_inflight_bytes: usize,
|
glyph_slots_total: u32,
|
||||||
gfx_slots_occupied: u32,
|
sound_slots_used: u32,
|
||||||
audio_used_bytes: usize,
|
sound_slots_total: u32,
|
||||||
audio_inflight_bytes: usize,
|
|
||||||
audio_slots_occupied: u32,
|
|
||||||
},
|
},
|
||||||
#[serde(rename = "fault")]
|
#[serde(rename = "fault")]
|
||||||
Fault {
|
Fault {
|
||||||
@ -97,12 +95,10 @@ mod tests {
|
|||||||
cycles_budget: 10000,
|
cycles_budget: 10000,
|
||||||
host_cpu_time_us: 1200,
|
host_cpu_time_us: 1200,
|
||||||
violations: 0,
|
violations: 0,
|
||||||
gfx_used_bytes: 1024,
|
glyph_slots_used: 1,
|
||||||
gfx_inflight_bytes: 0,
|
glyph_slots_total: 16,
|
||||||
gfx_slots_occupied: 1,
|
sound_slots_used: 2,
|
||||||
audio_used_bytes: 2048,
|
sound_slots_total: 16,
|
||||||
audio_inflight_bytes: 0,
|
|
||||||
audio_slots_occupied: 2,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let json = serde_json::to_string(&event).unwrap();
|
let json = serde_json::to_string(&event).unwrap();
|
||||||
|
|||||||
@ -13,15 +13,11 @@ pub struct TelemetryFrame {
|
|||||||
pub completed_logical_frames: u32,
|
pub completed_logical_frames: u32,
|
||||||
pub violations: u32,
|
pub violations: u32,
|
||||||
|
|
||||||
// GFX Banks
|
// Bank telemetry
|
||||||
pub gfx_used_bytes: usize,
|
pub glyph_slots_used: u32,
|
||||||
pub gfx_inflight_bytes: usize,
|
pub glyph_slots_total: u32,
|
||||||
pub gfx_slots_occupied: u32,
|
pub sound_slots_used: u32,
|
||||||
|
pub sound_slots_total: u32,
|
||||||
// Audio Banks
|
|
||||||
pub audio_used_bytes: usize,
|
|
||||||
pub audio_inflight_bytes: usize,
|
|
||||||
pub audio_slots_occupied: u32,
|
|
||||||
|
|
||||||
// RAM (Heap)
|
// RAM (Heap)
|
||||||
pub heap_used_bytes: usize,
|
pub heap_used_bytes: usize,
|
||||||
@ -44,15 +40,11 @@ pub struct AtomicTelemetry {
|
|||||||
pub completed_logical_frames: AtomicU32,
|
pub completed_logical_frames: AtomicU32,
|
||||||
pub violations: AtomicU32,
|
pub violations: AtomicU32,
|
||||||
|
|
||||||
// GFX Banks
|
// Bank telemetry
|
||||||
pub gfx_used_bytes: AtomicUsize,
|
pub glyph_slots_used: AtomicU32,
|
||||||
pub gfx_inflight_bytes: AtomicUsize,
|
pub glyph_slots_total: AtomicU32,
|
||||||
pub gfx_slots_occupied: AtomicU32,
|
pub sound_slots_used: AtomicU32,
|
||||||
|
pub sound_slots_total: AtomicU32,
|
||||||
// Audio Banks
|
|
||||||
pub audio_used_bytes: AtomicUsize,
|
|
||||||
pub audio_inflight_bytes: AtomicUsize,
|
|
||||||
pub audio_slots_occupied: AtomicU32,
|
|
||||||
|
|
||||||
// RAM (Heap)
|
// RAM (Heap)
|
||||||
pub heap_used_bytes: AtomicUsize,
|
pub heap_used_bytes: AtomicUsize,
|
||||||
@ -79,12 +71,10 @@ impl AtomicTelemetry {
|
|||||||
host_cpu_time_us: self.host_cpu_time_us.load(Ordering::Relaxed),
|
host_cpu_time_us: self.host_cpu_time_us.load(Ordering::Relaxed),
|
||||||
completed_logical_frames: self.completed_logical_frames.load(Ordering::Relaxed),
|
completed_logical_frames: self.completed_logical_frames.load(Ordering::Relaxed),
|
||||||
violations: self.violations.load(Ordering::Relaxed),
|
violations: self.violations.load(Ordering::Relaxed),
|
||||||
gfx_used_bytes: self.gfx_used_bytes.load(Ordering::Relaxed),
|
glyph_slots_used: self.glyph_slots_used.load(Ordering::Relaxed),
|
||||||
gfx_inflight_bytes: self.gfx_inflight_bytes.load(Ordering::Relaxed),
|
glyph_slots_total: self.glyph_slots_total.load(Ordering::Relaxed),
|
||||||
gfx_slots_occupied: self.gfx_slots_occupied.load(Ordering::Relaxed),
|
sound_slots_used: self.sound_slots_used.load(Ordering::Relaxed),
|
||||||
audio_used_bytes: self.audio_used_bytes.load(Ordering::Relaxed),
|
sound_slots_total: self.sound_slots_total.load(Ordering::Relaxed),
|
||||||
audio_inflight_bytes: self.audio_inflight_bytes.load(Ordering::Relaxed),
|
|
||||||
audio_slots_occupied: self.audio_slots_occupied.load(Ordering::Relaxed),
|
|
||||||
heap_used_bytes: self.heap_used_bytes.load(Ordering::Relaxed),
|
heap_used_bytes: self.heap_used_bytes.load(Ordering::Relaxed),
|
||||||
heap_max_bytes: self.heap_max_bytes.load(Ordering::Relaxed),
|
heap_max_bytes: self.heap_max_bytes.load(Ordering::Relaxed),
|
||||||
logs_count: self.logs_count.load(Ordering::Relaxed),
|
logs_count: self.logs_count.load(Ordering::Relaxed),
|
||||||
@ -99,12 +89,10 @@ impl AtomicTelemetry {
|
|||||||
self.host_cpu_time_us.store(0, Ordering::Relaxed);
|
self.host_cpu_time_us.store(0, Ordering::Relaxed);
|
||||||
self.completed_logical_frames.store(0, Ordering::Relaxed);
|
self.completed_logical_frames.store(0, Ordering::Relaxed);
|
||||||
self.violations.store(0, Ordering::Relaxed);
|
self.violations.store(0, Ordering::Relaxed);
|
||||||
self.gfx_used_bytes.store(0, Ordering::Relaxed);
|
self.glyph_slots_used.store(0, Ordering::Relaxed);
|
||||||
self.gfx_inflight_bytes.store(0, Ordering::Relaxed);
|
self.glyph_slots_total.store(0, Ordering::Relaxed);
|
||||||
self.gfx_slots_occupied.store(0, Ordering::Relaxed);
|
self.sound_slots_used.store(0, Ordering::Relaxed);
|
||||||
self.audio_used_bytes.store(0, Ordering::Relaxed);
|
self.sound_slots_total.store(0, Ordering::Relaxed);
|
||||||
self.audio_inflight_bytes.store(0, Ordering::Relaxed);
|
|
||||||
self.audio_slots_occupied.store(0, Ordering::Relaxed);
|
|
||||||
self.heap_used_bytes.store(0, Ordering::Relaxed);
|
self.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.logs_count.store(0, Ordering::Relaxed);
|
||||||
@ -118,8 +106,8 @@ pub struct CertificationConfig {
|
|||||||
pub cycles_budget_per_frame: Option<u64>,
|
pub cycles_budget_per_frame: Option<u64>,
|
||||||
pub max_syscalls_per_frame: Option<u32>,
|
pub max_syscalls_per_frame: Option<u32>,
|
||||||
pub max_host_cpu_us_per_frame: Option<u64>,
|
pub max_host_cpu_us_per_frame: Option<u64>,
|
||||||
pub max_gfx_bytes: Option<usize>,
|
pub max_glyph_slots_used: Option<u32>,
|
||||||
pub max_audio_bytes: Option<usize>,
|
pub max_sound_slots_used: Option<u32>,
|
||||||
pub max_heap_bytes: Option<usize>,
|
pub max_heap_bytes: Option<usize>,
|
||||||
pub max_logs_per_frame: Option<u32>,
|
pub max_logs_per_frame: Option<u32>,
|
||||||
}
|
}
|
||||||
@ -199,9 +187,9 @@ impl Certifier {
|
|||||||
violations += 1;
|
violations += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. GFX Memory
|
// 4. GLYPH bank slots
|
||||||
if let Some(limit) = self.config.max_gfx_bytes
|
if let Some(limit) = self.config.max_glyph_slots_used
|
||||||
&& telemetry.gfx_used_bytes > limit
|
&& telemetry.glyph_slots_used > limit
|
||||||
{
|
{
|
||||||
log_service.log(
|
log_service.log(
|
||||||
ts_ms,
|
ts_ms,
|
||||||
@ -210,16 +198,16 @@ impl Certifier {
|
|||||||
LogSource::Pos,
|
LogSource::Pos,
|
||||||
0xCA04,
|
0xCA04,
|
||||||
format!(
|
format!(
|
||||||
"Cert: GFX bank exceeded memory limit ({} > {})",
|
"Cert: GLYPH bank exceeded slot limit ({} > {})",
|
||||||
telemetry.gfx_used_bytes, limit
|
telemetry.glyph_slots_used, limit
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
violations += 1;
|
violations += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. Audio Memory
|
// 5. SOUNDS bank slots
|
||||||
if let Some(limit) = self.config.max_audio_bytes
|
if let Some(limit) = self.config.max_sound_slots_used
|
||||||
&& telemetry.audio_used_bytes > limit
|
&& telemetry.sound_slots_used > limit
|
||||||
{
|
{
|
||||||
log_service.log(
|
log_service.log(
|
||||||
ts_ms,
|
ts_ms,
|
||||||
@ -228,8 +216,8 @@ impl Certifier {
|
|||||||
LogSource::Pos,
|
LogSource::Pos,
|
||||||
0xCA05,
|
0xCA05,
|
||||||
format!(
|
format!(
|
||||||
"Cert: Audio bank exceeded memory limit ({} > {})",
|
"Cert: SOUNDS bank exceeded slot limit ({} > {})",
|
||||||
telemetry.audio_used_bytes, limit
|
telemetry.sound_slots_used, limit
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
violations += 1;
|
violations += 1;
|
||||||
@ -285,7 +273,7 @@ mod tests {
|
|||||||
cycles_budget_per_frame: Some(100),
|
cycles_budget_per_frame: Some(100),
|
||||||
max_syscalls_per_frame: Some(5),
|
max_syscalls_per_frame: Some(5),
|
||||||
max_host_cpu_us_per_frame: Some(1000),
|
max_host_cpu_us_per_frame: Some(1000),
|
||||||
max_gfx_bytes: Some(1024),
|
max_glyph_slots_used: Some(1),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let cert = Certifier::new(config);
|
let cert = Certifier::new(config);
|
||||||
@ -294,7 +282,7 @@ mod tests {
|
|||||||
tel.cycles_used = 150;
|
tel.cycles_used = 150;
|
||||||
tel.syscalls = 10;
|
tel.syscalls = 10;
|
||||||
tel.host_cpu_time_us = 500;
|
tel.host_cpu_time_us = 500;
|
||||||
tel.gfx_used_bytes = 2048;
|
tel.glyph_slots_used = 2;
|
||||||
|
|
||||||
let violations = cert.evaluate(&tel, &mut ls, 1000);
|
let violations = cert.evaluate(&tel, &mut ls, 1000);
|
||||||
assert_eq!(violations, 3);
|
assert_eq!(violations, 3);
|
||||||
@ -303,7 +291,7 @@ mod tests {
|
|||||||
assert_eq!(logs.len(), 3);
|
assert_eq!(logs.len(), 3);
|
||||||
assert!(logs[0].msg.contains("cycles_used"));
|
assert!(logs[0].msg.contains("cycles_used"));
|
||||||
assert!(logs[1].msg.contains("syscalls"));
|
assert!(logs[1].msg.contains("syscalls"));
|
||||||
assert!(logs[2].msg.contains("GFX bank"));
|
assert!(logs[2].msg.contains("GLYPH bank"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@ -486,8 +486,17 @@ impl NativeInterface for VirtualMachineRuntime {
|
|||||||
1 => BankType::SOUNDS,
|
1 => BankType::SOUNDS,
|
||||||
_ => return Err(VmFault::Trap(TRAP_TYPE, "Invalid asset type".to_string())),
|
_ => return Err(VmFault::Trap(TRAP_TYPE, "Invalid asset type".to_string())),
|
||||||
};
|
};
|
||||||
let json =
|
let telemetry = hw
|
||||||
serde_json::to_string(&hw.assets().bank_info(asset_type)).unwrap_or_default();
|
.assets()
|
||||||
|
.bank_telemetry()
|
||||||
|
.into_iter()
|
||||||
|
.find(|entry| entry.bank_type == asset_type)
|
||||||
|
.unwrap_or(prometeu_hal::asset::BankTelemetry {
|
||||||
|
bank_type: asset_type,
|
||||||
|
used_slots: 0,
|
||||||
|
total_slots: 0,
|
||||||
|
});
|
||||||
|
let json = serde_json::to_string(&telemetry).unwrap_or_default();
|
||||||
ret.push_string(json);
|
ret.push_string(json);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,26 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::CrashReport;
|
use crate::CrashReport;
|
||||||
use prometeu_hal::asset::BankType;
|
use prometeu_hal::asset::{BankTelemetry, BankType};
|
||||||
use prometeu_hal::log::{LogLevel, LogSource};
|
use prometeu_hal::log::{LogLevel, LogSource};
|
||||||
use prometeu_hal::{HardwareBridge, HostContext, InputSignals};
|
use prometeu_hal::{HardwareBridge, HostContext, InputSignals};
|
||||||
use prometeu_vm::LogicalFrameEndingReason;
|
use prometeu_vm::LogicalFrameEndingReason;
|
||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::Ordering;
|
||||||
|
|
||||||
impl VirtualMachineRuntime {
|
impl VirtualMachineRuntime {
|
||||||
|
fn bank_telemetry_summary(hw: &dyn HardwareBridge) -> (BankTelemetry, BankTelemetry) {
|
||||||
|
let telemetry = hw.assets().bank_telemetry();
|
||||||
|
let glyph =
|
||||||
|
telemetry.iter().find(|entry| entry.bank_type == BankType::GLYPH).cloned().unwrap_or(
|
||||||
|
BankTelemetry { bank_type: BankType::GLYPH, used_slots: 0, total_slots: 0 },
|
||||||
|
);
|
||||||
|
let sounds =
|
||||||
|
telemetry.iter().find(|entry| entry.bank_type == BankType::SOUNDS).cloned().unwrap_or(
|
||||||
|
BankTelemetry { bank_type: BankType::SOUNDS, used_slots: 0, total_slots: 0 },
|
||||||
|
);
|
||||||
|
|
||||||
|
(glyph, sounds)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn debug_step_instruction(
|
pub fn debug_step_instruction(
|
||||||
&mut self,
|
&mut self,
|
||||||
vm: &mut VirtualMachine,
|
vm: &mut VirtualMachine,
|
||||||
@ -134,27 +148,19 @@ impl VirtualMachineRuntime {
|
|||||||
hw.gfx_mut().render_all();
|
hw.gfx_mut().render_all();
|
||||||
|
|
||||||
// 1. Snapshot full telemetry at logical frame end
|
// 1. Snapshot full telemetry at logical frame end
|
||||||
let gfx_stats = hw.assets().bank_info(BankType::GLYPH);
|
let (glyph_bank, sound_bank) = Self::bank_telemetry_summary(hw);
|
||||||
self.atomic_telemetry
|
self.atomic_telemetry
|
||||||
.gfx_used_bytes
|
.glyph_slots_used
|
||||||
.store(gfx_stats.used_bytes, Ordering::Relaxed);
|
.store(glyph_bank.used_slots as u32, Ordering::Relaxed);
|
||||||
self.atomic_telemetry
|
self.atomic_telemetry
|
||||||
.gfx_inflight_bytes
|
.glyph_slots_total
|
||||||
.store(gfx_stats.inflight_bytes, Ordering::Relaxed);
|
.store(glyph_bank.total_slots as u32, Ordering::Relaxed);
|
||||||
self.atomic_telemetry
|
self.atomic_telemetry
|
||||||
.gfx_slots_occupied
|
.sound_slots_used
|
||||||
.store(gfx_stats.slots_occupied as u32, Ordering::Relaxed);
|
.store(sound_bank.used_slots as u32, Ordering::Relaxed);
|
||||||
|
|
||||||
let audio_stats = hw.assets().bank_info(BankType::SOUNDS);
|
|
||||||
self.atomic_telemetry
|
self.atomic_telemetry
|
||||||
.audio_used_bytes
|
.sound_slots_total
|
||||||
.store(audio_stats.used_bytes, Ordering::Relaxed);
|
.store(sound_bank.total_slots as u32, Ordering::Relaxed);
|
||||||
self.atomic_telemetry
|
|
||||||
.audio_inflight_bytes
|
|
||||||
.store(audio_stats.inflight_bytes, Ordering::Relaxed);
|
|
||||||
self.atomic_telemetry
|
|
||||||
.audio_slots_occupied
|
|
||||||
.store(audio_stats.slots_occupied as u32, Ordering::Relaxed);
|
|
||||||
|
|
||||||
self.atomic_telemetry
|
self.atomic_telemetry
|
||||||
.heap_used_bytes
|
.heap_used_bytes
|
||||||
@ -165,7 +171,9 @@ impl VirtualMachineRuntime {
|
|||||||
|
|
||||||
let current_frame_logs =
|
let current_frame_logs =
|
||||||
self.atomic_telemetry.current_logs_count.load(Ordering::Relaxed);
|
self.atomic_telemetry.current_logs_count.load(Ordering::Relaxed);
|
||||||
self.atomic_telemetry.logs_count.store(current_frame_logs, Ordering::Relaxed);
|
self.atomic_telemetry
|
||||||
|
.logs_count
|
||||||
|
.store(current_frame_logs, Ordering::Relaxed);
|
||||||
|
|
||||||
let ts_ms = self.boot_time.elapsed().as_millis() as u64;
|
let ts_ms = self.boot_time.elapsed().as_millis() as u64;
|
||||||
let telemetry_snapshot = self.atomic_telemetry.snapshot();
|
let telemetry_snapshot = self.atomic_telemetry.snapshot();
|
||||||
@ -210,23 +218,19 @@ impl VirtualMachineRuntime {
|
|||||||
|
|
||||||
// 2. High-frequency telemetry update (only if inspection is active)
|
// 2. High-frequency telemetry update (only if inspection is active)
|
||||||
if self.inspection_active {
|
if self.inspection_active {
|
||||||
let gfx_stats = hw.assets().bank_info(BankType::GLYPH);
|
let (glyph_bank, sound_bank) = Self::bank_telemetry_summary(hw);
|
||||||
self.atomic_telemetry.gfx_used_bytes.store(gfx_stats.used_bytes, Ordering::Relaxed);
|
|
||||||
self.atomic_telemetry
|
self.atomic_telemetry
|
||||||
.gfx_inflight_bytes
|
.glyph_slots_used
|
||||||
.store(gfx_stats.inflight_bytes, Ordering::Relaxed);
|
.store(glyph_bank.used_slots as u32, Ordering::Relaxed);
|
||||||
self.atomic_telemetry
|
self.atomic_telemetry
|
||||||
.gfx_slots_occupied
|
.glyph_slots_total
|
||||||
.store(gfx_stats.slots_occupied as u32, Ordering::Relaxed);
|
.store(glyph_bank.total_slots as u32, Ordering::Relaxed);
|
||||||
|
|
||||||
let audio_stats = hw.assets().bank_info(BankType::SOUNDS);
|
|
||||||
self.atomic_telemetry.audio_used_bytes.store(audio_stats.used_bytes, Ordering::Relaxed);
|
|
||||||
self.atomic_telemetry
|
self.atomic_telemetry
|
||||||
.audio_inflight_bytes
|
.sound_slots_used
|
||||||
.store(audio_stats.inflight_bytes, Ordering::Relaxed);
|
.store(sound_bank.used_slots as u32, Ordering::Relaxed);
|
||||||
self.atomic_telemetry
|
self.atomic_telemetry
|
||||||
.audio_slots_occupied
|
.sound_slots_total
|
||||||
.store(audio_stats.slots_occupied as u32, Ordering::Relaxed);
|
.store(sound_bank.total_slots as u32, Ordering::Relaxed);
|
||||||
|
|
||||||
self.atomic_telemetry
|
self.atomic_telemetry
|
||||||
.heap_used_bytes
|
.heap_used_bytes
|
||||||
|
|||||||
@ -272,6 +272,16 @@ impl HostDebugger {
|
|||||||
tel.host_cpu_time_us,
|
tel.host_cpu_time_us,
|
||||||
cert_config.max_host_cpu_us_per_frame.unwrap_or(0),
|
cert_config.max_host_cpu_us_per_frame.unwrap_or(0),
|
||||||
),
|
),
|
||||||
|
0xCA04 => (
|
||||||
|
"max_glyph_slots_used".to_string(),
|
||||||
|
tel.glyph_slots_used as u64,
|
||||||
|
cert_config.max_glyph_slots_used.unwrap_or(0) as u64,
|
||||||
|
),
|
||||||
|
0xCA05 => (
|
||||||
|
"max_sound_slots_used".to_string(),
|
||||||
|
tel.sound_slots_used as u64,
|
||||||
|
cert_config.max_sound_slots_used.unwrap_or(0) as u64,
|
||||||
|
),
|
||||||
_ => ("unknown".to_string(), 0, 0),
|
_ => ("unknown".to_string(), 0, 0),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -302,12 +312,10 @@ impl HostDebugger {
|
|||||||
cycles_budget: tel.cycles_budget,
|
cycles_budget: tel.cycles_budget,
|
||||||
host_cpu_time_us: tel.host_cpu_time_us,
|
host_cpu_time_us: tel.host_cpu_time_us,
|
||||||
violations: tel.violations,
|
violations: tel.violations,
|
||||||
gfx_used_bytes: tel.gfx_used_bytes,
|
glyph_slots_used: tel.glyph_slots_used,
|
||||||
gfx_inflight_bytes: tel.gfx_inflight_bytes,
|
glyph_slots_total: tel.glyph_slots_total,
|
||||||
gfx_slots_occupied: tel.gfx_slots_occupied,
|
sound_slots_used: tel.sound_slots_used,
|
||||||
audio_used_bytes: tel.audio_used_bytes,
|
sound_slots_total: tel.sound_slots_total,
|
||||||
audio_inflight_bytes: tel.audio_inflight_bytes,
|
|
||||||
audio_slots_occupied: tel.audio_slots_occupied,
|
|
||||||
});
|
});
|
||||||
self.last_telemetry_frame = current_frame;
|
self.last_telemetry_frame = current_frame;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -42,14 +42,30 @@ pub(crate) struct OverlaySnapshot {
|
|||||||
footer: Vec<OverlayMetric>,
|
footer: Vec<OverlayMetric>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
struct Rect {
|
||||||
|
x: usize,
|
||||||
|
y: usize,
|
||||||
|
width: usize,
|
||||||
|
height: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FrameCanvas<'a> {
|
||||||
|
frame: &'a mut [u8],
|
||||||
|
width: usize,
|
||||||
|
height: usize,
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn capture_snapshot(stats: &HostStats, firmware: &Firmware) -> OverlaySnapshot {
|
pub(crate) fn capture_snapshot(stats: &HostStats, firmware: &Firmware) -> OverlaySnapshot {
|
||||||
let tel = firmware.os.atomic_telemetry.snapshot();
|
let tel = firmware.os.atomic_telemetry.snapshot();
|
||||||
let recent_logs = firmware.os.log_service.get_recent(10);
|
let recent_logs = firmware.os.log_service.get_recent(10);
|
||||||
let violations_count = recent_logs.iter().filter(|e| e.tag >= 0xCA01 && e.tag <= 0xCA07).count();
|
let violations_count =
|
||||||
|
recent_logs.iter().filter(|e| e.tag >= 0xCA01 && e.tag <= 0xCA07).count();
|
||||||
|
|
||||||
let mut footer = Vec::new();
|
let mut footer = Vec::new();
|
||||||
if violations_count > 0
|
if violations_count > 0
|
||||||
&& let Some(event) = recent_logs.into_iter().rev().find(|e| e.tag >= 0xCA01 && e.tag <= 0xCA07)
|
&& let Some(event) =
|
||||||
|
recent_logs.into_iter().rev().find(|e| e.tag >= 0xCA01 && e.tag <= 0xCA07)
|
||||||
{
|
{
|
||||||
footer.push(OverlayMetric {
|
footer.push(OverlayMetric {
|
||||||
label: "CERT",
|
label: "CERT",
|
||||||
@ -75,8 +91,8 @@ pub(crate) fn capture_snapshot(stats: &HostStats, firmware: &Firmware) -> Overla
|
|||||||
.or(if tel.heap_max_bytes > 0 { Some(tel.heap_max_bytes) } else { None })
|
.or(if tel.heap_max_bytes > 0 { Some(tel.heap_max_bytes) } else { None })
|
||||||
.unwrap_or(OVERLAY_HEAP_FALLBACK_BYTES);
|
.unwrap_or(OVERLAY_HEAP_FALLBACK_BYTES);
|
||||||
let heap_ratio = ratio(tel.heap_used_bytes as u64, heap_total_bytes as u64);
|
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 glyph_ratio = ratio(tel.glyph_slots_used as u64, tel.glyph_slots_total as u64);
|
||||||
let audio_ratio = ratio(tel.audio_slots_occupied as u64, 16);
|
let sound_ratio = ratio(tel.sound_slots_used as u64, tel.sound_slots_total as u64);
|
||||||
|
|
||||||
OverlaySnapshot {
|
OverlaySnapshot {
|
||||||
rows: vec![
|
rows: vec![
|
||||||
@ -98,23 +114,11 @@ pub(crate) fn capture_snapshot(stats: &HostStats, firmware: &Firmware) -> Overla
|
|||||||
value: format!("{:.2}ms", stats.average_host_cpu_ms()),
|
value: format!("{:.2}ms", stats.average_host_cpu_ms()),
|
||||||
warn: false,
|
warn: false,
|
||||||
},
|
},
|
||||||
OverlayMetric {
|
OverlayMetric { label: "STEPS", value: tel.vm_steps.to_string(), warn: false },
|
||||||
label: "STEPS",
|
|
||||||
value: tel.vm_steps.to_string(),
|
|
||||||
warn: false,
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
OverlayMetric {
|
OverlayMetric { label: "SYSC", value: tel.syscalls.to_string(), warn: false },
|
||||||
label: "SYSC",
|
OverlayMetric { label: "LOGS", value: tel.logs_count.to_string(), warn: false },
|
||||||
value: tel.syscalls.to_string(),
|
|
||||||
warn: false,
|
|
||||||
},
|
|
||||||
OverlayMetric {
|
|
||||||
label: "LOGS",
|
|
||||||
value: tel.logs_count.to_string(),
|
|
||||||
warn: false,
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
bars: vec![
|
bars: vec![
|
||||||
@ -136,36 +140,27 @@ pub(crate) fn capture_snapshot(stats: &HostStats, firmware: &Firmware) -> Overla
|
|||||||
label: "BUDGET",
|
label: "BUDGET",
|
||||||
value: if tel.cycles_budget > 0 {
|
value: if tel.cycles_budget > 0 {
|
||||||
format!(
|
format!(
|
||||||
"{:.1}K/{:.1}K {:.1}%",
|
"{:.1}K/{:.1}K",
|
||||||
tel.cycles_used as f64 / 1000.0,
|
tel.cycles_used as f64 / 1000.0,
|
||||||
tel.cycles_budget as f64 / 1000.0,
|
tel.cycles_budget as f64 / 1000.0
|
||||||
cycles_ratio * 100.0
|
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
"0.0K/0.0K 0.0%".to_string()
|
"0.0K/0.0K".to_string()
|
||||||
},
|
},
|
||||||
ratio: cycles_ratio,
|
ratio: cycles_ratio,
|
||||||
warn: cycles_ratio >= 0.9,
|
warn: cycles_ratio >= 0.9,
|
||||||
},
|
},
|
||||||
OverlayBar {
|
OverlayBar {
|
||||||
label: "GFX",
|
label: "GLYPH",
|
||||||
value: format!(
|
value: format!("{} / {} slots", tel.glyph_slots_used, tel.glyph_slots_total),
|
||||||
"{} / 16 slots {}K",
|
ratio: glyph_ratio,
|
||||||
tel.gfx_slots_occupied,
|
warn: tel.glyph_slots_total > 0 && tel.glyph_slots_used >= tel.glyph_slots_total,
|
||||||
tel.gfx_used_bytes.div_ceil(1024)
|
|
||||||
),
|
|
||||||
ratio: gfx_ratio,
|
|
||||||
warn: tel.gfx_inflight_bytes > 0,
|
|
||||||
},
|
},
|
||||||
OverlayBar {
|
OverlayBar {
|
||||||
label: "AUD",
|
label: "SOUNDS",
|
||||||
value: format!(
|
value: format!("{} / {} slots", tel.sound_slots_used, tel.sound_slots_total),
|
||||||
"{} / 16 slots {}K",
|
ratio: sound_ratio,
|
||||||
tel.audio_slots_occupied,
|
warn: tel.sound_slots_total > 0 && tel.sound_slots_used >= tel.sound_slots_total,
|
||||||
tel.audio_used_bytes.div_ceil(1024)
|
|
||||||
),
|
|
||||||
ratio: audio_ratio,
|
|
||||||
warn: tel.audio_inflight_bytes > 0,
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
footer,
|
footer,
|
||||||
@ -178,44 +173,58 @@ pub(crate) fn draw_overlay(
|
|||||||
frame_height: usize,
|
frame_height: usize,
|
||||||
snapshot: &OverlaySnapshot,
|
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_height = frame_height.saturating_sub(PANEL_Y * 2);
|
||||||
|
let panel_rect = Rect { x: PANEL_X, y: PANEL_Y, width: PANEL_WIDTH, height: panel_height };
|
||||||
|
|
||||||
fill_rect_alpha(frame, frame_width, frame_height, PANEL_X, PANEL_Y, PANEL_WIDTH, panel_height, BG);
|
fill_rect_alpha(&mut canvas, panel_rect, BG);
|
||||||
stroke_rect(frame, frame_width, frame_height, PANEL_X, PANEL_Y, PANEL_WIDTH, panel_height, BORDER);
|
stroke_rect(&mut canvas, panel_rect, BORDER);
|
||||||
|
|
||||||
let mut y = PANEL_Y + PANEL_PADDING;
|
let mut y = PANEL_Y + PANEL_PADDING;
|
||||||
for (left, right) in &snapshot.rows {
|
for (left, right) in &snapshot.rows {
|
||||||
draw_metric_pair(frame, frame_width, frame_height, y, left, right);
|
draw_metric_pair(canvas.frame, canvas.width, canvas.height, y, left, right);
|
||||||
y += LINE_HEIGHT;
|
y += LINE_HEIGHT;
|
||||||
}
|
}
|
||||||
|
|
||||||
for bar in &snapshot.bars {
|
for bar in &snapshot.bars {
|
||||||
let color = if bar.warn { WARN } else { TEXT };
|
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(
|
||||||
draw_text(frame, frame_width, frame_height, PANEL_X + 48, y, &bar.value, color);
|
canvas.frame,
|
||||||
|
canvas.width,
|
||||||
|
canvas.height,
|
||||||
|
PANEL_X + PANEL_PADDING,
|
||||||
|
y,
|
||||||
|
bar.label,
|
||||||
|
DIM,
|
||||||
|
);
|
||||||
|
draw_text(canvas.frame, canvas.width, canvas.height, PANEL_X + 48, y, &bar.value, color);
|
||||||
y += LINE_HEIGHT - 2;
|
y += LINE_HEIGHT - 2;
|
||||||
|
|
||||||
let bar_x = PANEL_X + PANEL_PADDING;
|
let bar_x = PANEL_X + PANEL_PADDING;
|
||||||
fill_rect(frame, frame_width, frame_height, bar_x, y, BAR_WIDTH, BAR_HEIGHT, BAR_BG);
|
let bar_rect = Rect { x: bar_x, y, width: BAR_WIDTH, height: BAR_HEIGHT };
|
||||||
|
fill_rect(&mut canvas, bar_rect, BAR_BG);
|
||||||
let fill_width = ((BAR_WIDTH as f32) * bar.ratio.clamp(0.0, 1.0)).round() as usize;
|
let fill_width = ((BAR_WIDTH as f32) * bar.ratio.clamp(0.0, 1.0)).round() as usize;
|
||||||
fill_rect(
|
fill_rect(
|
||||||
frame,
|
&mut canvas,
|
||||||
frame_width,
|
Rect { x: bar_x, y, width: fill_width, height: BAR_HEIGHT },
|
||||||
frame_height,
|
|
||||||
bar_x,
|
|
||||||
y,
|
|
||||||
fill_width,
|
|
||||||
BAR_HEIGHT,
|
|
||||||
if bar.warn { BAR_WARN } else { BAR_FILL },
|
if bar.warn { BAR_WARN } else { BAR_FILL },
|
||||||
);
|
);
|
||||||
stroke_rect(frame, frame_width, frame_height, bar_x, y, BAR_WIDTH, BAR_HEIGHT, BORDER);
|
stroke_rect(&mut canvas, bar_rect, BORDER);
|
||||||
y += BAR_HEIGHT + 6;
|
y += BAR_HEIGHT + 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
for line in &snapshot.footer {
|
for line in &snapshot.footer {
|
||||||
let color = if line.warn { WARN } else { TEXT };
|
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(
|
||||||
draw_text(frame, frame_width, frame_height, PANEL_X + 48, y, &line.value, color);
|
canvas.frame,
|
||||||
|
canvas.width,
|
||||||
|
canvas.height,
|
||||||
|
PANEL_X + PANEL_PADDING,
|
||||||
|
y,
|
||||||
|
line.label,
|
||||||
|
DIM,
|
||||||
|
);
|
||||||
|
draw_text(canvas.frame, canvas.width, canvas.height, PANEL_X + 48, y, &line.value, color);
|
||||||
y += LINE_HEIGHT;
|
y += LINE_HEIGHT;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -260,7 +269,15 @@ fn truncate_value(value: &str, max_len: usize) -> String {
|
|||||||
upper
|
upper
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_text(frame: &mut [u8], frame_width: usize, frame_height: usize, x: usize, y: usize, text: &str, color: [u8; 4]) {
|
fn draw_text(
|
||||||
|
frame: &mut [u8],
|
||||||
|
frame_width: usize,
|
||||||
|
frame_height: usize,
|
||||||
|
x: usize,
|
||||||
|
y: usize,
|
||||||
|
text: &str,
|
||||||
|
color: [u8; 4],
|
||||||
|
) {
|
||||||
let mut cursor_x = x;
|
let mut cursor_x = x;
|
||||||
for ch in text.chars() {
|
for ch in text.chars() {
|
||||||
if ch == '\n' {
|
if ch == '\n' {
|
||||||
@ -271,72 +288,71 @@ fn draw_text(frame: &mut [u8], frame_width: usize, frame_height: usize, x: usize
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_char(frame: &mut [u8], frame_width: usize, frame_height: usize, x: usize, y: usize, ch: char, color: [u8; 4]) {
|
fn draw_char(
|
||||||
|
frame: &mut [u8],
|
||||||
|
frame_width: usize,
|
||||||
|
frame_height: usize,
|
||||||
|
x: usize,
|
||||||
|
y: usize,
|
||||||
|
ch: char,
|
||||||
|
color: [u8; 4],
|
||||||
|
) {
|
||||||
let glyph = glyph_bits(ch);
|
let glyph = glyph_bits(ch);
|
||||||
for (row, bits) in glyph.iter().enumerate() {
|
for (row, bits) in glyph.iter().enumerate() {
|
||||||
for col in 0..5 {
|
for col in 0..5 {
|
||||||
if bits & (1 << (4 - col)) != 0 {
|
if bits & (1 << (4 - col)) != 0 {
|
||||||
fill_rect(frame, frame_width, frame_height, x + col * CHAR_SCALE, y + row * CHAR_SCALE, CHAR_SCALE, CHAR_SCALE, color);
|
let mut canvas = FrameCanvas { frame, width: frame_width, height: frame_height };
|
||||||
|
fill_rect(
|
||||||
|
&mut canvas,
|
||||||
|
Rect {
|
||||||
|
x: x + col * CHAR_SCALE,
|
||||||
|
y: y + row * CHAR_SCALE,
|
||||||
|
width: CHAR_SCALE,
|
||||||
|
height: CHAR_SCALE,
|
||||||
|
},
|
||||||
|
color,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fill_rect_alpha(
|
fn fill_rect_alpha(canvas: &mut FrameCanvas<'_>, rect: Rect, color: [u8; 4]) {
|
||||||
frame: &mut [u8],
|
let max_x = (rect.x + rect.width).min(canvas.width);
|
||||||
frame_width: usize,
|
let max_y = (rect.y + rect.height).min(canvas.height);
|
||||||
frame_height: usize,
|
for py in rect.y..max_y {
|
||||||
x: usize,
|
for px in rect.x..max_x {
|
||||||
y: usize,
|
blend_pixel(canvas.frame, canvas.width, px, py, color);
|
||||||
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(
|
fn fill_rect(canvas: &mut FrameCanvas<'_>, rect: Rect, color: [u8; 4]) {
|
||||||
frame: &mut [u8],
|
let max_x = (rect.x + rect.width).min(canvas.width);
|
||||||
frame_width: usize,
|
let max_y = (rect.y + rect.height).min(canvas.height);
|
||||||
frame_height: usize,
|
for py in rect.y..max_y {
|
||||||
x: usize,
|
for px in rect.x..max_x {
|
||||||
y: usize,
|
write_pixel(canvas.frame, canvas.width, px, py, color);
|
||||||
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(
|
fn stroke_rect(canvas: &mut FrameCanvas<'_>, rect: Rect, color: [u8; 4]) {
|
||||||
frame: &mut [u8],
|
if rect.width == 0 || rect.height == 0 {
|
||||||
frame_width: usize,
|
|
||||||
frame_height: usize,
|
|
||||||
x: usize,
|
|
||||||
y: usize,
|
|
||||||
width: usize,
|
|
||||||
height: usize,
|
|
||||||
color: [u8; 4],
|
|
||||||
) {
|
|
||||||
if width == 0 || height == 0 {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
fill_rect(frame, frame_width, frame_height, x, y, width, 1, color);
|
fill_rect(canvas, Rect { x: rect.x, y: rect.y, width: rect.width, height: 1 }, color);
|
||||||
fill_rect(frame, frame_width, frame_height, x, y + height.saturating_sub(1), width, 1, color);
|
fill_rect(
|
||||||
fill_rect(frame, frame_width, frame_height, x, y, 1, height, color);
|
canvas,
|
||||||
fill_rect(frame, frame_width, frame_height, x + width.saturating_sub(1), y, 1, height, color);
|
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]) {
|
fn blend_pixel(frame: &mut [u8], frame_width: usize, x: usize, y: usize, color: [u8; 4]) {
|
||||||
@ -426,7 +442,8 @@ mod tests {
|
|||||||
OverlayMetric { label: "STEPS", value: "420".to_string(), warn: false },
|
OverlayMetric { label: "STEPS", value: "420".to_string(), warn: false },
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
bars: vec![OverlayBar {
|
bars: vec![
|
||||||
|
OverlayBar {
|
||||||
label: "HEAP",
|
label: "HEAP",
|
||||||
value: "1024K / 8192K".to_string(),
|
value: "1024K / 8192K".to_string(),
|
||||||
ratio: 0.125,
|
ratio: 0.125,
|
||||||
@ -434,10 +451,11 @@ mod tests {
|
|||||||
},
|
},
|
||||||
OverlayBar {
|
OverlayBar {
|
||||||
label: "BUDGET",
|
label: "BUDGET",
|
||||||
value: "9.0K/10.0K 90.0%".to_string(),
|
value: "9.0K/10.0K".to_string(),
|
||||||
ratio: 0.9,
|
ratio: 0.9,
|
||||||
warn: true,
|
warn: true,
|
||||||
}],
|
},
|
||||||
|
],
|
||||||
footer: vec![OverlayMetric {
|
footer: vec![OverlayMetric {
|
||||||
label: "CRASH",
|
label: "CRASH",
|
||||||
value: "VM PANIC".to_string(),
|
value: "VM PANIC".to_string(),
|
||||||
|
|||||||
@ -122,7 +122,6 @@ impl HostRunner {
|
|||||||
w.request_redraw();
|
w.request_redraw();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ApplicationHandler for HostRunner {
|
impl ApplicationHandler for HostRunner {
|
||||||
|
|||||||
@ -45,8 +45,10 @@ impl HostStats {
|
|||||||
|
|
||||||
pub fn record_host_cpu_time(&mut self, us: u64) {
|
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_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_cursor =
|
||||||
self.recent_host_cpu_count = self.recent_host_cpu_count.saturating_add(1).min(self.recent_host_cpu_us.len());
|
(self.recent_host_cpu_cursor + 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 {
|
pub fn average_host_cpu_ms(&self) -> f64 {
|
||||||
|
|||||||
@ -16,7 +16,7 @@
|
|||||||
{"type":"discussion","id":"DSC-0011","status":"open","ticket":"perf-gfx-render-pipeline-and-dirty-regions","title":"Agenda - [PERF] GFX Render Pipeline and Dirty Regions","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0010","file":"workflow/agendas/AGD-0010-perf-gfx-render-pipeline-and-dirty-regions.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]}
|
{"type":"discussion","id":"DSC-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":"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-0013","status":"open","ticket":"perf-host-debug-overlay-isolation","title":"Agenda - [PERF] Host Debug Overlay Isolation","created_at":"2026-03-27","updated_at":"2026-04-10","tags":[],"agendas":[{"id":"AGD-0012","file":"workflow/agendas/AGD-0012-perf-host-debug-overlay-isolation.md","status":"done","created_at":"2026-03-27","updated_at":"2026-04-10"},{"id":"AGD-0022","file":"AGD-0022-host-overlay-tooling-boundary-revision.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10"},{"id":"AGD-0023","file":"AGD-0023-overlay-log-metric-last-frame.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10"}],"decisions":[{"id":"DEC-0007","file":"workflow/decisions/DEC-0007-perf-host-debug-overlay-isolation.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10"},{"id":"DEC-0009","file":"DEC-0009-host-overlay-tooling-boundary.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10","ref_agenda":"AGD-0022"},{"id":"DEC-0010","file":"DEC-0010-overlay-log-metric-last-frame.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10","ref_agenda":"AGD-0023"}],"plans":[{"id":"PLN-0006","file":"workflow/plans/PLN-0006-perf-host-debug-overlay-isolation.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"},{"id":"PLN-0008","file":"PLN-0008-host-overlay-native-composition-alignment.md","status":"in_progress","created_at":"2026-04-10","updated_at":"2026-04-10","ref_decisions":["DEC-0007","DEC-0008","DEC-0009","DEC-0010"]}],"lessons":[{"id":"LSN-0027","file":"lessons/DSC-0013-perf-host-debug-overlay-isolation/LSN-0027-host-debug-overlay-isolation.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}]}
|
||||||
{"type":"discussion","id":"DSC-0024","status":"open","ticket":"generic-memory-bank-slot-contract","title":"Agenda - Generic Memory Bank Slot Contract","created_at":"2026-04-10","updated_at":"2026-04-10","tags":["runtime","asset","memory-bank","slots","host"],"agendas":[{"id":"AGD-0024","file":"AGD-0024-generic-memory-bank-slot-contract.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10"}],"decisions":[{"id":"DEC-0012","file":"DEC-0012-asset-manager-bank-telemetry-slot-contract.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10","ref_agenda":"AGD-0024"}],"plans":[{"id":"PLN-0010","file":"PLN-0010-asset-manager-bank-telemetry-slot-contract.md","status":"open","created_at":"2026-04-10","updated_at":"2026-04-10","ref_decisions":["DEC-0012"]}],"lessons":[]}
|
{"type":"discussion","id":"DSC-0024","status":"open","ticket":"generic-memory-bank-slot-contract","title":"Agenda - Generic Memory Bank Slot Contract","created_at":"2026-04-10","updated_at":"2026-04-10","tags":["runtime","asset","memory-bank","slots","host"],"agendas":[{"id":"AGD-0024","file":"AGD-0024-generic-memory-bank-slot-contract.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10"}],"decisions":[{"id":"DEC-0012","file":"DEC-0012-asset-manager-bank-telemetry-slot-contract.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10","ref_agenda":"AGD-0024"}],"plans":[{"id":"PLN-0010","file":"PLN-0010-asset-manager-bank-telemetry-slot-contract.md","status":"in_progress","created_at":"2026-04-10","updated_at":"2026-04-10","ref_decisions":["DEC-0012"]}],"lessons":[]}
|
||||||
{"type":"discussion","id":"DSC-0014","status":"open","ticket":"perf-vm-allocation-and-copy-pressure","title":"Agenda - [PERF] VM Allocation and Copy Pressure","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0013","file":"workflow/agendas/AGD-0013-perf-vm-allocation-and-copy-pressure.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]}
|
{"type":"discussion","id":"DSC-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"}]}
|
||||||
|
|||||||
@ -82,13 +82,13 @@ Seguir com a **Opção A**, com os seguintes princípios:
|
|||||||
2. O contrato exposto deve usar somente `used_slots` e `total_slots`.
|
2. O contrato exposto deve usar somente `used_slots` e `total_slots`.
|
||||||
3. `BankPolicy` e `BankStats` devem ser removidos por completo.
|
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.
|
4. `MemoryBanks` deve possuir um contrato específico e explícito para ocupação por slots.
|
||||||
5. O host overlay deve consumir esse modelo genérico sem saber detalhes especiais de `GLYPH` vs `SOUND`, além de rótulo e contagem.
|
5. O host overlay deve consumir esse modelo genérico sem saber detalhes especiais de `GLYPH` vs `SOUNDS`, além de rótulo e contagem.
|
||||||
6. A revisão deve evitar abstração vazia: o contrato genérico precisa mapear diretamente para `GlyphBank` e `SoundBank`.
|
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:
|
Nomenclatura canônica acordada para a camada genérica:
|
||||||
|
|
||||||
- `GLYPH`
|
- `GLYPH`
|
||||||
- `SOUND`
|
- `SOUNDS`
|
||||||
|
|
||||||
`GFX` e `AUD` não devem ser usados como nomes canônicos do contrato genérico de banks, pois são apelidos de apresentação e não os nomes corretos do domínio.
|
`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.
|
||||||
|
|
||||||
|
|||||||
@ -15,19 +15,22 @@ tags: [runtime, asset, memory-bank, slots, host, telemetry]
|
|||||||
**Accepted**
|
**Accepted**
|
||||||
|
|
||||||
## Contexto
|
## Contexto
|
||||||
O modelo atual de banks ainda carrega forte semântica orientada a bytes e estruturas antigas como `BankPolicy` e `BankStats`, enquanto o consumo real desejado pelo host e pela inspeção técnica é orientado a slots. Além disso, os bytes historicamente usados nesse caminho foram considerados inadequados para sustentar o novo contrato de telemetria de banks.
|
AGD-0024 fechou a mudança do contrato de banks de um modelo orientado a bytes para um modelo orientado a slots. A agenda também fechou que `BankPolicy` e `BankStats` não são base aceitável para a nova superfície visível e que a telemetria operacional consumida por host e overlay deve sair diretamente do `AssetManager`.
|
||||||
|
|
||||||
Durante a discussão, ficou explícito que:
|
Os pontos que a agenda resolveu e que esta decisão precisa cristalizar sem ambiguidade são:
|
||||||
|
|
||||||
- o resumo visível de banks deve ser exposto diretamente pelo `AssetManager`;
|
- o contrato visível é `slot-first`;
|
||||||
- a telemetria de banks deve usar somente o enum do tipo do bank;
|
- o resumo visível por bank usa apenas `bank_type`, `used_slots` e `total_slots`;
|
||||||
- a semântica operacional deve ser `used_slots / total_slots`;
|
- o detalhamento operacional ocorre por enum de slot;
|
||||||
- `GFX` e `AUD` não são nomes canônicos do domínio; os nomes corretos são `GLYPH` e `SOUND`;
|
- `GLYPH` e `SOUNDS` são os nomes canônicos do domínio;
|
||||||
- a certificação de banks deve deixar de usar limites em bytes e passar a usar limites por slots.
|
- `GFX` e `AUD` não são nomes canônicos do contrato;
|
||||||
|
- a certificação deixa de usar bytes e passa a usar limites por slots;
|
||||||
|
- a genericidade principal do contrato visível não reside em `MemoryBanks`.
|
||||||
|
|
||||||
## Decisao
|
## Decisao
|
||||||
1. O contrato visível de telemetria de banks MUST be exposed by `AssetManager`.
|
1. O contrato visível de telemetria de banks MUST ser exposto diretamente por `AssetManager`.
|
||||||
2. O resumo de bank MUST use the following structure shape:
|
2. O contrato visível de telemetria de banks MUST NOT ter `MemoryBanks` como superfície principal de exposição. `MemoryBanks` MAY manter detalhes internos de suporte, mas o ponto canônico de consumo visível do sistema SHALL ser `AssetManager`.
|
||||||
|
3. O resumo canônico por bank MUST usar somente os campos `bank_type`, `used_slots` e `total_slots`, no formato:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
pub struct BankTelemetry {
|
pub struct BankTelemetry {
|
||||||
@ -37,42 +40,53 @@ pub struct BankTelemetry {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
3. O `bank_type` MUST use canonical domain names. For the current banks, the canonical names are `GLYPH` and `SOUND`.
|
4. O contrato visível de telemetria de banks MUST ser `slot-first` e MUST NOT depender de contagens de bytes.
|
||||||
4. O contrato de telemetria de banks MUST be slot-first and MUST NOT depend on byte counts.
|
5. `bank_type` MUST usar nomes canônicos do domínio. Para os banks atuais, os nomes canônicos são `GLYPH` e `SOUNDS`.
|
||||||
5. `BankPolicy` e `BankStats` MUST be removed completely from the bank telemetry contract path.
|
6. `GFX` e `AUD` MUST NOT aparecer como nomes canônicos do contrato genérico de banks. Eles MAY existir apenas como rótulos de apresentação, quando estritamente necessário fora do contrato canônico.
|
||||||
6. O host overlay MUST consume `AssetManager` bank telemetry instead of hardcoded bank-specific logic.
|
7. `BankPolicy` e `BankStats` MUST ser removidos completamente do caminho do contrato visível de telemetria de banks e MUST NOT permanecer como modelagem principal desse domínio exposto.
|
||||||
7. As regras de certificação `max_gfx_bytes` e `max_audio_bytes` MUST be removed.
|
8. O overlay do host MUST consumir a telemetria exposta por `AssetManager` e MUST NOT manter lógica hardcoded dependente de bancos específicos para calcular ocupação principal.
|
||||||
8. A certificação de banks MUST migrate to slot-based limits, using canonical bank-specific slot limits such as:
|
9. O detalhamento operacional por slot MUST ser definido por enum de slot, e qualquer inspeção detalhada de ocupação SHALL seguir esse modelo, não um detalhamento baseado em bytes.
|
||||||
|
10. As regras de certificação `max_gfx_bytes` e `max_audio_bytes` MUST ser removidas.
|
||||||
|
11. A certificação de banks MUST migrar para limites por slots, usando nomes canônicos de bank, tais como:
|
||||||
- `max_glyph_slots_used`
|
- `max_glyph_slots_used`
|
||||||
- `max_sound_slots_used`
|
- `max_sound_slots_used`
|
||||||
9. Any remaining byte-based accounting MAY survive only as internal implementation detail if strictly necessary, but it MUST NOT remain part of the exposed bank telemetry contract.
|
12. Qualquer contabilização residual em bytes MAY sobreviver apenas como detalhe interno de implementação, quando estritamente necessária, e MUST NOT permanecer no contrato visível de telemetria de banks.
|
||||||
|
|
||||||
## Rationale
|
## Rationale
|
||||||
- Slots são a unidade operacional correta para entender ocupação de banks.
|
- Slots são a unidade operacional real percebida por host, overlay e depuração de banks.
|
||||||
- O `AssetManager` já é o lugar que conhece carregamento, commit e ocupação prática dos banks.
|
- `AssetManager` é o ponto que já conhece a ocupação efetiva dos banks e, portanto, é o lugar correto para expor o resumo visível.
|
||||||
- Remover bytes do contrato elimina uma fonte de telemetria considerada enganosa.
|
- Remover bytes do contrato visível evita uma telemetria enganosa e elimina a ambiguidade que a agenda abriu para resolver.
|
||||||
- `GLYPH` e `SOUND` preservam a linguagem correta do domínio e evitam apelidos frágeis na interface.
|
- Fixar `GLYPH` e `SOUNDS` como nomes canônicos evita contaminar o contrato com apelidos de apresentação.
|
||||||
- Certificação por slots mantém coerência entre contrato exposto, overlay e limites técnicos.
|
- Fixar enum de slot como detalhamento operacional evita reabrir a discussão sob outra forma durante a implementação.
|
||||||
|
- Migrar certificação para slots mantém o contrato exposto, o overlay e os limites técnicos usando a mesma semântica.
|
||||||
|
|
||||||
## Invariantes / Contrato
|
## Invariantes / Contrato
|
||||||
- A telemetria de banks exposta ao restante do sistema sai do `AssetManager`.
|
- A telemetria de banks exposta ao restante do sistema sai de `AssetManager`.
|
||||||
- Cada entrada de bank telemetria informa somente tipo do bank, slots usados e slots totais.
|
- O contrato visível principal não reside em `MemoryBanks`.
|
||||||
- O contrato canônico usa `GLYPH` e `SOUND`.
|
- Cada entrada de telemetria de bank informa somente `bank_type`, `used_slots` e `total_slots`.
|
||||||
|
- O contrato canônico usa `GLYPH` e `SOUNDS`.
|
||||||
|
- O detalhamento operacional de slots usa enum de slot.
|
||||||
- O overlay não depende de bytes para mostrar ocupação de banks.
|
- O overlay não depende de bytes para mostrar ocupação de banks.
|
||||||
- A certificação de banks não depende de bytes.
|
- A certificação de banks não depende de bytes.
|
||||||
|
|
||||||
## Impactos
|
## Impactos
|
||||||
- **AssetManager:** precisa expor `Vec<BankTelemetry>` ou equivalente direto.
|
- **AssetManager:** deve expor `Vec<BankTelemetry>` ou equivalente direto como superfície canônica visível.
|
||||||
- **HAL / Bridges:** precisam alinhar interfaces consumidoras ao contrato slot-first.
|
- **MemoryBanks:** pode continuar como suporte interno, mas não deve concentrar o contrato visível principal de telemetria.
|
||||||
- **Overlay:** deve iterar a telemetria de banks do `AssetManager`.
|
- **HAL / Bridges:** precisam alinhar qualquer interface consumidora ao contrato `slot-first`.
|
||||||
|
- **Overlay:** deve iterar a telemetria de banks do `AssetManager` e parar de recomputar a ocupação principal a partir de bytes.
|
||||||
- **Certifier:** precisa trocar limites de bytes por limites de slots para banks.
|
- **Certifier:** precisa trocar limites de bytes por limites de slots para banks.
|
||||||
- **Legado:** `BankPolicy`, `BankStats`, e caminhos ancorados em `max_gfx_bytes` / `max_audio_bytes` precisam ser removidos ou recolocados fora do contrato exposto.
|
- **Legado:** `BankPolicy`, `BankStats`, `max_gfx_bytes` e `max_audio_bytes` precisam ser removidos do caminho canônico dessa capacidade.
|
||||||
|
|
||||||
## Referencias
|
## Referencias
|
||||||
- `AGD-0024`: Generic Memory Bank Slot Contract.
|
- `AGD-0024`: Generic Memory Bank Slot Contract.
|
||||||
- `DEC-0010`: Overlay Log Metric Uses Last Completed Frame.
|
- `DEC-0010`: Overlay Log Metric Uses Last Completed Frame.
|
||||||
|
|
||||||
## Propagacao Necessaria
|
## Propagacao Necessaria
|
||||||
1. Criar plano de execução para refatorar `AssetManager`, HAL, overlay e certificação.
|
1. O plano de execução deve refatorar `AssetManager`, `MemoryBanks`, HAL/bridges, overlay e certificação sem reabrir a arquitetura base.
|
||||||
2. Remover o caminho de telemetria de banks baseado em bytes.
|
2. O caminho de telemetria de banks baseado em bytes deve ser removido do contrato visível.
|
||||||
3. Migrar limites de certificação de banks para slots.
|
3. O detalhamento operacional por enum de slot deve ser propagado para os pontos que inspecionam ocupação detalhada.
|
||||||
|
4. A certificação de banks deve migrar de limites em bytes para limites em slots.
|
||||||
|
|
||||||
|
## Revision Log
|
||||||
|
- 2026-04-10: Initial decision emitted from `AGD-0024`.
|
||||||
|
- 2026-04-10: Decision tightened to align explicitly with agenda closure on `AssetManager` ownership, slot-enum operational detail, and `MemoryBanks` non-canonical residency.
|
||||||
|
|||||||
@ -2,152 +2,193 @@
|
|||||||
id: PLN-0010
|
id: PLN-0010
|
||||||
ticket: generic-memory-bank-slot-contract
|
ticket: generic-memory-bank-slot-contract
|
||||||
title: PR/Plan - Asset Manager Bank Telemetry Slot Contract
|
title: PR/Plan - Asset Manager Bank Telemetry Slot Contract
|
||||||
status: open
|
status: in_progress
|
||||||
created: 2026-04-10
|
created: 2026-04-10
|
||||||
completed:
|
completed:
|
||||||
tags: [runtime, asset, memory-bank, slots, host, telemetry]
|
tags: [runtime, asset, memory-bank, slots, host, telemetry, certification]
|
||||||
---
|
---
|
||||||
|
|
||||||
## Objective
|
## Briefing
|
||||||
|
|
||||||
Implement the `DEC-0012` bank telemetry contract so `AssetManager` exposes slot-based bank summaries using canonical bank names `GLYPH` and `SOUND`, while host overlay and certification consume slot-based limits instead of byte-based bank metrics.
|
Implement `DEC-0012` by replacing the current byte-oriented bank telemetry path with a slot-first contract exposed directly by `AssetManager`. The execution MUST remove `BankPolicy` and `BankStats` from the visible telemetry path, keep canonical bank names aligned with `BankType` (`GLYPH`, `SOUNDS`), make the host overlay consume the new `AssetManager` summary, and migrate bank certification from byte limits to slot limits.
|
||||||
|
|
||||||
## Background
|
## Decisions de Origem
|
||||||
|
|
||||||
`DEC-0012` locked the bank telemetry contract around a simple slot-first structure:
|
- `DEC-0012` - Asset Manager Bank Telemetry Slot Contract
|
||||||
|
|
||||||
```rust
|
## Alvo
|
||||||
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.
|
Deliver a canonical bank telemetry path with these properties:
|
||||||
|
|
||||||
## Scope
|
- `AssetManager` exposes the visible per-bank summary.
|
||||||
|
- The visible summary shape is slot-first and limited to `bank_type`, `used_slots`, and `total_slots`.
|
||||||
|
- Detailed inspection remains slot-based through `SlotRef` / slot enums, not byte-based accounting.
|
||||||
|
- Host overlay and certification consume the new model.
|
||||||
|
- Legacy byte-oriented bank telemetry fields and limits are removed from the canonical path.
|
||||||
|
|
||||||
|
## Escopo
|
||||||
|
|
||||||
### Included
|
### 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
|
- Add a canonical `BankTelemetry` type to the shared HAL asset contract.
|
||||||
- Adding new bank kinds beyond the currently supported `GLYPH` and `SOUND`.
|
- Add an `AssetBridge` method for bank telemetry summaries exposed by `AssetManager`.
|
||||||
- Reworking unrelated asset load/commit semantics.
|
- Refactor `AssetManager` in `crates/console/prometeu-drivers` to compute slot-first bank telemetry.
|
||||||
- Redesigning generic slot-detail payloads beyond the accepted summary shape unless implementation requires a narrowly scoped helper.
|
- Remove `BankStats` from the visible bank telemetry path and stop using it for overlay and certification.
|
||||||
|
- Remove `BankPolicy` as the main modeling surface used to describe visible bank telemetry.
|
||||||
|
- Keep detailed slot inspection on explicit slot references instead of bank byte summaries.
|
||||||
|
- Update runtime telemetry / certification to track `max_glyph_slots_used` and `max_sound_slots_used`.
|
||||||
|
- Update host overlay consumers to render bank occupancy from the new `AssetManager` summary.
|
||||||
|
- Update runtime specs so the published contract describes slot-first bank telemetry and slot-based certification.
|
||||||
|
|
||||||
## Execution Steps
|
## Fora de Escopo
|
||||||
|
|
||||||
### Step 1 - Introduce `BankTelemetry` in the exposed asset contract
|
- Reopening the base architectural decision about whether the visible summary belongs in `AssetManager`.
|
||||||
|
- Introducing new bank categories beyond the current `GLYPH` and `SOUNDS`.
|
||||||
|
- Reworking unrelated asset loading semantics, cartridge payload layout, or host UI design beyond adapting bank telemetry consumption.
|
||||||
|
- Removing all internal byte accounting if some private implementation detail still needs it temporarily.
|
||||||
|
|
||||||
|
## Plano de Execucao
|
||||||
|
|
||||||
|
### Step 1 - Lock the shared contract in HAL
|
||||||
|
|
||||||
**What:**
|
**What:**
|
||||||
Define the new slot-based summary type and make it the canonical visible representation of bank telemetry.
|
Define the canonical shared types and trait surface for slot-first bank telemetry.
|
||||||
|
|
||||||
**How:**
|
**How:**
|
||||||
Add `BankTelemetry` to the HAL asset domain and ensure canonical naming uses `GLYPH` and `SOUND`. Remove or deprecate exposed `BankStats` paths that conflict with the new contract.
|
Add `BankTelemetry` to `crates/console/prometeu-hal/src/asset.rs` using `bank_type`, `used_slots`, and `total_slots`. Update `crates/console/prometeu-hal/src/asset_bridge.rs` so the visible bridge exposes a bank telemetry summary method from `AssetManager`. Remove or deprecate `BankStats` from the visible bridge path so downstream callers stop depending on byte-oriented bank summaries.
|
||||||
|
|
||||||
**File(s):**
|
**File(s):**
|
||||||
- `crates/console/prometeu-hal/src/asset.rs`
|
- `crates/console/prometeu-hal/src/asset.rs`
|
||||||
- `crates/console/prometeu-hal/src/asset_bridge.rs`
|
- `crates/console/prometeu-hal/src/asset_bridge.rs`
|
||||||
|
|
||||||
### Step 2 - Make `AssetManager` expose bank telemetry directly
|
**Depends on:**
|
||||||
|
- None
|
||||||
|
|
||||||
|
### Step 2 - Refactor AssetManager to produce slot-first summaries
|
||||||
|
|
||||||
**What:**
|
**What:**
|
||||||
Implement `AssetManager` support for returning `Vec<BankTelemetry>` from live slot occupancy.
|
Move visible bank summary generation to a canonical `AssetManager` slot telemetry path.
|
||||||
|
|
||||||
**How:**
|
**How:**
|
||||||
Compute `used_slots` from current slot occupancy and `total_slots` from the fixed slot arrays for `GLYPH` and `SOUND`. Keep implementation simple and derive the summary directly from the installed slots. Refactor any existing public bank-info consumers away from byte-oriented APIs.
|
Replace `bank_info(BankType) -> BankStats` with a summary path that reports only slot occupancy for each canonical bank type. Compute `used_slots` from the slot arrays already owned by `AssetManager` (`gfx_slots`, `sound_slots`) or equivalent canonical occupancy state. Keep detailed per-slot inspection in `slot_info(SlotRef)`. Remove `BankPolicy` and its byte counters as the visible modeling surface for bank telemetry; if any internal storage helper remains temporarily, it must not leak into the shared contract.
|
||||||
|
|
||||||
**File(s):**
|
**File(s):**
|
||||||
- `crates/console/prometeu-drivers/src/asset.rs`
|
- `crates/console/prometeu-drivers/src/asset.rs`
|
||||||
- `crates/console/prometeu-drivers/src/memory_banks.rs`
|
- `crates/console/prometeu-drivers/src/memory_banks.rs`
|
||||||
|
|
||||||
### Step 3 - Remove byte-based bank certification rules
|
**Depends on:**
|
||||||
|
- Step 1
|
||||||
|
|
||||||
|
### Step 3 - Migrate runtime and host consumers to the new summary
|
||||||
|
|
||||||
**What:**
|
**What:**
|
||||||
Replace byte-based certification limits for banks with slot-based limits.
|
Replace every bank summary consumer that still expects byte-oriented telemetry.
|
||||||
|
|
||||||
**How:**
|
**How:**
|
||||||
Remove `max_gfx_bytes` and `max_audio_bytes` from certification config and certifier checks. Introduce slot-based limits such as `max_glyph_slots_used` and `max_sound_slots_used`, and evaluate them against `BankTelemetry` summaries or equivalent slot-based data available at frame-end.
|
Update runtime and host callers to consume `BankTelemetry` from `AssetManager` instead of `BankStats`. Replace any direct `bank_info(BankType)` callsites with the new canonical summary path. Preserve slot-level inspection through `slot_info(SlotRef)` where detailed occupancy is needed. The host overlay must render bank occupancy from the `AssetManager` summary and must not reconstruct primary bank occupancy from bytes.
|
||||||
|
|
||||||
|
**File(s):**
|
||||||
|
- `crates/console/prometeu-system/src/virtual_machine_runtime/tick.rs`
|
||||||
|
- `crates/host/prometeu-host-desktop-winit/src/overlay.rs`
|
||||||
|
- Any additional callsites found through `rg 'bank_info|BankStats' crates`
|
||||||
|
|
||||||
|
**Depends on:**
|
||||||
|
- Step 2
|
||||||
|
|
||||||
|
### Step 4 - Replace certification byte limits with slot limits
|
||||||
|
|
||||||
|
**What:**
|
||||||
|
Move bank certification from byte ceilings to slot ceilings.
|
||||||
|
|
||||||
|
**How:**
|
||||||
|
Replace `max_gfx_bytes` and `max_audio_bytes` in telemetry / certification configuration and evaluation with slot-based limits such as `max_glyph_slots_used` and `max_sound_slots_used`. Update runtime accounting to record the maximum occupied slot count per bank during execution. Remove byte-limit evaluation from the bank certification path.
|
||||||
|
|
||||||
**File(s):**
|
**File(s):**
|
||||||
- `crates/console/prometeu-hal/src/telemetry.rs`
|
- `crates/console/prometeu-hal/src/telemetry.rs`
|
||||||
- `crates/console/prometeu-system/src/virtual_machine_runtime/tick.rs`
|
- `crates/console/prometeu-system/src/virtual_machine_runtime/tick.rs`
|
||||||
|
|
||||||
### Step 4 - Update host overlay to consume slot telemetry
|
**Depends on:**
|
||||||
|
- Step 3
|
||||||
|
|
||||||
|
### Step 5 - Remove residual canonical-path names and semantics that conflict with DEC-0012
|
||||||
|
|
||||||
**What:**
|
**What:**
|
||||||
Replace hardcoded bank presentation with iteration over `AssetManager` bank telemetry.
|
Eliminate remaining visible-path terminology and structures that preserve the old model.
|
||||||
|
|
||||||
**How:**
|
**How:**
|
||||||
Render one bar per bank using `bank_type`, `used_slots`, and `total_slots`. Use canonical labels `GLYPH` and `SOUND`, and remove any byte-based bank presentation from the overlay.
|
Ensure no visible bank telemetry path still uses `GFX` / `AUD` as canonical names, `BankStats` as the summary type, or byte fields as the public bank occupancy contract. Keep `GLYPH` and `SOUNDS` aligned with the existing `BankType` enum. If bridges, docs, or helper APIs still expose the old path, remove or rename them in the same change set so the canonical path is unambiguous.
|
||||||
|
|
||||||
**File(s):**
|
**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`
|
- `crates/console/prometeu-hal/src/asset.rs`
|
||||||
- Related callers revealed during implementation
|
- `crates/console/prometeu-hal/src/asset_bridge.rs`
|
||||||
|
- `crates/console/prometeu-drivers/src/asset.rs`
|
||||||
|
- `crates/console/prometeu-system/src/virtual_machine_runtime/tick.rs`
|
||||||
|
- `crates/host/prometeu-host-desktop-winit/src/overlay.rs`
|
||||||
|
|
||||||
### Step 6 - Update normative documentation
|
**Depends on:**
|
||||||
|
- Step 4
|
||||||
|
|
||||||
|
### Step 6 - Update published specifications
|
||||||
|
|
||||||
**What:**
|
**What:**
|
||||||
Align specifications with the new slot-based bank telemetry contract.
|
Align canonical docs with the implemented slot-first bank contract.
|
||||||
|
|
||||||
**How:**
|
**How:**
|
||||||
Update relevant runtime documentation to describe bank occupancy in terms of `GLYPH` and `SOUND` slot usage, and remove outdated references to byte-based bank telemetry where they were treated as visible contract.
|
Update runtime specs to state that bank telemetry is exposed from `AssetManager`, that the published per-bank summary is slot-first, that detailed occupancy inspection remains slot-based, and that certification uses slot ceilings instead of byte ceilings. Remove residual published references to `max_gfx_bytes`, `max_audio_bytes`, or byte-oriented bank summaries in canonical specs.
|
||||||
|
|
||||||
**File(s):**
|
**File(s):**
|
||||||
- `docs/specs/runtime/10-debug-inspection-and-profiling.md`
|
|
||||||
- `docs/specs/runtime/15-asset-management.md`
|
- `docs/specs/runtime/15-asset-management.md`
|
||||||
- Any additional touched runtime spec chapter
|
- `docs/specs/runtime/10-debug-inspection-and-profiling.md`
|
||||||
|
- Any additional runtime spec file that still documents byte-based bank certification or overlay bank summary consumption
|
||||||
|
|
||||||
## Test Requirements
|
**Depends on:**
|
||||||
|
- Step 5
|
||||||
|
|
||||||
|
## Criterios de Aceite
|
||||||
|
|
||||||
|
- [ ] `AssetManager` is the canonical visible source of bank telemetry.
|
||||||
|
- [ ] The visible bank summary contract uses only `bank_type`, `used_slots`, and `total_slots`.
|
||||||
|
- [ ] Canonical bank names in the visible contract are `GLYPH` and `SOUNDS`.
|
||||||
|
- [ ] `BankStats` is no longer used as the visible bank telemetry summary type.
|
||||||
|
- [ ] `BankPolicy` is not the main visible modeling surface for bank telemetry.
|
||||||
|
- [ ] Host overlay bank occupancy uses the new `AssetManager` bank summary.
|
||||||
|
- [ ] Detailed occupancy inspection remains slot-based via slot references or equivalent slot enums.
|
||||||
|
- [ ] `max_gfx_bytes` and `max_audio_bytes` are removed from bank certification.
|
||||||
|
- [ ] Slot-based bank certification limits exist and are enforced.
|
||||||
|
- [ ] Published runtime specs describe the slot-first contract and slot-based certification model.
|
||||||
|
|
||||||
|
## Tests / Validacao
|
||||||
|
|
||||||
### Unit Tests
|
### Unit Tests
|
||||||
- `AssetManager` reports correct `BankTelemetry` for empty and occupied `GLYPH` / `SOUND` slots.
|
|
||||||
- Certification slot limits trigger correctly for `GLYPH` and `SOUND`.
|
- Add or update tests in `crates/console/prometeu-drivers/src/asset.rs` to verify the bank summary reports correct `used_slots` and `total_slots` for `GLYPH` and `SOUNDS`.
|
||||||
- Overlay rendering accepts generic bank telemetry iteration.
|
- Add or update tests in `crates/console/prometeu-hal/src/telemetry.rs` to verify slot-based certification thresholds trigger correctly and byte-based limits no longer exist in the bank path.
|
||||||
|
- Keep slot inspection tests proving `slot_info(SlotRef)` remains the detailed inspection mechanism.
|
||||||
|
|
||||||
### Integration Tests
|
### Integration Tests
|
||||||
- `cargo check` for affected HAL, drivers, system, and host crates.
|
|
||||||
- Focused tests for asset bank telemetry and certification migration.
|
- Run `cargo test -p prometeu-drivers`.
|
||||||
|
- Run `cargo test -p prometeu-hal`.
|
||||||
|
- Run `cargo test -p prometeu-system`.
|
||||||
|
- Run `cargo test -p prometeu-host-desktop-winit` if overlay bank telemetry callsites are changed there.
|
||||||
|
- Run `cargo check` for the full touched workspace if cross-crate API changes make targeted tests insufficient.
|
||||||
|
|
||||||
### Manual Verification
|
### 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
|
- Start the desktop host and confirm the overlay shows per-bank occupancy using slot counts, not bytes.
|
||||||
|
- Confirm the overlay labels use `GLYPH` and `SOUNDS` or approved presentation labels derived from those canonical bank types.
|
||||||
- [ ] `AssetManager` exposes a visible `BankTelemetry` summary with `bank_type`, `used_slots`, and `total_slots`.
|
- Confirm certification output reports slot-limit violations and no longer reports `max_gfx_bytes` / `max_audio_bytes`.
|
||||||
- [ ] Canonical bank names in the new contract are `GLYPH` and `SOUND`.
|
- Confirm detailed slot inspection still works for both glyph and sound slots.
|
||||||
- [ ] 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
|
## Dependencies
|
||||||
|
|
||||||
- `DEC-0012` accepted and unchanged.
|
- `DEC-0012` remains accepted and unchanged.
|
||||||
|
- Existing `BankType` enum values remain `GLYPH` and `SOUNDS`.
|
||||||
|
- `AssetManager` remains the asset-domain owner for slot occupancy summaries.
|
||||||
|
|
||||||
## Risks
|
## Riscos
|
||||||
|
|
||||||
- Removing byte-based bank paths may have wider ripple effects than expected if old APIs are reused outside the overlay.
|
- Cross-crate trait changes in `AssetBridge` may create a wider refactor than the bank telemetry callsites initially suggest.
|
||||||
- The line between “removed from contract path” and “removed completely from implementation” must stay explicit during refactor to avoid accidental partial migration.
|
- Removing `BankStats` from the visible path may expose hidden byte-based assumptions in runtime telemetry, overlay formatting, or tests.
|
||||||
- Certification migration must stay synchronized with telemetry migration or the host and certifier will diverge semantically.
|
- Certification migration may fail partially if slot maxima are recorded in one layer while enforcement still reads old byte fields in another layer.
|
||||||
|
- If byte accounting is still needed internally during the refactor, the implementation can accidentally leak that detail back into public interfaces unless the change set is kept cohesive.
|
||||||
|
|||||||
@ -187,6 +187,23 @@ Limit:32KB
|
|||||||
|
|
||||||
These data directly feed the certification.
|
These data directly feed the certification.
|
||||||
|
|
||||||
|
### 7.1 Bank Occupancy Profiling
|
||||||
|
|
||||||
|
Bank occupancy diagnostics are slot-first.
|
||||||
|
|
||||||
|
The visible per-bank telemetry used by host inspection surfaces and certification is:
|
||||||
|
|
||||||
|
- `bank_type`
|
||||||
|
- `used_slots`
|
||||||
|
- `total_slots`
|
||||||
|
|
||||||
|
For the current runtime banks, the canonical bank names are:
|
||||||
|
|
||||||
|
- `GLYPH`
|
||||||
|
- `SOUNDS`
|
||||||
|
|
||||||
|
Byte-oriented bank occupancy is not the canonical visible profiling contract.
|
||||||
|
|
||||||
## 8 Breakpoints and Watchpoints
|
## 8 Breakpoints and Watchpoints
|
||||||
|
|
||||||
### 8.1 Breakpoints
|
### 8.1 Breakpoints
|
||||||
@ -218,6 +235,17 @@ Execution can pause when:
|
|||||||
|
|
||||||
## 9 Event and Fault Debugging
|
## 9 Event and Fault Debugging
|
||||||
|
|
||||||
|
## 10 Certification Diagnostics
|
||||||
|
|
||||||
|
Certification diagnostics may enforce bank occupancy ceilings.
|
||||||
|
|
||||||
|
For bank residency, certification uses slot-based limits, such as:
|
||||||
|
|
||||||
|
- `max_glyph_slots_used`
|
||||||
|
- `max_sound_slots_used`
|
||||||
|
|
||||||
|
Bank certification MUST NOT depend on `max_gfx_bytes` or `max_audio_bytes`.
|
||||||
|
|
||||||
PROMETEU allows observing:
|
PROMETEU allows observing:
|
||||||
|
|
||||||
- event queue
|
- event queue
|
||||||
|
|||||||
@ -169,7 +169,7 @@ For `TILES` v1:
|
|||||||
|
|
||||||
The current runtime exposes bank types:
|
The current runtime exposes bank types:
|
||||||
|
|
||||||
- `TILES`
|
- `GLYPH`
|
||||||
- `SOUNDS`
|
- `SOUNDS`
|
||||||
|
|
||||||
Assets are loaded into explicit slots identified by slot index at the public ABI boundary.
|
Assets are loaded into explicit slots identified by slot index at the public ABI boundary.
|
||||||
@ -184,6 +184,27 @@ SlotRef { bank_type, index }
|
|||||||
|
|
||||||
That internal representation is derived from the resolved `AssetEntry`, not supplied by the caller.
|
That internal representation is derived from the resolved `AssetEntry`, not supplied by the caller.
|
||||||
|
|
||||||
|
### 5.1 Canonical Bank Telemetry
|
||||||
|
|
||||||
|
The canonical visible bank telemetry contract is exposed by `AssetManager`.
|
||||||
|
|
||||||
|
The per-bank summary is slot-first and uses this shape:
|
||||||
|
|
||||||
|
```text
|
||||||
|
BankTelemetry {
|
||||||
|
bank_type
|
||||||
|
used_slots
|
||||||
|
total_slots
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
|
||||||
|
- the visible contract MUST NOT expose byte-oriented bank occupancy as the canonical summary;
|
||||||
|
- canonical bank names are `GLYPH` and `SOUNDS`;
|
||||||
|
- detailed occupancy inspection remains slot-based through slot references, not bank byte totals;
|
||||||
|
- any residual byte accounting MAY exist internally, but it is not part of the visible bank telemetry contract.
|
||||||
|
|
||||||
## 6 Load Lifecycle
|
## 6 Load Lifecycle
|
||||||
|
|
||||||
The runtime asset manager exposes a staged lifecycle:
|
The runtime asset manager exposes a staged lifecycle:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user