From 77351a8813cf77c792e0faac51aec401c1423487 Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Mon, 20 Apr 2026 18:04:52 +0100 Subject: [PATCH] implements PLN-0038 --- .../src/firmware/firmware.rs | 84 +++++++++++++++++++ .../src/debugger.rs | 60 +++++++++++++ .../prometeu-host-desktop-winit/src/runner.rs | 3 + discussion/index.ndjson | 2 +- ...ost-dependent-domain-coverage-expansion.md | 5 +- 5 files changed, 150 insertions(+), 4 deletions(-) diff --git a/crates/console/prometeu-firmware/src/firmware/firmware.rs b/crates/console/prometeu-firmware/src/firmware/firmware.rs index 262e5260..d6165936 100644 --- a/crates/console/prometeu-firmware/src/firmware/firmware.rs +++ b/crates/console/prometeu-firmware/src/firmware/firmware.rs @@ -175,6 +175,24 @@ mod tests { 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, @@ -225,6 +243,20 @@ mod tests { } } + 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); @@ -268,4 +300,56 @@ mod tests { 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(_))); + } } diff --git a/crates/host/prometeu-host-desktop-winit/src/debugger.rs b/crates/host/prometeu-host-desktop-winit/src/debugger.rs index c698ec2e..dca85fe5 100644 --- a/crates/host/prometeu-host-desktop-winit/src/debugger.rs +++ b/crates/host/prometeu-host-desktop-winit/src/debugger.rs @@ -368,3 +368,63 @@ impl HostDebugger { self.last_fault_summary = Some(summary); } } + +#[cfg(test)] +mod tests { + use super::*; + use prometeu_drivers::hardware::Hardware; + use prometeu_hal::debugger_protocol::DebugCommand; + + #[test] + fn setup_boot_target_ignores_hub_and_non_debug_cartridges() { + let mut debugger = HostDebugger::new(); + let mut firmware = Firmware::new(None); + + debugger.setup_boot_target(&BootTarget::Hub, &mut firmware); + assert!(!debugger.waiting_for_start); + assert!(debugger.listener.is_none()); + + debugger.setup_boot_target( + &BootTarget::Cartridge { + path: "dummy".into(), + debug: false, + debug_port: 7777, + }, + &mut firmware, + ); + assert!(!debugger.waiting_for_start); + assert!(debugger.listener.is_none()); + } + + #[test] + fn handle_command_updates_pause_and_step_flags_without_host_io() { + let mut debugger = HostDebugger::new(); + let mut firmware = Firmware::new(None); + let mut hardware = Hardware::new(); + + debugger.handle_command(DebugCommand::Pause, &mut firmware, &mut hardware); + assert!(firmware.os.paused); + + debugger.handle_command(DebugCommand::Resume, &mut firmware, &mut hardware); + assert!(!firmware.os.paused); + + debugger.handle_command(DebugCommand::StepFrame, &mut firmware, &mut hardware); + assert!(!firmware.os.paused); + assert!(firmware.os.debug_step_request); + } + + #[test] + fn handle_command_start_leaves_waiting_for_start_mode() { + let mut debugger = HostDebugger::new(); + let mut firmware = Firmware::new(None); + let mut hardware = Hardware::new(); + + debugger.waiting_for_start = true; + firmware.os.paused = true; + + debugger.handle_command(DebugCommand::Start, &mut firmware, &mut hardware); + + assert!(!debugger.waiting_for_start); + assert!(!firmware.os.paused); + } +} diff --git a/crates/host/prometeu-host-desktop-winit/src/runner.rs b/crates/host/prometeu-host-desktop-winit/src/runner.rs index fd0468fe..a8b28e39 100644 --- a/crates/host/prometeu-host-desktop-winit/src/runner.rs +++ b/crates/host/prometeu-host-desktop-winit/src/runner.rs @@ -604,6 +604,9 @@ mod tests { } } + // These ignored tests intentionally cover host-dependent behavior that requires + // real socket bind/connect coordination. Deterministic debugger state changes + // are covered below the host layer in `debugger.rs`. #[test] #[ignore = "requires localhost TCP bind/connect; run via `cargo test -p prometeu-host-desktop-winit --lib -- --ignored`"] fn test_debug_port_opens() { diff --git a/discussion/index.ndjson b/discussion/index.ndjson index 30aa64bf..61be358f 100644 --- a/discussion/index.ndjson +++ b/discussion/index.ndjson @@ -4,7 +4,7 @@ {"type":"discussion","id":"DSC-0021","status":"done","ticket":"asset-entry-codec-enum-with-metadata","title":"Asset Entry Codec Enum Contract","created_at":"2026-04-09","updated_at":"2026-04-09","tags":["asset","runtime","codec","metadata"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0024","file":"lessons/DSC-0021-asset-entry-codec-enum-contract/LSN-0024-string-on-the-wire-enum-in-runtime.md","status":"done","created_at":"2026-04-09","updated_at":"2026-04-09"}]} {"type":"discussion","id":"DSC-0022","status":"done","ticket":"tile-bank-vs-glyph-bank-domain-naming","title":"Glyph Bank Domain Naming Contract","created_at":"2026-04-09","updated_at":"2026-04-10","tags":["gfx","runtime","naming","domain-model"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0025","file":"lessons/DSC-0022-glyph-bank-domain-naming/LSN-0025-rename-artifact-by-meaning-not-by-token.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}]} {"type":"discussion","id":"DSC-0001","status":"done","ticket":"legacy-runtime-learn-import","title":"Import legacy runtime learn into discussion lessons","created_at":"2026-03-27","updated_at":"2026-03-27","tags":["migration","tech-debt"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0001","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0001-prometeu-learn-index.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0002","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0002-historical-asset-status-first-fault-and-return-contract.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0003","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0003-historical-audio-status-first-fault-and-return-contract.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0004","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0004-historical-cartridge-boot-protocol-and-manifest-authority.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0005","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0005-historical-game-memcard-slots-surface-and-semantics.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0006","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0006-historical-gfx-status-first-fault-and-return-contract.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0007","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0007-historical-retired-fault-and-input-decisions.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0008","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0008-historical-vm-core-and-assets.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0009","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0009-mental-model-asset-management.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0010","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0010-mental-model-audio.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0011","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0011-mental-model-gfx.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0012","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0012-mental-model-input.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0013","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0013-mental-model-observability-and-debugging.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0014","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0014-mental-model-portability-and-cross-platform.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0015","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0015-mental-model-save-memory-and-memcard.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0016","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0016-mental-model-status-first-and-fault-thinking.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0017","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0017-mental-model-time-and-cycles.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0018","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0018-mental-model-touch.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"}]} -{"type":"discussion","id":"DSC-0002","status":"open","ticket":"runtime-edge-test-plan","title":"Agenda - Runtime Edge Test Plan","created_at":"2026-03-27","updated_at":"2026-04-20","tags":[],"agendas":[{"id":"AGD-0001","file":"workflow/agendas/AGD-0001-runtime-edge-test-plan.md","status":"done","created_at":"2026-03-27","updated_at":"2026-04-20"}],"decisions":[{"id":"DEC-0020","file":"workflow/decisions/DEC-0020-runtime-edge-coverage-governance-by-domain.md","status":"in_progress","created_at":"2026-04-20","updated_at":"2026-04-20","ref_agenda":"AGD-0001"}],"plans":[{"id":"PLN-0037","file":"workflow/plans/PLN-0037-runtime-edge-coverage-governance-foundation.md","status":"done","created_at":"2026-04-20","updated_at":"2026-04-20","ref_decisions":["DEC-0020"]},{"id":"PLN-0038","file":"workflow/plans/PLN-0038-firmware-and-host-dependent-domain-coverage-expansion.md","status":"open","created_at":"2026-04-20","updated_at":"2026-04-20","ref_decisions":["DEC-0020"]},{"id":"PLN-0039","file":"workflow/plans/PLN-0039-incremental-runtime-domain-suite-split-and-baselines.md","status":"open","created_at":"2026-04-20","updated_at":"2026-04-20","ref_decisions":["DEC-0020"]}],"lessons":[]} +{"type":"discussion","id":"DSC-0002","status":"open","ticket":"runtime-edge-test-plan","title":"Agenda - Runtime Edge Test Plan","created_at":"2026-03-27","updated_at":"2026-04-20","tags":[],"agendas":[{"id":"AGD-0001","file":"workflow/agendas/AGD-0001-runtime-edge-test-plan.md","status":"done","created_at":"2026-03-27","updated_at":"2026-04-20"}],"decisions":[{"id":"DEC-0020","file":"workflow/decisions/DEC-0020-runtime-edge-coverage-governance-by-domain.md","status":"in_progress","created_at":"2026-04-20","updated_at":"2026-04-20","ref_agenda":"AGD-0001"}],"plans":[{"id":"PLN-0037","file":"workflow/plans/PLN-0037-runtime-edge-coverage-governance-foundation.md","status":"done","created_at":"2026-04-20","updated_at":"2026-04-20","ref_decisions":["DEC-0020"]},{"id":"PLN-0038","file":"workflow/plans/PLN-0038-firmware-and-host-dependent-domain-coverage-expansion.md","status":"done","created_at":"2026-04-20","updated_at":"2026-04-20","ref_decisions":["DEC-0020"]},{"id":"PLN-0039","file":"workflow/plans/PLN-0039-incremental-runtime-domain-suite-split-and-baselines.md","status":"open","created_at":"2026-04-20","updated_at":"2026-04-20","ref_decisions":["DEC-0020"]}],"lessons":[]} {"type":"discussion","id":"DSC-0003","status":"open","ticket":"packed-cartridge-loader-pmc","title":"Agenda - Packed Cartridge Loader PMC","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0002","file":"workflow/agendas/AGD-0002-packed-cartridge-loader-pmc.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]} {"type":"discussion","id":"DSC-0004","status":"open","ticket":"system-run-cart","title":"Agenda - System Run Cart","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0003","file":"workflow/agendas/AGD-0003-system-run-cart.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]} {"type":"discussion","id":"DSC-0005","status":"open","ticket":"system-fault-semantics-and-control-surface","title":"Agenda - System Fault Semantics and Control Surface","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0004","file":"workflow/agendas/AGD-0004-system-fault-semantics-and-control-surface.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]} diff --git a/discussion/workflow/plans/PLN-0038-firmware-and-host-dependent-domain-coverage-expansion.md b/discussion/workflow/plans/PLN-0038-firmware-and-host-dependent-domain-coverage-expansion.md index 34544562..01dce05b 100644 --- a/discussion/workflow/plans/PLN-0038-firmware-and-host-dependent-domain-coverage-expansion.md +++ b/discussion/workflow/plans/PLN-0038-firmware-and-host-dependent-domain-coverage-expansion.md @@ -2,9 +2,9 @@ id: PLN-0038 ticket: runtime-edge-test-plan title: Plan - Firmware and Host-Dependent Domain Coverage Expansion -status: open +status: done created: 2026-04-20 -completed: +completed: 2026-04-20 tags: [tests, firmware, host, coverage] --- @@ -133,4 +133,3 @@ Use the governance helpers from `PLN-0037` if available, or temporary documented - Adding host-only tests for behavior that belongs below the host boundary. - Expanding firmware tests without aligning them to the state machine contract in the specs. - Overfitting ignored integration tests to desktop timing details. -