From 3eeda25e9702101d6d5b6d1102adf2848429ca9b Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Sat, 4 Jul 2026 18:48:11 +0100 Subject: [PATCH] Foreground Stack, Game Pause, and VM-Backed Shell Coexistence --- .../src/os/facades/lifecycle.rs | 4 +- discussion/index.ndjson | 4 +- ...tralize-resident-game-resume-transition.md | 149 ++++++++++++++++++ ...lidate-lifecycle-process-lookup-helpers.md | 119 ++++++++++++++ ...ve-native-shell-profile-update-into-hub.md | 144 +++++++++++++++++ 5 files changed, 416 insertions(+), 4 deletions(-) create mode 100644 discussion/workflow/plans/PLN-0145-centralize-resident-game-resume-transition.md create mode 100644 discussion/workflow/plans/PLN-0146-consolidate-lifecycle-process-lookup-helpers.md create mode 100644 discussion/workflow/plans/PLN-0147-move-native-shell-profile-update-into-hub.md diff --git a/crates/console/prometeu-system/src/os/facades/lifecycle.rs b/crates/console/prometeu-system/src/os/facades/lifecycle.rs index 4f311fbc..9cf67528 100644 --- a/crates/console/prometeu-system/src/os/facades/lifecycle.rs +++ b/crates/console/prometeu-system/src/os/facades/lifecycle.rs @@ -39,8 +39,8 @@ impl<'a> LifecycleFacade<'a> { pub fn set_foreground_task(&mut self, task_id: TaskId) -> Result<(), LifecycleError> { match task_kind_for_task(self.os, task_id)? { - TaskKind::Game => return self.set_game_foreground_task(task_id), - TaskKind::Shell => return self.set_shell_foreground_task(task_id), + TaskKind::Game => self.set_game_foreground_task(task_id), + TaskKind::Shell => self.set_shell_foreground_task(task_id), } } diff --git a/discussion/index.ndjson b/discussion/index.ndjson index bcebe218..1f61aee2 100644 --- a/discussion/index.ndjson +++ b/discussion/index.ndjson @@ -1,8 +1,8 @@ -{"type":"meta","next_id":{"DSC":44,"AGD":46,"DEC":38,"PLN":145,"LSN":52,"CLSN":1}} +{"type":"meta","next_id":{"DSC":44,"AGD":46,"DEC":38,"PLN":148,"LSN":52,"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"}],"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"}],"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"]}],"lessons":[]} +{"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"}],"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"}],"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":"open","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":"open","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":"open","created_at":"2026-07-04","updated_at":"2026-07-04","ref_decisions":["DEC-0037"]}],"lessons":[]} {"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"}]} diff --git a/discussion/workflow/plans/PLN-0145-centralize-resident-game-resume-transition.md b/discussion/workflow/plans/PLN-0145-centralize-resident-game-resume-transition.md new file mode 100644 index 00000000..f3982d76 --- /dev/null +++ b/discussion/workflow/plans/PLN-0145-centralize-resident-game-resume-transition.md @@ -0,0 +1,149 @@ +--- +id: PLN-0145 +ticket: foreground-stack-game-pause-shell-vm-backed +title: Centralize Resident Game Resume Transition +status: open +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. diff --git a/discussion/workflow/plans/PLN-0146-consolidate-lifecycle-process-lookup-helpers.md b/discussion/workflow/plans/PLN-0146-consolidate-lifecycle-process-lookup-helpers.md new file mode 100644 index 00000000..f890917a --- /dev/null +++ b/discussion/workflow/plans/PLN-0146-consolidate-lifecycle-process-lookup-helpers.md @@ -0,0 +1,119 @@ +--- +id: PLN-0146 +ticket: foreground-stack-game-pause-shell-vm-backed +title: Consolidate Lifecycle Process Lookup Helpers +status: open +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. diff --git a/discussion/workflow/plans/PLN-0147-move-native-shell-profile-update-into-hub.md b/discussion/workflow/plans/PLN-0147-move-native-shell-profile-update-into-hub.md new file mode 100644 index 00000000..cdcc2ce1 --- /dev/null +++ b/discussion/workflow/plans/PLN-0147-move-native-shell-profile-update-into-hub.md @@ -0,0 +1,144 @@ +--- +id: PLN-0147 +ticket: foreground-stack-game-pause-shell-vm-backed +title: Move Native Shell Profile Update Into Hub +status: open +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.