dev/system-os-cartridge-switch-orchestrator #36
@ -358,6 +358,10 @@ mod tests {
|
||||
.serialize()
|
||||
}
|
||||
|
||||
fn repository_test_cartridges_root() -> PathBuf {
|
||||
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../..").join("test-cartridges")
|
||||
}
|
||||
|
||||
fn invalid_game_cartridge() -> Cartridge {
|
||||
Cartridge {
|
||||
app_id: 7,
|
||||
@ -819,6 +823,100 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn repository_stress_to_dummy_boy_switch_is_end_to_end() {
|
||||
let library = discover_games_root(repository_test_cartridges_root());
|
||||
assert!(
|
||||
library
|
||||
.entries()
|
||||
.iter()
|
||||
.any(|entry| entry.app_id == 1 && entry.title == "Stress Console")
|
||||
);
|
||||
assert!(
|
||||
library.entries().iter().any(|entry| entry.app_id == 2 && entry.title == "Dummy Boy")
|
||||
);
|
||||
|
||||
let (mut firmware, mut platform) = hub_home_firmware();
|
||||
firmware.hub.set_game_library(library);
|
||||
firmware.tick(&InputSignals::default(), &mut platform);
|
||||
|
||||
let stress_click =
|
||||
InputSignals { f_signal: true, x_pos: 72, y_pos: 118, ..Default::default() };
|
||||
firmware.tick(&stress_click, &mut platform);
|
||||
firmware.tick(&InputSignals::default(), &mut platform);
|
||||
|
||||
let stress_task = match &firmware.state {
|
||||
FirmwareState::GameRunning(step) => step.task_id,
|
||||
other => panic!("expected Stress GameRunning state, got {:?}", other),
|
||||
};
|
||||
assert_eq!(firmware.os.vm().current_app_id(), 1);
|
||||
assert_eq!(firmware.os.vm().current_cartridge_title(), "Stress Console");
|
||||
|
||||
firmware.request_home_from_host();
|
||||
firmware
|
||||
.os
|
||||
.lifecycle()
|
||||
.advance_game_pause_budget(stress_task)
|
||||
.expect("pause budget should force Stress to suspend");
|
||||
firmware.os.vm().transition_render_owner(AppMode::Shell, 0);
|
||||
firmware.change_state(
|
||||
FirmwareState::HubHome(HubHomeStep),
|
||||
&InputSignals::default(),
|
||||
&mut platform,
|
||||
);
|
||||
let hub_ownership = firmware.os.vm().active_render_ownership();
|
||||
assert_eq!(hub_ownership.app_mode, AppMode::Shell);
|
||||
assert_eq!(hub_ownership.app_id, 0);
|
||||
|
||||
firmware.tick(&InputSignals::default(), &mut platform);
|
||||
let dummy_click =
|
||||
InputSignals { f_signal: true, x_pos: 72, y_pos: 90, ..Default::default() };
|
||||
firmware.tick(&dummy_click, &mut platform);
|
||||
|
||||
assert_eq!(firmware.os.lifecycle().task_state(stress_task), Some(TaskState::Closed));
|
||||
assert_eq!(
|
||||
firmware.os.lifecycle().process_state_for_task(stress_task),
|
||||
Ok(ProcessState::Stopped)
|
||||
);
|
||||
assert_eq!(
|
||||
firmware.os.sessions().vm_session_for_task(stress_task).map(|session| session.task_id),
|
||||
None
|
||||
);
|
||||
|
||||
let dummy_task = firmware
|
||||
.os
|
||||
.lifecycle()
|
||||
.resident_game_task()
|
||||
.expect("Dummy Boy should be resident after switch load");
|
||||
assert_ne!(dummy_task, stress_task);
|
||||
assert_eq!(
|
||||
firmware.os.sessions().vm_session_for_task(dummy_task).map(|session| {
|
||||
(session.app_id, session.title.clone(), session.app_version.clone())
|
||||
}),
|
||||
Some((2, "Dummy Boy".to_string(), "0.1.0".to_string()))
|
||||
);
|
||||
|
||||
firmware.tick(&InputSignals::default(), &mut platform);
|
||||
|
||||
match &firmware.state {
|
||||
FirmwareState::GameRunning(step) => assert_eq!(step.task_id, dummy_task),
|
||||
other => panic!("expected Dummy Boy GameRunning state, got {:?}", other),
|
||||
}
|
||||
assert_eq!(firmware.os.lifecycle().foreground_owner(), ForegroundOwner::Game(dummy_task));
|
||||
assert_eq!(firmware.os.vm().current_app_id(), 2);
|
||||
assert_eq!(firmware.os.vm().current_cartridge_title(), "Dummy Boy");
|
||||
|
||||
firmware.tick(&InputSignals::default(), &mut platform);
|
||||
|
||||
let dummy_ownership = firmware.os.vm().active_render_ownership();
|
||||
assert_eq!(dummy_ownership.app_mode, AppMode::Game);
|
||||
assert_eq!(dummy_ownership.app_id, 2);
|
||||
|
||||
let front = platform.local_hardware().gfx.front_buffer();
|
||||
let center = 240 + 135 * 480;
|
||||
assert_eq!(front[center], Color::GREEN.raw());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn failed_game_switch_returns_home_without_restoring_previous_game() {
|
||||
let root = TestGameRoot::new();
|
||||
@ -1068,6 +1166,8 @@ mod tests {
|
||||
};
|
||||
let tick_index_before_home = firmware.os.vm().tick_index();
|
||||
let render_ownership_before_home = firmware.os.vm().active_render_ownership();
|
||||
assert_eq!(render_ownership_before_home.app_mode, AppMode::Game);
|
||||
assert_eq!(render_ownership_before_home.app_id, 9);
|
||||
|
||||
firmware.request_home_from_host();
|
||||
|
||||
@ -1099,7 +1199,6 @@ mod tests {
|
||||
let render_ownership_after_home = firmware.os.vm().active_render_ownership();
|
||||
assert_eq!(render_ownership_after_home.app_mode, AppMode::Shell);
|
||||
assert_eq!(render_ownership_after_home.app_id, 0);
|
||||
assert!(render_ownership_after_home.epoch > render_ownership_before_home.epoch);
|
||||
assert!(firmware.os.lifecycle().pending_game_lifecycle_events().is_empty());
|
||||
assert_eq!(
|
||||
firmware.os.vm().delivered_game_lifecycle_events().last().map(|event| event.kind),
|
||||
@ -1128,6 +1227,8 @@ mod tests {
|
||||
firmware.request_home_from_host();
|
||||
firmware.tick(&signals, &mut platform);
|
||||
let hub_ownership = firmware.os.vm().active_render_ownership();
|
||||
assert_eq!(hub_ownership.app_mode, AppMode::Shell);
|
||||
assert_eq!(hub_ownership.app_id, 0);
|
||||
|
||||
assert!(firmware.resume_resident_game_from_home());
|
||||
assert!(matches!(firmware.state, FirmwareState::GameRunning(_)));
|
||||
@ -1140,7 +1241,6 @@ mod tests {
|
||||
let game_ownership = firmware.os.vm().active_render_ownership();
|
||||
assert_eq!(game_ownership.app_mode, AppMode::Game);
|
||||
assert_eq!(game_ownership.app_id, 9);
|
||||
assert!(game_ownership.epoch > hub_ownership.epoch);
|
||||
|
||||
let held_signals =
|
||||
InputSignals { up_signal: true, f_signal: true, x_pos: 72, y_pos: 90, ..signals };
|
||||
|
||||
@ -250,6 +250,12 @@ impl<'a> VmFacade<'a> {
|
||||
}
|
||||
|
||||
pub fn active_render_ownership(&self) -> RenderOwnership {
|
||||
if let Some(task_id) = self.foreground_session_task_id()
|
||||
&& let Some(session) = self.os.vm_sessions.for_task(task_id)
|
||||
{
|
||||
return session.runtime.render_manager.active_ownership();
|
||||
}
|
||||
|
||||
self.os.vm_runtime.render_manager.active_ownership()
|
||||
}
|
||||
|
||||
@ -317,4 +323,8 @@ impl<'a> VmFacade<'a> {
|
||||
.foreground_task()
|
||||
.or_else(|| self.os.foreground_stack.resident_game_task())
|
||||
}
|
||||
|
||||
fn foreground_session_task_id(&self) -> Option<TaskId> {
|
||||
self.os.task_manager.foreground_task()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{"type":"meta","next_id":{"DSC":44,"AGD":47,"DEC":40,"PLN":163,"LSN":53,"CLSN":1}}
|
||||
{"type":"discussion","id":"DSC-0043","status":"in_progress","ticket":"system-os-cartridge-switch-orchestrator","title":"SystemOS Cartridge Switch Orchestrator","created_at":"2026-07-03","updated_at":"2026-07-05","tags":["runtime","os","lifecycle","game","cartridge","architecture"],"agendas":[{"id":"AGD-0044","file":"AGD-0044-systemos-cartridge-switch-orchestrator.md","status":"accepted","created_at":"2026-07-03","updated_at":"2026-07-05"}],"decisions":[{"id":"DEC-0039","file":"DEC-0039-systemos-game-cartridge-switch-orchestrator.md","status":"accepted","created_at":"2026-07-05","updated_at":"2026-07-05","ref_agenda":"AGD-0044"}],"plans":[{"id":"PLN-0157","file":"PLN-0157-introduce-systemos-game-switch-target-contract.md","status":"done","created_at":"2026-07-05","updated_at":"2026-07-05","ref_decisions":["DEC-0039"]},{"id":"PLN-0158","file":"PLN-0158-terminate-resident-game-before-switching-cartridges.md","status":"done","created_at":"2026-07-05","updated_at":"2026-07-05","ref_decisions":["DEC-0039"]},{"id":"PLN-0159","file":"PLN-0159-recover-failed-game-switches-back-to-home.md","status":"done","created_at":"2026-07-05","updated_at":"2026-07-05","ref_decisions":["DEC-0039"]},{"id":"PLN-0160","file":"PLN-0160-add-dummy-boy-visual-switch-test-cartridge.md","status":"done","created_at":"2026-07-05","updated_at":"2026-07-05","ref_decisions":["DEC-0039"]},{"id":"PLN-0161","file":"PLN-0161-validate-stress-to-dummy-boy-game-switch-end-to-end.md","status":"open","created_at":"2026-07-05","updated_at":"2026-07-05","ref_decisions":["DEC-0039"]},{"id":"PLN-0162","file":"PLN-0162-specify-game-switch-contract-and-direct-boot-separation.md","status":"open","created_at":"2026-07-05","updated_at":"2026-07-05","ref_decisions":["DEC-0039"]}],"lessons":[]}
|
||||
{"type":"discussion","id":"DSC-0043","status":"in_progress","ticket":"system-os-cartridge-switch-orchestrator","title":"SystemOS Cartridge Switch Orchestrator","created_at":"2026-07-03","updated_at":"2026-07-05","tags":["runtime","os","lifecycle","game","cartridge","architecture"],"agendas":[{"id":"AGD-0044","file":"AGD-0044-systemos-cartridge-switch-orchestrator.md","status":"accepted","created_at":"2026-07-03","updated_at":"2026-07-05"}],"decisions":[{"id":"DEC-0039","file":"DEC-0039-systemos-game-cartridge-switch-orchestrator.md","status":"accepted","created_at":"2026-07-05","updated_at":"2026-07-05","ref_agenda":"AGD-0044"}],"plans":[{"id":"PLN-0157","file":"PLN-0157-introduce-systemos-game-switch-target-contract.md","status":"done","created_at":"2026-07-05","updated_at":"2026-07-05","ref_decisions":["DEC-0039"]},{"id":"PLN-0158","file":"PLN-0158-terminate-resident-game-before-switching-cartridges.md","status":"done","created_at":"2026-07-05","updated_at":"2026-07-05","ref_decisions":["DEC-0039"]},{"id":"PLN-0159","file":"PLN-0159-recover-failed-game-switches-back-to-home.md","status":"done","created_at":"2026-07-05","updated_at":"2026-07-05","ref_decisions":["DEC-0039"]},{"id":"PLN-0160","file":"PLN-0160-add-dummy-boy-visual-switch-test-cartridge.md","status":"done","created_at":"2026-07-05","updated_at":"2026-07-05","ref_decisions":["DEC-0039"]},{"id":"PLN-0161","file":"PLN-0161-validate-stress-to-dummy-boy-game-switch-end-to-end.md","status":"done","created_at":"2026-07-05","updated_at":"2026-07-05","ref_decisions":["DEC-0039"]},{"id":"PLN-0162","file":"PLN-0162-specify-game-switch-contract-and-direct-boot-separation.md","status":"open","created_at":"2026-07-05","updated_at":"2026-07-05","ref_decisions":["DEC-0039"]}],"lessons":[]}
|
||||
{"type":"discussion","id":"DSC-0039","status":"abandoned","ticket":"render-pipeline-family-and-future-3d","title":"Render Pipeline Family and Future 3D","created_at":"2026-06-04","updated_at":"2026-06-04","tags":["gfx","renderer","runtime","architecture","pipeline"],"agendas":[{"id":"AGD-0039","file":"AGD-0039-render-pipeline-family-and-future-3d.md","status":"abandoned","created_at":"2026-06-04","updated_at":"2026-06-04","_override_reason":"User explicitly chose to close this agenda without a new decision because DSC-0038 already established enough architecture for future extension, and 3D is intentionally deferred."}],"decisions":[],"plans":[],"lessons":[],"_override_reason":"User explicitly chose to close this agenda without a new decision because DSC-0038 already established enough architecture for future extension, and 3D is intentionally deferred."}
|
||||
{"type":"discussion","id":"DSC-0040","status":"done","ticket":"vm-render-parallel-execution-boundary","title":"VM and Render Parallel Execution Boundary","created_at":"2026-06-04","updated_at":"2026-06-06","tags":["runtime","renderer","vm","concurrency","architecture","perf"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0048","file":"discussion/lessons/DSC-0040-vm-render-parallel-execution-boundary/LSN-0048-render-workers-need-a-closed-packet-contract.md","status":"done","created_at":"2026-06-06","updated_at":"2026-06-06"}]}
|
||||
{"type":"discussion","id":"DSC-0041","status":"done","ticket":"foreground-stack-game-pause-shell-vm-backed","title":"Foreground Stack, Game Pause, and VM-Backed Shell Coexistence","created_at":"2026-06-05","updated_at":"2026-07-05","tags":["runtime","os","lifecycle","shell","game","vm","foreground","architecture"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0052","file":"discussion/lessons/DSC-0041-foreground-stack-game-pause-shell-vm-backed/LSN-0052-foreground-ownership-and-vm-session-ownership-are-separate.md","status":"done","created_at":"2026-07-05","updated_at":"2026-07-05"}]}
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
id: PLN-0161
|
||||
ticket: system-os-cartridge-switch-orchestrator
|
||||
title: Validate Stress To Dummy Boy Game Switch End To End
|
||||
status: open
|
||||
status: done
|
||||
created: 2026-07-05
|
||||
ref_decisions: [DEC-0039]
|
||||
tags: [runtime, os, lifecycle, game, cartridge, architecture]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user