diff --git a/crates/console/prometeu-firmware/src/firmware/firmware_step_crash_screen.rs b/crates/console/prometeu-firmware/src/firmware/firmware_step_crash_screen.rs index 050ffe0c..8133a3cc 100644 --- a/crates/console/prometeu-firmware/src/firmware/firmware_step_crash_screen.rs +++ b/crates/console/prometeu-firmware/src/firmware/firmware_step_crash_screen.rs @@ -15,12 +15,7 @@ impl AppCrashesStep { } pub fn on_enter(&mut self, ctx: &mut PrometeuContext) { - ctx.os.vm_runtime.log( - LogLevel::Error, - LogSource::Pos, - self.report.log_tag(), - self.log_message(), - ); + ctx.os.log(LogLevel::Error, LogSource::Pos, self.report.log_tag(), self.log_message()); } pub fn on_update(&mut self, ctx: &mut PrometeuContext) -> Option { diff --git a/crates/console/prometeu-firmware/src/firmware/firmware_step_game_running.rs b/crates/console/prometeu-firmware/src/firmware/firmware_step_game_running.rs index 508cfd16..63fdfda1 100644 --- a/crates/console/prometeu-firmware/src/firmware/firmware_step_game_running.rs +++ b/crates/console/prometeu-firmware/src/firmware/firmware_step_game_running.rs @@ -15,12 +15,7 @@ impl GameRunningStep { } pub fn on_enter(&mut self, ctx: &mut PrometeuContext) { - ctx.os.vm_runtime.log( - LogLevel::Info, - LogSource::Pos, - 0, - "Entering GameRunning".to_string(), - ); + ctx.os.log(LogLevel::Info, LogSource::Pos, 0, "Entering GameRunning".to_string()); } pub fn on_update(&mut self, ctx: &mut PrometeuContext) -> Option { @@ -40,7 +35,7 @@ impl GameRunningStep { return Some(FirmwareState::AppCrashes(AppCrashesStep { report })); } - let result = ctx.os.vm_runtime.tick(ctx.vm, ctx.signals, ctx.hw); + let result = ctx.os.tick_vm(ctx.vm, ctx.signals, ctx.hw); if !ctx.os.vm_runtime.logical_frame_active { ctx.hw.gfx_mut().present(); diff --git a/crates/console/prometeu-firmware/src/firmware/firmware_step_hub_home.rs b/crates/console/prometeu-firmware/src/firmware/firmware_step_hub_home.rs index bd558cae..8ad987b5 100644 --- a/crates/console/prometeu-firmware/src/firmware/firmware_step_hub_home.rs +++ b/crates/console/prometeu-firmware/src/firmware/firmware_step_hub_home.rs @@ -7,7 +7,7 @@ pub struct HubHomeStep; impl HubHomeStep { pub fn on_enter(&mut self, ctx: &mut PrometeuContext) { - ctx.os.vm_runtime.log(LogLevel::Info, LogSource::Hub, 0, "Entering HubHome".to_string()); + ctx.os.log(LogLevel::Info, LogSource::Hub, 0, "Entering HubHome".to_string()); } pub fn on_update(&mut self, ctx: &mut PrometeuContext) -> Option { diff --git a/crates/console/prometeu-firmware/src/firmware/firmware_step_launch_hub.rs b/crates/console/prometeu-firmware/src/firmware/firmware_step_launch_hub.rs index c3f73043..eb20a8c4 100644 --- a/crates/console/prometeu-firmware/src/firmware/firmware_step_launch_hub.rs +++ b/crates/console/prometeu-firmware/src/firmware/firmware_step_launch_hub.rs @@ -9,7 +9,7 @@ pub struct LaunchHubStep; impl LaunchHubStep { pub fn on_enter(&mut self, ctx: &mut PrometeuContext) { - ctx.os.vm_runtime.log(LogLevel::Info, LogSource::Pos, 0, "Launching Hub".to_string()); + ctx.os.log(LogLevel::Info, LogSource::Pos, 0, "Launching Hub".to_string()); ctx.hub.init(); } @@ -22,7 +22,7 @@ impl LaunchHubStep { return Some(FirmwareState::LoadCartridge(LoadCartridgeStep::new(cartridge))); } Err(e) => { - ctx.os.vm_runtime.log( + ctx.os.log( LogLevel::Error, LogSource::Pos, 0, diff --git a/crates/console/prometeu-firmware/src/firmware/firmware_step_load_cartridge.rs b/crates/console/prometeu-firmware/src/firmware/firmware_step_load_cartridge.rs index ee7ed827..fdc4ae7a 100644 --- a/crates/console/prometeu-firmware/src/firmware/firmware_step_load_cartridge.rs +++ b/crates/console/prometeu-firmware/src/firmware/firmware_step_load_cartridge.rs @@ -20,7 +20,7 @@ impl LoadCartridgeStep { } pub fn on_enter(&mut self, ctx: &mut PrometeuContext) { - ctx.os.vm_runtime.log( + ctx.os.log( LogLevel::Info, LogSource::Pos, 0, @@ -34,7 +34,7 @@ impl LoadCartridgeStep { self.cartridge.assets.clone(), ); - self.init_error = ctx.os.vm_runtime.initialize_vm(ctx.vm, &self.cartridge).err(); + self.init_error = ctx.os.initialize_vm(ctx.vm, &self.cartridge).err(); } pub fn on_update(&mut self, ctx: &mut PrometeuContext) -> Option { diff --git a/crates/console/prometeu-firmware/src/firmware/firmware_step_reset.rs b/crates/console/prometeu-firmware/src/firmware/firmware_step_reset.rs index 8718369e..20cde51d 100644 --- a/crates/console/prometeu-firmware/src/firmware/firmware_step_reset.rs +++ b/crates/console/prometeu-firmware/src/firmware/firmware_step_reset.rs @@ -8,7 +8,7 @@ pub struct ResetStep; impl ResetStep { pub fn on_enter(&mut self, ctx: &mut PrometeuContext) { - ctx.os.vm_runtime.log(LogLevel::Info, LogSource::Pos, 0, "Firmware Reset".to_string()); + ctx.os.log(LogLevel::Info, LogSource::Pos, 0, "Firmware Reset".to_string()); ctx.os.vm_runtime.reset(ctx.vm); } diff --git a/crates/console/prometeu-firmware/src/firmware/firmware_step_splash_screen.rs b/crates/console/prometeu-firmware/src/firmware/firmware_step_splash_screen.rs index da1e14d1..6695a7dd 100644 --- a/crates/console/prometeu-firmware/src/firmware/firmware_step_splash_screen.rs +++ b/crates/console/prometeu-firmware/src/firmware/firmware_step_splash_screen.rs @@ -10,12 +10,7 @@ pub struct SplashScreenStep { impl SplashScreenStep { pub fn on_enter(&mut self, ctx: &mut PrometeuContext) { - ctx.os.vm_runtime.log( - LogLevel::Info, - LogSource::Pos, - 0, - "Showing SplashScreen".to_string(), - ); + ctx.os.log(LogLevel::Info, LogSource::Pos, 0, "Showing SplashScreen".to_string()); // Play sound on enter // ctx.hw.audio_mut().play(0, 0, 0, 255, 127, 1.0, 0, LoopMode::Off); } diff --git a/crates/console/prometeu-firmware/src/firmware/firmware_step_system_running.rs b/crates/console/prometeu-firmware/src/firmware/firmware_step_system_running.rs index 71c33289..d7dd7ab8 100644 --- a/crates/console/prometeu-firmware/src/firmware/firmware_step_system_running.rs +++ b/crates/console/prometeu-firmware/src/firmware/firmware_step_system_running.rs @@ -14,12 +14,7 @@ impl SystemRunningStep { } pub fn on_enter(&mut self, ctx: &mut PrometeuContext) { - ctx.os.vm_runtime.log( - LogLevel::Info, - LogSource::Hub, - 0, - "Entering SystemRunning".to_string(), - ); + ctx.os.log(LogLevel::Info, LogSource::Hub, 0, "Entering SystemRunning".to_string()); } pub fn on_update(&mut self, ctx: &mut PrometeuContext) -> Option { diff --git a/crates/console/prometeu-system/src/os/system_os.rs b/crates/console/prometeu-system/src/os/system_os.rs index c7551b48..d087c164 100644 --- a/crates/console/prometeu-system/src/os/system_os.rs +++ b/crates/console/prometeu-system/src/os/system_os.rs @@ -1,25 +1,62 @@ +use crate::CrashReport; use crate::VirtualMachineRuntime; use crate::process::ProcessManager; use crate::services::window_manager::WindowManager; use crate::task::TaskId; use crate::task::TaskManager; +use prometeu_hal::cartridge::Cartridge; +use prometeu_hal::log::{LogLevel, LogService, LogSource}; use prometeu_hal::telemetry::CertificationConfig; +use prometeu_hal::{HardwareBridge, InputSignals}; +use prometeu_vm::VirtualMachine; +use std::sync::Arc; pub struct SystemOS { pub vm_runtime: VirtualMachineRuntime, pub process_manager: ProcessManager, pub task_manager: TaskManager, pub window_manager: WindowManager, + pub log_service: LogService, } impl SystemOS { pub fn new(cap_config: Option) -> Self { - Self { - vm_runtime: VirtualMachineRuntime::new(cap_config), + let log_service = LogService::new(4096); + let mut os = Self { + vm_runtime: VirtualMachineRuntime::new_with_log_counter( + cap_config, + Arc::clone(&log_service.logs_count), + ), process_manager: ProcessManager::new(), task_manager: TaskManager::new(), window_manager: WindowManager::new(), - } + log_service, + }; + + os.log(LogLevel::Info, LogSource::Pos, 0, "PrometeuOS starting...".to_string()); + + os + } + + pub fn log(&mut self, level: LogLevel, source: LogSource, tag: u16, msg: String) { + self.vm_runtime.log(&mut self.log_service, level, source, tag, msg); + } + + pub fn initialize_vm( + &mut self, + vm: &mut VirtualMachine, + cartridge: &Cartridge, + ) -> Result<(), CrashReport> { + self.vm_runtime.initialize_vm(&mut self.log_service, vm, cartridge) + } + + pub fn tick_vm( + &mut self, + vm: &mut VirtualMachine, + signals: &InputSignals, + hw: &mut dyn HardwareBridge, + ) -> Option { + self.vm_runtime.tick(&mut self.log_service, vm, signals, hw) } pub fn create_vm_game_task(&mut self, app_id: u32, title: impl Into) -> TaskId { diff --git a/crates/console/prometeu-system/src/programs/prometeu_hub/prometeu_hub.rs b/crates/console/prometeu-system/src/programs/prometeu_hub/prometeu_hub.rs index 56203385..333ff302 100644 --- a/crates/console/prometeu-system/src/programs/prometeu_hub/prometeu_hub.rs +++ b/crates/console/prometeu-system/src/programs/prometeu_hub/prometeu_hub.rs @@ -34,28 +34,28 @@ impl PrometeuHub { let mut next_window = None; if hw.pad().a().pressed { - os.vm_runtime.log(LogLevel::Debug, LogSource::Hub, 0, "window A opened".to_string()); + os.log(LogLevel::Debug, LogSource::Hub, 0, "window A opened".to_string()); next_window = Some(( "Green Window".to_string(), Rect { x: 0, y: 0, w: 160, h: 90 }, Color::GREEN, )); } else if hw.pad().b().pressed { - os.vm_runtime.log(LogLevel::Debug, LogSource::Hub, 0, "window B opened".to_string()); + os.log(LogLevel::Debug, LogSource::Hub, 0, "window B opened".to_string()); next_window = Some(( "Indigo Window".to_string(), Rect { x: 160, y: 0, w: 160, h: 90 }, Color::INDIGO, )); } else if hw.pad().x().pressed { - os.vm_runtime.log(LogLevel::Debug, LogSource::Hub, 0, "window X opened".to_string()); + os.log(LogLevel::Debug, LogSource::Hub, 0, "window X opened".to_string()); next_window = Some(( "Yellow Window".to_string(), Rect { x: 0, y: 90, w: 160, h: 90 }, Color::YELLOW, )); } else if hw.pad().y().pressed { - os.vm_runtime.log(LogLevel::Debug, LogSource::Hub, 0, "window Y opened".to_string()); + os.log(LogLevel::Debug, LogSource::Hub, 0, "window Y opened".to_string()); next_window = Some(("Red Window".to_string(), Rect { x: 160, y: 90, w: 160, h: 90 }, Color::RED)); } @@ -94,7 +94,7 @@ impl PrometeuHub { if hw.pad().start().down { os.window_manager.remove_window(focused_id); } else { - crash = os.vm_runtime.tick(vm, signals, hw); + crash = os.tick_vm(vm, signals, hw); } } diff --git a/crates/console/prometeu-system/src/services/vm_runtime/dispatch.rs b/crates/console/prometeu-system/src/services/vm_runtime/dispatch.rs index 7c7f945b..4dd420e9 100644 --- a/crates/console/prometeu-system/src/services/vm_runtime/dispatch.rs +++ b/crates/console/prometeu-system/src/services/vm_runtime/dispatch.rs @@ -13,10 +13,34 @@ use prometeu_hal::{ AudioOpStatus, ComposerOpStatus, HostContext, HostReturn, NativeInterface, SyscallId, expect_bool, expect_int, }; +use std::ops::{Deref, DerefMut}; use std::panic::{AssertUnwindSafe, catch_unwind}; use std::sync::atomic::Ordering; -impl VirtualMachineRuntime { +pub(crate) struct VmRuntimeHost<'a> { + pub runtime: &'a mut VirtualMachineRuntime, + pub log_service: &'a mut LogService, +} + +impl Deref for VmRuntimeHost<'_> { + type Target = VirtualMachineRuntime; + + fn deref(&self) -> &Self::Target { + self.runtime + } +} + +impl DerefMut for VmRuntimeHost<'_> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.runtime + } +} + +impl VmRuntimeHost<'_> { + fn log(&mut self, level: LogLevel, source: LogSource, tag: u16, msg: String) { + self.runtime.log(self.log_service, level, source, tag, msg); + } + fn panic_payload_to_string(payload: Box) -> String { if let Some(message) = payload.downcast_ref::() { return message.clone(); @@ -40,8 +64,8 @@ impl VirtualMachineRuntime { let app_id = self.current_app_id; let count = *self.logs_written_this_frame.get(&app_id).unwrap_or(&0); - if count >= Self::MAX_LOGS_PER_FRAME { - if count == Self::MAX_LOGS_PER_FRAME { + if count >= VirtualMachineRuntime::MAX_LOGS_PER_FRAME { + if count == VirtualMachineRuntime::MAX_LOGS_PER_FRAME { self.logs_written_this_frame.insert(app_id, count + 1); self.log( LogLevel::Warn, @@ -56,8 +80,8 @@ impl VirtualMachineRuntime { self.logs_written_this_frame.insert(app_id, count + 1); let mut final_msg = msg; - if final_msg.len() > Self::MAX_LOG_LEN { - final_msg.truncate(Self::MAX_LOG_LEN); + if final_msg.len() > VirtualMachineRuntime::MAX_LOG_LEN { + final_msg.truncate(VirtualMachineRuntime::MAX_LOG_LEN); } self.log(level, LogSource::App { app_id }, tag, final_msg); @@ -122,7 +146,7 @@ impl VirtualMachineRuntime { } } -impl NativeInterface for VirtualMachineRuntime { +impl NativeInterface for VmRuntimeHost<'_> { fn syscall( &mut self, id: SyscallId, @@ -410,8 +434,9 @@ impl NativeInterface for VirtualMachineRuntime { let path = self .open_files .get(&handle) - .ok_or_else(|| VmFault::Panic("Invalid handle".into()))?; - match self.fs.read_file(path) { + .ok_or_else(|| VmFault::Panic("Invalid handle".into()))? + .clone(); + match self.fs.read_file(&path) { Ok(data) => ret.push_string(String::from_utf8_lossy(&data).into_owned()), Err(_) => ret.push_null(), } @@ -423,8 +448,9 @@ impl NativeInterface for VirtualMachineRuntime { let path = self .open_files .get(&handle) - .ok_or_else(|| VmFault::Panic("Invalid handle".into()))?; - match self.fs.write_file(path, &content) { + .ok_or_else(|| VmFault::Panic("Invalid handle".into()))? + .clone(); + match self.fs.write_file(&path, &content) { Ok(_) => ret.push_bool(true), Err(_) => ret.push_bool(false), } @@ -516,8 +542,14 @@ impl NativeInterface for VirtualMachineRuntime { return Ok(()); } let payload = hex_decode(&payload_hex)?; - let write = - self.memcard.slot_write(&self.fs, self.current_app_id, slot, offset, &payload); + let runtime = &mut self.runtime; + let write = runtime.memcard.slot_write( + &runtime.fs, + runtime.current_app_id, + slot, + offset, + &payload, + ); ret.push_int(write.status as i64); ret.push_int(write.bytes_written as i64); Ok(()) @@ -529,9 +561,10 @@ impl NativeInterface for VirtualMachineRuntime { return Ok(()); } let status = { - let memcard = &mut self.memcard; - let fs = &mut self.fs; - memcard.slot_commit(fs, self.current_app_id, slot) + let runtime = &mut self.runtime; + let memcard = &mut runtime.memcard; + let fs = &mut runtime.fs; + memcard.slot_commit(fs, runtime.current_app_id, slot) }; ret.push_int(status as i64); Ok(()) @@ -543,9 +576,10 @@ impl NativeInterface for VirtualMachineRuntime { return Ok(()); } let status = { - let memcard = &mut self.memcard; - let fs = &mut self.fs; - memcard.slot_clear(fs, self.current_app_id, slot) + let runtime = &mut self.runtime; + let memcard = &mut runtime.memcard; + let fs = &mut runtime.fs; + memcard.slot_clear(fs, runtime.current_app_id, slot) }; ret.push_int(status as i64); Ok(()) diff --git a/crates/console/prometeu-system/src/services/vm_runtime/lifecycle.rs b/crates/console/prometeu-system/src/services/vm_runtime/lifecycle.rs index 232c35d6..002395d8 100644 --- a/crates/console/prometeu-system/src/services/vm_runtime/lifecycle.rs +++ b/crates/console/prometeu-system/src/services/vm_runtime/lifecycle.rs @@ -5,11 +5,13 @@ use prometeu_hal::cartridge::Cartridge; use prometeu_hal::log::{LogLevel, LogSource}; impl VirtualMachineRuntime { - pub fn new(cap_config: Option) -> Self { + pub fn new_with_log_counter( + cap_config: Option, + logs_count: Arc, + ) -> Self { let boot_time = Instant::now(); - let log_service = LogService::new(4096); - let atomic_telemetry = Arc::new(AtomicTelemetry::new(Arc::clone(&log_service.logs_count))); - let mut os = Self { + let atomic_telemetry = Arc::new(AtomicTelemetry::new(logs_count)); + Self { tick_index: 0, logical_frame_index: 0, logical_frame_active: false, @@ -20,7 +22,6 @@ impl VirtualMachineRuntime { memcard: MemcardService::new(), open_files: HashMap::new(), next_handle: 1, - log_service, current_app_id: 0, current_cartridge_title: String::new(), current_cartridge_app_version: String::new(), @@ -36,25 +37,35 @@ impl VirtualMachineRuntime { frame_start_string_materializations: 0, needs_prepare_entry_call: false, boot_time, - }; - - os.log(LogLevel::Info, LogSource::Pos, 0, "PrometeuOS starting...".to_string()); - - os + } } - pub fn log(&mut self, level: LogLevel, source: LogSource, tag: u16, msg: String) { + pub fn log( + &self, + log_service: &mut LogService, + level: LogLevel, + source: LogSource, + tag: u16, + msg: String, + ) { let ts_ms = self.boot_time.elapsed().as_millis() as u64; let frame = self.logical_frame_index; - self.log_service.log(ts_ms, frame, level, source, tag, msg); + log_service.log(ts_ms, frame, level, source, tag, msg); } - pub fn mount_fs(&mut self, backend: Box) { - self.log(LogLevel::Info, LogSource::Fs, 0, "Attempting to mount filesystem".to_string()); + pub fn mount_fs(&mut self, log_service: &mut LogService, backend: Box) { + self.log( + log_service, + LogLevel::Info, + LogSource::Fs, + 0, + "Attempting to mount filesystem".to_string(), + ); match self.fs.mount(backend) { Ok(_) => { self.fs_state = FsState::Mounted; self.log( + log_service, LogLevel::Info, LogSource::Fs, 0, @@ -63,7 +74,7 @@ impl VirtualMachineRuntime { } Err(e) => { let err_msg = format!("Failed to mount filesystem: {:?}", e); - self.log(LogLevel::Error, LogSource::Fs, 0, err_msg); + self.log(log_service, LogLevel::Error, LogSource::Fs, 0, err_msg); self.fs_state = FsState::Error(e); } } @@ -74,9 +85,10 @@ impl VirtualMachineRuntime { self.fs_state = FsState::Unmounted; } - pub(crate) fn update_fs(&mut self) { + pub(crate) fn update_fs(&mut self, log_service: &mut LogService) { if self.fs_state == FsState::Mounted && !self.fs.is_healthy() { self.log( + log_service, LogLevel::Error, LogSource::Fs, 0, @@ -120,6 +132,7 @@ impl VirtualMachineRuntime { pub fn initialize_vm( &mut self, + log_service: &mut LogService, vm: &mut VirtualMachine, cartridge: &Cartridge, ) -> Result<(), CrashReport> { @@ -138,6 +151,7 @@ impl VirtualMachineRuntime { let report = CrashReport::VmInit { error: e }; self.last_crash_report = Some(report.clone()); self.log( + log_service, LogLevel::Error, LogSource::Vm, report.log_tag(), diff --git a/crates/console/prometeu-system/src/services/vm_runtime/mod.rs b/crates/console/prometeu-system/src/services/vm_runtime/mod.rs index 682ce694..b76b7560 100644 --- a/crates/console/prometeu-system/src/services/vm_runtime/mod.rs +++ b/crates/console/prometeu-system/src/services/vm_runtime/mod.rs @@ -14,6 +14,7 @@ use prometeu_hal::telemetry::{AtomicTelemetry, CertificationConfig, Certifier}; use prometeu_vm::VirtualMachine; use std::collections::HashMap; use std::sync::Arc; +use std::sync::atomic::AtomicU32; use std::time::Instant; pub struct VirtualMachineRuntime { @@ -27,7 +28,6 @@ pub struct VirtualMachineRuntime { pub memcard: MemcardService, pub open_files: HashMap, pub next_handle: u32, - pub log_service: LogService, pub current_app_id: u32, pub current_cartridge_title: String, pub current_cartridge_app_version: String, @@ -50,4 +50,8 @@ impl VirtualMachineRuntime { pub const SLICE_PER_TICK: u64 = 1_500_000; pub const MAX_LOG_LEN: usize = 256; pub const MAX_LOGS_PER_FRAME: u32 = 10; + + pub fn new(cap_config: Option) -> Self { + Self::new_with_log_counter(cap_config, Arc::new(AtomicU32::new(0))) + } } diff --git a/crates/console/prometeu-system/src/services/vm_runtime/tests.rs b/crates/console/prometeu-system/src/services/vm_runtime/tests.rs index 1867bcb9..578a097a 100644 --- a/crates/console/prometeu-system/src/services/vm_runtime/tests.rs +++ b/crates/console/prometeu-system/src/services/vm_runtime/tests.rs @@ -188,6 +188,7 @@ mod fs_memcard; #[test] fn initialize_vm_applies_cartridge_capabilities_before_loader_resolution() { let mut runtime = VirtualMachineRuntime::new(None); + let mut log_service = LogService::new(4096); let mut vm = VirtualMachine::default(); let code = assemble("PUSH_I32 0\nHOSTCALL 0\nHALT").expect("assemble"); let program = serialized_single_function_module( @@ -202,7 +203,7 @@ fn initialize_vm_applies_cartridge_capabilities_before_loader_resolution() { ); let cartridge = cartridge_with_program(program, caps::NONE); - let res = runtime.initialize_vm(&mut vm, &cartridge); + let res = runtime.initialize_vm(&mut log_service, &mut vm, &cartridge); assert!(matches!(res, Err(CrashReport::VmInit { error: VmInitError::LoaderPatchFailed(_) }))); assert_eq!(runtime.current_app_id, 0); @@ -213,6 +214,7 @@ fn initialize_vm_applies_cartridge_capabilities_before_loader_resolution() { #[test] fn initialize_vm_succeeds_when_cartridge_capabilities_cover_hostcalls() { let mut runtime = VirtualMachineRuntime::new(None); + let mut log_service = LogService::new(4096); let mut vm = VirtualMachine::default(); let code = assemble("PUSH_I32 0\nHOSTCALL 0\nHALT").expect("assemble"); let program = serialized_single_function_module( @@ -227,7 +229,7 @@ fn initialize_vm_succeeds_when_cartridge_capabilities_cover_hostcalls() { ); let cartridge = cartridge_with_program(program, caps::GFX); - let res = runtime.initialize_vm(&mut vm, &cartridge); + let res = runtime.initialize_vm(&mut log_service, &mut vm, &cartridge); assert!(res.is_ok()); assert_eq!(runtime.current_app_id, 42); @@ -237,6 +239,7 @@ fn initialize_vm_succeeds_when_cartridge_capabilities_cover_hostcalls() { #[test] fn tick_returns_error_when_vm_ends_slice_with_trap() { let mut runtime = VirtualMachineRuntime::new(None); + let mut log_service = LogService::new(4096); let mut vm = VirtualMachine::default(); let mut hardware = Hardware::new(); let signals = InputSignals::default(); @@ -253,10 +256,11 @@ fn tick_returns_error_when_vm_ends_slice_with_trap() { ); let cartridge = cartridge_with_program(program, caps::GFX); - runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize"); + runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize"); - let report = - runtime.tick(&mut vm, &signals, &mut hardware).expect("trap must surface as runtime error"); + let report = runtime + .tick(&mut log_service, &mut vm, &signals, &mut hardware) + .expect("trap must surface as runtime error"); match report { CrashReport::VmTrap { trap } => { @@ -271,6 +275,7 @@ fn tick_returns_error_when_vm_ends_slice_with_trap() { #[test] fn tick_system_profile_rejects_gfx_game_surface() { let mut runtime = VirtualMachineRuntime::new(None); + let mut log_service = LogService::new(4096); let mut vm = VirtualMachine::default(); let mut hardware = Hardware::new(); let signals = InputSignals::default(); @@ -287,8 +292,10 @@ fn tick_system_profile_rejects_gfx_game_surface() { ); let cartridge = cartridge_with_program_and_mode(program, caps::GFX, AppMode::Shell); - runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize"); - let report = runtime.tick(&mut vm, &signals, &mut hardware).expect("system gfx call must trap"); + runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize"); + let report = runtime + .tick(&mut log_service, &mut vm, &signals, &mut hardware) + .expect("system gfx call must trap"); match report { CrashReport::VmTrap { trap } => { @@ -302,6 +309,7 @@ fn tick_system_profile_rejects_gfx_game_surface() { #[test] fn tick_system_profile_rejects_composer_game_surface() { let mut runtime = VirtualMachineRuntime::new(None); + let mut log_service = LogService::new(4096); let mut vm = VirtualMachine::default(); let mut hardware = Hardware::new(); let signals = InputSignals::default(); @@ -318,9 +326,10 @@ fn tick_system_profile_rejects_composer_game_surface() { ); let cartridge = cartridge_with_program_and_mode(program, caps::GFX, AppMode::Shell); - runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize"); - let report = - runtime.tick(&mut vm, &signals, &mut hardware).expect("system composer call must trap"); + runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize"); + let report = runtime + .tick(&mut log_service, &mut vm, &signals, &mut hardware) + .expect("system composer call must trap"); match report { CrashReport::VmTrap { trap } => { @@ -336,6 +345,7 @@ fn tick_system_profile_rejects_composer_game_surface() { #[test] fn tick_system_profile_rejects_bank_game_surface() { let mut runtime = VirtualMachineRuntime::new(None); + let mut log_service = LogService::new(4096); let mut vm = VirtualMachine::default(); let mut hardware = Hardware::new(); let signals = InputSignals::default(); @@ -352,9 +362,10 @@ fn tick_system_profile_rejects_bank_game_surface() { ); let cartridge = cartridge_with_program_and_mode(program, caps::BANK, AppMode::Shell); - runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize"); - let report = - runtime.tick(&mut vm, &signals, &mut hardware).expect("system bank call must trap"); + runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize"); + let report = runtime + .tick(&mut log_service, &mut vm, &signals, &mut hardware) + .expect("system bank call must trap"); match report { CrashReport::VmTrap { trap } => { @@ -368,6 +379,7 @@ fn tick_system_profile_rejects_bank_game_surface() { #[test] fn tick_system_profile_can_use_shared_log_transport() { let mut runtime = VirtualMachineRuntime::new(None); + let mut log_service = LogService::new(4096); let mut vm = VirtualMachine::default(); let mut hardware = Hardware::new(); let signals = InputSignals::default(); @@ -385,8 +397,8 @@ fn tick_system_profile_can_use_shared_log_transport() { ); let cartridge = cartridge_with_program_and_mode(program, caps::LOG, AppMode::Shell); - runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize"); - let report = runtime.tick(&mut vm, &signals, &mut hardware); + runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize"); + let report = runtime.tick(&mut log_service, &mut vm, &signals, &mut hardware); assert!(report.is_none(), "system log transport must remain internally shared"); assert!(vm.is_halted()); @@ -395,12 +407,13 @@ fn tick_system_profile_can_use_shared_log_transport() { #[test] fn tick_returns_panic_report_distinct_from_trap() { let mut runtime = VirtualMachineRuntime::new(None); + let mut log_service = LogService::new(4096); let mut vm = VirtualMachine::new(assemble("HOSTCALL 0\nHALT").expect("assemble"), vec![]); let mut hardware = Hardware::new(); let signals = InputSignals::default(); let report = runtime - .tick(&mut vm, &signals, &mut hardware) + .tick(&mut log_service, &mut vm, &signals, &mut hardware) .expect("panic must surface as runtime error"); match report { @@ -416,6 +429,7 @@ fn tick_returns_panic_report_distinct_from_trap() { #[test] fn tick_renders_bound_eight_pixel_scene_through_frame_composer_path() { let mut runtime = VirtualMachineRuntime::new(None); + let mut log_service = LogService::new(4096); let mut vm = VirtualMachine::default(); let signals = InputSignals::default(); let program = @@ -432,8 +446,8 @@ fn tick_renders_bound_eight_pixel_scene_through_frame_composer_path() { glyph_slot_index.rebuild_from_slots(&slots); assert!(hardware.frame_composer.bind_scene(0)); - runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize"); - let report = runtime.tick(&mut vm, &signals, &mut hardware); + runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize"); + let report = runtime.tick(&mut log_service, &mut vm, &signals, &mut hardware); assert!(report.is_none(), "frame render path must not crash"); hardware.gfx.present(); @@ -443,6 +457,7 @@ fn tick_renders_bound_eight_pixel_scene_through_frame_composer_path() { #[test] fn tick_renders_scene_through_public_composer_syscalls() { let mut runtime = VirtualMachineRuntime::new(None); + let mut log_service = LogService::new(4096); let mut vm = VirtualMachine::default(); let signals = InputSignals::default(); let code = assemble( @@ -491,8 +506,8 @@ fn tick_renders_scene_through_public_composer_syscalls() { hardware.gfx.scene_fade_level = 31; hardware.gfx.hud_fade_level = 31; - runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize"); - let report = runtime.tick(&mut vm, &signals, &mut hardware); + runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize"); + let report = runtime.tick(&mut log_service, &mut vm, &signals, &mut hardware); assert!(report.is_none(), "public composer path must not crash"); assert_eq!(vm.operand_stack_top(1), vec![Value::Int64(ComposerOpStatus::Ok as i64)]); @@ -503,6 +518,7 @@ fn tick_renders_scene_through_public_composer_syscalls() { #[test] fn tick_draw_text_survives_scene_backed_frame_composition() { let mut runtime = VirtualMachineRuntime::new(None); + let mut log_service = LogService::new(4096); let mut vm = VirtualMachine::default(); let signals = InputSignals::default(); let code = assemble( @@ -552,8 +568,8 @@ fn tick_draw_text_survives_scene_backed_frame_composition() { hardware.gfx.scene_fade_level = 31; hardware.gfx.hud_fade_level = 31; - runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize"); - let report = runtime.tick(&mut vm, &signals, &mut hardware); + runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize"); + let report = runtime.tick(&mut log_service, &mut vm, &signals, &mut hardware); assert!(report.is_none(), "scene-backed overlay text must not crash"); hardware.gfx.present(); @@ -563,6 +579,7 @@ fn tick_draw_text_survives_scene_backed_frame_composition() { #[test] fn tick_draw_text_survives_no_scene_frame_path() { let mut runtime = VirtualMachineRuntime::new(None); + let mut log_service = LogService::new(4096); let mut vm = VirtualMachine::default(); let signals = InputSignals::default(); let code = assemble( @@ -585,8 +602,8 @@ fn tick_draw_text_survives_no_scene_frame_path() { hardware.gfx.scene_fade_level = 31; hardware.gfx.hud_fade_level = 31; - runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize"); - let report = runtime.tick(&mut vm, &signals, &mut hardware); + runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize"); + let report = runtime.tick(&mut log_service, &mut vm, &signals, &mut hardware); assert!(report.is_none(), "no-scene overlay text must not crash"); hardware.gfx.present(); @@ -596,6 +613,7 @@ fn tick_draw_text_survives_no_scene_frame_path() { #[test] fn initialize_vm_success_clears_previous_crash_report() { let mut runtime = VirtualMachineRuntime::new(None); + let mut log_service = LogService::new(4096); runtime.last_crash_report = Some(CrashReport::VmPanic { message: "stale".into(), pc: Some(1) }); let mut vm = VirtualMachine::default(); @@ -612,7 +630,7 @@ fn initialize_vm_success_clears_previous_crash_report() { ); let cartridge = cartridge_with_program(program, caps::GFX); - runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize"); + runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize"); assert!(runtime.last_crash_report.is_none()); } @@ -679,6 +697,7 @@ fn reset_clears_cartridge_scoped_runtime_state() { #[test] fn tick_numeric_happy_path_records_zero_internal_allocations() { let mut runtime = VirtualMachineRuntime::new(None); + let mut log_service = LogService::new(4096); let mut vm = VirtualMachine::default(); let mut hardware = Hardware::new(); let signals = InputSignals::default(); @@ -687,8 +706,8 @@ fn tick_numeric_happy_path_records_zero_internal_allocations() { let program = serialized_single_function_module(code, vec![]); let cartridge = cartridge_with_program(program, caps::NONE); - runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize"); - let report = runtime.tick(&mut vm, &signals, &mut hardware); + runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize"); + let report = runtime.tick(&mut log_service, &mut vm, &signals, &mut hardware); assert!(report.is_none()); let snapshot = runtime.atomic_telemetry.snapshot(); @@ -699,6 +718,7 @@ fn tick_numeric_happy_path_records_zero_internal_allocations() { #[test] fn tick_already_materialized_string_path_records_zero_internal_allocations() { let mut runtime = VirtualMachineRuntime::new(None); + let mut log_service = LogService::new(4096); let mut vm = VirtualMachine::default(); let mut hardware = Hardware::new(); let signals = InputSignals::default(); @@ -711,8 +731,8 @@ fn tick_already_materialized_string_path_records_zero_internal_allocations() { ); let cartridge = cartridge_with_program(program, caps::NONE); - runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize"); - let report = runtime.tick(&mut vm, &signals, &mut hardware); + runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize"); + let report = runtime.tick(&mut log_service, &mut vm, &signals, &mut hardware); assert!(report.is_none()); let snapshot = runtime.atomic_telemetry.snapshot(); @@ -723,6 +743,7 @@ fn tick_already_materialized_string_path_records_zero_internal_allocations() { #[test] fn initialize_vm_failure_clears_previous_identity_and_handles() { let mut runtime = VirtualMachineRuntime::new(None); + let mut log_service = LogService::new(4096); let mut vm = VirtualMachine::default(); let good_program = serialized_single_function_module( @@ -736,7 +757,9 @@ fn initialize_vm_failure_clears_previous_identity_and_handles() { }], ); let good_cartridge = cartridge_with_program(good_program, caps::GFX); - runtime.initialize_vm(&mut vm, &good_cartridge).expect("runtime must initialize"); + runtime + .initialize_vm(&mut log_service, &mut vm, &good_cartridge) + .expect("runtime must initialize"); runtime.open_files.insert(5, "/save.dat".into()); runtime.next_handle = 6; @@ -756,7 +779,7 @@ fn initialize_vm_failure_clears_previous_identity_and_handles() { ); let bad_cartridge = cartridge_with_program(bad_program, caps::NONE); - let res = runtime.initialize_vm(&mut vm, &bad_cartridge); + let res = runtime.initialize_vm(&mut log_service, &mut vm, &bad_cartridge); assert!(matches!(res, Err(CrashReport::VmInit { error: VmInitError::LoaderPatchFailed(_) }))); assert_eq!(runtime.current_app_id, 0); @@ -774,6 +797,7 @@ fn initialize_vm_failure_clears_previous_identity_and_handles() { #[test] fn tick_composer_bind_scene_operational_error_returns_status_not_crash() { let mut runtime = VirtualMachineRuntime::new(None); + let mut log_service = LogService::new(4096); let mut vm = VirtualMachine::default(); let mut hardware = Hardware::new(); let signals = InputSignals::default(); @@ -790,8 +814,8 @@ fn tick_composer_bind_scene_operational_error_returns_status_not_crash() { ); let cartridge = cartridge_with_program(program, caps::GFX); - runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize"); - let report = runtime.tick(&mut vm, &signals, &mut hardware); + runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize"); + let report = runtime.tick(&mut log_service, &mut vm, &signals, &mut hardware); assert!(report.is_none(), "operational error must not crash"); assert!(vm.is_halted()); assert_eq!( @@ -803,6 +827,7 @@ fn tick_composer_bind_scene_operational_error_returns_status_not_crash() { #[test] fn tick_composer_emit_sprite_operational_error_returns_status_not_crash() { let mut runtime = VirtualMachineRuntime::new(None); + let mut log_service = LogService::new(4096); let mut vm = VirtualMachine::default(); let mut hardware = Hardware::new(); let signals = InputSignals::default(); @@ -822,8 +847,8 @@ fn tick_composer_emit_sprite_operational_error_returns_status_not_crash() { ); let cartridge = cartridge_with_program(program, caps::GFX); - runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize"); - let report = runtime.tick(&mut vm, &signals, &mut hardware); + runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize"); + let report = runtime.tick(&mut log_service, &mut vm, &signals, &mut hardware); assert!(report.is_none(), "operational error must not crash"); assert!(vm.is_halted()); assert_eq!(vm.operand_stack_top(1), vec![Value::Int64(ComposerOpStatus::BankInvalid as i64)]); @@ -832,6 +857,7 @@ fn tick_composer_emit_sprite_operational_error_returns_status_not_crash() { #[test] fn tick_composer_emit_sprite_invalid_layer_returns_status_not_crash() { let mut runtime = VirtualMachineRuntime::new(None); + let mut log_service = LogService::new(4096); let mut vm = VirtualMachine::default(); let mut hardware = Hardware::new(); let signals = InputSignals::default(); @@ -851,8 +877,8 @@ fn tick_composer_emit_sprite_invalid_layer_returns_status_not_crash() { ); let cartridge = cartridge_with_program(program, caps::GFX); - runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize"); - let report = runtime.tick(&mut vm, &signals, &mut hardware); + runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize"); + let report = runtime.tick(&mut log_service, &mut vm, &signals, &mut hardware); assert!(report.is_none(), "invalid layer must not crash"); assert!(vm.is_halted()); assert_eq!(vm.operand_stack_top(1), vec![Value::Int64(ComposerOpStatus::LayerInvalid as i64)]); @@ -861,6 +887,7 @@ fn tick_composer_emit_sprite_invalid_layer_returns_status_not_crash() { #[test] fn tick_composer_emit_sprite_invalid_range_returns_status_not_crash() { let mut runtime = VirtualMachineRuntime::new(None); + let mut log_service = LogService::new(4096); let mut vm = VirtualMachine::default(); let mut hardware = Hardware::new(); let signals = InputSignals::default(); @@ -887,8 +914,8 @@ fn tick_composer_emit_sprite_invalid_range_returns_status_not_crash() { AssetsPayloadSource::from_bytes(asset_data), ); - runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize"); - let report = runtime.tick(&mut vm, &signals, &mut hardware); + runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize"); + let report = runtime.tick(&mut log_service, &mut vm, &signals, &mut hardware); assert!(report.is_none(), "invalid composer parameter range must not crash"); assert!(vm.is_halted()); assert_eq!( @@ -900,6 +927,7 @@ fn tick_composer_emit_sprite_invalid_range_returns_status_not_crash() { #[test] fn tick_audio_play_sample_operational_error_returns_status_not_crash() { let mut runtime = VirtualMachineRuntime::new(None); + let mut log_service = LogService::new(4096); let mut vm = VirtualMachine::default(); let mut hardware = Hardware::new(); let signals = InputSignals::default(); @@ -919,8 +947,8 @@ fn tick_audio_play_sample_operational_error_returns_status_not_crash() { ); let cartridge = cartridge_with_program(program, caps::AUDIO); - runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize"); - let report = runtime.tick(&mut vm, &signals, &mut hardware); + runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize"); + let report = runtime.tick(&mut log_service, &mut vm, &signals, &mut hardware); assert!(report.is_none(), "operational error must not crash"); assert!(vm.is_halted()); assert_eq!(vm.operand_stack_top(1), vec![Value::Int64(AudioOpStatus::ArgRangeInvalid as i64)]); @@ -929,6 +957,7 @@ fn tick_audio_play_sample_operational_error_returns_status_not_crash() { #[test] fn tick_audio_play_voice_invalid_returns_status_not_crash() { let mut runtime = VirtualMachineRuntime::new(None); + let mut log_service = LogService::new(4096); let mut vm = VirtualMachine::default(); let mut hardware = Hardware::new(); let signals = InputSignals::default(); @@ -948,8 +977,8 @@ fn tick_audio_play_voice_invalid_returns_status_not_crash() { ); let cartridge = cartridge_with_program(program, caps::AUDIO); - runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize"); - let report = runtime.tick(&mut vm, &signals, &mut hardware); + runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize"); + let report = runtime.tick(&mut log_service, &mut vm, &signals, &mut hardware); assert!(report.is_none(), "invalid voice must not crash"); assert!(vm.is_halted()); assert_eq!(vm.operand_stack_top(1), vec![Value::Int64(AudioOpStatus::VoiceInvalid as i64)]); @@ -958,6 +987,7 @@ fn tick_audio_play_voice_invalid_returns_status_not_crash() { #[test] fn tick_audio_play_missing_asset_returns_status_not_crash() { let mut runtime = VirtualMachineRuntime::new(None); + let mut log_service = LogService::new(4096); let mut vm = VirtualMachine::default(); let mut hardware = Hardware::new(); let signals = InputSignals::default(); @@ -977,8 +1007,8 @@ fn tick_audio_play_missing_asset_returns_status_not_crash() { ); let cartridge = cartridge_with_program(program, caps::AUDIO); - runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize"); - let report = runtime.tick(&mut vm, &signals, &mut hardware); + runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize"); + let report = runtime.tick(&mut log_service, &mut vm, &signals, &mut hardware); assert!(report.is_none(), "missing audio asset must not crash"); assert!(vm.is_halted()); assert_eq!(vm.operand_stack_top(1), vec![Value::Int64(AudioOpStatus::BankInvalid as i64)]); @@ -987,6 +1017,7 @@ fn tick_audio_play_missing_asset_returns_status_not_crash() { #[test] fn tick_audio_play_type_mismatch_surfaces_trap_not_panic() { let mut runtime = VirtualMachineRuntime::new(None); + let mut log_service = LogService::new(4096); let mut vm = VirtualMachine::default(); let mut hardware = Hardware::new(); let signals = InputSignals::default(); @@ -1006,9 +1037,10 @@ fn tick_audio_play_type_mismatch_surfaces_trap_not_panic() { ); let cartridge = cartridge_with_program(program, caps::AUDIO); - runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize"); - let report = - runtime.tick(&mut vm, &signals, &mut hardware).expect("type mismatch must surface as trap"); + runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize"); + let report = runtime + .tick(&mut log_service, &mut vm, &signals, &mut hardware) + .expect("type mismatch must surface as trap"); match report { CrashReport::VmTrap { trap } => { assert_eq!(trap.code, TRAP_TYPE); @@ -1022,6 +1054,7 @@ fn tick_audio_play_type_mismatch_surfaces_trap_not_panic() { #[test] fn tick_composer_emit_sprite_type_mismatch_surfaces_trap_not_panic() { let mut runtime = VirtualMachineRuntime::new(None); + let mut log_service = LogService::new(4096); let mut vm = VirtualMachine::default(); let mut hardware = Hardware::new(); let signals = InputSignals::default(); @@ -1041,9 +1074,10 @@ fn tick_composer_emit_sprite_type_mismatch_surfaces_trap_not_panic() { ); let cartridge = cartridge_with_program(program, caps::GFX); - runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize"); - let report = - runtime.tick(&mut vm, &signals, &mut hardware).expect("type mismatch must surface as trap"); + runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize"); + let report = runtime + .tick(&mut log_service, &mut vm, &signals, &mut hardware) + .expect("type mismatch must surface as trap"); match report { CrashReport::VmTrap { trap } => { assert_eq!(trap.code, TRAP_TYPE); diff --git a/crates/console/prometeu-system/src/services/vm_runtime/tests_asset_bank.rs b/crates/console/prometeu-system/src/services/vm_runtime/tests_asset_bank.rs index 39b99e4e..634b50c2 100644 --- a/crates/console/prometeu-system/src/services/vm_runtime/tests_asset_bank.rs +++ b/crates/console/prometeu-system/src/services/vm_runtime/tests_asset_bank.rs @@ -3,6 +3,7 @@ use super::*; #[test] fn tick_asset_commit_operational_error_returns_status_not_crash() { let mut runtime = VirtualMachineRuntime::new(None); + let mut log_service = LogService::new(4096); let mut vm = VirtualMachine::default(); let mut hardware = Hardware::new(); let signals = InputSignals::default(); @@ -19,8 +20,8 @@ fn tick_asset_commit_operational_error_returns_status_not_crash() { ); let cartridge = cartridge_with_program(program, caps::ASSET); - runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize"); - let report = runtime.tick(&mut vm, &signals, &mut hardware); + runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize"); + let report = runtime.tick(&mut log_service, &mut vm, &signals, &mut hardware); assert!(report.is_none(), "operational error must not crash"); assert!(vm.is_halted()); assert_eq!(vm.operand_stack_top(1), vec![Value::Int64(AssetOpStatus::UnknownHandle as i64)]); @@ -29,6 +30,7 @@ fn tick_asset_commit_operational_error_returns_status_not_crash() { #[test] fn tick_asset_load_missing_asset_returns_status_and_zero_handle() { let mut runtime = VirtualMachineRuntime::new(None); + let mut log_service = LogService::new(4096); let mut vm = VirtualMachine::default(); let mut hardware = Hardware::new(); let signals = InputSignals::default(); @@ -45,8 +47,8 @@ fn tick_asset_load_missing_asset_returns_status_and_zero_handle() { ); let cartridge = cartridge_with_program(program, caps::ASSET); - runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize"); - let report = runtime.tick(&mut vm, &signals, &mut hardware); + runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize"); + let report = runtime.tick(&mut log_service, &mut vm, &signals, &mut hardware); assert!(report.is_none(), "missing asset must not crash"); assert!(vm.is_halted()); assert_eq!( @@ -58,6 +60,7 @@ fn tick_asset_load_missing_asset_returns_status_and_zero_handle() { #[test] fn tick_asset_load_invalid_slot_returns_status_and_zero_handle() { let mut runtime = VirtualMachineRuntime::new(None); + let mut log_service = LogService::new(4096); let mut vm = VirtualMachine::default(); let mut hardware = Hardware::new(); let signals = InputSignals::default(); @@ -80,8 +83,8 @@ fn tick_asset_load_invalid_slot_returns_status_and_zero_handle() { ); let cartridge = cartridge_with_program(program, caps::ASSET); - runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize"); - let report = runtime.tick(&mut vm, &signals, &mut hardware); + runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize"); + let report = runtime.tick(&mut log_service, &mut vm, &signals, &mut hardware); assert!(report.is_none(), "invalid slot must not crash"); assert!(vm.is_halted()); assert_eq!( @@ -93,6 +96,7 @@ fn tick_asset_load_invalid_slot_returns_status_and_zero_handle() { #[test] fn tick_asset_status_unknown_handle_returns_status_not_crash() { let mut runtime = VirtualMachineRuntime::new(None); + let mut log_service = LogService::new(4096); let mut vm = VirtualMachine::default(); let mut hardware = Hardware::new(); let signals = InputSignals::default(); @@ -109,8 +113,8 @@ fn tick_asset_status_unknown_handle_returns_status_not_crash() { ); let cartridge = cartridge_with_program(program, caps::ASSET); - runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize"); - let report = runtime.tick(&mut vm, &signals, &mut hardware); + runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize"); + let report = runtime.tick(&mut log_service, &mut vm, &signals, &mut hardware); assert!(report.is_none(), "unknown asset handle must not crash"); assert!(vm.is_halted()); assert_eq!(vm.operand_stack_top(1), vec![Value::Int64(LoadStatus::UnknownHandle as i64)]); @@ -119,6 +123,7 @@ fn tick_asset_status_unknown_handle_returns_status_not_crash() { #[test] fn tick_bank_info_returns_slot_summary_not_json() { let mut runtime = VirtualMachineRuntime::new(None); + let mut log_service = LogService::new(4096); let mut vm = VirtualMachine::default(); let mut hardware = Hardware::new(); let signals = InputSignals::default(); @@ -141,8 +146,8 @@ fn tick_bank_info_returns_slot_summary_not_json() { ); let cartridge = cartridge_with_program(program, caps::BANK); - runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize"); - let report = runtime.tick(&mut vm, &signals, &mut hardware); + runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize"); + let report = runtime.tick(&mut log_service, &mut vm, &signals, &mut hardware); assert!(report.is_none(), "bank summary must not crash"); assert!(vm.is_halted()); assert_eq!(vm.operand_stack_top(2), vec![Value::Int64(16), Value::Int64(1)]); @@ -151,6 +156,7 @@ fn tick_bank_info_returns_slot_summary_not_json() { #[test] fn initialize_vm_rejects_removed_bank_slot_info_syscall_identity() { let mut runtime = VirtualMachineRuntime::new(None); + let mut log_service = LogService::new(4096); let mut vm = VirtualMachine::default(); let code = assemble("PUSH_I32 0\nPUSH_I32 0\nHOSTCALL 0\nHALT").expect("assemble"); let program = serialized_single_function_module( @@ -165,7 +171,7 @@ fn initialize_vm_rejects_removed_bank_slot_info_syscall_identity() { ); let cartridge = cartridge_with_program(program, caps::BANK); - let res = runtime.initialize_vm(&mut vm, &cartridge); + let res = runtime.initialize_vm(&mut log_service, &mut vm, &cartridge); assert!(matches!(res, Err(CrashReport::VmInit { error: VmInitError::LoaderPatchFailed(_) }))); } @@ -173,6 +179,7 @@ fn initialize_vm_rejects_removed_bank_slot_info_syscall_identity() { #[test] fn tick_asset_commit_invalid_transition_returns_status_not_crash() { let mut runtime = VirtualMachineRuntime::new(None); + let mut log_service = LogService::new(4096); let mut vm = VirtualMachine::default(); let mut hardware = Hardware::new(); let signals = InputSignals::default(); @@ -207,8 +214,8 @@ fn tick_asset_commit_invalid_transition_returns_status_not_crash() { ); let handle = hardware.assets.load(7, 0).expect("asset handle must be allocated"); - runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize"); - let report = runtime.tick(&mut vm, &signals, &mut hardware); + runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize"); + let report = runtime.tick(&mut log_service, &mut vm, &signals, &mut hardware); assert!(report.is_none(), "invalid transition must not crash"); assert!(vm.is_halted()); assert_eq!(handle, 1); @@ -218,6 +225,7 @@ fn tick_asset_commit_invalid_transition_returns_status_not_crash() { #[test] fn tick_asset_cancel_unknown_handle_returns_status_not_crash() { let mut runtime = VirtualMachineRuntime::new(None); + let mut log_service = LogService::new(4096); let mut vm = VirtualMachine::default(); let mut hardware = Hardware::new(); let signals = InputSignals::default(); @@ -234,8 +242,8 @@ fn tick_asset_cancel_unknown_handle_returns_status_not_crash() { ); let cartridge = cartridge_with_program(program, caps::ASSET); - runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize"); - let report = runtime.tick(&mut vm, &signals, &mut hardware); + runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize"); + let report = runtime.tick(&mut log_service, &mut vm, &signals, &mut hardware); assert!(report.is_none(), "unknown handle cancel must not crash"); assert!(vm.is_halted()); assert_eq!(vm.operand_stack_top(1), vec![Value::Int64(AssetOpStatus::UnknownHandle as i64)]); @@ -244,6 +252,7 @@ fn tick_asset_cancel_unknown_handle_returns_status_not_crash() { #[test] fn tick_asset_cancel_invalid_transition_returns_status_not_crash() { let mut runtime = VirtualMachineRuntime::new(None); + let mut log_service = LogService::new(4096); let mut vm = VirtualMachine::default(); let mut hardware = Hardware::new(); let signals = InputSignals::default(); @@ -268,7 +277,7 @@ fn tick_asset_cancel_invalid_transition_returns_status_not_crash() { ); let handle = hardware.assets.load(7, 0).expect("asset handle must be allocated"); - runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize"); + runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize"); loop { match hardware.assets.status(handle) { @@ -284,7 +293,7 @@ fn tick_asset_cancel_invalid_transition_returns_status_not_crash() { hardware.assets.apply_commits(); assert_eq!(hardware.assets.status(handle), LoadStatus::COMMITTED); - let report = runtime.tick(&mut vm, &signals, &mut hardware); + let report = runtime.tick(&mut log_service, &mut vm, &signals, &mut hardware); assert!(report.is_none(), "cancel after commit must not crash"); assert!(vm.is_halted()); assert_eq!(vm.operand_stack_top(1), vec![Value::Int64(AssetOpStatus::InvalidState as i64)]); @@ -293,6 +302,7 @@ fn tick_asset_cancel_invalid_transition_returns_status_not_crash() { #[test] fn tick_status_first_surface_smoke_across_composer_audio_and_asset() { let mut runtime = VirtualMachineRuntime::new(None); + let mut log_service = LogService::new(4096); let mut vm = VirtualMachine::default(); let mut hardware = Hardware::new(); let signals = InputSignals::default(); @@ -331,8 +341,8 @@ fn tick_status_first_surface_smoke_across_composer_audio_and_asset() { ); let cartridge = cartridge_with_program(program, caps::GFX | caps::AUDIO | caps::ASSET); - runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize"); - let report = runtime.tick(&mut vm, &signals, &mut hardware); + runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize"); + let report = runtime.tick(&mut log_service, &mut vm, &signals, &mut hardware); assert!(report.is_none(), "mixed status-first surface must not crash"); assert!(vm.is_halted()); assert_eq!( diff --git a/crates/console/prometeu-system/src/services/vm_runtime/tests_fs_memcard.rs b/crates/console/prometeu-system/src/services/vm_runtime/tests_fs_memcard.rs index bfd30f5a..3f48e915 100644 --- a/crates/console/prometeu-system/src/services/vm_runtime/tests_fs_memcard.rs +++ b/crates/console/prometeu-system/src/services/vm_runtime/tests_fs_memcard.rs @@ -3,7 +3,8 @@ use super::*; #[test] fn tick_memcard_slot_roundtrip_for_game_profile() { let mut runtime = VirtualMachineRuntime::new(None); - runtime.mount_fs(Box::new(MemFsBackend::default())); + let mut log_service = LogService::new(4096); + runtime.mount_fs(&mut log_service, Box::new(MemFsBackend::default())); let mut vm = VirtualMachine::default(); let mut hardware = Hardware::new(); let signals = InputSignals::default(); @@ -50,8 +51,8 @@ fn tick_memcard_slot_roundtrip_for_game_profile() { preload: vec![], }; - runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize"); - let report = runtime.tick(&mut vm, &signals, &mut hardware); + runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize"); + let report = runtime.tick(&mut log_service, &mut vm, &signals, &mut hardware); assert!(report.is_none(), "memcard roundtrip must not crash"); assert!(vm.is_halted()); assert_eq!( @@ -63,6 +64,7 @@ fn tick_memcard_slot_roundtrip_for_game_profile() { #[test] fn tick_memcard_access_is_denied_for_non_game_profile() { let mut runtime = VirtualMachineRuntime::new(None); + let mut log_service = LogService::new(4096); let mut vm = VirtualMachine::default(); let mut hardware = Hardware::new(); let signals = InputSignals::default(); @@ -89,8 +91,8 @@ fn tick_memcard_access_is_denied_for_non_game_profile() { preload: vec![], }; - runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize"); - let report = runtime.tick(&mut vm, &signals, &mut hardware); + runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize"); + let report = runtime.tick(&mut log_service, &mut vm, &signals, &mut hardware); assert!(report.is_none(), "non-game memcard call must return status"); assert!(vm.is_halted()); assert_eq!(vm.operand_stack_top(2), vec![Value::Int64(0), Value::Int64(4)]); diff --git a/crates/console/prometeu-system/src/services/vm_runtime/tick.rs b/crates/console/prometeu-system/src/services/vm_runtime/tick.rs index 38c237f3..b5196d1d 100644 --- a/crates/console/prometeu-system/src/services/vm_runtime/tick.rs +++ b/crates/console/prometeu-system/src/services/vm_runtime/tick.rs @@ -1,7 +1,8 @@ +use super::dispatch::VmRuntimeHost; use super::*; use crate::CrashReport; use prometeu_hal::asset::{BankTelemetry, BankType}; -use prometeu_hal::log::{LogLevel, LogSource}; +use prometeu_hal::log::{LogLevel, LogService, LogSource}; use prometeu_hal::{HardwareBridge, HostContext, InputSignals}; use prometeu_vm::LogicalFrameEndingReason; use std::panic::{AssertUnwindSafe, catch_unwind}; @@ -52,11 +53,17 @@ impl VirtualMachineRuntime { pub fn debug_step_instruction( &mut self, + log_service: &mut LogService, vm: &mut VirtualMachine, hw: &mut dyn HardwareBridge, ) -> Option { - let mut ctx = HostContext::new(Some(hw)); - match vm.step(self, &mut ctx) { + let step_result = { + let mut ctx = HostContext::new(Some(hw)); + let mut host = VmRuntimeHost { runtime: self, log_service }; + vm.step(&mut host, &mut ctx) + }; + + match step_result { Ok(_) => None, Err(e) => { let report = match e { @@ -70,6 +77,7 @@ impl VirtualMachineRuntime { }, }; self.log( + log_service, LogLevel::Error, LogSource::Vm, report.log_tag(), @@ -83,6 +91,7 @@ impl VirtualMachineRuntime { pub fn tick( &mut self, + log_service: &mut LogService, vm: &mut VirtualMachine, signals: &InputSignals, hw: &mut dyn HardwareBridge, @@ -94,7 +103,7 @@ impl VirtualMachineRuntime { return None; } - self.update_fs(); + self.update_fs(log_service); if !self.logical_frame_active { self.logical_frame_active = true; @@ -128,7 +137,8 @@ impl VirtualMachineRuntime { if budget > 0 { let run_result = { let mut ctx = HostContext::new(Some(hw)); - vm.run_budget(budget, self, &mut ctx) + let mut host = VmRuntimeHost { runtime: self, log_service }; + vm.run_budget(budget, &mut host, &mut ctx) }; match run_result { @@ -143,6 +153,7 @@ impl VirtualMachineRuntime { self.paused = true; self.debug_step_request = false; self.log( + log_service, LogLevel::Info, LogSource::Vm, 0xDEB1, @@ -154,6 +165,7 @@ impl VirtualMachineRuntime { let report = CrashReport::VmPanic { message: err, pc: Some(vm.pc() as u32) }; self.log( + log_service, LogLevel::Error, LogSource::Vm, report.log_tag(), @@ -166,6 +178,7 @@ impl VirtualMachineRuntime { if let LogicalFrameEndingReason::Trap(trap) = &run.reason { let report = CrashReport::VmTrap { trap: trap.clone() }; self.log( + log_service, LogLevel::Error, LogSource::Vm, report.log_tag(), @@ -182,6 +195,7 @@ impl VirtualMachineRuntime { let message = Self::host_panic_payload_to_string(payload); let report = CrashReport::VmPanic { message, pc: Some(vm.pc() as u32) }; self.log( + log_service, LogLevel::Error, LogSource::Vm, report.log_tag(), @@ -229,18 +243,15 @@ impl VirtualMachineRuntime { let ts_ms = self.boot_time.elapsed().as_millis() as u64; let telemetry_snapshot = self.atomic_telemetry.snapshot(); - let violations = self.certifier.evaluate( - &telemetry_snapshot, - &mut self.log_service, - ts_ms, - ) as u32; + let violations = + self.certifier.evaluate(&telemetry_snapshot, log_service, ts_ms) as u32; self.atomic_telemetry.violations.store(violations, Ordering::Relaxed); self.atomic_telemetry .completed_logical_frames .fetch_add(1, Ordering::Relaxed); - self.log_service.reset_count(); + log_service.reset_count(); self.logical_frame_index += 1; self.logical_frame_active = false; @@ -258,7 +269,13 @@ impl VirtualMachineRuntime { } Err(e) => { let report = CrashReport::VmPanic { message: e, pc: Some(vm.pc() as u32) }; - self.log(LogLevel::Error, LogSource::Vm, report.log_tag(), report.summary()); + self.log( + log_service, + LogLevel::Error, + LogSource::Vm, + report.log_tag(), + report.summary(), + ); self.last_crash_report = Some(report.clone()); return Some(report); } diff --git a/discussion/index.ndjson b/discussion/index.ndjson index 18547d91..e6b02bf5 100644 --- a/discussion/index.ndjson +++ b/discussion/index.ndjson @@ -31,4 +31,4 @@ {"type":"discussion","id":"DSC-0030","status":"done","ticket":"internal-viewport-270p","title":"Agenda - Internal Viewport 270p (480x270)","created_at":"2026-04-27","updated_at":"2026-04-28","tags":["gfx","runtime","viewport","resolution","frame-composer","host"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0039","file":"discussion/lessons/DSC-0030-internal-viewport-270p/LSN-0039-resolution-baseline-changes-are-runtime-wide-contracts.md","status":"done","created_at":"2026-04-28","updated_at":"2026-04-28"}]} {"type":"discussion","id":"DSC-0031","status":"done","ticket":"runtime-mode-separation-game-system","title":"Agenda - Runtime Mode Separation: Game and System","created_at":"2026-05-11","updated_at":"2026-05-14","tags":["runtime","firmware","hub","system-apps","game-mode","scheduler"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0040","file":"discussion/lessons/DSC-0031-runtime-mode-separation-game-system/LSN-0040-system-pipeline-separation.md","status":"done","created_at":"2026-05-14","updated_at":"2026-05-14"}]} {"type":"discussion","id":"DSC-0032","status":"open","ticket":"system-os-lifecycle-process-task-contract","title":"Agenda - SystemOS Lifecycle, Process and Task Contract","created_at":"2026-05-14","updated_at":"2026-05-14","tags":["runtime","os","lifecycle","process","task","shell","firmware"],"agendas":[{"id":"AGD-0032","file":"AGD-0032-system-os-lifecycle-process-task-contract.md","status":"open","created_at":"2026-05-14","updated_at":"2026-05-14"}],"decisions":[],"plans":[],"lessons":[]} -{"type":"discussion","id":"DSC-0033","status":"in_progress","ticket":"system-os-service-ownership-and-module-layout","title":"Agenda - SystemOS Service Ownership and Module Layout","created_at":"2026-05-14","updated_at":"2026-05-14","tags":["runtime","os","services","module-layout","vm","window-manager","logging"],"agendas":[{"id":"AGD-0033","file":"AGD-0033-system-os-service-ownership-and-module-layout.md","status":"accepted","created_at":"2026-05-14","updated_at":"2026-05-14"}],"decisions":[{"id":"DEC-0024","file":"DEC-0024-systemos-service-ownership-and-module-layout.md","status":"accepted","created_at":"2026-05-14","updated_at":"2026-05-14","ref_agenda":"AGD-0033"}],"plans":[{"id":"PLN-0051","file":"PLN-0051-move-vm-runtime-into-services.md","status":"done","created_at":"2026-05-14","updated_at":"2026-05-14","ref_decisions":["DEC-0024"]},{"id":"PLN-0052","file":"PLN-0052-promote-window-manager-to-systemos-service.md","status":"done","created_at":"2026-05-14","updated_at":"2026-05-14","ref_decisions":["DEC-0024"]},{"id":"PLN-0053","file":"PLN-0053-move-logging-ownership-to-systemos.md","status":"open","created_at":"2026-05-14","updated_at":"2026-05-14","ref_decisions":["DEC-0024"]},{"id":"PLN-0054","file":"PLN-0054-move-fs-and-memcard-ownership-to-systemos.md","status":"open","created_at":"2026-05-14","updated_at":"2026-05-14","ref_decisions":["DEC-0024"]}],"lessons":[]} +{"type":"discussion","id":"DSC-0033","status":"in_progress","ticket":"system-os-service-ownership-and-module-layout","title":"Agenda - SystemOS Service Ownership and Module Layout","created_at":"2026-05-14","updated_at":"2026-05-14","tags":["runtime","os","services","module-layout","vm","window-manager","logging"],"agendas":[{"id":"AGD-0033","file":"AGD-0033-system-os-service-ownership-and-module-layout.md","status":"accepted","created_at":"2026-05-14","updated_at":"2026-05-14"}],"decisions":[{"id":"DEC-0024","file":"DEC-0024-systemos-service-ownership-and-module-layout.md","status":"accepted","created_at":"2026-05-14","updated_at":"2026-05-14","ref_agenda":"AGD-0033"}],"plans":[{"id":"PLN-0051","file":"PLN-0051-move-vm-runtime-into-services.md","status":"done","created_at":"2026-05-14","updated_at":"2026-05-14","ref_decisions":["DEC-0024"]},{"id":"PLN-0052","file":"PLN-0052-promote-window-manager-to-systemos-service.md","status":"done","created_at":"2026-05-14","updated_at":"2026-05-14","ref_decisions":["DEC-0024"]},{"id":"PLN-0053","file":"PLN-0053-move-logging-ownership-to-systemos.md","status":"done","created_at":"2026-05-14","updated_at":"2026-05-14","ref_decisions":["DEC-0024"]},{"id":"PLN-0054","file":"PLN-0054-move-fs-and-memcard-ownership-to-systemos.md","status":"open","created_at":"2026-05-14","updated_at":"2026-05-14","ref_decisions":["DEC-0024"]}],"lessons":[]} diff --git a/discussion/workflow/plans/PLN-0053-move-logging-ownership-to-systemos.md b/discussion/workflow/plans/PLN-0053-move-logging-ownership-to-systemos.md index 31617743..b5bea105 100644 --- a/discussion/workflow/plans/PLN-0053-move-logging-ownership-to-systemos.md +++ b/discussion/workflow/plans/PLN-0053-move-logging-ownership-to-systemos.md @@ -2,7 +2,7 @@ id: PLN-0053 ticket: system-os-service-ownership-and-module-layout title: Move Logging Ownership To SystemOS -status: open +status: done created: 2026-05-14 ref_decisions: [DEC-0024] tags: [runtime, os, services, module-layout, vm, window-manager, logging]