diff --git a/crates/console/prometeu-system/src/os/system_os.rs b/crates/console/prometeu-system/src/os/system_os.rs index d087c164..8f9fecee 100644 --- a/crates/console/prometeu-system/src/os/system_os.rs +++ b/crates/console/prometeu-system/src/os/system_os.rs @@ -1,6 +1,8 @@ use crate::CrashReport; use crate::VirtualMachineRuntime; +use crate::fs::{FsBackend, FsState, VirtualFS}; use crate::process::ProcessManager; +use crate::services::memcard::MemcardService; use crate::services::window_manager::WindowManager; use crate::task::TaskId; use crate::task::TaskManager; @@ -9,6 +11,7 @@ use prometeu_hal::log::{LogLevel, LogService, LogSource}; use prometeu_hal::telemetry::CertificationConfig; use prometeu_hal::{HardwareBridge, InputSignals}; use prometeu_vm::VirtualMachine; +use std::collections::HashMap; use std::sync::Arc; pub struct SystemOS { @@ -17,6 +20,11 @@ pub struct SystemOS { pub task_manager: TaskManager, pub window_manager: WindowManager, pub log_service: LogService, + pub fs: VirtualFS, + pub fs_state: FsState, + pub memcard: MemcardService, + pub open_files: HashMap, + pub next_handle: u32, } impl SystemOS { @@ -31,6 +39,11 @@ impl SystemOS { task_manager: TaskManager::new(), window_manager: WindowManager::new(), log_service, + fs: VirtualFS::new(), + fs_state: FsState::Unmounted, + memcard: MemcardService::new(), + open_files: HashMap::new(), + next_handle: 1, }; os.log(LogLevel::Info, LogSource::Pos, 0, "PrometeuOS starting...".to_string()); @@ -47,6 +60,7 @@ impl SystemOS { vm: &mut VirtualMachine, cartridge: &Cartridge, ) -> Result<(), CrashReport> { + self.clear_cartridge_service_state(); self.vm_runtime.initialize_vm(&mut self.log_service, vm, cartridge) } @@ -56,7 +70,32 @@ impl SystemOS { signals: &InputSignals, hw: &mut dyn HardwareBridge, ) -> Option { - self.vm_runtime.tick(&mut self.log_service, vm, signals, hw) + self.vm_runtime.tick( + &mut self.log_service, + &mut self.fs, + &mut self.fs_state, + &mut self.memcard, + &mut self.open_files, + &mut self.next_handle, + vm, + signals, + hw, + ) + } + + pub fn mount_fs(&mut self, backend: Box) { + self.vm_runtime.mount_fs(&mut self.log_service, &mut self.fs, &mut self.fs_state, backend); + } + + pub fn unmount_fs(&mut self) { + self.fs.unmount(); + self.fs_state = FsState::Unmounted; + } + + fn clear_cartridge_service_state(&mut self) { + self.open_files.clear(); + self.next_handle = 1; + self.memcard.clear_all_staging(); } pub fn create_vm_game_task(&mut self, app_id: u32, title: impl Into) -> TaskId { 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 4dd420e9..0556241e 100644 --- a/crates/console/prometeu-system/src/services/vm_runtime/dispatch.rs +++ b/crates/console/prometeu-system/src/services/vm_runtime/dispatch.rs @@ -1,5 +1,6 @@ use super::*; -use crate::services::memcard::{MemcardSlotState, MemcardStatus}; +use crate::fs::{FsState, VirtualFS}; +use crate::services::memcard::{MemcardService, MemcardSlotState, MemcardStatus}; use prometeu_bytecode::{TRAP_INVALID_SYSCALL, TRAP_OOB, TRAP_TYPE, Value}; use prometeu_hal::asset::{AssetId, AssetOpStatus, BankType, SlotRef}; use prometeu_hal::cartridge::AppMode; @@ -13,6 +14,7 @@ use prometeu_hal::{ AudioOpStatus, ComposerOpStatus, HostContext, HostReturn, NativeInterface, SyscallId, expect_bool, expect_int, }; +use std::collections::HashMap; use std::ops::{Deref, DerefMut}; use std::panic::{AssertUnwindSafe, catch_unwind}; use std::sync::atomic::Ordering; @@ -20,6 +22,11 @@ use std::sync::atomic::Ordering; pub(crate) struct VmRuntimeHost<'a> { pub runtime: &'a mut VirtualMachineRuntime, pub log_service: &'a mut LogService, + pub fs: &'a mut VirtualFS, + pub fs_state: &'a mut FsState, + pub memcard: &'a mut MemcardService, + pub open_files: &'a mut HashMap, + pub next_handle: &'a mut u32, } impl Deref for VmRuntimeHost<'_> { @@ -419,13 +426,13 @@ impl NativeInterface for VmRuntimeHost<'_> { } Syscall::FsOpen => { let path = expect_string(args, 0, "path")?; - if self.fs_state != FsState::Mounted { + if *self.fs_state != FsState::Mounted { ret.push_int(-1); return Ok(()); } - let handle = self.next_handle; + let handle = *self.next_handle; self.open_files.insert(handle, path); - self.next_handle += 1; + *self.next_handle += 1; ret.push_int(handle as i64); Ok(()) } @@ -542,14 +549,8 @@ impl NativeInterface for VmRuntimeHost<'_> { return Ok(()); } let payload = hex_decode(&payload_hex)?; - let runtime = &mut self.runtime; - let write = runtime.memcard.slot_write( - &runtime.fs, - runtime.current_app_id, - slot, - offset, - &payload, - ); + let app_id = self.current_app_id; + let write = self.memcard.slot_write(&self.fs, app_id, slot, offset, &payload); ret.push_int(write.status as i64); ret.push_int(write.bytes_written as i64); Ok(()) @@ -561,10 +562,10 @@ impl NativeInterface for VmRuntimeHost<'_> { return Ok(()); } let status = { - let runtime = &mut self.runtime; - let memcard = &mut runtime.memcard; - let fs = &mut runtime.fs; - memcard.slot_commit(fs, runtime.current_app_id, slot) + let app_id = self.current_app_id; + let memcard = &mut self.memcard; + let fs = &mut self.fs; + memcard.slot_commit(fs, app_id, slot) }; ret.push_int(status as i64); Ok(()) @@ -576,10 +577,10 @@ impl NativeInterface for VmRuntimeHost<'_> { return Ok(()); } let status = { - let runtime = &mut self.runtime; - let memcard = &mut runtime.memcard; - let fs = &mut runtime.fs; - memcard.slot_clear(fs, runtime.current_app_id, slot) + let app_id = self.current_app_id; + let memcard = &mut self.memcard; + let fs = &mut self.fs; + memcard.slot_clear(fs, 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 002395d8..f74810f8 100644 --- a/crates/console/prometeu-system/src/services/vm_runtime/lifecycle.rs +++ b/crates/console/prometeu-system/src/services/vm_runtime/lifecycle.rs @@ -1,8 +1,9 @@ use super::*; use crate::CrashReport; -use crate::fs::FsBackend; +use crate::fs::{FsBackend, FsState, VirtualFS}; use prometeu_hal::cartridge::Cartridge; use prometeu_hal::log::{LogLevel, LogSource}; +use std::collections::HashMap; impl VirtualMachineRuntime { pub fn new_with_log_counter( @@ -17,11 +18,6 @@ impl VirtualMachineRuntime { logical_frame_active: false, logical_frame_remaining_cycles: 0, last_frame_cpu_time_us: 0, - fs: VirtualFS::new(), - fs_state: FsState::Unmounted, - memcard: MemcardService::new(), - open_files: HashMap::new(), - next_handle: 1, current_app_id: 0, current_cartridge_title: String::new(), current_cartridge_app_version: String::new(), @@ -53,7 +49,13 @@ impl VirtualMachineRuntime { log_service.log(ts_ms, frame, level, source, tag, msg); } - pub fn mount_fs(&mut self, log_service: &mut LogService, backend: Box) { + pub fn mount_fs( + &mut self, + log_service: &mut LogService, + fs: &mut VirtualFS, + fs_state: &mut FsState, + backend: Box, + ) { self.log( log_service, LogLevel::Info, @@ -61,9 +63,9 @@ impl VirtualMachineRuntime { 0, "Attempting to mount filesystem".to_string(), ); - match self.fs.mount(backend) { + match fs.mount(backend) { Ok(_) => { - self.fs_state = FsState::Mounted; + *fs_state = FsState::Mounted; self.log( log_service, LogLevel::Info, @@ -75,18 +77,23 @@ impl VirtualMachineRuntime { Err(e) => { let err_msg = format!("Failed to mount filesystem: {:?}", e); self.log(log_service, LogLevel::Error, LogSource::Fs, 0, err_msg); - self.fs_state = FsState::Error(e); + *fs_state = FsState::Error(e); } } } - pub fn unmount_fs(&mut self) { - self.fs.unmount(); - self.fs_state = FsState::Unmounted; + pub fn unmount_fs(&mut self, fs: &mut VirtualFS, fs_state: &mut FsState) { + fs.unmount(); + *fs_state = FsState::Unmounted; } - pub(crate) fn update_fs(&mut self, log_service: &mut LogService) { - if self.fs_state == FsState::Mounted && !self.fs.is_healthy() { + pub(crate) fn update_fs( + &mut self, + log_service: &mut LogService, + fs: &mut VirtualFS, + fs_state: &mut FsState, + ) { + if *fs_state == FsState::Mounted && !fs.is_healthy() { self.log( log_service, LogLevel::Error, @@ -94,7 +101,7 @@ impl VirtualMachineRuntime { 0, "Filesystem became unhealthy, unmounting".to_string(), ); - self.unmount_fs(); + self.unmount_fs(fs, fs_state); } } @@ -105,10 +112,6 @@ impl VirtualMachineRuntime { self.last_frame_cpu_time_us = 0; self.atomic_telemetry.reset(); - self.open_files.clear(); - self.next_handle = 1; - self.memcard.clear_all_staging(); - self.current_app_id = 0; self.current_cartridge_title.clear(); self.current_cartridge_app_version.clear(); 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 b76b7560..8ecf6a5b 100644 --- a/crates/console/prometeu-system/src/services/vm_runtime/mod.rs +++ b/crates/console/prometeu-system/src/services/vm_runtime/mod.rs @@ -5,8 +5,6 @@ mod tests; mod tick; use crate::CrashReport; -use crate::fs::{FsState, VirtualFS}; -use crate::services::memcard::MemcardService; use prometeu_bytecode::string_materialization_count; use prometeu_hal::cartridge::AppMode; use prometeu_hal::log::LogService; @@ -23,11 +21,6 @@ pub struct VirtualMachineRuntime { pub logical_frame_active: bool, pub logical_frame_remaining_cycles: u64, pub last_frame_cpu_time_us: u64, - pub fs: VirtualFS, - pub fs_state: FsState, - pub memcard: MemcardService, - pub open_files: HashMap, - pub next_handle: u32, pub current_app_id: u32, pub current_cartridge_title: String, pub current_cartridge_app_version: String, 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 578a097a..dae048e7 100644 --- a/crates/console/prometeu-system/src/services/vm_runtime/tests.rs +++ b/crates/console/prometeu-system/src/services/vm_runtime/tests.rs @@ -1,5 +1,6 @@ use super::*; -use crate::fs::{FsBackend, FsEntry, FsError}; +use crate::fs::{FsBackend, FsEntry, FsError, FsState, VirtualFS}; +use crate::services::memcard::MemcardService; use prometeu_bytecode::TRAP_TYPE; use prometeu_bytecode::Value; use prometeu_bytecode::assembler::assemble; @@ -189,6 +190,11 @@ mod fs_memcard; fn initialize_vm_applies_cartridge_capabilities_before_loader_resolution() { let mut runtime = VirtualMachineRuntime::new(None); let mut log_service = LogService::new(4096); + let mut fs = VirtualFS::new(); + let mut fs_state = FsState::Unmounted; + let mut memcard = MemcardService::new(); + let mut open_files: HashMap = HashMap::new(); + let mut next_handle = 1; let mut vm = VirtualMachine::default(); let code = assemble("PUSH_I32 0\nHOSTCALL 0\nHALT").expect("assemble"); let program = serialized_single_function_module( @@ -215,6 +221,11 @@ fn initialize_vm_applies_cartridge_capabilities_before_loader_resolution() { fn initialize_vm_succeeds_when_cartridge_capabilities_cover_hostcalls() { let mut runtime = VirtualMachineRuntime::new(None); let mut log_service = LogService::new(4096); + let mut fs = VirtualFS::new(); + let mut fs_state = FsState::Unmounted; + let mut memcard = MemcardService::new(); + let mut open_files: HashMap = HashMap::new(); + let mut next_handle = 1; let mut vm = VirtualMachine::default(); let code = assemble("PUSH_I32 0\nHOSTCALL 0\nHALT").expect("assemble"); let program = serialized_single_function_module( @@ -240,6 +251,11 @@ fn initialize_vm_succeeds_when_cartridge_capabilities_cover_hostcalls() { 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 fs = VirtualFS::new(); + let mut fs_state = FsState::Unmounted; + let mut memcard = MemcardService::new(); + let mut open_files: HashMap = HashMap::new(); + let mut next_handle = 1; let mut vm = VirtualMachine::default(); let mut hardware = Hardware::new(); let signals = InputSignals::default(); @@ -259,7 +275,17 @@ fn tick_returns_error_when_vm_ends_slice_with_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) + .tick( + &mut log_service, + &mut fs, + &mut fs_state, + &mut memcard, + &mut open_files, + &mut next_handle, + &mut vm, + &signals, + &mut hardware, + ) .expect("trap must surface as runtime error"); match report { @@ -276,6 +302,11 @@ fn tick_returns_error_when_vm_ends_slice_with_trap() { fn tick_system_profile_rejects_gfx_game_surface() { let mut runtime = VirtualMachineRuntime::new(None); let mut log_service = LogService::new(4096); + let mut fs = VirtualFS::new(); + let mut fs_state = FsState::Unmounted; + let mut memcard = MemcardService::new(); + let mut open_files: HashMap = HashMap::new(); + let mut next_handle = 1; let mut vm = VirtualMachine::default(); let mut hardware = Hardware::new(); let signals = InputSignals::default(); @@ -294,7 +325,17 @@ fn tick_system_profile_rejects_gfx_game_surface() { 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) + .tick( + &mut log_service, + &mut fs, + &mut fs_state, + &mut memcard, + &mut open_files, + &mut next_handle, + &mut vm, + &signals, + &mut hardware, + ) .expect("system gfx call must trap"); match report { @@ -310,6 +351,11 @@ fn tick_system_profile_rejects_gfx_game_surface() { fn tick_system_profile_rejects_composer_game_surface() { let mut runtime = VirtualMachineRuntime::new(None); let mut log_service = LogService::new(4096); + let mut fs = VirtualFS::new(); + let mut fs_state = FsState::Unmounted; + let mut memcard = MemcardService::new(); + let mut open_files: HashMap = HashMap::new(); + let mut next_handle = 1; let mut vm = VirtualMachine::default(); let mut hardware = Hardware::new(); let signals = InputSignals::default(); @@ -328,7 +374,17 @@ fn tick_system_profile_rejects_composer_game_surface() { 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) + .tick( + &mut log_service, + &mut fs, + &mut fs_state, + &mut memcard, + &mut open_files, + &mut next_handle, + &mut vm, + &signals, + &mut hardware, + ) .expect("system composer call must trap"); match report { @@ -346,6 +402,11 @@ fn tick_system_profile_rejects_composer_game_surface() { fn tick_system_profile_rejects_bank_game_surface() { let mut runtime = VirtualMachineRuntime::new(None); let mut log_service = LogService::new(4096); + let mut fs = VirtualFS::new(); + let mut fs_state = FsState::Unmounted; + let mut memcard = MemcardService::new(); + let mut open_files: HashMap = HashMap::new(); + let mut next_handle = 1; let mut vm = VirtualMachine::default(); let mut hardware = Hardware::new(); let signals = InputSignals::default(); @@ -364,7 +425,17 @@ fn tick_system_profile_rejects_bank_game_surface() { 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) + .tick( + &mut log_service, + &mut fs, + &mut fs_state, + &mut memcard, + &mut open_files, + &mut next_handle, + &mut vm, + &signals, + &mut hardware, + ) .expect("system bank call must trap"); match report { @@ -380,6 +451,11 @@ fn tick_system_profile_rejects_bank_game_surface() { fn tick_system_profile_can_use_shared_log_transport() { let mut runtime = VirtualMachineRuntime::new(None); let mut log_service = LogService::new(4096); + let mut fs = VirtualFS::new(); + let mut fs_state = FsState::Unmounted; + let mut memcard = MemcardService::new(); + let mut open_files: HashMap = HashMap::new(); + let mut next_handle = 1; let mut vm = VirtualMachine::default(); let mut hardware = Hardware::new(); let signals = InputSignals::default(); @@ -398,7 +474,17 @@ 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 log_service, &mut vm, &cartridge).expect("runtime must initialize"); - let report = runtime.tick(&mut log_service, &mut vm, &signals, &mut hardware); + let report = runtime.tick( + &mut log_service, + &mut fs, + &mut fs_state, + &mut memcard, + &mut open_files, + &mut next_handle, + &mut vm, + &signals, + &mut hardware, + ); assert!(report.is_none(), "system log transport must remain internally shared"); assert!(vm.is_halted()); @@ -408,12 +494,27 @@ fn tick_system_profile_can_use_shared_log_transport() { fn tick_returns_panic_report_distinct_from_trap() { let mut runtime = VirtualMachineRuntime::new(None); let mut log_service = LogService::new(4096); + let mut fs = VirtualFS::new(); + let mut fs_state = FsState::Unmounted; + let mut memcard = MemcardService::new(); + let mut open_files: HashMap = HashMap::new(); + let mut next_handle = 1; 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 log_service, &mut vm, &signals, &mut hardware) + .tick( + &mut log_service, + &mut fs, + &mut fs_state, + &mut memcard, + &mut open_files, + &mut next_handle, + &mut vm, + &signals, + &mut hardware, + ) .expect("panic must surface as runtime error"); match report { @@ -430,6 +531,11 @@ fn tick_returns_panic_report_distinct_from_trap() { 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 fs = VirtualFS::new(); + let mut fs_state = FsState::Unmounted; + let mut memcard = MemcardService::new(); + let mut open_files: HashMap = HashMap::new(); + let mut next_handle = 1; let mut vm = VirtualMachine::default(); let signals = InputSignals::default(); let program = @@ -447,7 +553,17 @@ fn tick_renders_bound_eight_pixel_scene_through_frame_composer_path() { assert!(hardware.frame_composer.bind_scene(0)); 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); + let report = runtime.tick( + &mut log_service, + &mut fs, + &mut fs_state, + &mut memcard, + &mut open_files, + &mut next_handle, + &mut vm, + &signals, + &mut hardware, + ); assert!(report.is_none(), "frame render path must not crash"); hardware.gfx.present(); @@ -458,6 +574,11 @@ fn tick_renders_bound_eight_pixel_scene_through_frame_composer_path() { fn tick_renders_scene_through_public_composer_syscalls() { let mut runtime = VirtualMachineRuntime::new(None); let mut log_service = LogService::new(4096); + let mut fs = VirtualFS::new(); + let mut fs_state = FsState::Unmounted; + let mut memcard = MemcardService::new(); + let mut open_files: HashMap = HashMap::new(); + let mut next_handle = 1; let mut vm = VirtualMachine::default(); let signals = InputSignals::default(); let code = assemble( @@ -507,7 +628,17 @@ fn tick_renders_scene_through_public_composer_syscalls() { hardware.gfx.hud_fade_level = 31; 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); + let report = runtime.tick( + &mut log_service, + &mut fs, + &mut fs_state, + &mut memcard, + &mut open_files, + &mut next_handle, + &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)]); @@ -519,6 +650,11 @@ fn tick_renders_scene_through_public_composer_syscalls() { fn tick_draw_text_survives_scene_backed_frame_composition() { let mut runtime = VirtualMachineRuntime::new(None); let mut log_service = LogService::new(4096); + let mut fs = VirtualFS::new(); + let mut fs_state = FsState::Unmounted; + let mut memcard = MemcardService::new(); + let mut open_files: HashMap = HashMap::new(); + let mut next_handle = 1; let mut vm = VirtualMachine::default(); let signals = InputSignals::default(); let code = assemble( @@ -569,7 +705,17 @@ fn tick_draw_text_survives_scene_backed_frame_composition() { hardware.gfx.hud_fade_level = 31; 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); + let report = runtime.tick( + &mut log_service, + &mut fs, + &mut fs_state, + &mut memcard, + &mut open_files, + &mut next_handle, + &mut vm, + &signals, + &mut hardware, + ); assert!(report.is_none(), "scene-backed overlay text must not crash"); hardware.gfx.present(); @@ -580,6 +726,11 @@ fn tick_draw_text_survives_scene_backed_frame_composition() { fn tick_draw_text_survives_no_scene_frame_path() { let mut runtime = VirtualMachineRuntime::new(None); let mut log_service = LogService::new(4096); + let mut fs = VirtualFS::new(); + let mut fs_state = FsState::Unmounted; + let mut memcard = MemcardService::new(); + let mut open_files: HashMap = HashMap::new(); + let mut next_handle = 1; let mut vm = VirtualMachine::default(); let signals = InputSignals::default(); let code = assemble( @@ -603,7 +754,17 @@ fn tick_draw_text_survives_no_scene_frame_path() { hardware.gfx.hud_fade_level = 31; 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); + let report = runtime.tick( + &mut log_service, + &mut fs, + &mut fs_state, + &mut memcard, + &mut open_files, + &mut next_handle, + &mut vm, + &signals, + &mut hardware, + ); assert!(report.is_none(), "no-scene overlay text must not crash"); hardware.gfx.present(); @@ -614,6 +775,11 @@ fn tick_draw_text_survives_no_scene_frame_path() { fn initialize_vm_success_clears_previous_crash_report() { let mut runtime = VirtualMachineRuntime::new(None); let mut log_service = LogService::new(4096); + let mut fs = VirtualFS::new(); + let mut fs_state = FsState::Unmounted; + let mut memcard = MemcardService::new(); + let mut open_files: HashMap = HashMap::new(); + let mut next_handle = 1; runtime.last_crash_report = Some(CrashReport::VmPanic { message: "stale".into(), pc: Some(1) }); let mut vm = VirtualMachine::default(); @@ -638,15 +804,16 @@ fn initialize_vm_success_clears_previous_crash_report() { fn reset_clears_cartridge_scoped_runtime_state() { let mut runtime = VirtualMachineRuntime::new(None); let mut vm = VirtualMachine::default(); + let mut fs_state = FsState::Mounted; + let mut open_files: HashMap = HashMap::new(); + let mut next_handle = 9; runtime.tick_index = 77; runtime.logical_frame_index = 12; runtime.logical_frame_active = true; runtime.logical_frame_remaining_cycles = 345; runtime.last_frame_cpu_time_us = 678; - runtime.fs_state = FsState::Mounted; - runtime.open_files.insert(7, "/save.dat".into()); - runtime.next_handle = 9; + open_files.insert(7, "/save.dat".into()); runtime.current_app_id = 42; runtime.current_cartridge_title = "Cart".into(); runtime.current_cartridge_app_version = "1.2.3".into(); @@ -668,13 +835,13 @@ fn reset_clears_cartridge_scoped_runtime_state() { runtime.reset(&mut vm); assert_eq!(runtime.tick_index, 77); - assert_eq!(runtime.fs_state, FsState::Mounted); + assert_eq!(fs_state, FsState::Mounted); assert_eq!(runtime.logical_frame_index, 0); assert!(!runtime.logical_frame_active); assert_eq!(runtime.logical_frame_remaining_cycles, 0); assert_eq!(runtime.last_frame_cpu_time_us, 0); - assert!(runtime.open_files.is_empty()); - assert_eq!(runtime.next_handle, 1); + assert_eq!(open_files.len(), 1); + assert_eq!(next_handle, 9); assert_eq!(runtime.current_app_id, 0); assert!(runtime.current_cartridge_title.is_empty()); assert!(runtime.current_cartridge_app_version.is_empty()); @@ -698,6 +865,11 @@ fn reset_clears_cartridge_scoped_runtime_state() { fn tick_numeric_happy_path_records_zero_internal_allocations() { let mut runtime = VirtualMachineRuntime::new(None); let mut log_service = LogService::new(4096); + let mut fs = VirtualFS::new(); + let mut fs_state = FsState::Unmounted; + let mut memcard = MemcardService::new(); + let mut open_files: HashMap = HashMap::new(); + let mut next_handle = 1; let mut vm = VirtualMachine::default(); let mut hardware = Hardware::new(); let signals = InputSignals::default(); @@ -707,7 +879,17 @@ fn tick_numeric_happy_path_records_zero_internal_allocations() { let cartridge = cartridge_with_program(program, caps::NONE); 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); + let report = runtime.tick( + &mut log_service, + &mut fs, + &mut fs_state, + &mut memcard, + &mut open_files, + &mut next_handle, + &mut vm, + &signals, + &mut hardware, + ); assert!(report.is_none()); let snapshot = runtime.atomic_telemetry.snapshot(); @@ -719,6 +901,11 @@ fn tick_numeric_happy_path_records_zero_internal_allocations() { 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 fs = VirtualFS::new(); + let mut fs_state = FsState::Unmounted; + let mut memcard = MemcardService::new(); + let mut open_files: HashMap = HashMap::new(); + let mut next_handle = 1; let mut vm = VirtualMachine::default(); let mut hardware = Hardware::new(); let signals = InputSignals::default(); @@ -732,7 +919,17 @@ fn tick_already_materialized_string_path_records_zero_internal_allocations() { let cartridge = cartridge_with_program(program, caps::NONE); 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); + let report = runtime.tick( + &mut log_service, + &mut fs, + &mut fs_state, + &mut memcard, + &mut open_files, + &mut next_handle, + &mut vm, + &signals, + &mut hardware, + ); assert!(report.is_none()); let snapshot = runtime.atomic_telemetry.snapshot(); @@ -744,6 +941,11 @@ fn tick_already_materialized_string_path_records_zero_internal_allocations() { fn initialize_vm_failure_clears_previous_identity_and_handles() { let mut runtime = VirtualMachineRuntime::new(None); let mut log_service = LogService::new(4096); + let mut fs = VirtualFS::new(); + let mut fs_state = FsState::Unmounted; + let mut memcard = MemcardService::new(); + let mut open_files: HashMap = HashMap::new(); + let mut next_handle = 1; let mut vm = VirtualMachine::default(); let good_program = serialized_single_function_module( @@ -761,8 +963,8 @@ fn initialize_vm_failure_clears_previous_identity_and_handles() { .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; + open_files.insert(5, "/save.dat".into()); + next_handle = 6; runtime.paused = true; runtime.debug_step_request = true; runtime.atomic_telemetry.cycles_used.store(123, Ordering::Relaxed); @@ -786,8 +988,8 @@ fn initialize_vm_failure_clears_previous_identity_and_handles() { assert!(runtime.current_cartridge_title.is_empty()); assert!(runtime.current_cartridge_app_version.is_empty()); assert_eq!(runtime.current_cartridge_app_mode, AppMode::Game); - assert!(runtime.open_files.is_empty()); - assert_eq!(runtime.next_handle, 1); + assert_eq!(open_files.len(), 1); + assert_eq!(next_handle, 6); assert!(!runtime.paused); assert!(!runtime.debug_step_request); assert_eq!(runtime.atomic_telemetry.cycles_used.load(Ordering::Relaxed), 0); @@ -798,6 +1000,11 @@ fn initialize_vm_failure_clears_previous_identity_and_handles() { 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 fs = VirtualFS::new(); + let mut fs_state = FsState::Unmounted; + let mut memcard = MemcardService::new(); + let mut open_files: HashMap = HashMap::new(); + let mut next_handle = 1; let mut vm = VirtualMachine::default(); let mut hardware = Hardware::new(); let signals = InputSignals::default(); @@ -815,7 +1022,17 @@ fn tick_composer_bind_scene_operational_error_returns_status_not_crash() { let cartridge = cartridge_with_program(program, caps::GFX); 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); + let report = runtime.tick( + &mut log_service, + &mut fs, + &mut fs_state, + &mut memcard, + &mut open_files, + &mut next_handle, + &mut vm, + &signals, + &mut hardware, + ); assert!(report.is_none(), "operational error must not crash"); assert!(vm.is_halted()); assert_eq!( @@ -828,6 +1045,11 @@ fn tick_composer_bind_scene_operational_error_returns_status_not_crash() { 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 fs = VirtualFS::new(); + let mut fs_state = FsState::Unmounted; + let mut memcard = MemcardService::new(); + let mut open_files: HashMap = HashMap::new(); + let mut next_handle = 1; let mut vm = VirtualMachine::default(); let mut hardware = Hardware::new(); let signals = InputSignals::default(); @@ -848,7 +1070,17 @@ fn tick_composer_emit_sprite_operational_error_returns_status_not_crash() { let cartridge = cartridge_with_program(program, caps::GFX); 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); + let report = runtime.tick( + &mut log_service, + &mut fs, + &mut fs_state, + &mut memcard, + &mut open_files, + &mut next_handle, + &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)]); @@ -858,6 +1090,11 @@ fn tick_composer_emit_sprite_operational_error_returns_status_not_crash() { 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 fs = VirtualFS::new(); + let mut fs_state = FsState::Unmounted; + let mut memcard = MemcardService::new(); + let mut open_files: HashMap = HashMap::new(); + let mut next_handle = 1; let mut vm = VirtualMachine::default(); let mut hardware = Hardware::new(); let signals = InputSignals::default(); @@ -878,7 +1115,17 @@ fn tick_composer_emit_sprite_invalid_layer_returns_status_not_crash() { let cartridge = cartridge_with_program(program, caps::GFX); 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); + let report = runtime.tick( + &mut log_service, + &mut fs, + &mut fs_state, + &mut memcard, + &mut open_files, + &mut next_handle, + &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)]); @@ -888,6 +1135,11 @@ fn tick_composer_emit_sprite_invalid_layer_returns_status_not_crash() { 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 fs = VirtualFS::new(); + let mut fs_state = FsState::Unmounted; + let mut memcard = MemcardService::new(); + let mut open_files: HashMap = HashMap::new(); + let mut next_handle = 1; let mut vm = VirtualMachine::default(); let mut hardware = Hardware::new(); let signals = InputSignals::default(); @@ -915,7 +1167,17 @@ fn tick_composer_emit_sprite_invalid_range_returns_status_not_crash() { ); 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); + let report = runtime.tick( + &mut log_service, + &mut fs, + &mut fs_state, + &mut memcard, + &mut open_files, + &mut next_handle, + &mut vm, + &signals, + &mut hardware, + ); assert!(report.is_none(), "invalid composer parameter range must not crash"); assert!(vm.is_halted()); assert_eq!( @@ -928,6 +1190,11 @@ fn tick_composer_emit_sprite_invalid_range_returns_status_not_crash() { 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 fs = VirtualFS::new(); + let mut fs_state = FsState::Unmounted; + let mut memcard = MemcardService::new(); + let mut open_files: HashMap = HashMap::new(); + let mut next_handle = 1; let mut vm = VirtualMachine::default(); let mut hardware = Hardware::new(); let signals = InputSignals::default(); @@ -948,7 +1215,17 @@ fn tick_audio_play_sample_operational_error_returns_status_not_crash() { let cartridge = cartridge_with_program(program, caps::AUDIO); 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); + let report = runtime.tick( + &mut log_service, + &mut fs, + &mut fs_state, + &mut memcard, + &mut open_files, + &mut next_handle, + &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)]); @@ -958,6 +1235,11 @@ fn tick_audio_play_sample_operational_error_returns_status_not_crash() { 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 fs = VirtualFS::new(); + let mut fs_state = FsState::Unmounted; + let mut memcard = MemcardService::new(); + let mut open_files: HashMap = HashMap::new(); + let mut next_handle = 1; let mut vm = VirtualMachine::default(); let mut hardware = Hardware::new(); let signals = InputSignals::default(); @@ -978,7 +1260,17 @@ fn tick_audio_play_voice_invalid_returns_status_not_crash() { let cartridge = cartridge_with_program(program, caps::AUDIO); 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); + let report = runtime.tick( + &mut log_service, + &mut fs, + &mut fs_state, + &mut memcard, + &mut open_files, + &mut next_handle, + &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)]); @@ -988,6 +1280,11 @@ fn tick_audio_play_voice_invalid_returns_status_not_crash() { 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 fs = VirtualFS::new(); + let mut fs_state = FsState::Unmounted; + let mut memcard = MemcardService::new(); + let mut open_files: HashMap = HashMap::new(); + let mut next_handle = 1; let mut vm = VirtualMachine::default(); let mut hardware = Hardware::new(); let signals = InputSignals::default(); @@ -1008,7 +1305,17 @@ fn tick_audio_play_missing_asset_returns_status_not_crash() { let cartridge = cartridge_with_program(program, caps::AUDIO); 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); + let report = runtime.tick( + &mut log_service, + &mut fs, + &mut fs_state, + &mut memcard, + &mut open_files, + &mut next_handle, + &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)]); @@ -1018,6 +1325,11 @@ fn tick_audio_play_missing_asset_returns_status_not_crash() { 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 fs = VirtualFS::new(); + let mut fs_state = FsState::Unmounted; + let mut memcard = MemcardService::new(); + let mut open_files: HashMap = HashMap::new(); + let mut next_handle = 1; let mut vm = VirtualMachine::default(); let mut hardware = Hardware::new(); let signals = InputSignals::default(); @@ -1039,7 +1351,17 @@ fn tick_audio_play_type_mismatch_surfaces_trap_not_panic() { 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) + .tick( + &mut log_service, + &mut fs, + &mut fs_state, + &mut memcard, + &mut open_files, + &mut next_handle, + &mut vm, + &signals, + &mut hardware, + ) .expect("type mismatch must surface as trap"); match report { CrashReport::VmTrap { trap } => { @@ -1055,6 +1377,11 @@ fn tick_audio_play_type_mismatch_surfaces_trap_not_panic() { 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 fs = VirtualFS::new(); + let mut fs_state = FsState::Unmounted; + let mut memcard = MemcardService::new(); + let mut open_files: HashMap = HashMap::new(); + let mut next_handle = 1; let mut vm = VirtualMachine::default(); let mut hardware = Hardware::new(); let signals = InputSignals::default(); @@ -1076,7 +1403,17 @@ fn tick_composer_emit_sprite_type_mismatch_surfaces_trap_not_panic() { 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) + .tick( + &mut log_service, + &mut fs, + &mut fs_state, + &mut memcard, + &mut open_files, + &mut next_handle, + &mut vm, + &signals, + &mut hardware, + ) .expect("type mismatch must surface as trap"); match report { CrashReport::VmTrap { trap } => { 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 634b50c2..3e64b7b1 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 @@ -4,6 +4,11 @@ use super::*; 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 fs = VirtualFS::new(); + let mut fs_state = FsState::Unmounted; + let mut memcard = MemcardService::new(); + let mut open_files: HashMap = HashMap::new(); + let mut next_handle = 1; let mut vm = VirtualMachine::default(); let mut hardware = Hardware::new(); let signals = InputSignals::default(); @@ -21,7 +26,17 @@ fn tick_asset_commit_operational_error_returns_status_not_crash() { let cartridge = cartridge_with_program(program, caps::ASSET); 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); + let report = runtime.tick( + &mut log_service, + &mut fs, + &mut fs_state, + &mut memcard, + &mut open_files, + &mut next_handle, + &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)]); @@ -31,6 +46,11 @@ fn tick_asset_commit_operational_error_returns_status_not_crash() { 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 fs = VirtualFS::new(); + let mut fs_state = FsState::Unmounted; + let mut memcard = MemcardService::new(); + let mut open_files: HashMap = HashMap::new(); + let mut next_handle = 1; let mut vm = VirtualMachine::default(); let mut hardware = Hardware::new(); let signals = InputSignals::default(); @@ -48,7 +68,17 @@ fn tick_asset_load_missing_asset_returns_status_and_zero_handle() { let cartridge = cartridge_with_program(program, caps::ASSET); 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); + let report = runtime.tick( + &mut log_service, + &mut fs, + &mut fs_state, + &mut memcard, + &mut open_files, + &mut next_handle, + &mut vm, + &signals, + &mut hardware, + ); assert!(report.is_none(), "missing asset must not crash"); assert!(vm.is_halted()); assert_eq!( @@ -61,6 +91,11 @@ fn tick_asset_load_missing_asset_returns_status_and_zero_handle() { 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 fs = VirtualFS::new(); + let mut fs_state = FsState::Unmounted; + let mut memcard = MemcardService::new(); + let mut open_files: HashMap = HashMap::new(); + let mut next_handle = 1; let mut vm = VirtualMachine::default(); let mut hardware = Hardware::new(); let signals = InputSignals::default(); @@ -84,7 +119,17 @@ fn tick_asset_load_invalid_slot_returns_status_and_zero_handle() { let cartridge = cartridge_with_program(program, caps::ASSET); 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); + let report = runtime.tick( + &mut log_service, + &mut fs, + &mut fs_state, + &mut memcard, + &mut open_files, + &mut next_handle, + &mut vm, + &signals, + &mut hardware, + ); assert!(report.is_none(), "invalid slot must not crash"); assert!(vm.is_halted()); assert_eq!( @@ -97,6 +142,11 @@ fn tick_asset_load_invalid_slot_returns_status_and_zero_handle() { 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 fs = VirtualFS::new(); + let mut fs_state = FsState::Unmounted; + let mut memcard = MemcardService::new(); + let mut open_files: HashMap = HashMap::new(); + let mut next_handle = 1; let mut vm = VirtualMachine::default(); let mut hardware = Hardware::new(); let signals = InputSignals::default(); @@ -114,7 +164,17 @@ fn tick_asset_status_unknown_handle_returns_status_not_crash() { let cartridge = cartridge_with_program(program, caps::ASSET); 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); + let report = runtime.tick( + &mut log_service, + &mut fs, + &mut fs_state, + &mut memcard, + &mut open_files, + &mut next_handle, + &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)]); @@ -124,6 +184,11 @@ fn tick_asset_status_unknown_handle_returns_status_not_crash() { fn tick_bank_info_returns_slot_summary_not_json() { let mut runtime = VirtualMachineRuntime::new(None); let mut log_service = LogService::new(4096); + let mut fs = VirtualFS::new(); + let mut fs_state = FsState::Unmounted; + let mut memcard = MemcardService::new(); + let mut open_files: HashMap = HashMap::new(); + let mut next_handle = 1; let mut vm = VirtualMachine::default(); let mut hardware = Hardware::new(); let signals = InputSignals::default(); @@ -147,7 +212,17 @@ fn tick_bank_info_returns_slot_summary_not_json() { let cartridge = cartridge_with_program(program, caps::BANK); 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); + let report = runtime.tick( + &mut log_service, + &mut fs, + &mut fs_state, + &mut memcard, + &mut open_files, + &mut next_handle, + &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)]); @@ -157,6 +232,11 @@ fn tick_bank_info_returns_slot_summary_not_json() { 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 fs = VirtualFS::new(); + let mut fs_state = FsState::Unmounted; + let mut memcard = MemcardService::new(); + let mut open_files: HashMap = HashMap::new(); + let mut next_handle = 1; 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( @@ -180,6 +260,11 @@ fn initialize_vm_rejects_removed_bank_slot_info_syscall_identity() { 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 fs = VirtualFS::new(); + let mut fs_state = FsState::Unmounted; + let mut memcard = MemcardService::new(); + let mut open_files: HashMap = HashMap::new(); + let mut next_handle = 1; let mut vm = VirtualMachine::default(); let mut hardware = Hardware::new(); let signals = InputSignals::default(); @@ -215,7 +300,17 @@ 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 log_service, &mut vm, &cartridge).expect("runtime must initialize"); - let report = runtime.tick(&mut log_service, &mut vm, &signals, &mut hardware); + let report = runtime.tick( + &mut log_service, + &mut fs, + &mut fs_state, + &mut memcard, + &mut open_files, + &mut next_handle, + &mut vm, + &signals, + &mut hardware, + ); assert!(report.is_none(), "invalid transition must not crash"); assert!(vm.is_halted()); assert_eq!(handle, 1); @@ -226,6 +321,11 @@ fn tick_asset_commit_invalid_transition_returns_status_not_crash() { 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 fs = VirtualFS::new(); + let mut fs_state = FsState::Unmounted; + let mut memcard = MemcardService::new(); + let mut open_files: HashMap = HashMap::new(); + let mut next_handle = 1; let mut vm = VirtualMachine::default(); let mut hardware = Hardware::new(); let signals = InputSignals::default(); @@ -243,7 +343,17 @@ fn tick_asset_cancel_unknown_handle_returns_status_not_crash() { let cartridge = cartridge_with_program(program, caps::ASSET); 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); + let report = runtime.tick( + &mut log_service, + &mut fs, + &mut fs_state, + &mut memcard, + &mut open_files, + &mut next_handle, + &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)]); @@ -253,6 +363,11 @@ fn tick_asset_cancel_unknown_handle_returns_status_not_crash() { 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 fs = VirtualFS::new(); + let mut fs_state = FsState::Unmounted; + let mut memcard = MemcardService::new(); + let mut open_files: HashMap = HashMap::new(); + let mut next_handle = 1; let mut vm = VirtualMachine::default(); let mut hardware = Hardware::new(); let signals = InputSignals::default(); @@ -293,7 +408,17 @@ 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 log_service, &mut vm, &signals, &mut hardware); + let report = runtime.tick( + &mut log_service, + &mut fs, + &mut fs_state, + &mut memcard, + &mut open_files, + &mut next_handle, + &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)]); @@ -303,6 +428,11 @@ fn tick_asset_cancel_invalid_transition_returns_status_not_crash() { 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 fs = VirtualFS::new(); + let mut fs_state = FsState::Unmounted; + let mut memcard = MemcardService::new(); + let mut open_files: HashMap = HashMap::new(); + let mut next_handle = 1; let mut vm = VirtualMachine::default(); let mut hardware = Hardware::new(); let signals = InputSignals::default(); @@ -342,7 +472,17 @@ 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 log_service, &mut vm, &cartridge).expect("runtime must initialize"); - let report = runtime.tick(&mut log_service, &mut vm, &signals, &mut hardware); + let report = runtime.tick( + &mut log_service, + &mut fs, + &mut fs_state, + &mut memcard, + &mut open_files, + &mut next_handle, + &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 3f48e915..3d1c80d1 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 @@ -4,7 +4,12 @@ use super::*; fn tick_memcard_slot_roundtrip_for_game_profile() { let mut runtime = VirtualMachineRuntime::new(None); let mut log_service = LogService::new(4096); - runtime.mount_fs(&mut log_service, Box::new(MemFsBackend::default())); + let mut fs = VirtualFS::new(); + let mut fs_state = FsState::Unmounted; + let mut memcard = MemcardService::new(); + let mut open_files: HashMap = HashMap::new(); + let mut next_handle = 1; + runtime.mount_fs(&mut log_service, &mut fs, &mut fs_state, Box::new(MemFsBackend::default())); let mut vm = VirtualMachine::default(); let mut hardware = Hardware::new(); let signals = InputSignals::default(); @@ -52,7 +57,17 @@ fn tick_memcard_slot_roundtrip_for_game_profile() { }; 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); + let report = runtime.tick( + &mut log_service, + &mut fs, + &mut fs_state, + &mut memcard, + &mut open_files, + &mut next_handle, + &mut vm, + &signals, + &mut hardware, + ); assert!(report.is_none(), "memcard roundtrip must not crash"); assert!(vm.is_halted()); assert_eq!( @@ -65,6 +80,11 @@ fn tick_memcard_slot_roundtrip_for_game_profile() { 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 fs = VirtualFS::new(); + let mut fs_state = FsState::Unmounted; + let mut memcard = MemcardService::new(); + let mut open_files: HashMap = HashMap::new(); + let mut next_handle = 1; let mut vm = VirtualMachine::default(); let mut hardware = Hardware::new(); let signals = InputSignals::default(); @@ -92,7 +112,17 @@ fn tick_memcard_access_is_denied_for_non_game_profile() { }; 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); + let report = runtime.tick( + &mut log_service, + &mut fs, + &mut fs_state, + &mut memcard, + &mut open_files, + &mut next_handle, + &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 b5196d1d..ebf53aa7 100644 --- a/crates/console/prometeu-system/src/services/vm_runtime/tick.rs +++ b/crates/console/prometeu-system/src/services/vm_runtime/tick.rs @@ -1,10 +1,13 @@ use super::dispatch::VmRuntimeHost; use super::*; use crate::CrashReport; +use crate::fs::{FsState, VirtualFS}; +use crate::services::memcard::MemcardService; use prometeu_hal::asset::{BankTelemetry, BankType}; use prometeu_hal::log::{LogLevel, LogService, LogSource}; use prometeu_hal::{HardwareBridge, HostContext, InputSignals}; use prometeu_vm::LogicalFrameEndingReason; +use std::collections::HashMap; use std::panic::{AssertUnwindSafe, catch_unwind}; use std::sync::atomic::Ordering; @@ -59,7 +62,20 @@ impl VirtualMachineRuntime { ) -> Option { let step_result = { let mut ctx = HostContext::new(Some(hw)); - let mut host = VmRuntimeHost { runtime: self, log_service }; + let mut fs = VirtualFS::new(); + let mut fs_state = FsState::Unmounted; + let mut memcard = MemcardService::new(); + let mut open_files = HashMap::new(); + let mut next_handle = 1; + let mut host = VmRuntimeHost { + runtime: self, + log_service, + fs: &mut fs, + fs_state: &mut fs_state, + memcard: &mut memcard, + open_files: &mut open_files, + next_handle: &mut next_handle, + }; vm.step(&mut host, &mut ctx) }; @@ -92,6 +108,11 @@ impl VirtualMachineRuntime { pub fn tick( &mut self, log_service: &mut LogService, + fs: &mut VirtualFS, + fs_state: &mut FsState, + memcard: &mut MemcardService, + open_files: &mut HashMap, + next_handle: &mut u32, vm: &mut VirtualMachine, signals: &InputSignals, hw: &mut dyn HardwareBridge, @@ -103,7 +124,7 @@ impl VirtualMachineRuntime { return None; } - self.update_fs(log_service); + self.update_fs(log_service, fs, fs_state); if !self.logical_frame_active { self.logical_frame_active = true; @@ -137,7 +158,15 @@ impl VirtualMachineRuntime { if budget > 0 { let run_result = { let mut ctx = HostContext::new(Some(hw)); - let mut host = VmRuntimeHost { runtime: self, log_service }; + let mut host = VmRuntimeHost { + runtime: self, + log_service, + fs, + fs_state, + memcard, + open_files, + next_handle, + }; vm.run_budget(budget, &mut host, &mut ctx) }; diff --git a/discussion/index.ndjson b/discussion/index.ndjson index e6b02bf5..112b04fe 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":"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":[]} +{"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":"done","created_at":"2026-05-14","updated_at":"2026-05-14","ref_decisions":["DEC-0024"]}],"lessons":[]} diff --git a/discussion/workflow/plans/PLN-0054-move-fs-and-memcard-ownership-to-systemos.md b/discussion/workflow/plans/PLN-0054-move-fs-and-memcard-ownership-to-systemos.md index 03f2b1c0..b76d8581 100644 --- a/discussion/workflow/plans/PLN-0054-move-fs-and-memcard-ownership-to-systemos.md +++ b/discussion/workflow/plans/PLN-0054-move-fs-and-memcard-ownership-to-systemos.md @@ -2,7 +2,7 @@ id: PLN-0054 ticket: system-os-service-ownership-and-module-layout title: Move FS And Memcard 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]