bQUARKz 27e034f89f
All checks were successful
Intrepid/Prometeu/Runtime/pipeline/head This commit looks good
Intrepid/Prometeu/Runtime/pipeline/pr-master This commit looks good
Runtime Edge Test Plan
2026-04-20 18:34:00 +01:00

353 lines
12 KiB
Rust

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<CertificationConfig>) -> 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<FirmwareState> {
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<u8> {
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(_)));
}
}