Compare commits

...

5 Commits

Author SHA1 Message Date
6dc8cb5372
VM Context Ownership for Resident Game and VM-Backed Shell
All checks were successful
Intrepid/Prometeu/Runtime/pipeline/pr-master This commit looks good
2026-07-05 00:36:15 +01:00
c34bbaf936
implements PLN-0156 2026-07-05 00:30:56 +01:00
e613ab43c9
implements PLN-0155 2026-07-05 00:26:16 +01:00
4e05daf280
implements PLN-0154 2026-07-05 00:21:10 +01:00
8098dbd7f4
VM Context Ownership for Resident Game and VM-Backed Shell 2026-07-05 00:16:50 +01:00
39 changed files with 533 additions and 2880 deletions

View File

@ -9,7 +9,6 @@ use prometeu_hal::log::LogSource;
use prometeu_hal::telemetry::CertificationConfig;
use prometeu_hal::{InputSignals, RuntimePlatform};
use prometeu_system::{PrometeuHub, ResidentGameState, SystemOS};
use prometeu_vm::VirtualMachine;
/// PROMETEU Firmware.
///
@ -27,8 +26,6 @@ use prometeu_vm::VirtualMachine;
/// 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: SystemOS,
/// The internal state of the system launcher (Hub).
@ -46,7 +43,6 @@ impl Firmware {
/// Initializes the firmware in the `Reset` state.
pub fn new(cap_config: Option<CertificationConfig>) -> Self {
Self {
vm: VirtualMachine::default(),
os: SystemOS::new(cap_config),
hub: PrometeuHub::new(),
state: FirmwareState::Reset(ResetStep),
@ -122,7 +118,6 @@ impl Firmware {
/// Dispatches the `on_enter` event to the current state implementation.
fn on_enter(&mut self, signals: &InputSignals, platform: &mut dyn RuntimePlatform) {
let mut req = PrometeuContext {
vm: &mut self.vm,
os: &mut self.os,
hub: &mut self.hub,
boot_target: &self.boot_target,
@ -150,7 +145,6 @@ impl Firmware {
platform: &mut dyn RuntimePlatform,
) -> Option<FirmwareState> {
let mut req = PrometeuContext {
vm: &mut self.vm,
os: &mut self.os,
hub: &mut self.hub,
boot_target: &self.boot_target,
@ -173,7 +167,6 @@ impl Firmware {
/// Dispatches the `on_exit` event to the current state implementation.
fn on_exit(&mut self, signals: &InputSignals, platform: &mut dyn RuntimePlatform) {
let mut req = PrometeuContext {
vm: &mut self.vm,
os: &mut self.os,
hub: &mut self.hub,
boot_target: &self.boot_target,
@ -532,6 +525,24 @@ mod tests {
assert!(matches!(firmware.state, FirmwareState::LaunchHub(_)));
}
#[test]
fn reset_clears_vm_sessions_and_runtime_identity() {
let mut firmware = Firmware::new(None);
let mut platform = TestPlatform::new();
let signals = InputSignals::default();
firmware.load_cartridge(valid_cartridge(AppMode::Game));
firmware.tick(&signals, &mut platform);
assert_eq!(firmware.os.sessions().vm_session_count(), 1);
assert_eq!(firmware.os.vm().current_cartridge_title(), "Valid Cart");
firmware.change_state(FirmwareState::Reset(ResetStep), &signals, &mut platform);
assert_eq!(firmware.os.sessions().vm_session_count(), 0);
assert_eq!(firmware.os.vm().current_cartridge_title(), "");
}
#[test]
fn load_cartridge_routes_system_apps_to_system_running() {
let mut firmware = Firmware::new(None);

View File

@ -20,14 +20,29 @@ impl HubHomeStep {
}
pub fn on_update(&mut self, ctx: &mut PrometeuContext) -> Option<FirmwareState> {
let outcome = ctx.hub.update_shell_profile(ctx.os, ctx.vm, ctx.signals, ctx.platform);
let outcome = ctx.hub.update_shell_profile(ctx.os, ctx.platform);
if let Some(report) = outcome.crash {
return Some(FirmwareState::AppCrashes(AppCrashesStep { report }));
}
match outcome.action {
Some(SystemProfileAction::LaunchNativeShell(app)) => {
let task_id = ctx.os.sessions().create_native_shell_task(app.app_id(), app.title());
let task_id = match ctx
.os
.sessions()
.try_create_native_shell_task(app.app_id(), app.title())
{
Ok(task_id) => task_id,
Err(error) => {
ctx.os.log(
LogLevel::Warn,
LogSource::Hub,
0,
format!("Failed to launch native shell {}: {:?}", app.title(), error),
);
return None;
}
};
let id = ctx.os.windows().add_window(
app.title().to_string(),
WindowOwner::Task(task_id),

View File

@ -38,13 +38,13 @@ impl LoadCartridgeStep {
self.cartridge.assets.clone(),
);
match ctx.os.sessions().load_vm_cartridge(ctx.vm, &self.cartridge) {
match ctx.os.sessions().load_vm_cartridge(&self.cartridge) {
Ok(loaded) => {
self.loaded_task_id = Some(loaded.task_id);
self.loaded_app_mode = Some(loaded.app_mode);
}
Err(report) => {
self.init_error = Some(report);
self.init_error = Some(report.into_crash_report("load VM cartridge into session"));
}
}
}

View File

@ -9,7 +9,8 @@ pub struct ResetStep;
impl ResetStep {
pub fn on_enter(&mut self, ctx: &mut PrometeuContext) {
ctx.os.log(LogLevel::Info, LogSource::Pos, 0, "Firmware Reset".to_string());
ctx.os.vm().reset(ctx.vm);
ctx.os.sessions().clear_vm_sessions();
ctx.os.vm().reset_global_runtime_state();
}
pub fn on_update(&mut self, ctx: &mut PrometeuContext) -> Option<FirmwareState> {

View File

@ -1,10 +1,8 @@
use crate::firmware::boot_target::BootTarget;
use prometeu_hal::{InputSignals, RuntimePlatform};
use prometeu_system::{PrometeuHub, SystemOS};
use prometeu_vm::VirtualMachine;
pub struct PrometeuContext<'a> {
pub vm: &'a mut VirtualMachine,
pub os: &'a mut SystemOS,
pub hub: &'a mut PrometeuHub,
pub boot_target: &'a BootTarget,

View File

@ -40,7 +40,7 @@ pub enum DebugResponse {
Breakpoints { pcs: Vec<usize> },
}
#[derive(Debug, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HandshakeCartridge {
pub app_id: u32,
pub title: String,

View File

@ -6,7 +6,7 @@ mod services;
pub use crash_report::CrashReport;
pub use os::{
DEFAULT_GAME_PAUSE_BUDGET_TICKS, GameLifecycleEvent, GameLifecycleEventKind, LifecycleError,
LifecycleOperation, SystemOS,
LifecycleOperation, SessionError, SystemOS,
};
pub use programs::{NativeShellApp, PrometeuHub, SystemProfileAction, SystemProfileUpdate};
pub use prometeu_hal::{RenderWorkerBackend, RenderWorkerFrameSink};

View File

@ -6,6 +6,6 @@ mod window;
pub use fs::FsFacade;
pub use lifecycle::LifecycleFacade;
pub use sessions::SessionsFacade;
pub use sessions::{SessionError, SessionsFacade};
pub use vm::VmFacade;
pub use window::WindowFacade;

View File

@ -1,11 +1,11 @@
use crate::CrashReport;
use crate::os::LifecycleError;
use crate::os::SystemOS;
use crate::process::ProcessId;
use crate::task::TaskId;
use crate::vm_session::{VmSession, VmSessionError};
use prometeu_hal::app_mode::AppMode;
use prometeu_hal::cartridge::Cartridge;
use prometeu_vm::VirtualMachine;
use std::sync::Arc;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@ -15,21 +15,55 @@ pub struct LoadedVmCartridge {
pub reused_existing_session: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SessionError {
Lifecycle(LifecycleError),
VmSession(VmSessionError),
TaskNotFound(TaskId),
ProcessNotFound(ProcessId),
MissingVmSession(TaskId),
Initialization(CrashReport),
}
impl SessionError {
pub fn into_crash_report(self, operation: &str) -> CrashReport {
match self {
Self::Initialization(report) => report,
error => {
CrashReport::VmPanic { message: format!("{operation} failed: {error:?}"), pc: None }
}
}
}
}
impl From<LifecycleError> for SessionError {
fn from(error: LifecycleError) -> Self {
Self::Lifecycle(error)
}
}
impl From<VmSessionError> for SessionError {
fn from(error: VmSessionError) -> Self {
Self::VmSession(error)
}
}
pub struct SessionsFacade<'a> {
pub(in crate::os) os: &'a mut SystemOS,
}
impl<'a> SessionsFacade<'a> {
#[cfg(test)]
pub fn create_vm_game_task(&mut self, app_id: u32, title: impl Into<String>) -> TaskId {
self.try_create_vm_game_task(app_id, title)
.expect("newly created game task should have an associated process")
.expect("test/setup VM game creation should succeed")
}
pub fn try_create_vm_game_task(
&mut self,
app_id: u32,
title: impl Into<String>,
) -> Result<TaskId, LifecycleError> {
) -> Result<TaskId, SessionError> {
if let Some(session) = self.os.vm_sessions.resident_game_for_app(app_id) {
return Ok(session.task_id);
}
@ -38,43 +72,54 @@ impl<'a> SessionsFacade<'a> {
let process_id = self.os.process_manager.spawn_vm_game(app_id, title.clone());
let task_id = self.os.task_manager.create_game_task(process_id, app_id, title);
self.os.lifecycle().set_game_foreground_task(task_id)?;
self.create_vm_session_for_task(task_id)
.expect("newly created game task should create a VM session");
self.try_create_vm_session_for_task(task_id)?;
Ok(task_id)
}
#[cfg(test)]
pub fn create_vm_shell_task(&mut self, app_id: u32, title: impl Into<String>) -> TaskId {
self.try_create_vm_shell_task(app_id, title)
.expect("test/setup VM shell creation should succeed")
}
pub fn try_create_vm_shell_task(
&mut self,
app_id: u32,
title: impl Into<String>,
) -> Result<TaskId, SessionError> {
let title = title.into();
let process_id = self.os.process_manager.spawn_vm_shell(app_id, title.clone());
let task_id = self.os.task_manager.create_shell_task(process_id, app_id, title);
self.os
.lifecycle()
.set_shell_foreground_task(task_id)
.expect("newly created shell task should have an associated process");
self.create_vm_session_for_task(task_id)
.expect("newly created shell task should create a VM session");
self.os.lifecycle().set_shell_foreground_task(task_id)?;
self.try_create_vm_session_for_task(task_id)?;
task_id
Ok(task_id)
}
#[cfg(test)]
pub fn create_native_shell_task(&mut self, app_id: u32, title: impl Into<String>) -> TaskId {
self.try_create_native_shell_task(app_id, title)
.expect("test/setup native shell creation should succeed")
}
pub fn try_create_native_shell_task(
&mut self,
app_id: u32,
title: impl Into<String>,
) -> Result<TaskId, SessionError> {
let title = title.into();
let process_id = self.os.process_manager.spawn_native_shell(app_id, title.clone());
let task_id = self.os.task_manager.create_shell_task(process_id, app_id, title);
self.os
.lifecycle()
.set_shell_foreground_task(task_id)
.expect("newly created native shell task should have an associated process");
self.os.lifecycle().set_shell_foreground_task(task_id)?;
task_id
Ok(task_id)
}
pub fn load_vm_cartridge(
&mut self,
legacy_vm: &mut VirtualMachine,
cartridge: &Cartridge,
) -> Result<LoadedVmCartridge, CrashReport> {
) -> Result<LoadedVmCartridge, SessionError> {
let existing_game_task = if cartridge.app_mode == AppMode::Game {
self.os
.vm_sessions
@ -87,20 +132,15 @@ impl<'a> SessionsFacade<'a> {
let (task_id, reused_existing_session) = if let Some(task_id) = existing_game_task {
(task_id, true)
} else if cartridge.app_mode == AppMode::Shell {
(self.create_vm_shell_task(cartridge.app_id, cartridge.title.clone()), false)
(self.try_create_vm_shell_task(cartridge.app_id, cartridge.title.clone())?, false)
} else {
let task_id = self
.try_create_vm_game_task(cartridge.app_id, cartridge.title.clone())
.map_err(|error| CrashReport::VmPanic {
message: format!("failed to create VM game task: {error:?}"),
pc: None,
})?;
let task_id =
self.try_create_vm_game_task(cartridge.app_id, cartridge.title.clone())?;
(task_id, false)
};
if !reused_existing_session {
self.initialize_session_cartridge(task_id, cartridge)?;
self.initialize_legacy_active_vm(legacy_vm, cartridge)?;
self.try_initialize_session_cartridge(task_id, cartridge)?;
}
Ok(LoadedVmCartridge { task_id, app_mode: cartridge.app_mode, reused_existing_session })
@ -122,13 +162,18 @@ impl<'a> SessionsFacade<'a> {
self.os.vm_sessions.len()
}
fn create_vm_session_for_task(&mut self, task_id: TaskId) -> Result<(), VmSessionError> {
let task = self.os.task_manager.get(task_id).expect("session task should exist").clone();
pub fn clear_vm_sessions(&mut self) {
self.os.vm_sessions.clear();
}
pub fn try_create_vm_session_for_task(&mut self, task_id: TaskId) -> Result<(), SessionError> {
let task =
self.os.task_manager.get(task_id).ok_or(SessionError::TaskNotFound(task_id))?.clone();
let process = self
.os
.process_manager
.get(task.process_id)
.expect("session process should exist")
.ok_or(SessionError::ProcessNotFound(task.process_id))?
.clone();
let cap_config = self.os.vm_runtime.certifier.config;
@ -137,33 +182,28 @@ impl<'a> SessionsFacade<'a> {
.vm_sessions
.create_for_task(&task, &process, "", Some(cap_config), logs_count)
.map(|_| ())
.map_err(SessionError::from)
}
fn initialize_session_cartridge(
pub fn try_initialize_session_cartridge(
&mut self,
task_id: TaskId,
cartridge: &Cartridge,
) -> Result<(), CrashReport> {
) -> Result<(), SessionError> {
let session = self
.os
.vm_sessions
.for_task_mut(task_id)
.expect("loaded VM task should have a VM session");
.ok_or(SessionError::MissingVmSession(task_id))?;
session.app_id = cartridge.app_id;
session.title = cartridge.title.clone();
session.app_version = cartridge.app_version.clone();
session.app_mode = cartridge.app_mode;
session.clear_cartridge_service_state();
session.runtime.initialize_vm(&mut self.os.log_service, &mut session.vm, cartridge)
}
fn initialize_legacy_active_vm(
&mut self,
legacy_vm: &mut VirtualMachine,
cartridge: &Cartridge,
) -> Result<(), CrashReport> {
self.os.clear_cartridge_service_state();
self.os.vm_runtime.initialize_vm(&mut self.os.log_service, legacy_vm, cartridge)
session
.runtime
.initialize_vm(&mut self.os.log_service, &mut session.vm, cartridge)
.map_err(SessionError::Initialization)
}
}

View File

@ -3,14 +3,13 @@ use crate::os::{GameLifecycleEvent, SystemOS};
use crate::task::TaskId;
use crate::vm_session::VmSession;
use crate::{RenderWorkerConfig, RenderWorkerController};
use prometeu_bytecode::Value;
use prometeu_hal::app_mode::AppMode;
use prometeu_hal::cartridge::Cartridge;
use prometeu_hal::telemetry::{CertificationConfig, TelemetryFrame};
use prometeu_hal::{
FrameId, InputSignals, RenderOwnership, RenderWorkerBackend, RenderWorkerError,
RenderWorkerFrameSink, RuntimePlatform,
};
use prometeu_vm::VirtualMachine;
use std::sync::atomic::Ordering;
pub struct VmFacade<'a> {
@ -18,36 +17,6 @@ pub struct VmFacade<'a> {
}
impl<'a> VmFacade<'a> {
pub fn initialize(
&mut self,
vm: &mut VirtualMachine,
cartridge: &Cartridge,
) -> Result<(), CrashReport> {
self.os.clear_cartridge_service_state();
self.os.vm_runtime.initialize_vm(&mut self.os.log_service, vm, cartridge)
}
pub fn tick(
&mut self,
vm: &mut VirtualMachine,
signals: &InputSignals,
platform: &mut dyn RuntimePlatform,
) -> Option<CrashReport> {
self.deliver_pending_game_lifecycle_events();
self.os.vm_runtime.tick(
&mut self.os.log_service,
&mut self.os.fs,
&mut self.os.fs_state,
&mut self.os.memcard,
&mut self.os.open_files,
&mut self.os.next_handle,
vm,
signals,
platform,
)
}
pub fn tick_session(
&mut self,
task_id: TaskId,
@ -76,25 +45,6 @@ impl<'a> VmFacade<'a> {
)
}
pub fn deliver_pending_game_lifecycle_events(&mut self) {
if self.os.vm_runtime.current_cartridge_app_mode == AppMode::Game {
let resident_game_task = self.os.foreground_stack.resident_game_task();
let mut retained = Vec::new();
let mut delivered = Vec::new();
for event in self.os.game_lifecycle_events.drain(..) {
if Some(event.task_id) == resident_game_task {
delivered.push(event);
} else {
retained.push(event);
}
}
self.os.game_lifecycle_events = retained;
self.os.vm_runtime.deliver_game_lifecycle_events(delivered);
}
}
fn deliver_pending_game_lifecycle_events_to_session(&mut self, task_id: TaskId) {
let Some(session) = self.os.vm_sessions.for_task(task_id) else {
return;
@ -121,16 +71,59 @@ impl<'a> VmFacade<'a> {
}
}
pub fn reset(&mut self, vm: &mut VirtualMachine) {
self.os.vm_runtime.reset(vm);
pub fn reset_global_runtime_state(&mut self) {
self.os.clear_cartridge_service_state();
self.os.vm_runtime.clear_cartridge_state();
}
pub fn debug_step_instruction(
pub fn debug_step_active_session(
&mut self,
vm: &mut VirtualMachine,
platform: &mut dyn RuntimePlatform,
) -> Option<CrashReport> {
self.os.vm_runtime.debug_step_instruction(&mut self.os.log_service, vm, platform)
let Some(task_id) = self.active_session_task_id() else {
return Some(CrashReport::VmPanic {
message: "debug step requested without an active VM session".to_string(),
pc: None,
});
};
let Some(session) = self.os.vm_sessions.for_task_mut(task_id) else {
return Some(CrashReport::VmPanic {
message: format!("active debug task {:?} has no VM session", task_id),
pc: None,
});
};
session.runtime.debug_step_instruction(&mut self.os.log_service, &mut session.vm, platform)
}
pub fn active_pc(&self) -> Option<usize> {
self.active_session().map(|session| session.vm.pc())
}
pub fn active_operand_stack_top(&self, n: usize) -> Vec<Value> {
self.active_session().map(|session| session.vm.operand_stack_top(n)).unwrap_or_default()
}
pub fn insert_active_breakpoint(&mut self, pc: usize) -> bool {
if let Some(session) = self.active_session_mut() {
session.vm.insert_breakpoint(pc);
return true;
}
false
}
pub fn remove_active_breakpoint(&mut self, pc: usize) -> bool {
if let Some(session) = self.active_session_mut() {
session.vm.remove_breakpoint(pc);
return true;
}
false
}
pub fn active_breakpoints_list(&self) -> Vec<usize> {
self.active_session().map(|session| session.vm.breakpoints_list()).unwrap_or_default()
}
pub fn paused(&self) -> bool {
@ -314,11 +307,14 @@ impl<'a> VmFacade<'a> {
}
fn active_session_mut(&mut self) -> Option<&mut VmSession> {
let task_id = self
.os
.task_manager
.foreground_task()
.or_else(|| self.os.foreground_stack.resident_game_task())?;
let task_id = self.active_session_task_id()?;
self.os.vm_sessions.for_task_mut(task_id)
}
fn active_session_task_id(&self) -> Option<TaskId> {
self.os
.task_manager
.foreground_task()
.or_else(|| self.os.foreground_stack.resident_game_task())
}
}

View File

@ -2,7 +2,9 @@ mod facades;
mod lifecycle;
mod system_os;
pub use facades::{FsFacade, LifecycleFacade, SessionsFacade, VmFacade, WindowFacade};
pub use facades::{
FsFacade, LifecycleFacade, SessionError, SessionsFacade, VmFacade, WindowFacade,
};
pub use lifecycle::{
DEFAULT_GAME_PAUSE_BUDGET_TICKS, GameLifecycleEvent, GameLifecycleEventKind, LifecycleError,
LifecycleOperation,

View File

@ -143,17 +143,33 @@ impl SystemOS {
#[cfg(test)]
mod tests {
use super::*;
use crate::os::{GameLifecycleEventKind, LifecycleError, LifecycleOperation};
use crate::process::{ProcessKind, ProcessState};
use crate::os::{GameLifecycleEventKind, LifecycleError, LifecycleOperation, SessionError};
use crate::process::{ProcessId, ProcessKind, ProcessState};
use crate::services::foreground::{ForegroundOwner, ResidentGame, ResidentGameState};
use crate::task::{TaskId, TaskState};
use prometeu_hal::app_mode::AppMode;
use prometeu_hal::cartridge::{AssetsPayloadSource, Cartridge};
use prometeu_hal::syscalls::caps;
fn process_state_for_task(os: &SystemOS, task_id: TaskId) -> ProcessState {
let task = os.task_manager.get(task_id).expect("task should exist");
os.process_manager.get(task.process_id).expect("process should exist").state
}
fn session_test_cartridge(app_mode: AppMode, app_id: u32, title: &str) -> Cartridge {
Cartridge {
app_id,
title: title.to_string(),
app_version: "1.0.0".to_string(),
app_mode,
capabilities: caps::NONE,
program: vec![],
assets: AssetsPayloadSource::empty(),
asset_table: vec![],
preload: vec![],
}
}
#[test]
fn set_foreground_task_marks_task_and_process_running() {
let mut os = SystemOS::new(None);
@ -295,6 +311,43 @@ mod tests {
assert_eq!(shell_session.open_files.get(&1).map(String::as_str), Some("/shell.dat"));
}
#[test]
fn vm_debug_operations_target_foreground_shell_before_resident_game() {
let mut os = SystemOS::new(None);
let game = os.sessions().create_vm_game_task(42, "Sector Crawl");
os.lifecycle().request_home_from_game(game).expect("home request should succeed");
os.lifecycle().advance_game_pause_budget(game).expect("budget should expire");
let shell = os.sessions().create_vm_shell_task(7, "Settings");
{
let mut sessions = os.sessions();
let game_session =
sessions.vm_session_for_task_mut(game).expect("game VM session should exist");
game_session.vm.insert_breakpoint(10);
}
assert!(os.vm().insert_active_breakpoint(20));
assert_eq!(os.vm().active_breakpoints_list(), vec![20]);
let sessions = os.sessions();
assert_eq!(
sessions
.vm_session_for_task(game)
.expect("game VM session should exist")
.vm
.breakpoints_list(),
vec![10]
);
assert_eq!(
sessions
.vm_session_for_task(shell)
.expect("shell VM session should exist")
.vm
.breakpoints_list(),
vec![20]
);
}
#[test]
fn home_request_notifies_game_before_budget_suspends_it() {
let mut os = SystemOS::new(None);
@ -416,6 +469,60 @@ mod tests {
);
}
#[test]
fn vm_shell_load_returns_typed_error_when_foreground_shell_exists() {
let mut os = SystemOS::new(None);
let first = os.sessions().create_vm_shell_task(7, "Settings");
let cartridge = session_test_cartridge(AppMode::Shell, 8, "Files");
let error = os.sessions().load_vm_cartridge(&cartridge).expect_err("load should fail");
match error {
SessionError::Lifecycle(LifecycleError::ForegroundShellAlreadyExists {
existing,
requested,
}) => {
assert_eq!(existing, first);
assert_ne!(requested, first);
}
other => panic!("expected foreground shell lifecycle error, got {:?}", other),
}
}
#[test]
fn vm_session_creation_returns_typed_error_for_missing_task() {
let mut os = SystemOS::new(None);
assert_eq!(
os.sessions().try_create_vm_session_for_task(TaskId(999)),
Err(SessionError::TaskNotFound(TaskId(999)))
);
}
#[test]
fn vm_session_creation_returns_typed_error_for_missing_process() {
let mut os = SystemOS::new(None);
let task_id = os.task_manager.create_game_task(ProcessId(999), 42, "Broken");
assert_eq!(
os.sessions().try_create_vm_session_for_task(task_id),
Err(SessionError::ProcessNotFound(ProcessId(999)))
);
}
#[test]
fn session_cartridge_initialization_returns_typed_error_for_missing_session() {
let mut os = SystemOS::new(None);
let process_id = os.process_manager.spawn_vm_game(42, "Sector Crawl");
let task_id = os.task_manager.create_game_task(process_id, 42, "Sector Crawl");
let cartridge = session_test_cartridge(AppMode::Game, 42, "Sector Crawl");
assert_eq!(
os.sessions().try_initialize_session_cartridge(task_id, &cartridge),
Err(SessionError::MissingVmSession(task_id))
);
}
#[test]
fn closing_foreground_shell_returns_to_hub() {
let mut os = SystemOS::new(None);

View File

@ -6,7 +6,6 @@ use prometeu_hal::{
FrameId, GfxUiCommand, InputPlatform, InputSignals, RenderSubmission, RuntimePlatform,
ShellUiFramePacket,
};
use prometeu_vm::VirtualMachine;
use std::path::PathBuf;
const SHELL_A_BUTTON: Rect = Rect { x: 112, y: 172, w: 112, h: 32 };
@ -175,24 +174,17 @@ impl PrometeuHub {
pub fn update_shell_profile(
&mut self,
os: &mut SystemOS,
vm: &mut VirtualMachine,
signals: &InputSignals,
platform: &mut dyn RuntimePlatform,
) -> SystemProfileUpdate {
let mut crash = None;
let mut action = self.gui_update(os, platform);
if os.windows().focused_window().is_some() {
if platform.input().pad().start().down {
if os.windows().focused_window().is_some() && platform.input().pad().start().down {
action = Some(SystemProfileAction::CloseShell);
} else if action != Some(SystemProfileAction::CloseShell) {
crash = os.vm().tick(vm, signals, platform);
}
}
self.render(os, platform);
SystemProfileUpdate { crash, action }
SystemProfileUpdate { crash: None, action }
}
pub fn update_vm_shell_profile(

View File

@ -138,6 +138,10 @@ impl VmSessionRegistry {
pub fn is_empty(&self) -> bool {
self.sessions.is_empty()
}
pub fn clear(&mut self) {
self.sessions.clear();
}
}
fn app_mode_for_task_and_process(

View File

@ -28,6 +28,8 @@ pub struct HostDebugger {
last_telemetry_frame: u64,
/// Last fault summary sent to the debugger client.
last_fault_summary: Option<String>,
/// Debug cartridge metadata captured before the VM session exists.
debug_cartridge: Option<HandshakeCartridge>,
}
impl Default for HostDebugger {
@ -46,19 +48,25 @@ impl HostDebugger {
last_log_seq: 0,
last_telemetry_frame: 0,
last_fault_summary: None,
debug_cartridge: None,
}
}
/// Configures the debugger based on the boot target.
/// If debug mode is enabled, it binds to the specified TCP port.
pub fn setup_boot_target(&mut self, boot_target: &BootTarget, firmware: &mut Firmware) {
pub fn setup_boot_target(&mut self, boot_target: &BootTarget, _firmware: &mut Firmware) {
if let BootTarget::Cartridge { path, debug: true, debug_port } = boot_target {
self.waiting_for_start = true;
// Pre-load cartridge metadata so the Handshake message can contain
// valid information about the App being debugged.
// valid information before the VM session is created.
if let Ok(cartridge) = CartridgeLoader::load(path) {
let _ = firmware.os.vm().initialize(&mut firmware.vm, &cartridge);
self.debug_cartridge = Some(HandshakeCartridge {
app_id: cartridge.app_id,
title: cartridge.title,
app_version: cartridge.app_version,
app_mode: cartridge.app_mode,
});
}
match TcpListener::bind(format!("127.0.0.1:{}", debug_port)) {
@ -116,12 +124,7 @@ impl HostDebugger {
let handshake = DebugResponse::Handshake {
protocol_version: DEVTOOLS_PROTOCOL_VERSION,
runtime_version: "0.1".to_string(),
cartridge: HandshakeCartridge {
app_id: firmware.os.vm().current_app_id(),
title: firmware.os.vm().current_cartridge_title(),
app_version: firmware.os.vm().current_cartridge_app_version(),
app_mode: firmware.os.vm().current_cartridge_app_mode(),
},
cartridge: self.handshake_cartridge(firmware),
};
self.send_response(handshake);
} else {
@ -201,7 +204,7 @@ impl HostDebugger {
DebugCommand::Step => {
// Execute exactly one instruction and keep paused.
firmware.os.vm().set_paused(true);
let _ = firmware.os.vm().debug_step_instruction(&mut firmware.vm, platform);
let _ = firmware.os.vm().debug_step_active_session(platform);
}
DebugCommand::StepFrame => {
// Execute until the end of the current logical frame.
@ -210,10 +213,10 @@ impl HostDebugger {
}
DebugCommand::GetState => {
// Return detailed VM register and stack state.
let stack_top = firmware.vm.operand_stack_top(10);
let stack_top = firmware.os.vm().active_operand_stack_top(10);
let resp = DebugResponse::GetState {
pc: firmware.vm.pc(),
pc: firmware.os.vm().active_pc().unwrap_or(0),
stack_top,
frame_index: firmware.os.vm().logical_frame_index(),
app_id: firmware.os.vm().current_app_id(),
@ -221,18 +224,37 @@ impl HostDebugger {
self.send_response(resp);
}
DebugCommand::SetBreakpoint { pc } => {
firmware.vm.insert_breakpoint(pc);
let _ = firmware.os.vm().insert_active_breakpoint(pc);
}
DebugCommand::ListBreakpoints => {
let pcs = firmware.vm.breakpoints_list();
let pcs = firmware.os.vm().active_breakpoints_list();
self.send_response(DebugResponse::Breakpoints { pcs });
}
DebugCommand::ClearBreakpoint { pc } => {
firmware.vm.remove_breakpoint(pc);
let _ = firmware.os.vm().remove_active_breakpoint(pc);
}
}
}
fn handshake_cartridge(&self, firmware: &mut Firmware) -> HandshakeCartridge {
let app_id = firmware.os.vm().current_app_id();
if app_id != 0 {
return HandshakeCartridge {
app_id,
title: firmware.os.vm().current_cartridge_title(),
app_version: firmware.os.vm().current_cartridge_app_version(),
app_mode: firmware.os.vm().current_cartridge_app_mode(),
};
}
self.debug_cartridge.clone().unwrap_or_else(|| HandshakeCartridge {
app_id,
title: firmware.os.vm().current_cartridge_title(),
app_version: firmware.os.vm().current_cartridge_app_version(),
app_mode: firmware.os.vm().current_cartridge_app_mode(),
})
}
pub(crate) fn cert_event_from_snapshot(
tag: u16,
telemetry: TelemetryFrame,
@ -315,7 +337,7 @@ impl HostDebugger {
// Map specific internal log tags to protocol events.
if event.tag == 0xDEB1 {
self.send_event(DebugEvent::BreakpointHit {
pc: firmware.vm.pc(),
pc: firmware.os.vm().active_pc().unwrap_or(0),
frame_index: firmware.os.vm().logical_frame_index(),
});
}
@ -409,6 +431,52 @@ mod tests {
assert!(firmware.os.vm().debug_step_requested());
}
#[test]
fn breakpoint_commands_mutate_active_vm_session() {
let mut debugger = HostDebugger::new();
let mut firmware = Firmware::new(None);
let mut platform = TestPlatform::new();
let task_id = firmware
.os
.sessions()
.try_create_vm_game_task(42, "Sector Crawl")
.expect("debugger test should create a game session");
debugger.handle_command(
DebugCommand::SetBreakpoint { pc: 123 },
&mut firmware,
&mut platform,
);
assert_eq!(
firmware
.os
.sessions()
.vm_session_for_task(task_id)
.expect("game VM session should exist")
.vm
.breakpoints_list(),
vec![123]
);
debugger.handle_command(
DebugCommand::ClearBreakpoint { pc: 123 },
&mut firmware,
&mut platform,
);
assert!(
firmware
.os
.sessions()
.vm_session_for_task(task_id)
.expect("game VM session should exist")
.vm
.breakpoints_list()
.is_empty()
);
}
#[test]
fn handle_command_start_leaves_waiting_for_start_mode() {
let mut debugger = HostDebugger::new();

View File

@ -1,8 +1,8 @@
{"type":"meta","next_id":{"DSC":44,"AGD":47,"DEC":39,"PLN":154,"LSN":52,"CLSN":1}}
{"type":"meta","next_id":{"DSC":44,"AGD":47,"DEC":39,"PLN":157,"LSN":53,"CLSN":1}}
{"type":"discussion","id":"DSC-0043","status":"open","ticket":"system-os-cartridge-switch-orchestrator","title":"SystemOS Cartridge Switch Orchestrator","created_at":"2026-07-03","updated_at":"2026-07-03","tags":["runtime","os","lifecycle","game","cartridge","architecture"],"agendas":[{"id":"AGD-0044","file":"AGD-0044-systemos-cartridge-switch-orchestrator.md","status":"open","created_at":"2026-07-03","updated_at":"2026-07-03"}],"decisions":[],"plans":[],"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":"in_progress","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-04","tags":["runtime","os","lifecycle","shell","game","vm","foreground","architecture"],"agendas":[{"id":"AGD-0041","file":"AGD-0041-foreground-stack-game-pause-shell-vm-backed.md","status":"accepted","created_at":"2026-06-05","updated_at":"2026-07-03"},{"id":"AGD-0046","file":"AGD-0046-vm-context-ownership-for-resident-game-and-vm-backed-shell.md","status":"accepted","created_at":"2026-07-04","updated_at":"2026-07-04"}],"decisions":[{"id":"DEC-0037","file":"DEC-0037-foreground-stack-and-game-pause-contract.md","status":"accepted","created_at":"2026-07-03","updated_at":"2026-07-03","ref_agenda":"AGD-0041"},{"id":"DEC-0038","file":"DEC-0038-vm-session-ownership-for-vm-backed-processes.md","status":"accepted","created_at":"2026-07-04","updated_at":"2026-07-04","ref_agenda":"AGD-0046"}],"plans":[{"id":"PLN-0136","file":"PLN-0136-route-desktop-home-key-outside-guest-input.md","status":"done","created_at":"2026-07-03","updated_at":"2026-07-03","ref_decisions":["DEC-0037"]},{"id":"PLN-0137","file":"PLN-0137-implement-systemos-foreground-stack-state.md","status":"done","created_at":"2026-07-03","updated_at":"2026-07-03","ref_decisions":["DEC-0037"]},{"id":"PLN-0138","file":"PLN-0138-specify-foreground-pause-and-home-contract.md","status":"done","created_at":"2026-07-03","updated_at":"2026-07-03","ref_decisions":["DEC-0037"]},{"id":"PLN-0139","file":"PLN-0139-validate-game-home-shell-game-end-to-end.md","status":"done","created_at":"2026-07-03","updated_at":"2026-07-03","ref_decisions":["DEC-0037"]},{"id":"PLN-0140","file":"PLN-0140-deliver-game-pause-resume-and-suspension.md","status":"done","created_at":"2026-07-03","updated_at":"2026-07-03","ref_decisions":["DEC-0037"]},{"id":"PLN-0141","file":"PLN-0141-integrate-render-audio-and-input-pause-boundaries.md","status":"done","created_at":"2026-07-03","updated_at":"2026-07-03","ref_decisions":["DEC-0037"]},{"id":"PLN-0142","file":"PLN-0142-deliver-game-lifecycle-events-to-vm-runtime.md","status":"done","created_at":"2026-07-04","updated_at":"2026-07-04","ref_decisions":["DEC-0037"]},{"id":"PLN-0143","file":"PLN-0143-guarantee-cooperative-pause-budget-tick-before-suspension.md","status":"done","created_at":"2026-07-04","updated_at":"2026-07-04","ref_decisions":["DEC-0037"]},{"id":"PLN-0144","file":"PLN-0144-suspend-resident-game-when-shell-takes-foreground.md","status":"done","created_at":"2026-07-04","updated_at":"2026-07-04","ref_decisions":["DEC-0037"]},{"id":"PLN-0145","file":"PLN-0145-centralize-resident-game-resume-transition.md","status":"done","created_at":"2026-07-04","updated_at":"2026-07-04","ref_decisions":["DEC-0037"]},{"id":"PLN-0146","file":"PLN-0146-consolidate-lifecycle-process-lookup-helpers.md","status":"done","created_at":"2026-07-04","updated_at":"2026-07-04","ref_decisions":["DEC-0037"]},{"id":"PLN-0147","file":"PLN-0147-move-native-shell-profile-update-into-hub.md","status":"done","created_at":"2026-07-04","updated_at":"2026-07-04","ref_decisions":["DEC-0037"]},{"id":"PLN-0148","file":"PLN-0148-introduce-vm-session-registry-and-identity.md","status":"done","created_at":"2026-07-04","updated_at":"2026-07-04","ref_decisions":["DEC-0038"]},{"id":"PLN-0149","file":"PLN-0149-move-vm-runtime-state-into-vm-sessions.md","status":"done","created_at":"2026-07-04","updated_at":"2026-07-04","ref_decisions":["DEC-0038"]},{"id":"PLN-0150","file":"PLN-0150-route-cartridge-loading-through-vm-sessions.md","status":"done","created_at":"2026-07-04","updated_at":"2026-07-04","ref_decisions":["DEC-0038"]},{"id":"PLN-0151","file":"PLN-0151-schedule-foreground-vm-session-ticks.md","status":"done","created_at":"2026-07-04","updated_at":"2026-07-04","ref_decisions":["DEC-0038"]},{"id":"PLN-0152","file":"PLN-0152-validate-game-and-vm-shell-session-coexistence.md","status":"done","created_at":"2026-07-04","updated_at":"2026-07-04","ref_decisions":["DEC-0038"]},{"id":"PLN-0153","file":"PLN-0153-specify-vm-session-ownership-and-background-ready-scheduling.md","status":"done","created_at":"2026-07-04","updated_at":"2026-07-04","ref_decisions":["DEC-0038"]}],"lessons":[]}
{"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"}]}
{"type":"discussion","id":"DSC-0042","status":"done","ticket":"real-render-worker-establishment","title":"Real Render Worker Establishment","created_at":"2026-06-06","updated_at":"2026-06-20","tags":["runtime","renderer","worker","concurrency","host","hal","architecture"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0049","file":"discussion/lessons/DSC-0042-real-render-worker-establishment/LSN-0049-render-worker-publication-boundary.md","status":"done","created_at":"2026-06-20","updated_at":"2026-06-20"}]}
{"type":"discussion","id":"DSC-0038","status":"done","ticket":"render-frame-packet-boundary","title":"Logical Render Pipelines and Command Packets","created_at":"2026-05-25","updated_at":"2026-06-04","tags":["gfx","renderer","runtime","frame-composer","architecture","ui","pipeline"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0047","file":"discussion/lessons/DSC-0038-render-frame-packet-boundary/LSN-0047-typed-render-submissions-preserve-domain-boundaries.md","status":"done","created_at":"2026-06-04","updated_at":"2026-06-04"}]}
{"type":"discussion","id":"DSC-0035","status":"done","ticket":"task-owned-shell-windows","title":"Agenda - Task-Owned Shell Windows","created_at":"2026-05-15","updated_at":"2026-05-15","tags":["runtime","os","task","window-manager","shell","lifecycle"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0044","file":"discussion/lessons/DSC-0035-task-owned-shell-windows/LSN-0044-task-window-liveness-belongs-to-the-task.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15"}]}

View File

@ -0,0 +1,145 @@
---
id: LSN-0052
ticket: foreground-stack-game-pause-shell-vm-backed
title: Foreground Ownership and VM Session Ownership Are Separate
created: 2026-07-05
tags: [runtime, os, lifecycle, shell, game, vm, foreground, architecture]
---
# LSN-0052: Foreground Ownership and VM Session Ownership Are Separate
## Context
DSC-0041 closed the `Game -> Home/Shell -> same Game` lifecycle for the first
runtime implementation that supports both native Shell apps and VM-backed Shell
apps. The important architectural shift was not only pausing the Game. The
runtime also had to stop treating one firmware-owned VM as the place where all
guest execution state lives.
The final model separates two questions:
- Who owns the foreground visual/input authority right now?
- Which VM-backed process owns a mutable VM execution context?
Foreground ownership is global SystemOS policy. VM execution state belongs to a
session associated with a task/process. A suspended resident Game is not a saved
snapshot and not a special global VM. It is a live VM session that is temporarily
ineligible for normal foreground ticks, input, frame pacing, and render
publication.
## Key Decisions
### Foreground Stack and Game Pause Contract
**What:** PROMETEU uses a single foreground owner in v1. Hub/Home is the root
Shell owner, a Game may remain resident, and one foreground Shell app may run in
front of the resident Game. Pressing desktop `Esc` is a host/SystemOS Home
request, not guest input.
**Why:** This preserves console-like navigation without turning the runtime into
a general multitasking scheduler. It lets the system pause and suspend a Game,
run Hub or Shell, then resume the same Game deterministically.
**Trade-offs:** The model intentionally rejects multiple resident Games, direct
Shell-to-Game return, background execution, and Game-to-Game switching in v1.
Those features remain future work, but the lifecycle structure now has a place
to add them.
### VM Session Ownership for VM-Backed Processes
**What:** VM-backed mutable execution state is owned by VM sessions. A
VM-backed Game and a VM-backed Shell never share the same VM, stack, heap, PC,
open handles, cartridge identity, or VM-scoped runtime state.
**Why:** A single firmware-owned VM cannot represent a suspended resident Game
and a foreground VM Shell at the same time. Loading the Shell into that global
VM would overwrite the Game context that the lifecycle model says is resident.
**Trade-offs:** Session ownership is more explicit than one global VM, but it
keeps the model simpler than snapshot/restore. It also prepares the runtime for
future background-capable sessions without committing to background scheduling
yet.
## Patterns and Algorithms
### Pause Is Cooperative, Suspension Is OS Authority
The Game receives lifecycle events such as pause and resume, but the OS owns the
actual scheduling state. If the Game does not cooperate within the bounded pause
budget, SystemOS can suspend it anyway. On resume, the Game receives a foreground
restore event and may still keep its internal pause screen while it
synchronizes.
This distinction prevents userland from owning system navigation. The Game may
observe and react. It cannot veto Home.
### Foreground Owner Is Not the Same as Tick Eligibility
Foreground ownership controls input, visual authority, render ownership, and
normal frame pacing. Tick eligibility controls whether a VM session may execute.
In v1, only the foreground VM session receives normal ticks, and the resident
Game does not tick while Shell or Hub is foreground.
The important design detail is that the data model does not assume this must be
true forever. Sessions can later become background-eligible without changing who
owns render/input foreground.
### VM Sessions Are the Unit of Mutable Guest State
Each VM-backed task/process maps to a `VmSession`. The session owns the VM,
runtime state, session-scoped filesystem state, open file handles, next handle
allocation, cartridge identity, lifecycle delivery state, and debug state.
Cartridge loading creates or reuses a VM session through SystemOS session
services. Firmware orchestrates macro states such as `LoadCartridge`,
`GameRunning`, `HubHome`, and `ShellRunning`, but it no longer owns a canonical
guest VM.
### Debugger and Host Inspection Must Follow the Active Session
Host debugging must inspect and mutate the active session VM, not a transitional
global VM. PC, operand stack, breakpoints, debug-step execution, cartridge
identity, and breakpoint-hit events all need to resolve through active session
state.
This keeps debugger behavior aligned with actual execution. A debugger that
observes a different VM than the scheduler ticks is worse than no debugger: it
creates false confidence and hides lifecycle bugs.
### Operational Failures Should Cross Facades as Typed Errors
Session creation and loading are operational boundaries. Missing task, missing
process, duplicate VM session, missing VM session during initialization, and
foreground Shell rejection should be typed errors. Firmware can then choose a
controlled crash report, a launch rejection, or a logged no-transition outcome.
Panics are reserved for test/setup wrappers or impossible internal invariants
that have already been proven by prior checks.
## Pitfalls
- Do not use a global VM as a compatibility bridge after session ownership is
introduced. It will eventually become stale or contradictory.
- Do not let Home/SystemOS be guest input. It changes OS authority, so it must
bypass guest pad state.
- Do not present old frames after foreground ownership changes. Render ownership
and epoch validation are part of lifecycle correctness, not renderer polish.
- Do not model pause and suspension as the same state. Pause is visible to the
Game; suspension is the scheduler mechanism.
- Do not encode v1 limits such as one resident Game or one foreground Shell by
collapsing storage into one VM. Express those limits as lifecycle/session
policy.
- Do not let debugger state drift from scheduler state. The debugger must use
the same active session as normal execution.
## Takeaways
- Foreground ownership is global policy; VM execution state is session-owned.
- A suspended Game is a preserved session, not a serialized VM and not a global
VM waiting to be reused.
- Shell apps can be native or VM-backed, but only VM-backed processes need VM
sessions.
- Session ownership is the foundation for future background-capable processes,
even before background execution exists.
- Typed session/lifecycle errors make launch and recovery behavior explicit at
firmware boundaries.

View File

@ -1,178 +0,0 @@
---
id: AGD-0041
ticket: foreground-stack-game-pause-shell-vm-backed
title: Foreground Stack, Game Pause, and VM-Backed Shell Coexistence
status: accepted
created: 2026-06-05
resolved: 2026-07-03
decision: DEC-0037
tags: [runtime, os, lifecycle, shell, game, vm, foreground, architecture]
---
## Contexto
Durante a discussao da `DSC-0040` sobre separar VM e render worker, surgiu um caso que nao pertence somente ao render: `Game -> Shell -> Game`.
O caso minimo e:
- um Game esta rodando;
- o usuario aperta Home;
- o Game deve sair do foreground sem ser encerrado;
- o Hub/Shell assume a tela;
- o usuario pode abrir um app Shell, inclusive VM-backed;
- depois o usuario retorna ao mesmo Game.
O render worker precisa saber qual visual owner pode apresentar frames, mas a decisao principal e de lifecycle: quais VMs existem, quais podem executar, o que significa pausar um Game, e como Shell apps coexistem com um Game pausado.
Material relacionado:
- `DSC-0031`: separacao entre perfis Game e System/Shell.
- `DSC-0032`: SystemOS como autoridade de lifecycle/process/task.
- `DSC-0035`: janelas Shell pertencem a tasks e definem liveness do app.
- `DSC-0036`: Hub/Shell prova fatias de lifecycle.
- `DSC-0040`: render ownership/epoch durante transicoes e paralelismo VM/render.
## Problema
Precisamos definir o contrato de foreground para impedir ambiguidades como:
- Game pausado continuando a executar frames logicos enquanto Shell esta no foreground;
- Shell VM-backed competindo com Game por pacing, input, superficie ou render ownership;
- multiplos Shells foreground ou multiplos Games ativos sem uma politica explicita;
- retorno ao Game sem clareza sobre estado pausado, input, render invalidation e retomada da VM.
Sem esse contrato, a separacao VM/render da `DSC-0040` pode ficar tecnicamente correta, mas o SystemOS ainda pode permitir estados incoerentes.
## Pontos Criticos
- Existe apenas um foreground visual owner por vez.
- Background execution e outro assunto e deve ficar fora da primeira decisao, salvo se for necessario definir uma proibicao explicita.
- Shell VM-backed nao deve herdar politica de game loop so por usar VM.
- Game pausado provavelmente deve reter estado, mas nao deve consumir frames logicos.
- O Hub/Shell precisa ser capaz de rodar enquanto o Game esta suspenso.
- A transicao deve invalidar render submissions antigas para impedir present de frames obsoletos.
- O modelo precisa caber no SystemOS/lifecycle existente, nao diretamente no render worker.
## Opcoes
### Opcao A - Foreground unico, Game suspenso, Shell unico
- **Abordagem:** permitir no maximo um Game carregado e no maximo um Shell foreground; ao apertar Home, o Game entra em estado pausado/suspenso e Shell/Hub assume o foreground. Shell VM-backed pode executar apenas enquanto for o foreground Shell.
- **Pro:** modelo simples, previsivel e alinhado com console; reduz concorrencia real entre VMs; combina com a regra de render ownership unico.
- **Contra:** nao cobre multitarefa/background; futuras features de overlay ou apps persistentes precisarao expandir o contrato.
- **Manutenibilidade:** alta; SystemOS pode modelar uma pilha pequena de foreground/suspended sem virar scheduler generico.
### Opcao B - Multiplas VMs residentes, somente foreground executa
- **Abordagem:** permitir que Game e Shell VM-backed coexistam como VMs residentes, mas somente a entidade foreground executa. O Game pausado fica residente, sem tick; Shell VM-backed roda sob lifecycle Shell.
- **Pro:** preserva estado e permite retorno rapido; nao exige background execution.
- **Contra:** precisa contrato claro de memoria, ownership de input, foco, cancelamento e fechamento.
- **Manutenibilidade:** boa se os estados forem explicitos; ruim se a residencia for confundida com execucao em background.
### Opcao C - Encerrar Game ao entrar no Shell VM-backed
- **Abordagem:** Home fecha/salva o Game antes de abrir Shell VM-backed, evitando coexistencia.
- **Pro:** simplifica o runtime.
- **Contra:** quebra a expectativa de console moderno; torna `Game -> Shell -> Game` um reload, nao uma pausa.
- **Manutenibilidade:** simples no curto prazo, mas provavelmente gera mais excecoes e UX ruim depois.
### Opcao D - Background real desde ja
- **Abordagem:** permitir Game pausado ou Shell apps rodando em background com politicas de prioridade.
- **Pro:** cobre casos futuros ricos.
- **Contra:** amplia demais escopo: scheduler, budget, energia, audio, input, IO e memoria.
- **Manutenibilidade:** baixa para v1; mistura a decisao de foreground com multitarefa real.
## Sugestao / Recomendacao
Recomendo seguir com a combinacao das opcoes A e B:
- somente um Game residente por vez;
- somente um Shell foreground por vez;
- Game pode ficar pausado/suspenso enquanto Shell esta foreground;
- Shell VM-backed pode existir junto com Game pausado, mas segue lifecycle Shell;
- somente o owner foreground executa VM/tick/event loop conforme sua politica;
- background execution fica explicitamente fora de escopo;
- render ownership muda junto com foreground owner e deve invalidar submissions antigas via epoch/generation, conforme `DSC-0040`.
Isso preserva o ciclo `Game -> Home/Shell -> Game` sem transformar o runtime em multitarefa geral agora.
## Perguntas em Aberto
- [x] Qual e o nome canonico dos estados de Game fora do foreground: `Paused`, `Suspended`, ambos, ou outro?
- [x] Ao apertar Home, o Game deve receber um evento/interrupt/trap de pausa antes de parar de executar?
- [x] O Hub e um Shell especial sempre presente ou uma task Shell comum?
- [x] Um app Shell VM-backed pode substituir o Hub no foreground ou o Hub permanece como owner raiz?
- [x] Quando o Shell VM-backed fecha, o retorno vai sempre para Hub ou pode voltar diretamente ao Game?
- [x] O input do Game e drenado/limpo ao pausar e ao retomar?
- [x] Audio do Game pausa junto com a VM ou tem politica propria?
- [x] Existe limite de memoria/residencia para manter Game pausado enquanto Shell app roda?
- [x] O retorno ao Game exige novo frame antes de trocar a superficie, ou a tela pode manter Hub/Shell ate a primeira submission valida?
- [x] Quais eventos de lifecycle precisam ser visiveis para firmware, OS, VM e host?
## Resolucao
Fica aceito o modelo `Game -> Home/Shell -> mesmo Game` com foreground unico e
residencia limitada:
- somente um Game pode estar residente por vez;
- somente um Shell pode ser foreground por vez;
- Hub e o Shell root/especial e permanece owner raiz/back-stack;
- um Shell VM-backed pode assumir o foreground visual, mas segue lifecycle de
Shell e nao herda politica de game loop;
- fechar um Shell VM-backed retorna ao Hub em v1;
- background execution real fica fora de escopo;
- no host desktop, `Esc` e a tecla principal para solicitar Home/SystemOS e a
tecla fisica `Home` pode existir como alias;
- a acao Home/SystemOS e interceptada pelo host/SystemOS e nao entra no pad
logico visivel ao Game;
- `Paused` e o estado/evento cooperativo visivel ao Game;
- `Suspended` e o estado operacional imposto pelo OS/scheduler;
- ao sair do foreground, o OS notifica o Game para pausar e concede um budget
curto de reacao;
- se o Game nao reagir ao pause dentro do budget, o OS suspende a VM mesmo
assim;
- ao retornar, o OS reativa a VM, notifica o Game para resume/foreground
restore e permite que o Game sincronize antes de voltar ao gameplay;
- input pendente do Game e drenado/limpo nas fronteiras de pause/resume;
- audio do Game pausa junto com a VM em v1;
- retorno ao Game deve manter Hub/Shell visivel ate a primeira render submission
valida do Game retomado;
- render ownership muda junto com o foreground owner e invalida submissions
antigas via epoch/generation;
- firmware observa transicoes macro; OS observa lifecycle completo; VM observa
pause/resume; host observa owner/render epoch e estado de apresentacao.
## Criterio para Encerrar
A agenda pode ser encerrada quando tivermos uma decisao clara sobre:
- cardinalidade de Games e Shells residentes/foreground;
- semantica de Game pausado/suspenso;
- politica de execucao para Shell VM-backed durante Game pausado;
- regra de foreground visual owner e render invalidation;
- comportamento minimo do ciclo `Game -> Shell -> Game`;
- itens explicitamente fora de escopo, especialmente background execution real.
## Discussao
- 2026-06-05: Agenda criada a partir da `DSC-0040`, quando o caso de Shell VM-backed aberto sobre um Game pausado mostrou que a discussao pertence ao lifecycle/SystemOS, nao apenas ao render worker.
- 2026-07-03: Direcao provisoria aceita para as perguntas 2-10:
Game deve receber evento de pausa com budget curto antes da suspensao; Hub e
Shell root/especial; Shell VM-backed pode assumir foreground visual, mas Hub
permanece como owner raiz/back-stack; fechar Shell VM-backed retorna ao Hub em
v1; input do Game deve ser drenado/limpo em pause/resume; audio do Game pausa
junto com VM em v1; apenas um Game pausado residente e permitido; retorno ao
Game deve manter Hub/Shell visivel ate a primeira submission valida; firmware
ve transicoes macro, OS ve lifecycle completo, VM ve pause/resume, host ve
owner/render epoch e estado de apresentacao.
- 2026-07-03: Pergunta 1 permanece aberta para esclarecer se `Paused` e
`Suspended` sao estados distintos, nomes para camadas diferentes, ou se v1
deve expor somente um deles.
- 2026-07-03: Direcao proposta para pergunta 1: `Paused` e o estado/evento
cooperativo visivel ao Game; `Suspended` e o estado operacional imposto pelo
OS. Ao sair do foreground, o OS notifica o Game para pausar e concede um
budget curto de reacao; se o Game nao reagir, o OS suspende a VM mesmo assim.
Ao retornar, o OS notifica o Game antes de recoloca-lo no foreground visual,
permitindo sincronizacao/revalidacao. O Game nao controla a suspensao, mas
pode observar `pause`/`resume` para ajustar seu estado interno.

View File

@ -1,186 +0,0 @@
---
id: AGD-0046
ticket: foreground-stack-game-pause-shell-vm-backed
title: VM Context Ownership for Resident Game and VM-Backed Shell
status: accepted
created: 2026-07-04
resolved:
decision: DEC-0038
tags: [runtime, os, lifecycle, shell, game, vm, foreground, architecture]
---
## Contexto
`DEC-0037` define que o v1 deve suportar o fluxo `Game -> Home/Shell -> same
Game`, com o Game residente pausado/suspenso enquanto Hub ou Shell possuem o
foreground. A implementacao atual ja impede que Shell nativo ticke o Game
suspenso, porque Shell nativo nao usa a VM.
A lacuna aparece quando o Shell tambem eh VM-backed. Hoje `Firmware` possui um
unico `VirtualMachine` global (`Firmware.vm`). O Game residente e um Shell
VM-backed competem por esse mesmo contexto. Se o Shell VM-backed inicializa uma
nova cartridge nesse objeto, ele sobrescreve o contexto do Game que deveria
permanecer residente/suspenso.
Isso significa que o modelo de foreground/task/process ja avancou, mas o modelo
de contexto VM ainda esta acoplado ao firmware global.
## Problema
Precisamos decidir onde vive o contexto de execucao VM quando existem, ao mesmo
tempo, um Game residente suspenso e um Shell VM-backed foreground.
O problema nao eh apenas pausar ticks. Para coexistencia real, cada VM-backed
process precisa preservar seu proprio estado: PC, stack, heap, globals,
cartridge identity, runtime services escopados, render ownership metadata e
eventos lifecycle pendentes.
Enquanto `Firmware.vm` for unico, "suspender" um Game VM nao preserva o
contexto se outra cartridge VM-backed for carregada.
## Pontos Criticos
- `DEC-0037` diz que um Game suspenso nao recebe ticks/input/frame pacing, mas
continua residente para voltar ao mesmo Game.
- Shell nativo nao precisa de VM context proprio, mas Shell VM-backed precisa.
- `SystemOS` ja eh autoridade de lifecycle/process/task, entao eh candidato
natural para possuir ou localizar contextos VM por sessao.
- `Firmware` deve orquestrar macro estados, mas nao deveria ser dono unico do
contexto de todos os processos VM-backed.
- `VirtualMachineRuntime` contem estado atualmente misto: identidade da
cartridge atual, render manager, lifecycle delivery, fs/memcard/open handles
e telemetria. Precisamos separar o que eh global do OS do que eh por sessao
VM-backed.
- A solucao deve preservar a restricao v1: no maximo um Game residente e no
maximo um Shell foreground. Ela nao deve abrir multitasking geral.
- Game-to-Game switching continua fora desta agenda e pertence a `DSC-0043`.
## Opcoes
### Opcao A - Manter VM global no Firmware e bloquear Shell VM-backed com Game residente
- **Abordagem:** documentar que Shell VM-backed nao pode abrir enquanto um Game
VM esta residente; apenas Shell nativo coexiste com Game suspenso.
- **Pro:** menor implementacao imediata.
- **Contra:** contradiz a intencao de coexistencia de VM-backed Shell em
`DEC-0037` e deixa a arquitetura incompleta.
- **Manutenibilidade:** baixa, porque o sistema teria regras diferentes para
Shell nativo e Shell VM-backed sem uma fronteira clara de sessao.
### Opcao B - Serializar o Game VM ao suspender e restaurar ao resumir
- **Abordagem:** ao suspender Game, extrair snapshot do `VirtualMachine` e do
runtime associado; ao resumir, restaurar snapshot depois do Shell VM-backed.
- **Pro:** preserva a ideia de um unico VM executor ativo.
- **Contra:** exige serializacao profunda e correta de todo o estado VM/runtime,
incluindo handles, assets, render state e side effects. Isso eh muito mais
complexo que a necessidade v1.
- **Manutenibilidade:** baixa a media; snapshots viram contrato dificil e
fragil antes de termos necessidade de hibernacao real.
### Opcao C - Tornar o contexto VM propriedade da sessao/processo VM-backed
- **Abordagem:** cada processo VM-backed possui seu proprio `VirtualMachine` e
seu contexto runtime VM-scoped. O scheduler/firmware seleciona qual sessao
recebe tick conforme foreground/lifecycle. Game suspenso simplesmente deixa
de ser tickado, mas seu contexto permanece armazenado na sessao Game.
- **Pro:** alinha com task/process/lifecycle, suporta Game residente + Shell
VM-backed sem sobrescrever contexto, e prepara o terreno para futuras sessoes
sem implementar multitasking geral agora.
- **Contra:** exige separar estado hoje concentrado em `Firmware.vm` e
`SystemOS.vm_runtime`, decidindo o que eh global do OS e o que eh por sessao.
- **Manutenibilidade:** alta, porque a posse do contexto acompanha o processo
que executa a VM.
### Opcao D - Criar apenas dois slots VM fixos: Game VM e Shell VM
- **Abordagem:** manter um slot `game_vm` e um slot `shell_vm`, em vez de uma VM
por processo/sessao.
- **Pro:** mais simples que uma tabela geral de sessoes.
- **Contra:** codifica uma limitacao acidental. Ainda precisariamos decidir
ownership, runtime scoped state e lifecycle por slot. Pode virar refactor
duplo quando mais de um Shell ou Game switching aparecer.
- **Manutenibilidade:** media; aceitavel como stepping stone somente se modelado
internamente como sessao, nao como campos especiais permanentes.
## Sugestao / Recomendacao
Seguir a Opcao C: contexto VM deve pertencer a uma sessao/processo VM-backed,
nao ao `Firmware` global.
Para v1, a implementacao pode continuar limitada a uma sessao Game residente e
uma sessao Shell foreground, mas o modelo deve ser "session-owned VM context".
Isso permite que Shell VM-backed rode enquanto o Game fica suspenso sem perder
PC/stack/heap/estado runtime.
O firmware deve deixar de possuir a VM como estado global de todos os apps e
passar a pedir ao SystemOS/runtime scheduler o contexto VM ativo para o owner
foreground. `SystemOS` deve continuar sendo a autoridade de lifecycle; o
runtime deve separar estado VM-scoped de estado global de OS/render/telemetria
conforme necessario.
## Perguntas em Aberto
- [ ] Qual objeto deve representar a sessao VM-backed: `Process`, novo
`VmSession`, ou uma tabela separada ligada a `ProcessId`/`TaskId`?
- [ ] Quais campos de `VirtualMachineRuntime` sao por sessao VM-backed e quais
devem permanecer globais em `SystemOS`?
- [ ] O render manager fica global por foreground owner ou dentro de cada
contexto VM com handoff global?
- [ ] Handles de FS, memcard, assets e open files devem ser por sessao,
por app_id, ou globais com namespace por processo?
- [ ] Como `LoadCartridgeStep` deve mudar: criar sessao VM nova, ativar sessao
existente, ou delegar tudo para uma facade de sessions?
- [ ] Como preservar os testes e invariantes de `DEC-0037` durante a migracao
incremental sem abrir multitasking geral?
- [ ] Como processos background-capable, como um player de musica, entram no
modelo sem confundir foreground visual ownership com elegibilidade de
execucao?
- [ ] Cada Shell VM-backed deve ter uma VM propria, mesmo quando existe mais de
um Shell em memoria no futuro?
## Criterio para Encerrar
Esta agenda pode virar decision quando definirmos:
- o proprietario canonico do contexto VM;
- a fronteira entre estado VM-scoped e estado global do OS;
- como firmware seleciona/ticka a sessao VM ativa;
- como Game residente suspenso e Shell VM-backed foreground coexistem;
- quais restricoes v1 permanecem explicitamente fora de escopo.
## Discussion
- 2026-07-04: Aberta a partir da observacao de que Shell nativo coexiste com
Game suspenso, mas Shell VM-backed ainda conflita com o `Firmware.vm` unico.
- 2026-07-04: Discussao adicional separou foreground ownership de background
execution. A recomendacao atual eh que todo processo VM-backed tenha sua
propria `VmSession`; processos background-capable devem ser modelados como
sessoes com classe/politica de scheduling propria, nao como foreground owners.
## Resolution
Resolvido em 2026-07-04:
- O modelo deve seguir a Opcao C: todo processo VM-backed deve possuir uma
`VmSession` propria, ligada ao lifecycle/task/process, em vez de depender de
um `Firmware.vm` global.
- Cada Shell VM-backed deve ter sua propria sessao VM. Ele pode compartilhar a
engine/runtime e caches imutaveis, mas nao stack, heap, PC, globals, handles
ou identidade mutavel de execucao.
- O Game residente suspenso preserva seu contexto porque sua `VmSession`
continua viva; ele apenas deixa de ser elegivel para ticks normais enquanto
Hub/Shell possui foreground.
- Foreground ownership e background execution sao conceitos separados.
Foreground ownership decide input visual, render ownership e frame pacing
normal. Execution eligibility decide quais sessoes podem receber ticks sem
serem foreground.
- Background execution, como player de musica ou background services, nao entra
na primeira implementacao desta agenda. Mesmo assim, o modelo de `VmSession`
deve ser obrigatoriamente compativel com essa evolucao em um futuro proximo.
- Render ownership permanece autoridade global do SystemOS. Sessoes VM-backed
produzem frames, mas nao decidem se podem apresentar.
- `LoadCartridgeStep` nao deve inicializar uma VM global diretamente. Ele deve
delegar criacao/ativacao de sessao para uma facade de sessions/scheduler.
Esta agenda esta pronta para virar decision normativa.

View File

@ -1,191 +0,0 @@
---
id: DEC-0037
ticket: foreground-stack-game-pause-shell-vm-backed
title: Foreground Stack and Game Pause Contract
status: accepted
created: 2026-07-03
ref_agenda: AGD-0041
tags: [runtime, os, lifecycle, shell, game, vm, foreground, architecture]
---
## Status
Accepted.
## Contexto
`AGD-0041` defines the minimum lifecycle contract for `Game -> Home/Shell ->
same Game`.
The issue is not only rendering. The render worker needs a single valid visual
owner, but SystemOS must decide which VMs may exist, which owner receives input,
which owner may tick, what happens to a Game when Home takes foreground, and how
a VM-backed Shell app coexists with a paused Game.
This decision builds on:
- `DSC-0031`: runtime mode separation between Game and System/Shell;
- `DSC-0032`: SystemOS lifecycle/process/task authority;
- `DSC-0035`: task-owned Shell windows and Shell liveness;
- `DSC-0036`: Hub/Shell as a lifecycle proving surface;
- `DSC-0040`: render ownership/epoch and stale submission invalidation.
## Decisao
PROMETEU MUST use a single-foreground-owner model for v1.
At most one Game MAY be resident at a time. At most one Shell app MAY be the
foreground visual owner at a time.
The Hub MUST be treated as the root/special Shell owner for Home. A VM-backed
Shell app MAY replace the Hub as the foreground visual owner, but the Hub
remains the root/back-stack controller. In v1, closing a VM-backed Shell app
MUST return to Hub/Home, not directly to the Game.
When the user requests Home while a Game is running, SystemOS MUST start a
cooperative pause transition:
- the Game receives a pause lifecycle notification;
- the Game is given a short bounded budget to react;
- after that budget, SystemOS MAY suspend the Game VM even if the Game did not
cooperate;
- the Game remains resident but MUST NOT receive normal gameplay ticks, normal
game input, or frame pacing while suspended;
- the Shell/Hub foreground owner MAY execute according to Shell lifecycle rules.
`Paused` is the Game-visible lifecycle state/event. `Suspended` is the
OS/scheduler operational state. A Game may observe pause/resume, but it MUST NOT
control whether the OS suspends or resumes its VM.
When returning to the same Game, SystemOS MUST reactivate the Game VM and send a
resume or foreground-restore lifecycle notification before restoring visual
foreground. The Game MAY remain internally paused while it synchronizes. The
system MUST keep Hub/Shell visible until the resumed Game produces a valid new
render submission for the current ownership epoch.
Input pending for the Game MUST be drained or cleared across pause/resume
boundaries. Game audio MUST pause with the Game VM in v1. Independent background
audio policy is out of scope.
Render ownership MUST move with the foreground owner. Foreground transitions
MUST invalidate stale render submissions through ownership epoch/generation so
old Game frames cannot be presented after Shell takes foreground, and old Shell
frames cannot be presented after the Game resumes.
On the desktop host, `Esc` MUST be the primary keyboard request for Home/SystemOS.
The physical `Home` key MAY be supported as an alias. This Home request is a
host/SystemOS control action and MUST NOT be exposed as a normal guest pad
button or userland input capability.
Real background execution, multiple resident Games, multiple foreground Shells,
Game-to-Game switching, Shell app persistence policy, memory eviction policy,
and rich multitasking are out of scope for this decision.
## Rationale
The chosen model preserves the expected console-like flow: a Game can be
paused, Home can run, Shell apps can open, and the same Game can resume without
turning the runtime into a general multitasking scheduler.
Separating `Paused` from `Suspended` keeps authority clear. `Paused` is the
contract the Game can observe and react to. `Suspended` is the OS mechanism that
prevents the paused Game from continuing to consume ticks, input, audio, and
render ownership while another owner is foreground.
Keeping Hub as root/back-stack prevents VM-backed Shell apps from becoming
independent lifecycle roots. Returning to Hub before resuming Game keeps v1
navigation deterministic and leaves more complex direct-return behavior for a
future decision.
Keeping Hub/Shell visible until the Game submits a new valid frame avoids
presenting stale pixels from the previous Game epoch. This aligns lifecycle
state with the render ownership model from `DSC-0040`.
Using `Esc` as desktop Home makes the control available on compact keyboards.
Supporting the physical `Home` key as an alias is acceptable, but the portable
contract cannot depend on it.
## Invariantes / Contrato
- There is exactly one foreground visual owner at a time.
- V1 supports at most one resident Game.
- V1 supports at most one foreground Shell app.
- Hub/Home is the root Shell owner and back-stack controller.
- VM-backed Shell apps follow Shell lifecycle, not Game loop lifecycle.
- A paused Game may remain resident, but a suspended Game does not receive
normal gameplay ticks, normal game input, or frame pacing.
- Pause/resume are cooperative Game-visible lifecycle events.
- Suspend/resume scheduling authority belongs to SystemOS.
- SystemOS may suspend a Game after the pause budget even if the Game does not
cooperate.
- Input is cleared or barriered across Game pause/resume boundaries.
- Game audio pauses with the Game VM in v1.
- Foreground transitions advance render ownership epoch/generation.
- Visual foreground returns to Game only after a valid current-epoch Game render
submission exists.
- Desktop `Esc` is a host/SystemOS Home request; physical `Home` may alias it.
- Home/SystemOS is not a guest pad button and is not userland input.
- Background execution and general multitasking are explicitly excluded.
## Impactos
- **Spec:** runtime lifecycle, firmware/Hub, input, and render ownership specs
must document foreground ownership, pause/suspend semantics, Home key mapping,
and stale frame invalidation.
- **Runtime / SystemOS:** SystemOS needs explicit foreground owner, Hub root,
Shell foreground, Game paused, and Game suspended state.
- **VM:** VM execution must support pause/resume lifecycle delivery and OS-owned
suspension of ticks.
- **Firmware:** firmware should observe macro transitions such as GameRunning,
Hub/Home, Shell foreground, pause request, and resume request without owning
every internal OS detail.
- **Host:** host must route desktop `Esc` and optional physical `Home` to a
SystemOS Home request instead of guest pad input.
- **Input:** Game input must be drained or barriered during pause/resume and
Home/SystemOS must stay outside the guest-visible pad ABI.
- **Audio:** v1 audio follows Game pause/suspend and does not continue as
independent background audio.
- **Renderer:** render publication must reject stale submissions after ownership
epoch changes and keep previous foreground visible until the resumed Game
publishes a valid current-epoch frame.
- **Tests:** coverage must prove pause budget behavior, non-foreground VM
non-execution, input clearing, host Home routing, Shell return-to-Hub, and
stale render rejection.
## Alternativas Descartadas
- **Terminate Game when entering Shell:** rejected because it makes
`Game -> Shell -> Game` a reload rather than a pause/resume flow.
- **Real background execution now:** rejected because it pulls in scheduler,
budget, energy, audio, input, IO, memory, and persistence policy beyond v1.
- **Multiple resident Games:** rejected for v1 because Game-to-Game switching is
owned by `DSC-0043 / AGD-0044`.
- **Expose Home as guest input:** rejected because Home changes system
foreground authority and must not be handled as ordinary game input.
- **Return directly from Shell app to Game in v1:** rejected to keep Hub as the
deterministic root/back-stack owner.
## Referencias
- Source agenda: `AGD-0041`
- Runtime mode separation: `DSC-0031`
- SystemOS lifecycle authority: `DSC-0032`
- Shell task/window lifecycle: `DSC-0035`
- Hub UI direction: `DSC-0036`
- Render ownership and stale submission handling: `DSC-0040`
- Future Game A -> Home -> Game B switching: `DSC-0043 / AGD-0044`
## Propagacao Necessaria
- Update canonical runtime specs for foreground ownership and pause/suspend.
- Specify desktop Home request mapping in host/input documentation.
- Add plan(s) for SystemOS foreground stack state and Game pause/suspend.
- Add plan(s) for VM lifecycle event delivery and forced suspension budget.
- Add plan(s) for host `Esc`/`Home` routing outside guest pad input.
- Add plan(s) for render epoch integration during Game/Shell transitions.
- Add tests covering lifecycle, input, host routing, and render publication
invariants.
## Revision Log
- 2026-07-03: Initial decision from `AGD-0041`.

View File

@ -1,199 +0,0 @@
---
id: DEC-0038
ticket: foreground-stack-game-pause-shell-vm-backed
title: VM Session Ownership for VM-Backed Processes
status: accepted
created: 2026-07-04
ref_agenda: AGD-0046
tags: [runtime, os, lifecycle, shell, game, vm, foreground, architecture]
---
## Status
Open for review.
## Contexto
`DEC-0037` established the foreground stack and Game pause contract for
`Game -> Home/Shell -> same Game`. It requires a suspended resident Game to stop
receiving normal gameplay ticks, input, and frame pacing while remaining
resident for later resume.
The current implementation works for native Shell apps because native Shell
apps do not use the VM. It does not fully support VM-backed Shell coexistence:
`Firmware` owns a single global `VirtualMachine` (`Firmware.vm`), so loading a
VM-backed Shell can overwrite the suspended Game context that should remain
resident.
`AGD-0046` closes this gap by separating VM execution context ownership from
foreground ownership. A foreground owner decides who receives visual/input
authority. A VM execution context decides where PC, stack, heap, globals,
handles, cartridge identity, lifecycle delivery state, and VM-scoped runtime
state live.
## Decisao
PROMETEU MUST make VM execution context session-owned for VM-backed processes.
Every VM-backed process MUST have its own `VmSession` or equivalent
session-owned context. A VM-backed Game and a VM-backed Shell MUST NOT share the
same mutable `VirtualMachine` instance, stack, heap, PC, globals, open handles,
cartridge identity, or VM-scoped lifecycle state.
`Firmware.vm` MUST NOT remain the canonical owner of all VM-backed app context.
Firmware MAY continue to orchestrate macro states such as loading, Home, Shell,
and GameRunning, but it MUST delegate VM session creation, activation, lookup,
and ticking to SystemOS/session services.
For v1, the system MAY still enforce:
- at most one resident Game;
- at most one foreground Shell;
- no Game-to-Game switching;
- no general background execution.
Those restrictions MUST NOT be implemented by relying on a single global VM
context. They must be expressed as lifecycle/session policy.
Each VM-backed Shell MUST receive its own VM session. If future Shell
persistence allows more than one Shell app to remain in memory, each retained
VM-backed Shell MUST still have distinct mutable VM context.
A suspended resident Game MUST preserve its VM session. Suspension means the
session is not eligible for normal foreground ticks, normal Game input, or
normal frame pacing. It does not mean serializing, deleting, or overwriting the
VM context.
Foreground ownership and execution eligibility MUST be separate concepts:
- foreground ownership controls visual owner, primary input, render ownership,
and normal foreground frame pacing;
- execution eligibility controls which sessions may receive ticks or restricted
callbacks while not foreground.
Background execution, including music players or background services, is out of
scope for the first implementation of this decision. However, the session model
MUST be compatible with adding background-capable scheduling soon. The initial
model MUST NOT bake in assumptions that only the foreground session can ever be
eligible to execute.
Render ownership MUST remain a global SystemOS authority. VM sessions may
produce render submissions, but they MUST NOT decide whether those submissions
can be presented. SystemOS/render ownership policy remains responsible for
active owner, epoch/generation validity, stale submission rejection, and final
publication authority.
`LoadCartridgeStep` MUST stop initializing a global firmware VM directly as the
long-term model. Cartridge load/activation MUST be delegated to a sessions or
VM-session facade that can create a new VM session, activate an existing VM
session, or reject an unsupported transition according to lifecycle policy.
## Rationale
The foreground stack is already task/process-oriented. VM execution context must
follow the same ownership model. Otherwise the system can mark a Game as
resident and suspended while the only real VM context has already been reused
for a Shell.
Session ownership solves the actual coexistence problem without requiring VM
snapshot/restore. A suspended Game is simply a live session that is not scheduled
for normal ticks. A VM-backed Shell is another session with its own mutable
state.
Keeping background execution out of the first implementation avoids expanding
this work into a general scheduler. At the same time, making execution
eligibility separate from foreground ownership prevents an immediate future
background audio/player feature from requiring another foundational refactor.
Keeping render ownership global preserves the contract from `DEC-0037` and the
render ownership model from `DSC-0040`: sessions can generate frames, but only
the current foreground/render owner can publish valid current-epoch output.
## Invariantes / Contrato
- VM-backed mutable execution state is owned by a VM session, not by a global
firmware VM.
- A VM-backed Game and VM-backed Shell never share mutable VM context.
- A suspended resident Game retains its VM session.
- Suspension prevents normal foreground Game ticks/input/frame pacing; it does
not destroy or overwrite VM state.
- Native Shell apps do not require VM sessions.
- VM-backed Shell apps always require VM sessions.
- Each VM-backed Shell gets its own mutable VM session.
- Foreground ownership and execution eligibility are distinct.
- General background execution is out of scope for the initial implementation,
but the model must support adding it without replacing session ownership.
- SystemOS remains lifecycle and render ownership authority.
- Render ownership and stale frame rejection remain global policy, not per-VM
session policy.
- Cartridge load/activation must go through session services rather than
initializing one global firmware VM.
- V1 restrictions on one resident Game and one foreground Shell remain policy,
not a consequence of single-VM storage.
## Impactos
- **Runtime / SystemOS:** add a VM session model or equivalent service that
stores VM-backed process execution context and maps it to task/process
lifecycle.
- **Firmware:** remove `Firmware.vm` as the canonical VM context owner for all
apps. Firmware should request session creation/activation/tick from SystemOS.
- **VM:** support multiple distinct `VirtualMachine` instances or equivalent
isolated VM contexts in one OS lifetime.
- **Lifecycle:** task/process states must control session scheduling
eligibility without conflating foreground ownership with future background
execution.
- **Hub / Shell:** VM-backed Shell load must create or activate a Shell VM
session without overwriting a resident Game session.
- **Game:** Game pause/suspend/resume must preserve the Game VM session and
deliver lifecycle events to that session.
- **Renderer:** render manager stays global and validates ownership/epoch for
submissions produced by sessions.
- **FS / Memcard / Assets:** mutable handles and per-app runtime handles must
be evaluated for session scoping. Durable memcard/app data can remain keyed by
`app_id`; open handles and staging state should not accidentally leak across
sessions.
- **Tests:** add coverage proving VM-backed Shell creation does not overwrite a
suspended Game session, and that only eligible sessions tick.
## Alternativas Descartadas
- **Keep one global firmware VM and block VM-backed Shell when a Game is
resident:** rejected because it leaves `DEC-0037` incomplete and creates
divergent Shell behavior.
- **Serialize/snapshot the Game VM on suspension:** rejected because snapshot
correctness is harder than needed for v1 and would include VM, handles,
render state, assets, and side effects.
- **Hard-code two VM slots (`game_vm`, `shell_vm`):** rejected as a permanent
model because it encodes a short-term limit as architecture. It may only be
acceptable as an internal stepping stone if represented through the same
session abstraction.
- **Treat background-capable apps as hidden foreground owners:** rejected
because it would violate foreground ownership semantics and make render/input
authority ambiguous.
## Referencias
- Source agenda: `AGD-0046`
- Foreground stack and Game pause contract: `DEC-0037`
- Runtime mode separation: `DSC-0031`
- SystemOS lifecycle/process/task authority: `DSC-0032`
- VM render parallel execution boundary: `DSC-0040`
- Future Game-to-Game switching: `DSC-0043`
## Propagacao Necessaria
- Add implementation plans for introducing VM sessions and moving VM context
out of `Firmware`.
- Add implementation plans for separating VM-scoped runtime state from global
SystemOS/render state.
- Update runtime/firmware specs to describe session-owned VM context and the
distinction between foreground ownership and execution eligibility.
- Update tests for Game resident suspension, VM-backed Shell creation, and
session isolation.
- Keep future background execution as out of scope for first implementation,
but document that the model must support background-capable scheduling.
## Revision Log
- 2026-07-04: Initial decision draft from `AGD-0046`.

View File

@ -1,96 +0,0 @@
---
id: PLN-0136
ticket: foreground-stack-game-pause-shell-vm-backed
title: Route Desktop Home Key Outside Guest Input
status: done
created: 2026-07-03
ref_decisions: [DEC-0037]
tags: [host, input, systemos, keyboard, lifecycle]
---
## Briefing
`DEC-0037` makes desktop `Esc` the primary Home/SystemOS request and allows the
physical `Home` key as an alias. This request must be intercepted by the
host/SystemOS and must not appear as a guest pad button or input intrinsic.
This plan adds the host input route for the Home request without changing the
guest-visible pad surface.
## Decisions of Origin
- `DEC-0037` - Foreground Stack and Game Pause Contract.
## Target
Route desktop `Esc` and optional physical `Home` key to a SystemOS Home request
outside `InputSignals`.
## Scope
- Extend `crates/host/prometeu-host-desktop-winit/src/input.rs` or a nearby
host input module with a host/system control signal separate from
`prometeu_hal::InputSignals`.
- Map `KeyCode::Escape` to Home/SystemOS request.
- Map `KeyCode::Home` as an alias.
- Ensure neither key mutates `start_signal`, `select_signal`, or any pad field.
- Route the request through `crates/host/prometeu-host-desktop-winit/src/runner.rs`
into firmware/SystemOS.
- Add tests proving `Esc` and `Home` do not affect guest pad state.
## Out of Scope
- No pause/resume lifecycle implementation.
- No render ownership gating.
- No guest-visible Home button.
- No gamepad hardware Home mapping unless a future plan defines controller
system controls separately.
## Execution Plan
1. Define host control state.
Add a small host-only type such as `HostSystemControls` with a
`home_requested` edge or event. Store it in `HostInputHandler` or in the
runner next to the existing input handler.
2. Update keyboard handling.
In `HostInputHandler::handle_event`, recognize `KeyCode::Escape` and
`KeyCode::Home` on press. Set the host control event and return without
modifying `InputSignals`.
3. Preserve pad mapping.
Keep existing pad mappings unchanged:
arrows, `A/D/W/S`, `Q/E`, `Z`, and Shift. Do not reuse `Start` or `Select`
for Home.
4. Route to firmware/SystemOS.
In `crates/host/prometeu-host-desktop-winit/src/runner.rs`, consume the
host Home event before or during `about_to_wait` and call the firmware or
SystemOS method introduced by `PLN-0137` / `PLN-0140`.
5. Add tests.
Add host input tests for `Esc`, physical `Home`, and existing pad mappings.
Tests must prove Home produces a system event and no guest pad mutation.
## Acceptance Criteria
- Pressing desktop `Esc` emits a Home/SystemOS host control event.
- Pressing physical `Home` emits the same event.
- Neither key affects `InputSignals`.
- Existing guest pad keyboard mappings still work.
- The runner consumes the Home request as a system action.
- The implementation does not add a guest syscall, intrinsic, or pad button.
## Tests / Validation
- Add unit tests in `prometeu-host-desktop-winit` for host input mapping.
- Run `cargo test -p prometeu-host-desktop-winit input`.
- Run `cargo test -p prometeu-hal input` if pad signal behavior changes.
- Run `discussion validate`.
## Risks
- `Esc` may conflict with debugger or window-close expectations. Keep debugger
start handling on `D` unchanged and do not map `Esc` to process exit.
- Physical `Home` is not available on every keyboard, so it must remain an
alias rather than the primary portable mapping.

View File

@ -1,116 +0,0 @@
---
id: PLN-0137
ticket: foreground-stack-game-pause-shell-vm-backed
title: Implement SystemOS Foreground Stack State
status: done
created: 2026-07-03
ref_decisions: [DEC-0037]
tags: [runtime, os, lifecycle, foreground, shell, game]
---
## Briefing
`DEC-0037` requires SystemOS to own foreground authority. The system must know
which owner is foreground, keep Hub/Home as root, allow only one resident Game,
allow only one foreground Shell, and return from VM-backed Shell apps to Hub in
v1.
This plan implements the SystemOS state model only. It does not yet deliver
Game pause/resume events, host keyboard routing, or render/audio/input boundary
changes.
## Decisions of Origin
- `DEC-0037` - Foreground Stack and Game Pause Contract.
## Target
Add an explicit SystemOS foreground stack model that can represent Hub, a
foreground Shell task, and a paused/suspended resident Game.
## Scope
- Add or extend SystemOS-owned foreground state under
`crates/console/prometeu-system/src/os/` or
`crates/console/prometeu-system/src/services/`.
- Represent v1 foreground owners:
- Hub/Home root;
- Shell task foreground;
- Game foreground;
- Game paused/suspended resident.
- Enforce at most one resident Game and at most one foreground Shell.
- Keep Hub as root/back-stack controller.
- Ensure Shell close returns to Hub/Home in v1.
- Expose focused queries through existing or new facades, likely
`crates/console/prometeu-system/src/os/facades/lifecycle.rs` and
`crates/console/prometeu-system/src/os/facades/sessions.rs`.
## Out of Scope
- No VM pause/resume event delivery.
- No host `Esc`/`Home` keyboard mapping.
- No render epoch implementation.
- No Game-to-Game switching.
- No background execution.
- No memory eviction for the resident Game.
## Execution Plan
1. Define foreground model types.
Add enums/structs for foreground owner and resident Game state in the most
local SystemOS module. The model must distinguish Game foreground, Game
paused/suspended, Hub foreground, and Shell task foreground.
2. Add SystemOS storage.
Store the foreground model in `SystemOS` from
`crates/console/prometeu-system/src/os/system_os.rs` with initialization to
Hub/Home root.
3. Wire lifecycle facade operations.
Extend lifecycle/session facades so firmware can request:
- set Game foreground;
- request Home from Game;
- set Shell foreground from Hub;
- close Shell and return to Hub;
- query current foreground owner;
- query resident Game task.
4. Enforce cardinality.
Add checks that reject a second resident Game and a second foreground Shell
with typed lifecycle errors. Use existing `LifecycleError` style or add
precise variants.
5. Integrate existing Shell close behavior.
Update `crates/console/prometeu-firmware/src/firmware/firmware_step_shell_running.rs`
only where needed so closing a Shell task routes through the foreground model
and ends in `FirmwareState::HubHome`.
6. Add tests.
Cover initial Hub foreground, Game foreground registration, Home request
moving Game to resident paused/suspended state, Shell foreground activation,
Shell close returning to Hub, and cardinality rejection.
## Acceptance Criteria
- SystemOS has an explicit foreground owner model.
- Hub/Home is the initialized root foreground owner.
- Only one resident Game can exist in v1.
- Only one Shell task can be foreground in v1.
- VM-backed Shell close returns to Hub/Home.
- Foreground queries are available to firmware and later plans.
- No implementation path enables background execution.
## Tests / Validation
- Add focused unit tests in `prometeu-system` around the new model and facade.
- Run `cargo test -p prometeu-system lifecycle foreground`.
- Run `cargo test -p prometeu-firmware shell`.
- Run `discussion validate`.
## Risks
- Existing `TaskState::Foreground` and `TaskState::Suspended` may look like the
complete model. Do not overload them until foreground ownership and Hub root
authority are explicit.
- Shell windows already have focus semantics. Keep window focus as visual/task
evidence, not the sole source of SystemOS foreground truth.

View File

@ -1,114 +0,0 @@
---
id: PLN-0138
ticket: foreground-stack-game-pause-shell-vm-backed
title: Specify Foreground Pause and Home Contract
status: done
created: 2026-07-03
ref_decisions: [DEC-0037]
tags: [runtime, specs, lifecycle, input, renderer, foreground]
---
## Briefing
`DEC-0037` establishes the v1 foreground stack contract: one foreground visual
owner, one resident Game, one foreground Shell, Hub as root, Game-visible
pause/resume, OS-owned suspension, desktop `Esc` as Home/SystemOS request, and
render epoch invalidation across foreground transitions.
This plan updates the canonical runtime specs before implementation so code
plans cannot drift into guest-owned Home input, background execution, or stale
frame presentation.
## Decisions of Origin
- `DEC-0037` - Foreground Stack and Game Pause Contract.
## Target
Document the foreground, pause/suspend, Home request, input, audio, and render
ownership contract in the canonical runtime specs.
## Scope
- Update `docs/specs/runtime/12-firmware-pos-and-prometeuhub.md` with:
- Hub as root/special Shell owner;
- one foreground visual owner;
- one resident Game;
- one foreground Shell;
- `Game -> Home/Shell -> same Game` lifecycle;
- VM-backed Shell app return-to-Hub behavior.
- Update `docs/specs/runtime/06-input-peripheral.md` with:
- Home/SystemOS as host/system control, not pad input;
- desktop `Esc` as primary Home request;
- physical `Home` key as optional alias;
- input clear/barrier requirements across Game pause/resume.
- Update `docs/specs/runtime/09-events-and-concurrency.md` with:
- pause notification budget;
- OS-owned suspension after the budget;
- resume/foreground-restore notification before visual return.
- Update `docs/specs/runtime/14-boot-profiles.md` only to clarify that this
foreground contract is not Game-to-Game switching and does not change direct
`--run` or `--games-root` boot semantics.
- Update renderer-related spec text in the most local existing runtime spec if
present, otherwise add a short subsection to `12-firmware-pos-and-prometeuhub.md`
describing ownership epoch invalidation and first-valid-frame gating.
## Out of Scope
- No Rust implementation.
- No new guest ABI or pad button.
- No background execution policy.
- No Game A -> Home -> Game B switching.
- No memory eviction policy for resident Games.
## Execution Plan
1. Edit `12-firmware-pos-and-prometeuhub.md`.
Define foreground owner cardinality, Hub root ownership, Shell foreground
behavior, Game resident/suspended behavior, return-to-Hub from Shell, and the
requirement to keep Hub/Shell visible until a resumed Game submits a valid
current-epoch frame.
2. Edit `06-input-peripheral.md`.
State that Home/SystemOS is outside the guest-visible pad and input
intrinsic surface. Document desktop `Esc` as the primary host mapping and
physical `Home` as an optional alias.
3. Edit `09-events-and-concurrency.md`.
Add the pause/resume event ordering: Home request, pause notification,
bounded reaction budget, OS suspension, resume/foreground-restore
notification, first-valid-frame visual return.
4. Edit `14-boot-profiles.md`.
Cross-reference the foreground contract and explicitly keep direct boot,
games-root Home launch, and future Game-to-Game switching separate.
5. Run text checks.
Search for wording that implies Home is a guest input button, a guest ABI, or
background execution. Replace misleading text with the `DEC-0037` contract.
## Acceptance Criteria
- Specs define `Paused` as Game-visible and `Suspended` as OS-owned.
- Specs state that desktop `Esc` is the primary Home request and is not guest
pad input.
- Specs state that the Game receives pause/resume notifications but does not
control OS suspension.
- Specs state that Hub is the root Shell owner and Shell app close returns to
Hub in v1.
- Specs state that visual return to Game waits for a valid current-epoch render
submission.
- Specs explicitly exclude background execution and Game-to-Game switching.
## Tests / Validation
- Run `discussion validate`.
- Run `rg -n "Home|Esc|Suspended|Paused|foreground|background execution" docs/specs/runtime`.
- Verify no edited spec describes Home/SystemOS as a guest-visible pad button.
## Risks
- The spec could accidentally reopen Game-to-Game switching; keep that scoped to
`DSC-0043 / AGD-0044`.
- The input spec could confuse physical keyboard mapping with VM input; keep
host/system controls outside the guest input surface.

View File

@ -1,106 +0,0 @@
---
id: PLN-0139
ticket: foreground-stack-game-pause-shell-vm-backed
title: Validate Game Home Shell Game End To End
status: done
created: 2026-07-03
ref_decisions: [DEC-0037]
tags: [runtime, validation, firmware, host, lifecycle]
---
## Briefing
`DEC-0037` is not complete until the product flow is proven end to end:
a Game runs, the user requests Home, the Game is paused/suspended, Hub or a
VM-backed Shell takes foreground, closing Shell returns to Hub, and the same
Game can resume only after a valid current-epoch frame exists.
This plan adds final validation after the spec, SystemOS, VM, host input, and
render/audio/input boundary plans are implemented.
## Decisions of Origin
- `DEC-0037` - Foreground Stack and Game Pause Contract.
## Target
Prove the complete `Game -> Home/Shell -> same Game` lifecycle and prevent
regressions around guest input, foreground authority, and stale rendering.
## Scope
- Add integration-style tests for:
- Game running to Home request;
- pause notification and forced suspension;
- Hub foreground while Game is resident;
- VM-backed Shell foreground while Game is suspended;
- Shell close returning to Hub;
- resume request returning to same Game;
- first-valid-frame visual restore;
- host `Esc`/`Home` not entering guest input.
- Add manual smoke instructions for desktop host.
- Confirm direct `--run` and games-root Home launch remain separate.
## Out of Scope
- No new implementation beyond tests and developer-facing validation notes.
- No Game A -> Home -> Game B switching.
- No performance tuning.
- No polished UI.
## Execution Plan
1. Add firmware/SystemOS integration tests.
Use the most stable existing seams in `crates/console/prometeu-firmware/src/firmware/firmware.rs`
and `crates/console/prometeu-system` tests to drive Game foreground, Home
request, Shell foreground, Shell close, and Game resume.
2. Add host input integration tests.
Ensure `Esc` and physical `Home` create a host Home event and do not mutate
guest `InputSignals`.
3. Add render publication validation.
Assert stale submissions from the pre-pause Game epoch are rejected after
Home takes foreground and that the Game is not visually restored until a new
current-epoch submission exists.
4. Add audio/input boundary validation.
Assert Game input is cleared or barriered across pause/resume. Assert Game
audio pause follows Game suspension.
5. Add direct boot and games-root regression checks.
Ensure direct `--run <cart>` remains independent from Home request handling
and that games-root Home launch still enters the Game path.
6. Document manual smoke commands.
Add or update developer-facing spec text with release-mode commands:
- `cargo run --release -p prometeu-host-desktop-winit -- --run test-cartridges/stress-console`
- `cargo run --release -p prometeu-host-desktop-winit -- --games-root test-cartridges`
Include the expected `Esc -> Home -> Shell -> Hub -> resume Game` behavior
once implemented.
## Acceptance Criteria
- Automated tests cover `Game -> Home -> same Game`.
- Automated tests cover `Game -> Home -> Shell -> Hub -> same Game`.
- Tests prove Home request is host/SystemOS control, not guest pad input.
- Tests prove suspended Game does not tick.
- Tests prove stale render submissions are not presented.
- Tests prove visual return waits for a current-epoch Game frame.
- Direct `--run` and games-root Home launch regressions pass.
## Tests / Validation
- Run `cargo test -p prometeu-system`.
- Run `cargo test -p prometeu-firmware`.
- Run `cargo test -p prometeu-host-desktop-winit`.
- Run any targeted render worker tests affected by ownership epoch changes.
- Run `discussion validate`.
## Risks
- The current test seams may not expose enough host-driven lifecycle behavior.
If needed, add narrow test-only helpers rather than testing through sleeps or
real-time window loops.
- End-to-end validation can become flaky if it depends on wall-clock timing.
Use deterministic ticks and explicit state transitions.

View File

@ -1,110 +0,0 @@
---
id: PLN-0140
ticket: foreground-stack-game-pause-shell-vm-backed
title: Deliver Game Pause Resume and Suspension
status: done
created: 2026-07-03
ref_decisions: [DEC-0037]
tags: [runtime, os, vm, lifecycle, pause, suspension]
---
## Briefing
`DEC-0037` separates Game-visible `Paused` from OS-owned `Suspended`. When Home
is requested during Game execution, SystemOS must notify the Game, give it a
short bounded budget, then suspend the VM even if the Game does not cooperate.
When returning, SystemOS must reactivate the VM and send a resume or
foreground-restore notification before the visual owner returns to Game.
This plan implements the pause/resume and suspension mechanics after the
foreground state model exists.
## Decisions of Origin
- `DEC-0037` - Foreground Stack and Game Pause Contract.
## Target
Add Game pause/resume lifecycle delivery and OS-enforced VM suspension for the
same resident Game.
## Scope
- Add a Game lifecycle event model for pause and resume/foreground-restore.
- Add a bounded pause reaction budget owned by SystemOS.
- Suspend the Game VM after the budget even if the Game does not cooperate.
- Ensure a suspended Game receives no normal gameplay ticks.
- Reactivate the VM and deliver resume/foreground-restore before Game visual
foreground restoration.
- Integrate with `GameRunningStep` and VM runtime tick control.
## Out of Scope
- No render first-valid-frame gating; that is in `PLN-0141`.
- No host keyboard routing; that is in `PLN-0136`.
- No new guest syscall.
- No background execution.
- No Game-to-Game switching.
## Execution Plan
1. Add lifecycle event representation.
Define pause/resume events in the most local runtime lifecycle module,
likely under `crates/console/prometeu-system/src/os/lifecycle.rs` or a new
SystemOS lifecycle submodule. Events must be observable by the VM/runtime
path without giving the Game authority over suspension.
2. Add pause transition state.
Extend the foreground state from `PLN-0137` with a `PauseRequested` or
equivalent transition state that records the target Game task and the
remaining pause budget.
3. Gate VM ticks.
Update `crates/console/prometeu-firmware/src/firmware/firmware_step_game_running.rs`
and `crates/console/prometeu-system/src/services/vm_runtime/tick.rs` so a
suspended Game does not receive normal ticks, normal input, or frame pacing.
4. Deliver pause notification.
Add the mechanism that makes the pause event visible to the running Game
before suspension. Use an existing VM event channel if present; otherwise add
a narrow lifecycle-event queue owned by SystemOS/VM runtime.
5. Enforce the budget.
Advance the pause budget on firmware ticks. When it expires, transition the
Game task/process to suspended through SystemOS lifecycle regardless of Game
cooperation.
6. Deliver resume notification.
On return to the resident Game, transition from suspended to resume-requested,
reactivate VM execution, deliver resume/foreground-restore, and leave visual
restoration to the render boundary plan.
7. Add tests.
Cover cooperative pause, non-cooperative forced suspension, no tick while
suspended, resume notification delivery, and no Game-owned ability to cancel
OS suspension.
## Acceptance Criteria
- Game receives pause before suspension.
- SystemOS suspends after a bounded budget even if the Game does not cooperate.
- Suspended Game does not receive normal gameplay ticks.
- Resume/foreground-restore is delivered before visual return to Game.
- Game cannot prevent OS suspension through guest-visible behavior.
- Existing debugger pause behavior remains distinct from lifecycle suspension.
## Tests / Validation
- Add unit tests in `prometeu-system` for lifecycle transition state.
- Add firmware tests around `GameRunningStep` pause request and suspension.
- Add VM runtime tests proving suspended state suppresses normal ticks.
- Run `cargo test -p prometeu-system`.
- Run `cargo test -p prometeu-firmware`.
- Run `discussion validate`.
## Risks
- Debugger pause already exists in VM runtime. Keep debug pause separate from
lifecycle pause/suspend.
- A lifecycle event queue could become a general app event system. Keep this
plan limited to pause/resume required by `DEC-0037`.

View File

@ -1,104 +0,0 @@
---
id: PLN-0141
ticket: foreground-stack-game-pause-shell-vm-backed
title: Integrate Render Audio and Input Pause Boundaries
status: done
created: 2026-07-03
ref_decisions: [DEC-0037]
tags: [runtime, renderer, audio, input, foreground, lifecycle]
---
## Briefing
`DEC-0037` requires foreground transitions to invalidate stale render
submissions, keep Hub/Shell visible until the resumed Game publishes a valid
current-epoch frame, clear or barrier Game input across pause/resume, and pause
Game audio with the Game VM in v1.
This plan integrates those boundary effects after SystemOS foreground state and
Game pause/resume mechanics exist.
## Decisions of Origin
- `DEC-0037` - Foreground Stack and Game Pause Contract.
## Target
Make foreground transitions safe at the render, input, and audio boundaries.
## Scope
- Advance render ownership epoch/generation whenever foreground owner changes.
- Discard stale Game frames after Shell/Hub takes foreground.
- Discard stale Shell frames after Game resumes.
- Keep Hub/Shell visible until a resumed Game publishes a valid current-epoch
submission.
- Clear or barrier Game input across pause/resume boundaries.
- Pause Game audio with the Game VM in v1.
- Add telemetry/tests for stale discards and first-valid-frame gating.
## Out of Scope
- No independent background audio policy.
- No overlay rendering policy.
- No Game-to-Game switch rendering.
- No full memory eviction behavior.
## Execution Plan
1. Extend render ownership transition calls.
Update `crates/console/prometeu-system/src/services/vm_runtime/render_manager.rs`
and call sites in `tick.rs` so foreground owner changes explicitly advance
ownership epoch/generation, not only app mode/app id changes.
2. Add foreground restore gating.
Add state in SystemOS or firmware that records when visual return to Game is
pending. The state must remain in Hub/Shell presentation until a
current-epoch Game submission is observed.
3. Update host presentation selection.
Update `crates/host/prometeu-host-desktop-winit/src/runner.rs` if necessary
so `should_present_worker_frame` and fallback framebuffer presentation obey
the foreground restore gate.
4. Add stale discard coverage.
Extend render manager and render worker tests to prove stale submissions
from the previous owner are discarded after epoch change.
5. Add input boundary clearing.
Add a SystemOS or firmware input barrier when Game pause starts and when
Game resume starts. The barrier must prevent held or pressed Game input from
leaking across the lifecycle boundary.
6. Add audio pause integration.
Ensure Game pause/suspend produces the existing host audio pause command path
or a new explicit lifecycle audio pause signal. Keep this separate from
debugger pause if the observable cause matters in tests.
7. Add tests.
Cover stale frame discard, first-valid-frame visual return, input clearing,
and Game audio pause/resume behavior.
## Acceptance Criteria
- Foreground owner changes advance render ownership epoch/generation.
- Old Game frames cannot present after Hub/Shell takes foreground.
- Old Hub/Shell frames cannot present after Game resumes.
- Visual return to Game waits for a valid current-epoch Game submission.
- Game input does not leak across pause/resume boundaries.
- Game audio pauses with Game suspension in v1.
## Tests / Validation
- Run `cargo test -p prometeu-system render foreground`.
- Run `cargo test -p prometeu-firmware pause`.
- Run `cargo test -p prometeu-host-desktop-winit presentation`.
- Run `discussion validate`.
## Risks
- Existing render epoch logic may already key off app mode/app id. Foreground
owner changes must be explicit so Shell/Hub and resumed Game cannot share a
stale epoch by accident.
- Audio pause currently follows VM paused/debugger state in the host. Keep Game
lifecycle pause distinct enough that tests can prove the decision contract.

View File

@ -1,127 +0,0 @@
---
id: PLN-0142
ticket: foreground-stack-game-pause-shell-vm-backed
title: Deliver Game Lifecycle Events To VM Runtime
status: done
created: 2026-07-04
ref_decisions: [DEC-0037]
tags: [runtime, os, lifecycle, shell, game, vm, foreground, architecture]
---
## Briefing
`DEC-0037` requires pause/resume to be Game-visible lifecycle events. The
current implementation records `GameLifecycleEvent` values in SystemOS, but no
runtime or VM execution path consumes them. Tests currently prove that the OS
queue contains events, not that the Game/runtime can observe them before
suspension or after resume.
## Objective
Deliver Game lifecycle pause/resume events from SystemOS into the VM runtime
boundary in a deterministic, testable way, without exposing Home as guest pad
input or a userland syscall.
## Dependencies
- Source decision: `DEC-0037`.
- Fixes the lifecycle event delivery gap found after `PLN-0140` and
`PLN-0141`.
- `PLN-0143` depends on this plan if it needs to prove that the pause event is
consumed before the pause budget expires.
## Scope
Included:
- Define the runtime-facing lifecycle event delivery API for `Pause` and
`ResumeForeground`.
- Move events from the SystemOS queue into the VM/runtime tick boundary.
- Make delivered events observable in tests without creating a broad app event
system.
- Preserve the distinction between lifecycle pause and debugger pause.
- Ensure events are delivered only to the resident Game task they target.
Excluded:
- No guest Home input, pad field, intrinsic, or syscall.
- No general userland event bus.
- No background execution while suspended.
- No new Shell lifecycle event model.
## Non-Goals
- Do not implement Game-to-Game switching.
- Do not change renderer ownership policy except where tests need to assert
lifecycle event ordering.
- Do not add wall-clock timers or asynchronous callbacks.
## Execution Method
1. Add a narrow VM runtime lifecycle inbox.
- What changes: introduce a small data structure for pending/delivered Game
lifecycle events in `VirtualMachineRuntime`.
- How it changes: add methods such as `queue_game_lifecycle_events`,
`take_delivered_game_lifecycle_events`, or a more idiomatic local name
that preserves deterministic frame-boundary delivery.
- File(s): `crates/console/prometeu-system/src/services/vm_runtime/mod.rs`,
`crates/console/prometeu-system/src/services/vm_runtime/tick.rs`, and
focused tests under `crates/console/prometeu-system/src/services/vm_runtime/`.
2. Route SystemOS events into the VM facade before ticking.
- What changes: `VmFacade::tick` must drain lifecycle events from SystemOS
and pass only events targeted at the currently running resident Game into
the VM runtime lifecycle inbox.
- How it changes: use the existing `LifecycleFacade::take_game_lifecycle_events`
or replace it with an equivalent internal method if borrowing requires a
cleaner API. Do not leave events only observable through
`pending_game_lifecycle_events`.
- File(s): `crates/console/prometeu-system/src/os/facades/vm.rs`,
`crates/console/prometeu-system/src/os/facades/lifecycle.rs`.
3. Expose deterministic test visibility.
- What changes: tests must be able to assert that `Pause` and
`ResumeForeground` reached the VM runtime boundary.
- How it changes: add test-only or public diagnostic accessors on
`VmFacade`/`VirtualMachineRuntime` for delivered lifecycle events, scoped
to lifecycle validation rather than a guest ABI.
- File(s): `crates/console/prometeu-system/src/os/facades/vm.rs`,
`crates/console/prometeu-system/src/services/vm_runtime/tests.rs`.
4. Update firmware tests to assert delivery, not only queue presence.
- What changes: existing firmware tests that inspect
`pending_game_lifecycle_events` should assert VM-runtime delivery at the
tick boundary.
- How it changes: after a Game pause/resume flow, verify that the VM/runtime
observed the lifecycle event in order.
- File(s): `crates/console/prometeu-firmware/src/firmware/firmware.rs`.
## Acceptance Criteria
- [ ] `Pause` is moved from SystemOS into the VM runtime boundary before
suspension is allowed to complete.
- [ ] `ResumeForeground` is moved into the VM runtime boundary before the Game
is considered visually restored.
- [ ] Tests fail if events remain only in `SystemOS.game_lifecycle_events`.
- [ ] Lifecycle event delivery does not mutate `InputSignals`.
- [ ] Debugger pause remains separate from lifecycle pause.
## Tests
- Unit tests in `prometeu-system` proving VM runtime lifecycle event delivery
and drain semantics.
- Firmware tests proving Home request delivers `Pause` to the VM/runtime before
suspension.
- Firmware tests proving resident Game resume delivers `ResumeForeground`
before Game foreground restoration.
- Run `cargo test -p prometeu-system`.
- Run `cargo test -p prometeu-firmware`.
- Run `discussion validate`.
## Affected Artifacts
- `crates/console/prometeu-system/src/os/facades/vm.rs`
- `crates/console/prometeu-system/src/os/facades/lifecycle.rs`
- `crates/console/prometeu-system/src/services/vm_runtime/mod.rs`
- `crates/console/prometeu-system/src/services/vm_runtime/tick.rs`
- `crates/console/prometeu-firmware/src/firmware/firmware.rs`

View File

@ -1,122 +0,0 @@
---
id: PLN-0143
ticket: foreground-stack-game-pause-shell-vm-backed
title: Guarantee Cooperative Pause Budget Tick Before Suspension
status: done
created: 2026-07-04
ref_decisions: [DEC-0037]
tags: [runtime, os, lifecycle, shell, game, vm, foreground, architecture]
---
## Briefing
`DEC-0037` requires a Game-visible `Paused` event followed by a bounded
cooperative reaction budget before SystemOS imposes `Suspended`. The current
implementation uses a one-tick budget and advances it before `vm.tick` in
`GameRunningStep`, so the Game can be suspended without any runtime tick in
which it could observe or react to pause.
## Objective
Guarantee at least one deterministic VM/runtime delivery point for the Game to
observe pause before forced suspension, while preserving SystemOS authority to
suspend the Game after the budget.
## Dependencies
- Source decision: `DEC-0037`.
- Should be executed after or together with `PLN-0142`, because budget tests
must prove delivered lifecycle events, not only queued OS events.
## Scope
Included:
- Fix pause budget ordering so the pause budget cannot expire before the VM
runtime has a delivery point for `Pause`.
- Define the budget in deterministic machine ticks/frames, not wall-clock time.
- Ensure a Game receives no normal gameplay ticks after it reaches
`PausedSuspended`.
- Preserve the existing forced-suspension authority when the budget expires.
- Update tests that currently assert no VM tick occurs immediately after Home.
Excluded:
- No guest-controlled cancellation of suspension.
- No background execution after suspension.
- No extension of the pause budget based on host timing.
- No direct visual-return behavior; render restore gating remains covered by
the existing render boundary work.
## Non-Goals
- Do not add a general scheduler.
- Do not make pause cooperation mandatory for suspension.
- Do not conflate lifecycle pause with debugger pause.
## Execution Method
1. Redefine pause budget state transitions.
- What changes: `ResidentGameState::PauseRequested` must represent a state
in which one or more delivery ticks remain before `PausedSuspended`.
- How it changes: adjust `ForegroundStack::advance_pause_budget` or split it
into explicit methods such as `mark_pause_delivered` and
`advance_pause_budget_after_delivery`.
- File(s): `crates/console/prometeu-system/src/services/foreground.rs`.
2. Move budget advancement after lifecycle delivery.
- What changes: `GameRunningStep::on_update` must not suspend before the VM
runtime has a chance to receive and process the pause lifecycle event.
- How it changes: reorder the GameRunning update so the event delivery path
runs first, then the budget is advanced, and only a subsequent eligible
boundary can force suspension.
- File(s): `crates/console/prometeu-firmware/src/firmware/firmware_step_game_running.rs`,
`crates/console/prometeu-system/src/os/facades/vm.rs`.
3. Preserve no-tick-after-suspended behavior.
- What changes: once `ResidentGameState::PausedSuspended` and
`TaskState::Suspended` are reached, the Game must not receive normal ticks,
normal input, or frame pacing.
- How it changes: keep the existing early transition to Hub after suspension
expires, but ensure it occurs after the cooperative pause window rather
than before it.
- File(s): `crates/console/prometeu-firmware/src/firmware/firmware_step_game_running.rs`.
4. Update tests to match the corrected semantics.
- What changes: tests must stop asserting that the first tick after Home does
zero VM work purely because Home was requested.
- How it changes: assert that the pause event was delivered during the
cooperative window, then assert that a later deterministic tick forces
suspension and no further Game ticks occur.
- File(s): `crates/console/prometeu-firmware/src/firmware/firmware.rs`,
`crates/console/prometeu-system/src/os/system_os.rs`.
## Acceptance Criteria
- [ ] A Game receives a deterministic pause delivery point before SystemOS
suspends it.
- [ ] SystemOS still suspends the Game after the bounded budget even if the
Game does not cooperate.
- [ ] A suspended Game receives no normal gameplay ticks, normal Game input, or
frame pacing.
- [ ] Tests prove the order: Home request -> pause delivery -> budget expiry ->
suspension.
- [ ] Debugger pause behavior is unchanged.
## Tests
- Unit tests in `prometeu-system` for pause budget transitions.
- Firmware tests for non-cooperative forced suspension after at least one
delivered pause boundary.
- Regression tests proving suspended Game tick count does not advance.
- Run `cargo test -p prometeu-system foreground lifecycle`.
- Run `cargo test -p prometeu-firmware pause`.
- Run `discussion validate`.
## Affected Artifacts
- `crates/console/prometeu-system/src/services/foreground.rs`
- `crates/console/prometeu-system/src/os/facades/lifecycle.rs`
- `crates/console/prometeu-system/src/os/facades/vm.rs`
- `crates/console/prometeu-firmware/src/firmware/firmware_step_game_running.rs`
- `crates/console/prometeu-firmware/src/firmware/firmware.rs`

View File

@ -1,124 +0,0 @@
---
id: PLN-0144
ticket: foreground-stack-game-pause-shell-vm-backed
title: Suspend Resident Game When Shell Takes Foreground
status: done
created: 2026-07-04
ref_decisions: [DEC-0037]
tags: [runtime, os, lifecycle, shell, game, vm, foreground, architecture]
---
## Briefing
`DEC-0037` says a suspended resident Game must not receive normal gameplay
ticks, normal Game input, or frame pacing while Hub/Shell owns foreground. The
current `set_shell_foreground` path can mark the resident Game as
`PausedSuspended` in the foreground stack while `TaskManager::set_foreground`
only demotes the previous foreground Game task to `Background`. This creates a
state mismatch if a Shell is opened while the Game has not already gone through
the Home suspension path.
## Objective
Make Shell foreground acquisition either require an already suspended resident
Game or perform the necessary OS-owned suspension consistently across
foreground stack, task manager, and process manager.
## Dependencies
- Source decision: `DEC-0037`.
- Can be implemented independently of `PLN-0142` and `PLN-0143`, but tests
should be rerun with those fixes if they are executed first.
## Scope
Included:
- Close the mismatch between `ResidentGameState::PausedSuspended` and
`TaskState::Background`.
- Ensure Shell foreground cannot leave a resident Game running or backgrounded.
- Cover both `create_vm_shell_task` and `create_native_shell_task` paths.
- Preserve the rule that only one foreground Shell may exist.
- Keep the normal Home -> Shell flow working.
Excluded:
- No direct Shell -> Game return behavior; v1 still returns Shell close to Hub.
- No background Game execution.
- No multiple resident Games.
- No Shell persistence policy changes.
## Non-Goals
- Do not broaden Shell lifecycle semantics.
- Do not implement Game-to-Game switching.
- Do not add UI controls for resume.
## Execution Method
1. Define the legal Shell foreground precondition.
- What changes: decide in code whether Shell foreground requires the
resident Game to already be `PausedSuspended`, or whether
`set_shell_foreground_task` is responsible for suspending it.
- How it changes: prefer centralizing the invariant in
`LifecycleFacade::set_shell_foreground_task`, because it owns task/process
transitions.
- File(s): `crates/console/prometeu-system/src/os/facades/lifecycle.rs`,
`crates/console/prometeu-system/src/services/foreground.rs`.
2. Synchronize task/process state when Shell takes foreground.
- What changes: if a resident Game exists and is not already suspended,
mark the Game task and process `Suspended` before or during Shell
foreground transition.
- How it changes: use `process_id_for_task` for the resident Game and call
`task_manager.mark_suspended` and `process_manager.mark_suspended` in the
same lifecycle operation that sets Shell foreground.
- File(s): `crates/console/prometeu-system/src/os/facades/lifecycle.rs`.
3. Tighten foreground stack behavior.
- What changes: `ForegroundStack::set_shell_foreground` must not silently
hide invalid state transitions.
- How it changes: either reject Shell foreground when the resident Game is
still `Foreground`/`PauseRequested`, or return enough information for the
lifecycle facade to suspend the resident Game explicitly.
- File(s): `crates/console/prometeu-system/src/services/foreground.rs`.
4. Add regression tests for reachable session paths.
- What changes: create tests that call `create_vm_shell_task` and
`create_native_shell_task` while a Game task is foreground.
- How it changes: assert foreground owner is Shell, resident Game remains
recorded, Game task state is `Suspended`, and Game process state is
`Suspended`.
- File(s): `crates/console/prometeu-system/src/os/system_os.rs`,
`crates/console/prometeu-firmware/src/firmware/firmware.rs` if the
firmware Home/Shell path needs coverage.
## Acceptance Criteria
- [ ] A Shell foreground transition cannot leave the resident Game in
`TaskState::Background`.
- [ ] Foreground stack state, task state, and process state agree that the
resident Game is suspended while Shell is foreground.
- [ ] `create_vm_shell_task` and `create_native_shell_task` both preserve the
invariant.
- [ ] Opening a second foreground Shell remains rejected.
- [ ] Closing Shell still returns to Hub/Home, not directly to Game.
## Tests
- Unit tests in `prometeu-system` for `set_shell_foreground_task` with a
resident foreground Game.
- Session facade tests for VM Shell and native Shell creation while a Game is
resident.
- Existing firmware Shell close tests must continue to pass.
- Run `cargo test -p prometeu-system foreground lifecycle shell`.
- Run `cargo test -p prometeu-firmware shell`.
- Run `discussion validate`.
## Affected Artifacts
- `crates/console/prometeu-system/src/services/foreground.rs`
- `crates/console/prometeu-system/src/os/facades/lifecycle.rs`
- `crates/console/prometeu-system/src/os/facades/sessions.rs`
- `crates/console/prometeu-system/src/os/system_os.rs`
- `crates/console/prometeu-firmware/src/firmware/firmware.rs`

View File

@ -1,149 +0,0 @@
---
id: PLN-0145
ticket: foreground-stack-game-pause-shell-vm-backed
title: Centralize Resident Game Resume Transition
status: done
created: 2026-07-04
ref_decisions: [DEC-0037]
tags: [runtime, os, lifecycle, shell, game, vm, foreground, architecture]
---
## Briefing
`DEC-0037` requires returning to the same resident Game to reactivate the Game
VM, restore render ownership, send a resume/foreground lifecycle notification,
and clear or barrier pending Game input. That transition currently exists in
more than one firmware path: `Firmware::resume_resident_game_from_home` and the
Hub library click path that resumes the selected resident Game by `app_id`.
The behavior is correct, but the duplication is fragile. Any future change to
resume ordering, input barriers, render ownership, or lifecycle event delivery
could fix one path while leaving the other path stale.
## Objective
Create one firmware-owned resident Game resume helper and route every
Home/Hub-to-Game resume path through it.
## Dependencies
- Source decision: `DEC-0037`.
- Depends on the accepted v1 identity rule that `app_id` is unique for all
games and may be used to decide whether a Hub game selection refers to the
current resident Game.
- Should preserve the behavior introduced by `PLN-0142`, `PLN-0143`, and
`PLN-0144`.
## Scope
Included:
- Centralize resident Game resume orchestration in `Firmware`.
- Preserve resume lifecycle event queuing through `LifecycleFacade::resume_task`.
- Preserve render ownership transition back to `AppMode::Game`.
- Preserve input barrier behavior across all Home/Hub-to-Game resume paths.
- Preserve the Hub rule that selecting the same resident Game resumes it, while
selecting a different Game remains blocked by v1 scope.
- Keep tests proving same-task resume, lifecycle event delivery, and input
barriering.
Excluded:
- No Game-to-Game switching.
- No multiple resident Games.
- No change to `app_id` uniqueness policy.
- No direct Shell-to-Game close behavior.
## Non-Goals
- Do not move foreground authority from SystemOS into firmware.
- Do not change pause budget or suspend behavior.
- Do not add UI affordances beyond the existing Hub game row selection.
## Execution Method
1. Introduce a single resident Game resume helper.
- What changes: create a private firmware method that performs the complete
resident Game resume operation and returns the target `TaskId` or a typed
failure.
- How it changes: move the shared sequence currently duplicated across
`resume_resident_game_from_home` and `HubHomeStep`: read resident task,
call `LifecycleFacade::resume_task`, transition VM render ownership to
`AppMode::Game`, install `GameRunningStep`, clear state initialization,
and arm the input barrier.
- File(s): `crates/console/prometeu-firmware/src/firmware/firmware.rs`.
2. Route explicit resume through the helper.
- What changes: `Firmware::resume_resident_game_from_home` becomes a thin
wrapper around the centralized helper.
- How it changes: preserve its public `bool` return contract for existing
tests and call sites.
- File(s): `crates/console/prometeu-firmware/src/firmware/firmware.rs`.
3. Route Hub same-game selection through the helper.
- What changes: `HubHomeStep` must no longer hand-roll resume task, render
ownership, and input clearing.
- How it changes: expose a state transition request that lets the firmware
perform the centralized helper, or restructure the Hub action handling so
the parent `Firmware` owns this transition. Keep `HubHomeStep` responsible
only for recognizing that the selected cartridge matches the resident
`app_id`.
- File(s): `crates/console/prometeu-firmware/src/firmware/firmware.rs`,
`crates/console/prometeu-firmware/src/firmware/firmware_step_hub_home.rs`,
`crates/console/prometeu-firmware/src/firmware/firmware_state.rs` if a new
transition variant is needed.
4. Keep different-game launch blocked under v1.
- What changes: selecting a different `app_id` while a resident Game exists
continues to log and remain in Hub.
- How it changes: keep the existing warning behavior, but ensure it does not
duplicate resume side effects.
- File(s): `crates/console/prometeu-firmware/src/firmware/firmware_step_hub_home.rs`.
5. Update regression tests.
- What changes: tests should prove both explicit resume and Hub same-game
selection use the same observable transition.
- How it changes: assert same `TaskId`, foreground owner restored to Game,
pending resume lifecycle event exists before the next Game tick, delivered
resume event after the Game tick, and input barrier suppresses held input.
- File(s): `crates/console/prometeu-firmware/src/firmware/firmware.rs`.
## Acceptance Criteria
- [ ] There is exactly one implementation of the resident Game resume sequence.
- [ ] `Firmware::resume_resident_game_from_home` preserves its external
behavior.
- [ ] Hub selection of the same resident Game resumes the existing `TaskId`
without creating or loading a new Game task.
- [ ] The resume path always queues and delivers the Game lifecycle
`ResumeForeground` event.
- [ ] The resume path always barriers held Game input.
- [ ] Selecting a different Game while one Game is resident remains blocked in
v1.
## Tests
- Unit/integration tests in `prometeu-firmware` for explicit resume from Home.
- Unit/integration tests in `prometeu-firmware` for Hub same-game selection
after Home suspension.
- Regression assertion that held input does not leak into the Game immediately
after resume.
- Run `cargo test -p prometeu-firmware resident_game_resume`.
- Run `cargo test -p prometeu-firmware hub_home_clicking_resident_game`.
- Run `cargo test -p prometeu-system`.
- Run `discussion validate`.
## Affected Artifacts
- `crates/console/prometeu-firmware/src/firmware/firmware.rs`
- `crates/console/prometeu-firmware/src/firmware/firmware_step_hub_home.rs`
- `crates/console/prometeu-firmware/src/firmware/firmware_state.rs`
## Risks
- The current step/state architecture gives `HubHomeStep` a `PrometeuContext`
but not direct access to private `Firmware` fields. If a new transition
variant is introduced, it must stay narrow and must not turn state steps into
owners of lifecycle authority.
- Duplicating less logic must not accidentally weaken the input barrier that was
added for Hub-to-Game transitions.

View File

@ -1,119 +0,0 @@
---
id: PLN-0146
ticket: foreground-stack-game-pause-shell-vm-backed
title: Consolidate Lifecycle Process Lookup Helpers
status: done
created: 2026-07-04
ref_decisions: [DEC-0037]
tags: [runtime, os, lifecycle, shell, game, vm, foreground, architecture]
---
## Briefing
`DEC-0037` depends on SystemOS lifecycle authority being internally
consistent. The lifecycle facade now exposes both process state and process kind
for a task so firmware can distinguish VM-backed Shell tasks from native Shell
tasks. Both accessors repeat the same task-to-process lookup pattern.
The duplication is small, but this facade is now part of the foreground
contract surface. Keeping lookup and error mapping in one place reduces the
chance that future lifecycle accessors drift in behavior.
## Objective
Consolidate task-to-process lookup in `LifecycleFacade` behind one private
helper, while preserving the public lifecycle facade behavior and errors.
## Dependencies
- Source decision: `DEC-0037`.
- Depends on the process/task authority from SystemOS lifecycle work.
- Can be implemented independently of `PLN-0145` and `PLN-0147`.
## Scope
Included:
- Add one private helper that resolves a `TaskId` to its backing process.
- Route `process_state_for_task` and `process_kind_for_task` through that
helper.
- Preserve `LifecycleError::TaskNotFound` and
`LifecycleError::ProcessNotFound` behavior exactly.
- Keep the current process/task manager ownership boundaries intact.
- Keep the current non-architectural cleanup in `set_foreground_task` if it is
still present in the working tree.
Excluded:
- No new public process manager API.
- No changes to task or process IDs.
- No changes to Shell/Game lifecycle semantics.
- No broader facade refactor outside lifecycle lookup.
## Non-Goals
- Do not expose raw process manager access through SystemOS.
- Do not change `ProcessKind` or `TaskKind` definitions.
- Do not alter foreground stack behavior.
## Execution Method
1. Add a private lifecycle lookup helper.
- What changes: add a private function near `process_id_for_task` that
returns a process reference for a `TaskId`.
- How it changes: the helper should call `process_id_for_task`, then
retrieve the process from `process_manager`, returning
`LifecycleError::ProcessNotFound(process_id)` if the process is missing.
- File(s): `crates/console/prometeu-system/src/os/facades/lifecycle.rs`.
2. Route state and kind accessors through the helper.
- What changes: `LifecycleFacade::process_state_for_task` and
`LifecycleFacade::process_kind_for_task` stop duplicating the process
manager lookup.
- How it changes: both methods call the helper and map the process reference
to `ProcessState` or `ProcessKind`.
- File(s): `crates/console/prometeu-system/src/os/facades/lifecycle.rs`.
3. Preserve existing foreground code cleanup.
- What changes: keep `set_foreground_task` as an expression-returning match
rather than using redundant `return` statements.
- How it changes: ensure formatting and clippy stay clean.
- File(s): `crates/console/prometeu-system/src/os/facades/lifecycle.rs`.
4. Tighten tests around lookup behavior.
- What changes: extend or keep tests proving VM Shell and native Shell
process kinds are observable through lifecycle.
- How it changes: add missing-task and missing-process coverage only if the
helper changes the observable path enough to justify it.
- File(s): `crates/console/prometeu-system/src/os/system_os.rs`.
## Acceptance Criteria
- [ ] `process_state_for_task` and `process_kind_for_task` share one private
task-to-process lookup implementation.
- [ ] Missing task and missing process errors remain unchanged.
- [ ] Existing lifecycle tests continue to pass.
- [ ] Firmware can still distinguish `ProcessKind::VmShell` from
`ProcessKind::NativeShell`.
- [ ] Clippy reports no warnings for the touched crates.
## Tests
- Unit tests in `prometeu-system` for shell process kind lookup.
- Existing lifecycle missing task/process tests must continue to pass.
- Run `cargo test -p prometeu-system lifecycle process_kind`.
- Run `cargo test -p prometeu-firmware shell`.
- Run `cargo clippy -p prometeu-system -p prometeu-firmware --all-targets -- -D warnings`.
- Run `discussion validate`.
## Affected Artifacts
- `crates/console/prometeu-system/src/os/facades/lifecycle.rs`
- `crates/console/prometeu-system/src/os/system_os.rs`
## Risks
- Over-generalizing the helper could leak process manager concepts outside the
lifecycle facade. Keep it private and narrowly typed.
- This is intentionally small cleanup. It must not become a broad SystemOS
facade redesign.

View File

@ -1,144 +0,0 @@
---
id: PLN-0147
ticket: foreground-stack-game-pause-shell-vm-backed
title: Move Native Shell Profile Update Into Hub
status: done
created: 2026-07-04
ref_decisions: [DEC-0037]
tags: [runtime, os, lifecycle, shell, game, vm, foreground, architecture]
---
## Briefing
`DEC-0037` says a suspended Game must not receive normal gameplay ticks while
Hub or Shell owns foreground. A recent fix made `ShellRunningStep` branch on
`ProcessKind`: VM-backed Shell tasks use the VM-backed shell profile path, while
native Shell tasks call `PrometeuHub::gui_update` and `PrometeuHub::render`
without ticking the VM.
That behavior is correct, but the native Shell update sequence now lives in
firmware rather than in the Hub profile API. This duplicates part of
`PrometeuHub::update_shell_profile` and creates a drift risk if Shell close,
render, or input rules change later.
## Objective
Move native Shell profile updating into `PrometeuHub` so `ShellRunningStep`
chooses the correct profile API without reimplementing Hub behavior.
## Dependencies
- Source decision: `DEC-0037`.
- Depends on the `ProcessKind::VmShell` vs `ProcessKind::NativeShell`
distinction available through `LifecycleFacade::process_kind_for_task`.
- May be implemented before or after `PLN-0146`, but should use the consolidated
lookup helper if `PLN-0146` has already landed.
## Scope
Included:
- Add a Hub-owned native Shell update API that does not tick the VM.
- Keep VM-backed Shell update behavior in the existing VM-backed profile path.
- Keep Shell close handling consistent between native and VM-backed Shells.
- Keep native Shell rendering in the Hub/Shell UI path.
- Keep firmware responsible for choosing which profile applies to the current
Shell task.
- Preserve regression coverage that a suspended resident Game does not advance
while native Shell is foreground.
Excluded:
- No change to VM-backed Shell execution semantics.
- No change to native Shell UI content.
- No direct Shell close to Game.
- No multiple foreground Shells.
- No background Game ticking.
## Non-Goals
- Do not move foreground lifecycle authority into `PrometeuHub`.
- Do not make native Shells VM tasks.
- Do not introduce a general scheduler abstraction.
## Execution Method
1. Add a native Shell profile update method on `PrometeuHub`.
- What changes: create `update_native_shell_profile` or an equivalent
clearly named method that returns `SystemProfileUpdate`.
- How it changes: the method calls `gui_update`, maps Start input to
`SystemProfileAction::CloseShell` when a shell window is focused, calls
`render`, and always returns `crash: None`.
- File(s):
`crates/console/prometeu-system/src/programs/prometeu_hub/prometeu_hub.rs`.
2. Keep the VM-backed Shell profile method explicitly VM-backed.
- What changes: either keep `update_shell_profile` and document/name its VM
tick behavior, or rename/split it if the codebase convention supports that
without churn.
- How it changes: ensure the VM-backed path is the only Hub profile update
method that calls `os.vm().tick(...)`.
- File(s):
`crates/console/prometeu-system/src/programs/prometeu_hub/prometeu_hub.rs`.
3. Simplify `ShellRunningStep`.
- What changes: remove manual native Shell `gui_update`, Start handling, and
render calls from firmware.
- How it changes: branch on `ProcessKind` and call the appropriate Hub
profile method; keep crash handling only for the VM-backed method.
- File(s):
`crates/console/prometeu-firmware/src/firmware/firmware_step_shell_running.rs`.
4. Add Hub-level native profile tests.
- What changes: prove native Shell profile update emits close on the close
button and on Start, renders Shell UI, and does not tick the VM.
- How it changes: use existing Hub/system test helpers where possible; assert
VM tick index remains unchanged for native profile update.
- File(s):
`crates/console/prometeu-system/src/programs/prometeu_hub/prometeu_hub.rs`
and/or `crates/console/prometeu-firmware/src/firmware/firmware.rs`.
5. Preserve end-to-end regression coverage.
- What changes: keep the Game -> Home -> native Shell test asserting that
repeated Shell frames do not advance the resident Game VM tick index.
- How it changes: rerun the firmware regression added for native Shell
foreground.
- File(s): `crates/console/prometeu-firmware/src/firmware/firmware.rs`.
## Acceptance Criteria
- [ ] Native Shell update behavior is owned by `PrometeuHub`, not hand-coded in
`ShellRunningStep`.
- [ ] The native Shell profile update never calls `os.vm().tick(...)`.
- [ ] VM-backed Shell update remains the only Shell profile path that ticks the
VM runtime.
- [ ] Start and close-button Shell close behavior remains unchanged for native
Shells.
- [ ] A suspended resident Game receives no normal gameplay ticks while native
Shell is foreground.
## Tests
- Hub-level tests for native Shell profile close behavior.
- Hub-level or firmware-level tests proving native Shell profile update does not
tick the VM.
- Existing firmware regression for Game -> Home -> native Shell repeated frames.
- Run `cargo test -p prometeu-system native_shell shell_profile`.
- Run `cargo test -p prometeu-firmware game_home_shell_hub_same_game_flow`.
- Run `cargo test -p prometeu-firmware`.
- Run `cargo test -p prometeu-system`.
- Run `discussion validate`.
## Affected Artifacts
- `crates/console/prometeu-system/src/programs/prometeu_hub/prometeu_hub.rs`
- `crates/console/prometeu-firmware/src/firmware/firmware_step_shell_running.rs`
- `crates/console/prometeu-firmware/src/firmware/firmware.rs`
## Risks
- Renaming the existing VM-backed shell profile method may create unnecessary
churn. Prefer adding the native method and keeping call sites explicit unless
the rename is small and clearly improves readability.
- Hub APIs must remain UI/profile orchestration only; lifecycle close and task
state changes still belong to firmware/SystemOS lifecycle.

View File

@ -1,92 +0,0 @@
---
id: PLN-0148
ticket: foreground-stack-game-pause-shell-vm-backed
title: Introduce VM Session Registry and Identity
status: done
created: 2026-07-04
ref_decisions: [DEC-0038]
tags: [runtime, os, lifecycle, shell, game, vm, foreground, architecture]
---
## Briefing
DEC-0038 requires every VM-backed process to own its own mutable VM context instead of sharing `Firmware.vm`. This plan introduces the identity and registry layer required before moving runtime state or changing cartridge loading.
The implementation must make VM session ownership explicit inside `SystemOS`. A VM-backed Game task and a VM-backed Shell task must be able to coexist as distinct sessions, even before all execution paths are migrated to session-owned VMs.
## Objective
Add a `VmSession` model and registry owned by `SystemOS`, keyed by a stable session identity tied to the owning task/process. The registry must support creating, retrieving, and validating VM sessions for VM-backed Game and Shell tasks.
## Dependencies
- Source decision: DEC-0038.
- Existing foreground/task/process lifecycle from DEC-0037 remains authoritative.
- Must be implemented before PLN-0149, PLN-0150, PLN-0151, and PLN-0152.
## Scope
- Add a session identity type, preferably `VmSessionId`, with a deliberate mapping from the owning `TaskId`.
- Add a `VmSession` data structure containing at least:
- owning `TaskId`;
- owning process id or process lookup path;
- `app_id`, title, version, and `AppMode`;
- initial placeholder for the session-owned `VirtualMachine`;
- initial placeholder for session-owned runtime state to be populated by PLN-0149.
- Add a registry under `crates/console/prometeu-system/src/services/` or an equivalent existing services module.
- Expose creation and lookup through `crates/console/prometeu-system/src/os/facades/sessions.rs`.
- Update `SystemOS` construction in `crates/console/prometeu-system/src/os/system_os.rs` to own the registry.
- Enforce V1 uniqueness:
- at most one resident Game session;
- one foreground Shell task at a time;
- same `app_id` resident Game relaunch maps to the existing resident session rather than creating a second Game session.
## Non-Goals
- Do not move all VM runtime fields yet; that is PLN-0149.
- Do not route cartridge loading through sessions yet; that is PLN-0150.
- Do not remove `Firmware.vm` yet.
- Do not implement general background execution.
- Do not allow Game-to-Game switching.
## Execution Method
1. Inspect `TaskId`, process ids, and `ProcessKind` definitions in `crates/console/prometeu-system/src/services/task/` and `crates/console/prometeu-system/src/services/process/`.
2. Add the VM session module with a small API:
- create session for an existing VM-backed task/process;
- get immutable/mutable session by task id;
- get resident Game session by `app_id`;
- reject incompatible duplicate Game sessions.
3. Wire the registry into `SystemOS`.
4. Extend `SessionsFacade::create_vm_game_task` and `create_vm_shell_task` so task creation also creates a VM session record.
5. Keep native Shell task creation out of the VM session registry.
6. Add focused unit tests under `prometeu-system` for registry identity and duplicate handling.
## Acceptance Criteria
- `SystemOS` owns a VM session registry.
- Creating a VM Game task creates exactly one VM session associated with that task.
- Creating a VM Shell task creates exactly one VM session associated with that task.
- Creating a native Shell task creates no VM session.
- Attempting to create a second resident Game session for a different `app_id` is rejected or routed through the existing lifecycle rejection path.
- Relaunching the same resident Game can resolve the existing VM session by `app_id`.
- No firmware state transition depends on the new registry yet except through task/session creation.
## Tests
- `cargo test -p prometeu-system`
- Add unit tests for:
- VM Game task creates a VM session;
- VM Shell task creates a VM session;
- native Shell task does not create a VM session;
- lookup by `TaskId`;
- same resident Game `app_id` resolves to the existing session;
- different resident Game `app_id` remains blocked under V1.
## Affected Artifacts
- `crates/console/prometeu-system/src/os/system_os.rs`
- `crates/console/prometeu-system/src/os/facades/sessions.rs`
- `crates/console/prometeu-system/src/services/mod.rs`
- New or extended module under `crates/console/prometeu-system/src/services/`
- `crates/console/prometeu-system/src/lib.rs` if exports are required

View File

@ -1,99 +0,0 @@
---
id: PLN-0149
ticket: foreground-stack-game-pause-shell-vm-backed
title: Move VM Runtime State Into VM Sessions
status: done
created: 2026-07-04
ref_decisions: [DEC-0038]
tags: [runtime, os, lifecycle, shell, game, vm, foreground, architecture]
---
## Briefing
DEC-0038 states that mutable VM context must belong to the VM-backed process session, not to firmware or a single global runtime slot. The current split has `Firmware.vm: VirtualMachine` and `SystemOS.vm_runtime: VirtualMachineRuntime`, which makes a suspended Game vulnerable to VM-backed Shell execution.
This plan migrates mutable per-application VM runtime state into the session model introduced by PLN-0148 while keeping global OS services global.
## Objective
Make each VM session own the state needed to suspend and later resume that VM-backed process without being overwritten by another VM-backed process.
## Dependencies
- Source decision: DEC-0038.
- Requires PLN-0148.
- Must precede full cartridge-loading and scheduler migration in PLN-0150 and PLN-0151.
## Scope
- Classify `VirtualMachineRuntime` fields into session-owned versus global OS-owned state.
- Move session-owned fields into the `VmSession` context.
- Keep global services in `SystemOS`, including:
- `LogService`;
- process/task/window managers;
- foreground stack;
- durable `MemcardService`;
- global render publication authority or worker coordination that must remain host-facing.
- Treat these as session-owned unless proven otherwise during implementation:
- `VirtualMachine`;
- current cartridge identity;
- frame and tick counters;
- lifecycle delivery bookkeeping;
- crash report for that session;
- pause/debug flags that apply to that session;
- per-session open files, handles, and staging state used by VM syscalls.
- Keep durable memcard contents keyed by `app_id`; do not turn save data into session-private durable storage.
- Update `VmFacade` and VM runtime helpers so callers can operate on a selected session.
## Non-Goals
- Do not redesign render workers beyond separating session-local render command state from global presentation authority.
- Do not implement background execution.
- Do not change syscall semantics except where required to route state through the active session.
- Do not remove host debugger features; preserve or adapt their access path.
## Execution Method
1. Add an internal field-by-field classification comment or test-only assertion near `VirtualMachineRuntime` before moving fields.
2. Introduce a session runtime/context type in the VM session module or by splitting `VirtualMachineRuntime` into:
- session-local runtime;
- global render/log/runtime services.
3. Move `VirtualMachine` ownership from firmware placeholder/session stub into the session context.
4. Move per-cartridge and per-frame state from `SystemOS.vm_runtime` into the session context.
5. Move `open_files`, `next_handle`, and VM syscall staging state out of global `SystemOS` if they are VM-session specific.
6. Update `VmRuntimeHost` in `crates/console/prometeu-system/src/services/vm_runtime/dispatch.rs` to receive session-local state plus references to required global services.
7. Update `VmFacade` in `crates/console/prometeu-system/src/os/facades/vm.rs` with explicit session-targeted methods.
8. Preserve temporary compatibility methods only where needed for existing host/debugger code, and mark them as active-session views rather than canonical global ownership.
9. Add tests proving two VM sessions can hold different app identity and counters at the same time.
## Acceptance Criteria
- No canonical VM state lives in `Firmware`.
- `SystemOS` can hold at least two VM sessions with independent `VirtualMachine` and per-session runtime state.
- VM session A and VM session B can have different `app_id`, title, frame counters, lifecycle delivery history, crash report, open file handles, and debug pause state.
- Durable memcard contents remain shared by `app_id` according to the existing memcard contract.
- Global render ownership remains controlled by SystemOS/host-facing runtime services, not by individual sessions.
- Existing `prometeu-system` VM runtime tests still pass after adapting their setup.
## Tests
- `cargo test -p prometeu-system`
- `cargo test -p prometeu-firmware`
- Add tests for:
- independent session app identity;
- independent tick/logical-frame counters;
- independent lifecycle delivery bookkeeping;
- VM Shell session initialization does not mutate resident Game session identity;
- session-scoped file handles do not collide across sessions.
## Affected Artifacts
- `crates/console/prometeu-system/src/services/vm_runtime/`
- VM session module introduced by PLN-0148
- `crates/console/prometeu-system/src/os/facades/vm.rs`
- `crates/console/prometeu-system/src/os/system_os.rs`
- `crates/console/prometeu-system/src/os/facades/fs.rs`
- `crates/console/prometeu-firmware/src/firmware/firmware.rs`
- `crates/host/prometeu-host-desktop-winit/src/debugger.rs`
- `crates/host/prometeu-host-desktop-winit/src/runner.rs`
- `crates/host/prometeu-host-desktop-winit/src/stats.rs`

View File

@ -1,90 +0,0 @@
---
id: PLN-0150
ticket: foreground-stack-game-pause-shell-vm-backed
title: Route Cartridge Loading Through VM Sessions
status: done
created: 2026-07-04
ref_decisions: [DEC-0038]
tags: [runtime, os, lifecycle, shell, game, vm, foreground, architecture]
---
## Briefing
DEC-0038 requires cartridge loading to create or activate a VM-backed process session instead of initializing a single global VM. The current `LoadCartridgeStep` initializes `ctx.os.vm().initialize(ctx.vm, &cartridge)` before task/session ownership is selected.
This plan moves cartridge initialization behind session services so Game and VM-backed Shell loading cannot overwrite each other.
## Objective
Route Game and VM Shell cartridge loading through the VM session registry and initialize the cartridge inside the target session-owned VM context.
## Dependencies
- Source decision: DEC-0038.
- Requires PLN-0148.
- Requires enough of PLN-0149 to expose session-owned VM/runtime initialization.
- Must precede the end-to-end coexistence validation in PLN-0152.
## Scope
- Replace direct global VM initialization in `LoadCartridgeStep`.
- Add a session/facade operation that:
- selects an existing resident Game session when the same `app_id` is relaunched;
- rejects or reports different resident Game launches under V1;
- creates a new VM Shell session for a Shell cartridge;
- initializes the cartridge inside the selected session VM.
- Keep asset manager initialization aligned with the foreground cartridge being launched.
- Ensure `AppCrashesStep` receives initialization failures from the target session.
- Update Hub cartridge selection logic so resume versus load is based on lifecycle/session identity, not global VM identity.
## Non-Goals
- Do not implement multiple resident Games.
- Do not implement background execution.
- Do not redesign cartridge format or asset packaging.
- Do not broaden Shell windowing behavior beyond the existing one-foreground-Shell model.
## Execution Method
1. Add a `load_vm_cartridge` or equivalent method to `SessionsFacade` or `VmFacade`.
2. Make the method return an execution target:
- existing Game task for same resident `app_id`;
- new Game task for first Game launch;
- new VM Shell task for Shell launch;
- structured error/crash report for invalid VM initialization;
- explicit lifecycle error for blocked second Game.
3. Update `crates/console/prometeu-firmware/src/firmware/firmware_step_load_cartridge.rs` to call the facade method instead of initializing `ctx.vm`.
4. Update `crates/console/prometeu-firmware/src/firmware/firmware_step_hub_home.rs` so same-Game relaunch checks session/lifecycle state rather than `ctx.os.vm().current_app_id()`.
5. Preserve AppMode-based transition:
- Game target transitions to `GameRunning`;
- Shell target creates/focuses a window and transitions to `ShellRunning`.
6. Adapt crash tests and resident-game relaunch tests in firmware.
## Acceptance Criteria
- `LoadCartridgeStep` no longer initializes a global firmware VM.
- Loading a Shell cartridge creates/initializes a Shell VM session.
- Loading the first Game cartridge creates/initializes the resident Game VM session.
- Clicking the same resident Game from Hub resumes the existing Game session without reinitializing it.
- Attempting to load a different Game while one resident Game exists remains blocked under V1 with a controlled outcome.
- VM initialization failure still transitions to `AppCrashesStep`.
## Tests
- `cargo test -p prometeu-firmware`
- `cargo test -p prometeu-system`
- Add or update firmware tests for:
- first Game load creates one session and enters `GameRunning`;
- same Game relaunch resumes existing task/session;
- VM Shell load creates a distinct session;
- Shell load does not change resident Game session app identity;
- invalid cartridge reports `CrashReport::VmInit`.
## Affected Artifacts
- `crates/console/prometeu-firmware/src/firmware/firmware_step_load_cartridge.rs`
- `crates/console/prometeu-firmware/src/firmware/firmware_step_hub_home.rs`
- `crates/console/prometeu-firmware/src/firmware/firmware.rs`
- `crates/console/prometeu-system/src/os/facades/sessions.rs`
- `crates/console/prometeu-system/src/os/facades/vm.rs`
- VM session module introduced by PLN-0148

View File

@ -1,92 +0,0 @@
---
id: PLN-0151
ticket: foreground-stack-game-pause-shell-vm-backed
title: Schedule Foreground VM Session Ticks
status: done
created: 2026-07-04
ref_decisions: [DEC-0038]
tags: [runtime, os, lifecycle, shell, game, vm, foreground, architecture]
---
## Briefing
DEC-0038 separates session ownership from foreground ownership and states that V1 does not provide general background execution. The runtime therefore needs an explicit scheduler path that ticks the VM session eligible for foreground execution, while leaving suspended resident Game sessions intact.
## Objective
Make firmware and SystemOS tick the VM session associated with the current foreground VM-backed task, instead of ticking a global VM/runtime pair.
## Dependencies
- Source decision: DEC-0038.
- Requires PLN-0148.
- Requires PLN-0149.
- Requires PLN-0150 for loaded cartridges to map to sessions.
## Scope
- Add scheduler/facade operations that tick a specific VM session by `TaskId`.
- Update `GameRunningStep` to tick the Game session identified by its `task_id`.
- Update `ShellRunningStep` to tick only VM-backed Shell sessions.
- Ensure native Shell foreground state performs native profile updates without ticking any VM session.
- Keep suspended Game sessions non-executing while Hub or Shell is foreground.
- Preserve lifecycle event delivery:
- pause/resume events go to the resident Game session;
- Shell lifecycle events, if any, remain scoped to the Shell session.
- Preserve host debugger pause/step behavior against the active VM session view.
## Non-Goals
- Do not add background execution classes yet.
- Do not allow multiple simultaneous VM ticks in one host frame.
- Do not implement general service/player processes.
- Do not change the 60 Hz firmware tick contract.
## Execution Method
1. Add `SystemOS` facade methods for:
- ticking a VM session by task id;
- querying whether a task is VM-backed;
- exposing the active VM session to host/debugger tooling.
2. Update `crates/console/prometeu-firmware/src/firmware/firmware_step_game_running.rs` to tick through the task/session facade.
3. Update `crates/console/prometeu-firmware/src/firmware/firmware_step_shell_running.rs` to branch on process kind:
- VM Shell ticks its VM session;
- native Shell runs native profile logic only.
4. Remove direct `ctx.vm` use from Game/Shell execution steps.
5. Ensure resident Game pause budget can deliver one final pause tick and then stop ticking once suspended.
6. Update host/debugger active VM access paths to resolve through the foreground session.
7. Add regression tests for tick counters across Game, Hub, native Shell, and VM Shell transitions.
## Acceptance Criteria
- A suspended Game session does not advance while Hub is foreground.
- A suspended Game session does not advance while a Shell is foreground.
- A VM Shell session advances while that VM Shell is foreground.
- A native Shell does not tick any VM session.
- Returning from Hub to resident Game resumes ticking the same Game session.
- Host debugger and stats read the active VM session, not a stale global VM.
## Tests
- `cargo test -p prometeu-firmware`
- `cargo test -p prometeu-system`
- `cargo test -p prometeu-host-desktop-winit`
- Add tests for:
- Game tick count freezes after Home suspension;
- VM Shell tick count advances independently;
- native Shell does not advance Game tick count;
- resume returns to same Game session tick counter;
- active-session debugger snapshot uses foreground VM session.
## Affected Artifacts
- `crates/console/prometeu-firmware/src/firmware/firmware_step_game_running.rs`
- `crates/console/prometeu-firmware/src/firmware/firmware_step_shell_running.rs`
- `crates/console/prometeu-firmware/src/firmware/prometeu_context.rs`
- `crates/console/prometeu-firmware/src/firmware/firmware.rs`
- `crates/console/prometeu-system/src/os/facades/vm.rs`
- `crates/console/prometeu-system/src/os/facades/lifecycle.rs`
- VM session module introduced by PLN-0148
- `crates/host/prometeu-host-desktop-winit/src/debugger.rs`
- `crates/host/prometeu-host-desktop-winit/src/runner.rs`
- `crates/host/prometeu-host-desktop-winit/src/stats.rs`

View File

@ -1,86 +0,0 @@
---
id: PLN-0152
ticket: foreground-stack-game-pause-shell-vm-backed
title: Validate Game and VM Shell Session Coexistence
status: done
created: 2026-07-04
ref_decisions: [DEC-0038]
tags: [runtime, os, lifecycle, shell, game, vm, foreground, architecture]
---
## Briefing
DEC-0038 was motivated by the concrete failure mode where a resident Game is suspended, a Shell is opened, and Game logs or state unexpectedly continue under the wrong foreground owner. After VM sessions and scheduling are implemented, the system needs explicit regression coverage for this workflow.
## Objective
Add end-to-end and integration validation proving a resident Game VM session and a VM-backed Shell session can coexist without overwriting or ticking each other incorrectly.
## Dependencies
- Source decision: DEC-0038.
- Requires PLN-0148, PLN-0149, PLN-0150, and PLN-0151.
- Should be executed before closing the broader DEC-0038 implementation thread.
## Scope
- Add firmware-level regression tests for:
- Game launch;
- Home/SystemOS request;
- Game pause/suspend;
- VM Shell launch;
- Shell foreground ticking;
- Shell exit;
- Hub return;
- resident Game resume.
- Add system-level tests for independent session state.
- Add host-level tests only where active VM/debugger/stat routing cannot be covered at firmware/system level.
- Verify logs from the stress cartridge or equivalent test cartridge do not appear while Game is suspended and Shell is foreground.
- Verify Shell logs, frames, or tick counters belong to the Shell session.
## Non-Goals
- Do not introduce new architecture in this plan.
- Do not broaden background execution.
- Do not rewrite Hub UI behavior except where required to exercise the flow.
- Do not depend on manual desktop testing as the only evidence.
## Execution Method
1. Identify or add deterministic test cartridges/helpers that can emit per-session tick/log evidence.
2. Add a firmware integration test for `Game -> Home -> VM Shell -> Hub -> same Game`.
3. Capture Game session counters before Shell launch and after Shell foreground ticks.
4. Assert the Game session counters and lifecycle delivery state remain stable while Shell is foreground.
5. Assert the Shell session counters advance while Shell is foreground.
6. Assert returning to the same Game resumes the same task/session and then advances the Game counter.
7. Add a regression specifically covering the prior symptom: Game `stress` logs must not appear during Shell foreground execution.
8. Run the relevant crate tests and record evidence in the plan completion notes when implemented.
## Acceptance Criteria
- The failing manual scenario is covered by automated tests.
- The Game session is suspended and non-ticking while VM Shell is foreground.
- The VM Shell session ticks independently.
- Returning to Hub and selecting the same Game resumes the resident Game session instead of creating a duplicate process/session.
- No test relies on a single global `current_app_id` to infer correctness.
## Tests
- `cargo test -p prometeu-system`
- `cargo test -p prometeu-firmware`
- `cargo test -p prometeu-host-desktop-winit` if host active-session plumbing changed.
- Optional manual smoke:
- run `cargo run --bin prometeu-host-desktop-winit --profile release --manifest-path crates/host/prometeu-host-desktop-winit/Cargo.toml -- --games-root test-cartridges`;
- open Stress Console;
- press ESC/Home;
- open VM-backed Shell;
- verify no Stress logs appear while Shell is foreground;
- return and resume Stress Console.
## Affected Artifacts
- `crates/console/prometeu-firmware/src/firmware/firmware.rs` tests
- `crates/console/prometeu-system/src/os/system_os.rs` tests
- `crates/console/prometeu-system/src/services/vm_runtime/tests.rs`
- `crates/host/prometeu-host-desktop-winit/src/runner.rs` tests if needed
- `test-cartridges/` only if a deterministic VM Shell fixture is missing

View File

@ -1,82 +0,0 @@
---
id: PLN-0153
ticket: foreground-stack-game-pause-shell-vm-backed
title: Specify VM Session Ownership and Background-Ready Scheduling
status: done
created: 2026-07-04
ref_decisions: [DEC-0038]
tags: [runtime, os, lifecycle, shell, game, vm, foreground, architecture]
---
## Briefing
DEC-0038 locks the runtime direction: VM context is session-owned, firmware orchestrates macro-state, and background execution is out of V1 but must remain a near-term compatible extension. The canonical specs must describe that contract in English so future implementation and debugging do not drift back to a single global VM model.
## Objective
Update canonical runtime specifications to document VM session ownership, foreground execution eligibility, render publication authority, and the background-ready shape of the scheduler without committing to background execution in this implementation batch.
## Dependencies
- Source decision: DEC-0038.
- Should be informed by PLN-0148 through PLN-0151 once implementation names settle.
- May be done before PLN-0152 if the implementation contract is already stable, but final wording must match implemented APIs.
## Scope
- Update the canonical specs under `docs/specs/runtime/` in English.
- Document:
- each VM-backed process owns one VM session;
- VM sessions own mutable VM context;
- foreground ownership and execution eligibility are separate concepts;
- V1 executes only the foreground VM-backed task, except the existing pause handoff budget;
- suspended resident Game sessions preserve VM context but do not tick;
- VM-backed Shell sessions have their own VM context;
- native Shell does not own a VM session;
- durable app data remains keyed by `app_id`;
- session-scoped handles and staging state are not durable app data;
- global render publication remains SystemOS/host authority.
- Add background-ready wording:
- scheduler model can later mark sessions or service processes as eligible without changing ownership;
- no current guarantee of background progress is introduced.
## Non-Goals
- Do not write implementation plans into specs.
- Do not document background execution as available.
- Do not move normative text into `discussion/lessons/`.
- Do not create new legacy docs paths.
## Execution Method
1. Locate the most relevant runtime specs, likely:
- `docs/specs/runtime/09-events-and-concurrency.md`;
- `docs/specs/runtime/12-firmware-pos-and-prometeuhub.md`;
- `docs/specs/runtime/14-boot-profiles.md`;
- `docs/specs/runtime/16-host-abi-and-syscalls.md` if host/debugger active VM views are affected.
2. Add concise normative sections using `MUST`, `MUST NOT`, and `SHALL` where appropriate.
3. Keep discussion history out of the spec; cite behavior, not agenda chronology.
4. Align terminology with implemented type names from PLN-0148 through PLN-0151.
5. Run discussion validation after edits and any repository tests required by touched docs tooling.
## Acceptance Criteria
- Specs explicitly state that VM context is session-owned for VM-backed processes.
- Specs explicitly state that native Shell does not own a VM session.
- Specs distinguish foreground ownership from future execution eligibility.
- Specs state that V1 does not support general background execution.
- Specs preserve a future path for background-capable sessions/services without changing VM ownership.
- No new framework artifacts are written outside `discussion/`.
## Tests
- `discussion validate`
- Manual review of touched specs for English-only normative text.
- Run repository doc checks if such a command exists; otherwise no code test is required for this plan.
## Affected Artifacts
- `docs/specs/runtime/09-events-and-concurrency.md`
- `docs/specs/runtime/12-firmware-pos-and-prometeuhub.md`
- `docs/specs/runtime/14-boot-profiles.md`
- `docs/specs/runtime/16-host-abi-and-syscalls.md` if active VM host/debugger wording is needed