use crate::firmware::boot_target::BootTarget; use crate::firmware::firmware_state::{FirmwareState, LoadCartridgeStep, ResetStep}; use crate::firmware::prometeu_context::PrometeuContext; use prometeu_hal::cartridge::Cartridge; use prometeu_hal::telemetry::CertificationConfig; use prometeu_hal::{HardwareBridge, InputSignals}; use prometeu_system::{PrometeuHub, SystemOS}; use prometeu_vm::VirtualMachine; /// PROMETEU Firmware. /// /// The central orchestrator of the console. The firmware acts as the /// "Control Unit", managing the high-level state machine of the system. /// /// It is responsible for transitioning between different modes of operation, /// such as showing the splash screen, running the Hub (launcher), or /// executing a game/app. /// /// ### Execution Loop: /// The firmware is designed to be ticked once per frame (60Hz). During each /// tick, it: /// 1. Updates peripherals with the latest input signals. /// 2. Delegates the logic update to the current active state. /// 3. Handles state transitions (e.g., from Loading to Playing). pub struct Firmware { /// The execution engine (PVM) for user applications. pub vm: VirtualMachine, /// The underlying OS services (Syscalls, Filesystem, Telemetry). pub os: SystemOS, /// The internal state of the system launcher (Hub). pub hub: PrometeuHub, /// The current operational state (e.g., Reset, SplashScreen, GameRunning). pub state: FirmwareState, /// The desired application to run after boot (Hub or specific Cartridge). pub boot_target: BootTarget, /// State-machine lifecycle tracker. state_initialized: bool, } impl Firmware { /// Initializes the firmware in the `Reset` state. pub fn new(cap_config: Option) -> Self { Self { vm: VirtualMachine::default(), os: SystemOS::new(cap_config), hub: PrometeuHub::new(), state: FirmwareState::Reset(ResetStep), boot_target: BootTarget::Hub, state_initialized: false, } } /// The main entry point for the Host to advance the system logic. /// /// This method is called exactly once per Host frame (60Hz). /// It updates peripheral signals and delegates the logic to the current state. pub fn tick(&mut self, signals: &InputSignals, hw: &mut dyn HardwareBridge) { // 0. Process asset commits at the beginning of the frame boundary. hw.assets_mut().apply_commits(); // 1. Update the peripheral state using the latest signals from the Host. // This ensures input is consistent throughout the entire update. hw.pad_mut().begin_frame(signals); hw.touch_mut().begin_frame(signals); // 2. State machine lifecycle management. if !self.state_initialized { self.on_enter(signals, hw); self.state_initialized = true; } // 3. Update the current state and check for transitions. if let Some(next_state) = self.on_update(signals, hw) { self.change_state(next_state, signals, hw); } } /// Transitions the system to a new state, handling lifecycle hooks. pub fn change_state( &mut self, new_state: FirmwareState, signals: &InputSignals, hw: &mut dyn HardwareBridge, ) { self.on_exit(signals, hw); self.state = new_state; self.state_initialized = false; // Enter the new state immediately to avoid "empty" frames during transitions. self.on_enter(signals, hw); self.state_initialized = true; } /// Dispatches the `on_enter` event to the current state implementation. fn on_enter(&mut self, signals: &InputSignals, hw: &mut dyn HardwareBridge) { let mut req = PrometeuContext { vm: &mut self.vm, os: &mut self.os, hub: &mut self.hub, boot_target: &self.boot_target, signals, hw, }; match &mut self.state { FirmwareState::Reset(s) => s.on_enter(&mut req), FirmwareState::SplashScreen(s) => s.on_enter(&mut req), FirmwareState::LaunchHub(s) => s.on_enter(&mut req), FirmwareState::HubHome(s) => s.on_enter(&mut req), FirmwareState::LoadCartridge(s) => s.on_enter(&mut req), FirmwareState::GameRunning(s) => s.on_enter(&mut req), FirmwareState::ShellRunning(s) => s.on_enter(&mut req), FirmwareState::AppCrashes(s) => s.on_enter(&mut req), } } /// Dispatches the `on_update` event to the current state implementation. /// Returns an optional `FirmwareState` if a transition is requested. fn on_update( &mut self, signals: &InputSignals, hw: &mut dyn HardwareBridge, ) -> Option { let mut req = PrometeuContext { vm: &mut self.vm, os: &mut self.os, hub: &mut self.hub, boot_target: &self.boot_target, signals, hw, }; match &mut self.state { FirmwareState::Reset(s) => s.on_update(&mut req), FirmwareState::SplashScreen(s) => s.on_update(&mut req), FirmwareState::LaunchHub(s) => s.on_update(&mut req), FirmwareState::HubHome(s) => s.on_update(&mut req), FirmwareState::LoadCartridge(s) => s.on_update(&mut req), FirmwareState::GameRunning(s) => s.on_update(&mut req), FirmwareState::ShellRunning(s) => s.on_update(&mut req), FirmwareState::AppCrashes(s) => s.on_update(&mut req), } } /// Dispatches the `on_exit` event to the current state implementation. fn on_exit(&mut self, signals: &InputSignals, hw: &mut dyn HardwareBridge) { let mut req = PrometeuContext { vm: &mut self.vm, os: &mut self.os, hub: &mut self.hub, boot_target: &self.boot_target, signals, hw, }; match &mut self.state { FirmwareState::Reset(s) => s.on_exit(&mut req), FirmwareState::SplashScreen(s) => s.on_exit(&mut req), FirmwareState::LaunchHub(s) => s.on_exit(&mut req), FirmwareState::HubHome(s) => s.on_exit(&mut req), FirmwareState::LoadCartridge(s) => s.on_exit(&mut req), FirmwareState::GameRunning(s) => s.on_exit(&mut req), FirmwareState::ShellRunning(s) => s.on_exit(&mut req), FirmwareState::AppCrashes(s) => s.on_exit(&mut req), } } pub fn load_cartridge(&mut self, cartridge: Cartridge) { self.state = FirmwareState::LoadCartridge(LoadCartridgeStep::new(cartridge)); self.state_initialized = false; } } #[cfg(test)] mod tests { use super::*; use crate::firmware::firmware_state::HubHomeStep; use prometeu_bytecode::assembler::assemble; use prometeu_bytecode::model::{BytecodeModule, FunctionMeta, SyscallDecl}; use prometeu_drivers::hardware::Hardware; use prometeu_hal::cartridge::{AppMode, AssetsPayloadSource}; use prometeu_hal::color::Color; use prometeu_hal::primitives::Rect; use prometeu_hal::syscalls::caps; use prometeu_system::CrashReport; use prometeu_system::process::ProcessState; use prometeu_system::task::{TaskId, TaskState}; use prometeu_system::windows::WindowOwner; fn halting_program() -> Vec { let code = assemble("HALT").expect("assemble"); BytecodeModule { version: 0, const_pool: vec![], functions: vec![FunctionMeta { code_offset: 0, code_len: code.len() as u32, ..Default::default() }], code, debug_info: None, exports: vec![], syscalls: vec![], } .serialize() } fn invalid_game_cartridge() -> Cartridge { Cartridge { app_id: 7, title: "Broken Cart".into(), app_version: "1.0.0".into(), app_mode: AppMode::Game, capabilities: 0, program: vec![0, 0, 0, 0], assets: AssetsPayloadSource::empty(), asset_table: vec![], preload: vec![], } } fn trapping_game_cartridge() -> Cartridge { let code = assemble("PUSH_BOOL 1\nHOSTCALL 0\nHALT").expect("assemble"); let program = BytecodeModule { version: 0, const_pool: vec![], functions: vec![FunctionMeta { code_offset: 0, code_len: code.len() as u32, ..Default::default() }], code, debug_info: None, exports: vec![], syscalls: vec![SyscallDecl { module: "gfx".into(), name: "clear".into(), version: 1, arg_slots: 1, ret_slots: 0, }], } .serialize(); Cartridge { app_id: 8, title: "Trap Cart".into(), app_version: "1.0.0".into(), app_mode: AppMode::Game, capabilities: caps::GFX, program, assets: AssetsPayloadSource::empty(), asset_table: vec![], preload: vec![], } } fn valid_cartridge(app_mode: AppMode) -> Cartridge { Cartridge { app_id: 9, title: "Valid Cart".into(), app_version: "1.0.0".into(), app_mode, capabilities: caps::NONE, program: halting_program(), assets: AssetsPayloadSource::empty(), asset_table: vec![], preload: vec![], } } fn load_shell_running_firmware() -> (Firmware, Hardware, InputSignals, TaskId) { let mut firmware = Firmware::new(None); let mut hardware = Hardware::new(); let signals = InputSignals::default(); firmware.load_cartridge(valid_cartridge(AppMode::Shell)); firmware.tick(&signals, &mut hardware); let task_id = match &firmware.state { FirmwareState::ShellRunning(step) => step.task_id, other => panic!("expected ShellRunning state, got {:?}", other), }; (firmware, hardware, signals, task_id) } fn hub_home_firmware() -> (Firmware, Hardware) { let mut firmware = Firmware::new(None); firmware.state = FirmwareState::HubHome(HubHomeStep); firmware.state_initialized = false; (firmware, Hardware::new()) } fn focus_window(firmware: &mut Firmware, owner: WindowOwner) { let mut windows = firmware.os.windows(); windows.remove_all_windows(); let id = windows.add_window( "Test Window".to_string(), owner, Rect { x: 0, y: 0, w: 10, h: 10 }, Color::WHITE, ); windows.set_focus(id); } #[test] fn load_cartridge_transitions_to_app_crashes_when_vm_init_fails() { let mut firmware = Firmware::new(None); let mut hardware = Hardware::new(); let signals = InputSignals::default(); firmware.load_cartridge(invalid_game_cartridge()); firmware.tick(&signals, &mut hardware); match &firmware.state { FirmwareState::AppCrashes(step) => match &step.report { CrashReport::VmInit { error } => { assert!(matches!(error, prometeu_vm::VmInitError::InvalidFormat)); } other => panic!("expected VmInit crash report, got {:?}", other), }, other => panic!("expected AppCrashes state, got {:?}", other), } } #[test] fn game_running_transitions_to_app_crashes_when_runtime_surfaces_trap() { let mut firmware = Firmware::new(None); let mut hardware = Hardware::new(); let signals = InputSignals::default(); firmware.load_cartridge(trapping_game_cartridge()); firmware.tick(&signals, &mut hardware); let task_id = match &firmware.state { FirmwareState::GameRunning(step) => step.task_id, other => panic!("expected GameRunning state, got {:?}", other), }; firmware.tick(&signals, &mut hardware); match &firmware.state { FirmwareState::AppCrashes(step) => match &step.report { CrashReport::VmTrap { trap } => { assert!(trap.message.contains("Expected integer at index 0")); } other => panic!("expected VmTrap crash report, got {:?}", other), }, other => panic!("expected AppCrashes state, got {:?}", other), } assert_eq!(firmware.os.lifecycle().task_state(task_id), Some(TaskState::Crashed)); assert_eq!( firmware.os.lifecycle().process_state_for_task(task_id), Ok(ProcessState::Crashed) ); } #[test] fn reset_routes_hub_boot_target_to_splash_screen() { let mut firmware = Firmware::new(None); let mut hardware = Hardware::new(); let signals = InputSignals::default(); firmware.boot_target = BootTarget::Hub; firmware.tick(&signals, &mut hardware); assert!(matches!(firmware.state, FirmwareState::SplashScreen(_))); } #[test] fn reset_routes_cartridge_boot_target_to_launch_hub() { let mut firmware = Firmware::new(None); let mut hardware = Hardware::new(); let signals = InputSignals::default(); firmware.boot_target = BootTarget::Cartridge { path: "missing-cart".into(), debug: false, debug_port: 7777 }; firmware.tick(&signals, &mut hardware); assert!(matches!(firmware.state, FirmwareState::LaunchHub(_))); } #[test] fn load_cartridge_routes_system_apps_to_system_running() { let mut firmware = Firmware::new(None); let mut hardware = Hardware::new(); let signals = InputSignals::default(); firmware.load_cartridge(valid_cartridge(AppMode::Shell)); firmware.tick(&signals, &mut hardware); assert!(matches!(firmware.state, FirmwareState::ShellRunning(_))); assert!(firmware.os.windows().focused_window().is_some()); assert_eq!(firmware.os.windows().window_count(), 1); } #[test] fn system_running_ticks_focused_system_app_through_hub_pipeline() { let mut firmware = Firmware::new(None); let mut hardware = Hardware::new(); let signals = InputSignals::default(); firmware.load_cartridge(valid_cartridge(AppMode::Shell)); firmware.tick(&signals, &mut hardware); let task_id = match &firmware.state { FirmwareState::ShellRunning(step) => step.task_id, other => panic!("expected ShellRunning state, got {:?}", other), }; let tick_index_before_system_update = firmware.os.vm().tick_index(); firmware.tick(&signals, &mut hardware); assert!(matches!(firmware.state, FirmwareState::ShellRunning(_))); assert!(firmware.os.windows().focused_window_belongs_to_task(task_id)); assert_eq!(firmware.os.vm().tick_index(), tick_index_before_system_update + 1); } #[test] fn hub_home_launches_shell_a_as_native_shell_task_window() { let (mut firmware, mut hardware) = hub_home_firmware(); let signals = InputSignals { a_signal: true, ..Default::default() }; firmware.tick(&signals, &mut hardware); let task_id = match &firmware.state { FirmwareState::ShellRunning(step) => step.task_id, other => panic!("expected ShellRunning state, got {:?}", other), }; assert!(firmware.os.windows().focused_window_belongs_to_task(task_id)); assert_eq!(firmware.os.windows().window_count(), 1); assert_eq!(firmware.os.windows().windows()[0].title, "ShellA"); assert_eq!(firmware.os.lifecycle().task_state(task_id), Some(TaskState::Foreground)); assert_eq!( firmware.os.lifecycle().process_state_for_task(task_id), Ok(ProcessState::Running) ); } #[test] fn hub_home_launches_shell_b_as_native_shell_task_window() { let (mut firmware, mut hardware) = hub_home_firmware(); let signals = InputSignals { b_signal: true, ..Default::default() }; firmware.tick(&signals, &mut hardware); let task_id = match &firmware.state { FirmwareState::ShellRunning(step) => step.task_id, other => panic!("expected ShellRunning state, got {:?}", other), }; assert!(firmware.os.windows().focused_window_belongs_to_task(task_id)); assert_eq!(firmware.os.windows().window_count(), 1); assert_eq!(firmware.os.windows().windows()[0].title, "ShellB"); assert_eq!(firmware.os.lifecycle().task_state(task_id), Some(TaskState::Foreground)); assert_eq!( firmware.os.lifecycle().process_state_for_task(task_id), Ok(ProcessState::Running) ); } #[test] fn shell_running_start_close_uses_lifecycle_and_returns_hub_home() { let (mut firmware, mut hardware, _signals, task_id) = load_shell_running_firmware(); let close_signals = InputSignals { start_signal: true, ..Default::default() }; firmware.tick(&close_signals, &mut hardware); assert!(matches!(firmware.state, FirmwareState::HubHome(_))); assert_eq!(firmware.os.lifecycle().task_state(task_id), Some(TaskState::Closed)); assert_eq!( firmware.os.lifecycle().process_state_for_task(task_id), Ok(ProcessState::Stopped) ); } #[test] fn shell_running_closes_task_and_returns_hub_home_without_focused_window() { let (mut firmware, mut hardware, signals, task_id) = load_shell_running_firmware(); firmware.os.windows().remove_all_windows(); firmware.tick(&signals, &mut hardware); assert!(matches!(firmware.state, FirmwareState::HubHome(_))); assert_eq!(firmware.os.lifecycle().task_state(task_id), Some(TaskState::Closed)); assert_eq!( firmware.os.lifecycle().process_state_for_task(task_id), Ok(ProcessState::Stopped) ); } #[test] fn shell_running_closes_task_and_returns_hub_home_for_focused_hub_window() { let (mut firmware, mut hardware, signals, task_id) = load_shell_running_firmware(); focus_window(&mut firmware, WindowOwner::Hub); firmware.tick(&signals, &mut hardware); assert!(matches!(firmware.state, FirmwareState::HubHome(_))); assert_eq!(firmware.os.lifecycle().task_state(task_id), Some(TaskState::Closed)); assert_eq!( firmware.os.lifecycle().process_state_for_task(task_id), Ok(ProcessState::Stopped) ); } #[test] fn shell_running_closes_task_and_returns_hub_home_for_focused_overlay_window() { let (mut firmware, mut hardware, signals, task_id) = load_shell_running_firmware(); focus_window(&mut firmware, WindowOwner::Overlay); firmware.tick(&signals, &mut hardware); assert!(matches!(firmware.state, FirmwareState::HubHome(_))); assert_eq!(firmware.os.lifecycle().task_state(task_id), Some(TaskState::Closed)); assert_eq!( firmware.os.lifecycle().process_state_for_task(task_id), Ok(ProcessState::Stopped) ); } #[test] fn shell_running_closes_task_and_returns_hub_home_for_other_task_window() { let (mut firmware, mut hardware, signals, task_id) = load_shell_running_firmware(); focus_window(&mut firmware, WindowOwner::Task(TaskId(task_id.0 + 1))); firmware.tick(&signals, &mut hardware); assert!(matches!(firmware.state, FirmwareState::HubHome(_))); assert_eq!(firmware.os.lifecycle().task_state(task_id), Some(TaskState::Closed)); assert_eq!( firmware.os.lifecycle().process_state_for_task(task_id), Ok(ProcessState::Stopped) ); } #[test] fn load_cartridge_routes_game_apps_to_game_running() { let mut firmware = Firmware::new(None); let mut hardware = Hardware::new(); let signals = InputSignals::default(); firmware.load_cartridge(valid_cartridge(AppMode::Game)); firmware.tick(&signals, &mut hardware); assert!(matches!(firmware.state, FirmwareState::GameRunning(_))); assert_eq!(firmware.os.windows().window_count(), 0); } }