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, VirtualMachineRuntime}; 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: VirtualMachineRuntime, /// 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: VirtualMachineRuntime::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::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::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::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 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::syscalls::caps; use prometeu_system::CrashReport; 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![], } } #[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); assert!(matches!(firmware.state, FirmwareState::GameRunning(_))); 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), } } #[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_back_to_hub_home() { let mut firmware = Firmware::new(None); let mut hardware = Hardware::new(); let signals = InputSignals::default(); firmware.load_cartridge(valid_cartridge(AppMode::System)); firmware.tick(&signals, &mut hardware); assert!(matches!(firmware.state, FirmwareState::HubHome(_))); } #[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(_))); } }