implements PLN-0151

This commit is contained in:
bQUARKz 2026-07-04 19:40:05 +01:00
parent fa2e329d4b
commit f26731413f
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
6 changed files with 181 additions and 20 deletions

View File

@ -37,13 +37,11 @@ impl GameRunningStep {
return Some(FirmwareState::AppCrashes(AppCrashesStep { report }));
}
ctx.os.vm().deliver_pending_game_lifecycle_events();
let pause_requested = matches!(
ctx.os.lifecycle().resident_game().map(|game| game.state),
Some(ResidentGameState::PauseRequested { .. })
);
let result = ctx.os.vm().tick(ctx.vm, ctx.signals, ctx.platform);
let result = ctx.os.vm().tick_session(self.task_id, ctx.signals, ctx.platform);
if let Some(report) = result {
let _ = ctx.os.lifecycle().crash_task(self.task_id, Some(&report));

View File

@ -76,7 +76,7 @@ impl ShellRunningStep {
let outcome = match process_kind {
ProcessKind::VmShell => {
ctx.hub.update_shell_profile(ctx.os, ctx.vm, ctx.signals, ctx.platform)
ctx.hub.update_vm_shell_profile(ctx.os, self.task_id, ctx.signals, ctx.platform)
}
ProcessKind::NativeShell => ctx.hub.update_native_shell_profile(ctx.os, ctx.platform),
ProcessKind::VmGame => {

View File

@ -1,5 +1,7 @@
use crate::CrashReport;
use crate::os::{GameLifecycleEvent, SystemOS};
use crate::task::TaskId;
use crate::vm_session::VmSession;
use crate::{RenderWorkerConfig, RenderWorkerController};
use prometeu_hal::app_mode::AppMode;
use prometeu_hal::cartridge::Cartridge;
@ -46,6 +48,34 @@ impl<'a> VmFacade<'a> {
)
}
pub fn tick_session(
&mut self,
task_id: TaskId,
signals: &InputSignals,
platform: &mut dyn RuntimePlatform,
) -> Option<CrashReport> {
self.deliver_pending_game_lifecycle_events_to_session(task_id);
let Some(session) = self.os.vm_sessions.for_task_mut(task_id) else {
return Some(CrashReport::VmPanic {
message: format!("task {:?} has no VM session", task_id),
pc: None,
});
};
session.runtime.tick(
&mut self.os.log_service,
&mut self.os.fs,
&mut session.fs_state,
&mut self.os.memcard,
&mut session.open_files,
&mut session.next_handle,
&mut session.vm,
signals,
platform,
)
}
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();
@ -65,6 +95,32 @@ impl<'a> VmFacade<'a> {
}
}
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;
};
if session.runtime.current_cartridge_app_mode != AppMode::Game {
return;
}
let mut retained = Vec::new();
let mut delivered = Vec::new();
for event in self.os.game_lifecycle_events.drain(..) {
if event.task_id == task_id {
delivered.push(event);
} else {
retained.push(event);
}
}
self.os.game_lifecycle_events = retained;
if let Some(session) = self.os.vm_sessions.for_task_mut(task_id) {
session.runtime.deliver_game_lifecycle_events(delivered);
}
}
pub fn reset(&mut self, vm: &mut VirtualMachine) {
self.os.vm_runtime.reset(vm);
}
@ -78,55 +134,106 @@ impl<'a> VmFacade<'a> {
}
pub fn paused(&self) -> bool {
self.os.vm_runtime.paused
self.active_session()
.map(|session| session.runtime.paused)
.unwrap_or(self.os.vm_runtime.paused)
}
pub fn set_paused(&mut self, paused: bool) {
if let Some(session) = self.active_session_mut() {
session.runtime.paused = paused;
return;
}
self.os.vm_runtime.paused = paused;
}
pub fn set_inspection_active(&mut self, active: bool) {
if let Some(session) = self.active_session_mut() {
session.runtime.inspection_active = active;
return;
}
self.os.vm_runtime.inspection_active = active;
}
pub fn request_debug_step(&mut self) {
if let Some(session) = self.active_session_mut() {
session.runtime.debug_step_request = true;
return;
}
self.os.vm_runtime.debug_step_request = true;
}
pub fn debug_step_requested(&self) -> bool {
self.os.vm_runtime.debug_step_request
self.active_session()
.map(|session| session.runtime.debug_step_request)
.unwrap_or(self.os.vm_runtime.debug_step_request)
}
pub fn logical_frame_active(&self) -> bool {
self.os.vm_runtime.logical_frame_active
self.active_session()
.map(|session| session.runtime.logical_frame_active)
.unwrap_or(self.os.vm_runtime.logical_frame_active)
}
pub fn delivered_game_lifecycle_events(&self) -> &[GameLifecycleEvent] {
self.os.vm_runtime.delivered_game_lifecycle_events()
self.active_session()
.map(|session| session.runtime.delivered_game_lifecycle_events())
.unwrap_or_else(|| self.os.vm_runtime.delivered_game_lifecycle_events())
}
pub fn take_delivered_game_lifecycle_events(&mut self) -> Vec<GameLifecycleEvent> {
if let Some(session) = self.active_session_mut() {
return session.runtime.take_delivered_game_lifecycle_events();
}
self.os.vm_runtime.take_delivered_game_lifecycle_events()
}
pub fn tick_index(&self) -> u64 {
self.os.vm_runtime.tick_index
self.active_session()
.map(|session| session.runtime.tick_index)
.unwrap_or(self.os.vm_runtime.tick_index)
}
pub fn logical_frame_index(&self) -> u64 {
self.os.vm_runtime.logical_frame_index
self.active_session()
.map(|session| session.runtime.logical_frame_index)
.unwrap_or(self.os.vm_runtime.logical_frame_index)
}
pub fn last_frame_cpu_time_us(&self) -> u64 {
self.os.vm_runtime.last_frame_cpu_time_us
self.active_session()
.map(|session| session.runtime.last_frame_cpu_time_us)
.unwrap_or(self.os.vm_runtime.last_frame_cpu_time_us)
}
pub fn completed_logical_frames(&self) -> u64 {
self.os.vm_runtime.atomic_telemetry.completed_logical_frames.load(Ordering::Relaxed).into()
self.active_session()
.map(|session| {
session
.runtime
.atomic_telemetry
.completed_logical_frames
.load(Ordering::Relaxed)
.into()
})
.unwrap_or_else(|| {
self.os
.vm_runtime
.atomic_telemetry
.completed_logical_frames
.load(Ordering::Relaxed)
.into()
})
}
pub fn telemetry_snapshot(&self) -> TelemetryFrame {
self.os.vm_runtime.atomic_telemetry.snapshot()
self.active_session()
.map(|session| session.runtime.atomic_telemetry.snapshot())
.unwrap_or_else(|| self.os.vm_runtime.atomic_telemetry.snapshot())
}
pub fn start_render_worker<B, S>(&mut self, config: RenderWorkerConfig, backend: B, sink: S)
@ -164,22 +271,54 @@ impl<'a> VmFacade<'a> {
}
pub fn last_crash_report(&self) -> Option<&CrashReport> {
self.os.vm_runtime.last_crash_report.as_ref()
self.active_session()
.and_then(|session| session.runtime.last_crash_report.as_ref())
.or(self.os.vm_runtime.last_crash_report.as_ref())
}
pub fn current_app_id(&self) -> u32 {
self.os.vm_runtime.current_app_id
self.active_session()
.map(|session| session.runtime.current_app_id)
.unwrap_or(self.os.vm_runtime.current_app_id)
}
pub fn current_cartridge_title(&self) -> String {
self.os.vm_runtime.current_cartridge_title.clone()
self.active_session()
.map(|session| session.runtime.current_cartridge_title.clone())
.unwrap_or_else(|| self.os.vm_runtime.current_cartridge_title.clone())
}
pub fn current_cartridge_app_version(&self) -> String {
self.os.vm_runtime.current_cartridge_app_version.clone()
self.active_session()
.map(|session| session.runtime.current_cartridge_app_version.clone())
.unwrap_or_else(|| self.os.vm_runtime.current_cartridge_app_version.clone())
}
pub fn current_cartridge_app_mode(&self) -> AppMode {
self.os.vm_runtime.current_cartridge_app_mode
self.active_session()
.map(|session| session.runtime.current_cartridge_app_mode)
.unwrap_or(self.os.vm_runtime.current_cartridge_app_mode)
}
fn active_session(&self) -> Option<&VmSession> {
self.os
.task_manager
.foreground_task()
.and_then(|task_id| self.os.vm_sessions.for_task(task_id))
.or_else(|| {
self.os
.foreground_stack
.resident_game_task()
.and_then(|task_id| self.os.vm_sessions.for_task(task_id))
})
}
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())?;
self.os.vm_sessions.for_task_mut(task_id)
}
}

View File

@ -1,3 +1,4 @@
use crate::task::TaskId;
use crate::{CrashReport, GameLibrary, SystemOS};
use prometeu_hal::color::Color;
use prometeu_hal::primitives::Rect;
@ -194,6 +195,29 @@ impl PrometeuHub {
SystemProfileUpdate { crash, action }
}
pub fn update_vm_shell_profile(
&mut self,
os: &mut SystemOS,
task_id: TaskId,
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 {
action = Some(SystemProfileAction::CloseShell);
} else if action != Some(SystemProfileAction::CloseShell) {
crash = os.vm().tick_session(task_id, signals, platform);
}
}
self.render(os, platform);
SystemProfileUpdate { crash, action }
}
pub fn update_native_shell_profile(
&mut self,
os: &mut SystemOS,

View File

@ -2,7 +2,7 @@
{"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":"open","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":"open","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":"open","created_at":"2026-07-04","updated_at":"2026-07-04","ref_decisions":["DEC-0038"]}],"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"},{"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":"open","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":"open","created_at":"2026-07-04","updated_at":"2026-07-04","ref_decisions":["DEC-0038"]}],"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"}]}

View File

@ -2,7 +2,7 @@
id: PLN-0151
ticket: foreground-stack-game-pause-shell-vm-backed
title: Schedule Foreground VM Session Ticks
status: open
status: done
created: 2026-07-04
ref_decisions: [DEC-0038]
tags: [runtime, os, lifecycle, shell, game, vm, foreground, architecture]