diff --git a/crates/console/prometeu-firmware/src/firmware/firmware.rs b/crates/console/prometeu-firmware/src/firmware/firmware.rs index 9f8ad7ba..aa550d97 100644 --- a/crates/console/prometeu-firmware/src/firmware/firmware.rs +++ b/crates/console/prometeu-firmware/src/firmware/firmware.rs @@ -96,9 +96,14 @@ impl Firmware { signals: &InputSignals, platform: &mut dyn RuntimePlatform, ) { + let entering_game_from_home = matches!(self.state, FirmwareState::HubHome(_)) + && matches!(new_state, FirmwareState::GameRunning(_)); self.on_exit(signals, platform); self.state = new_state; self.state_initialized = false; + if entering_game_from_home { + self.game_input_barrier_frames = self.game_input_barrier_frames.max(1); + } // Enter the new state immediately to avoid "empty" frames during transitions. self.on_enter(signals, platform); @@ -620,6 +625,60 @@ mod tests { assert_eq!(firmware.os.windows().window_count(), 0); } + #[test] + fn hub_home_clicking_resident_game_resumes_same_task_without_reloading() { + let root = TestGameRoot::new(); + root.add_game("stress", true); + + let (mut firmware, mut platform) = hub_home_firmware(); + firmware.hub.set_game_library(discover_games_root(&root.path)); + firmware.tick(&InputSignals::default(), &mut platform); + + let launch_click = + InputSignals { f_signal: true, x_pos: 72, y_pos: 90, ..Default::default() }; + firmware.tick(&launch_click, &mut platform); + firmware.tick(&InputSignals::default(), &mut platform); + + let game_task = match &firmware.state { + FirmwareState::GameRunning(step) => step.task_id, + other => panic!("expected GameRunning state, got {:?}", other), + }; + + firmware.request_home_from_host(); + firmware.tick(&InputSignals::default(), &mut platform); + + assert!(matches!(firmware.state, FirmwareState::HubHome(_))); + assert_eq!(firmware.os.lifecycle().resident_game_task(), Some(game_task)); + assert_eq!(firmware.os.lifecycle().task_state(game_task), Some(TaskState::Suspended)); + + firmware.tick(&InputSignals::default(), &mut platform); + firmware.tick(&launch_click, &mut platform); + + match &firmware.state { + FirmwareState::GameRunning(step) => assert_eq!(step.task_id, game_task), + other => panic!("expected resident GameRunning state, got {:?}", other), + } + assert_eq!(firmware.os.lifecycle().resident_game_task(), Some(game_task)); + assert_eq!(firmware.os.lifecycle().foreground_owner(), ForegroundOwner::Game(game_task)); + assert_eq!(firmware.os.lifecycle().task_state(game_task), Some(TaskState::Foreground)); + assert_eq!( + firmware.os.lifecycle().pending_game_lifecycle_events().last().map(|event| event.kind), + Some(GameLifecycleEventKind::ResumeForeground) + ); + + let held_click = + InputSignals { f_signal: true, x_pos: 72, y_pos: 90, ..Default::default() }; + firmware.tick(&held_click, &mut platform); + + assert!(!platform.input().pad().any()); + assert!(!platform.input().touch().f().down); + assert!(firmware.os.lifecycle().pending_game_lifecycle_events().is_empty()); + assert_eq!( + firmware.os.vm().delivered_game_lifecycle_events().last().map(|event| event.kind), + Some(GameLifecycleEventKind::ResumeForeground) + ); + } + #[test] fn invalid_games_root_candidate_is_omitted_from_home_launch_boundary() { let root = TestGameRoot::new(); diff --git a/crates/console/prometeu-firmware/src/firmware/firmware_step_hub_home.rs b/crates/console/prometeu-firmware/src/firmware/firmware_step_hub_home.rs index 1e39275c..e42deff6 100644 --- a/crates/console/prometeu-firmware/src/firmware/firmware_step_hub_home.rs +++ b/crates/console/prometeu-firmware/src/firmware/firmware_step_hub_home.rs @@ -1,7 +1,9 @@ use crate::firmware::firmware_state::{ - AppCrashesStep, FirmwareState, LoadCartridgeStep, ShellRunningStep, + AppCrashesStep, FirmwareState, GameRunningStep, LoadCartridgeStep, ShellRunningStep, }; use crate::firmware::prometeu_context::PrometeuContext; +use prometeu_hal::InputSignals; +use prometeu_hal::app_mode::AppMode; use prometeu_hal::cartridge_loader::CartridgeLoader; use prometeu_hal::log::{LogLevel, LogSource}; use prometeu_hal::primitives::Rect; @@ -38,6 +40,55 @@ impl HubHomeStep { } Some(SystemProfileAction::LaunchGame { path }) => match CartridgeLoader::load(&path) { Ok(cartridge) => { + if cartridge.app_mode == AppMode::Game + && let Some(task_id) = ctx.os.lifecycle().resident_game_task() + { + if ctx.os.vm().current_app_id() == cartridge.app_id { + match ctx.os.lifecycle().resume_task(task_id) { + Ok(()) => { + ctx.os + .vm() + .transition_render_owner(AppMode::Game, cartridge.app_id); + ctx.platform + .input_mut() + .pad_mut() + .begin_frame(&InputSignals::default()); + ctx.platform + .input_mut() + .touch_mut() + .begin_frame(&InputSignals::default()); + return Some(FirmwareState::GameRunning(GameRunningStep::new( + task_id, + ))); + } + Err(error) => { + ctx.os.log( + LogLevel::Warn, + LogSource::Hub, + 0, + format!( + "Failed to resume resident Home game {}: {error:?}", + cartridge.title + ), + ); + return None; + } + } + } + + let resident_app_id = ctx.os.vm().current_app_id(); + ctx.os.log( + LogLevel::Warn, + LogSource::Hub, + 0, + format!( + "Cannot launch Home game {} while resident game app_id {} is active", + cartridge.title, resident_app_id + ), + ); + return None; + } + return Some(FirmwareState::LoadCartridge(LoadCartridgeStep::new(cartridge))); } Err(error) => {