Merge pull request 'dev/runtime-mode-separation-game-system' (#24) from dev/runtime-mode-separation-game-system into master
All checks were successful
Intrepid/Prometeu/Runtime/pipeline/head This commit looks good
All checks were successful
Intrepid/Prometeu/Runtime/pipeline/head This commit looks good
Reviewed-on: #24
This commit is contained in:
commit
6694bd2dd1
@ -108,6 +108,7 @@ impl Firmware {
|
|||||||
FirmwareState::HubHome(s) => s.on_enter(&mut req),
|
FirmwareState::HubHome(s) => s.on_enter(&mut req),
|
||||||
FirmwareState::LoadCartridge(s) => s.on_enter(&mut req),
|
FirmwareState::LoadCartridge(s) => s.on_enter(&mut req),
|
||||||
FirmwareState::GameRunning(s) => s.on_enter(&mut req),
|
FirmwareState::GameRunning(s) => s.on_enter(&mut req),
|
||||||
|
FirmwareState::SystemRunning(s) => s.on_enter(&mut req),
|
||||||
FirmwareState::AppCrashes(s) => s.on_enter(&mut req),
|
FirmwareState::AppCrashes(s) => s.on_enter(&mut req),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -134,6 +135,7 @@ impl Firmware {
|
|||||||
FirmwareState::HubHome(s) => s.on_update(&mut req),
|
FirmwareState::HubHome(s) => s.on_update(&mut req),
|
||||||
FirmwareState::LoadCartridge(s) => s.on_update(&mut req),
|
FirmwareState::LoadCartridge(s) => s.on_update(&mut req),
|
||||||
FirmwareState::GameRunning(s) => s.on_update(&mut req),
|
FirmwareState::GameRunning(s) => s.on_update(&mut req),
|
||||||
|
FirmwareState::SystemRunning(s) => s.on_update(&mut req),
|
||||||
FirmwareState::AppCrashes(s) => s.on_update(&mut req),
|
FirmwareState::AppCrashes(s) => s.on_update(&mut req),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -155,6 +157,7 @@ impl Firmware {
|
|||||||
FirmwareState::HubHome(s) => s.on_exit(&mut req),
|
FirmwareState::HubHome(s) => s.on_exit(&mut req),
|
||||||
FirmwareState::LoadCartridge(s) => s.on_exit(&mut req),
|
FirmwareState::LoadCartridge(s) => s.on_exit(&mut req),
|
||||||
FirmwareState::GameRunning(s) => s.on_exit(&mut req),
|
FirmwareState::GameRunning(s) => s.on_exit(&mut req),
|
||||||
|
FirmwareState::SystemRunning(s) => s.on_exit(&mut req),
|
||||||
FirmwareState::AppCrashes(s) => s.on_exit(&mut req),
|
FirmwareState::AppCrashes(s) => s.on_exit(&mut req),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -327,7 +330,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn load_cartridge_routes_system_apps_back_to_hub_home() {
|
fn load_cartridge_routes_system_apps_to_system_running() {
|
||||||
let mut firmware = Firmware::new(None);
|
let mut firmware = Firmware::new(None);
|
||||||
let mut hardware = Hardware::new();
|
let mut hardware = Hardware::new();
|
||||||
let signals = InputSignals::default();
|
let signals = InputSignals::default();
|
||||||
@ -335,7 +338,25 @@ mod tests {
|
|||||||
firmware.load_cartridge(valid_cartridge(AppMode::System));
|
firmware.load_cartridge(valid_cartridge(AppMode::System));
|
||||||
firmware.tick(&signals, &mut hardware);
|
firmware.tick(&signals, &mut hardware);
|
||||||
|
|
||||||
assert!(matches!(firmware.state, FirmwareState::HubHome(_)));
|
assert!(matches!(firmware.state, FirmwareState::SystemRunning(_)));
|
||||||
|
assert!(firmware.hub.window_manager.focused.is_some());
|
||||||
|
assert_eq!(firmware.hub.window_manager.windows.len(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn system_running_ticks_focused_system_app_through_hub_pipeline() {
|
||||||
|
let mut firmware = Firmware::new(None);
|
||||||
|
let mut hardware = Hardware::new();
|
||||||
|
let signals = InputSignals::default();
|
||||||
|
|
||||||
|
firmware.load_cartridge(valid_cartridge(AppMode::System));
|
||||||
|
firmware.tick(&signals, &mut hardware);
|
||||||
|
|
||||||
|
let tick_index_before_system_update = firmware.os.tick_index;
|
||||||
|
firmware.tick(&signals, &mut hardware);
|
||||||
|
|
||||||
|
assert!(matches!(firmware.state, FirmwareState::SystemRunning(_)));
|
||||||
|
assert_eq!(firmware.os.tick_index, tick_index_before_system_update + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@ -5,6 +5,7 @@ pub use crate::firmware::firmware_step_launch_hub::LaunchHubStep;
|
|||||||
pub use crate::firmware::firmware_step_load_cartridge::LoadCartridgeStep;
|
pub use crate::firmware::firmware_step_load_cartridge::LoadCartridgeStep;
|
||||||
pub use crate::firmware::firmware_step_reset::ResetStep;
|
pub use crate::firmware::firmware_step_reset::ResetStep;
|
||||||
pub use crate::firmware::firmware_step_splash_screen::SplashScreenStep;
|
pub use crate::firmware::firmware_step_splash_screen::SplashScreenStep;
|
||||||
|
pub use crate::firmware::firmware_step_system_running::SystemRunningStep;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum FirmwareState {
|
pub enum FirmwareState {
|
||||||
@ -14,5 +15,6 @@ pub enum FirmwareState {
|
|||||||
HubHome(HubHomeStep),
|
HubHome(HubHomeStep),
|
||||||
LoadCartridge(LoadCartridgeStep),
|
LoadCartridge(LoadCartridgeStep),
|
||||||
GameRunning(GameRunningStep),
|
GameRunning(GameRunningStep),
|
||||||
|
SystemRunning(SystemRunningStep),
|
||||||
AppCrashes(AppCrashesStep),
|
AppCrashes(AppCrashesStep),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,26 +11,8 @@ impl HubHomeStep {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_update(&mut self, ctx: &mut PrometeuContext) -> Option<FirmwareState> {
|
pub fn on_update(&mut self, ctx: &mut PrometeuContext) -> Option<FirmwareState> {
|
||||||
let mut error = None;
|
let outcome = ctx.hub.update_system_profile(ctx.os, ctx.vm, ctx.signals, ctx.hw);
|
||||||
|
if let Some(report) = outcome.crash {
|
||||||
// Always updates the Hub GUI (clears screen and processes input if no focus)
|
|
||||||
ctx.hub.gui_update(ctx.os, ctx.hw);
|
|
||||||
|
|
||||||
if let Some(focused_id) = ctx.hub.window_manager.focused {
|
|
||||||
if ctx.hw.pad().start().down {
|
|
||||||
ctx.hub.window_manager.remove_window(focused_id);
|
|
||||||
} else {
|
|
||||||
// System App runs here, drawing over the Hub background
|
|
||||||
error = ctx.os.tick(ctx.vm, ctx.signals, ctx.hw);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Renders the System App window borders
|
|
||||||
ctx.hub.render(ctx.os, ctx.hw);
|
|
||||||
|
|
||||||
ctx.hw.gfx_mut().present();
|
|
||||||
|
|
||||||
if let Some(report) = error {
|
|
||||||
return Some(FirmwareState::AppCrashes(AppCrashesStep { report }));
|
return Some(FirmwareState::AppCrashes(AppCrashesStep { report }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
use crate::firmware::firmware_state::{
|
use crate::firmware::firmware_state::{
|
||||||
AppCrashesStep, FirmwareState, GameRunningStep, HubHomeStep,
|
AppCrashesStep, FirmwareState, GameRunningStep, SystemRunningStep,
|
||||||
};
|
};
|
||||||
use crate::firmware::prometeu_context::PrometeuContext;
|
use crate::firmware::prometeu_context::PrometeuContext;
|
||||||
use prometeu_hal::cartridge::{AppMode, Cartridge};
|
use prometeu_hal::cartridge::{AppMode, Cartridge};
|
||||||
@ -50,9 +50,7 @@ impl LoadCartridgeStep {
|
|||||||
);
|
);
|
||||||
ctx.hub.window_manager.set_focus(id);
|
ctx.hub.window_manager.set_focus(id);
|
||||||
|
|
||||||
// System apps do not change the firmware state to GameRunning.
|
return Some(FirmwareState::SystemRunning(SystemRunningStep));
|
||||||
// They run in the background or via windows in the Hub.
|
|
||||||
return Some(FirmwareState::HubHome(HubHomeStep));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(FirmwareState::GameRunning(GameRunningStep))
|
Some(FirmwareState::GameRunning(GameRunningStep))
|
||||||
|
|||||||
@ -0,0 +1,27 @@
|
|||||||
|
use crate::firmware::firmware_state::{AppCrashesStep, FirmwareState, HubHomeStep};
|
||||||
|
use crate::firmware::prometeu_context::PrometeuContext;
|
||||||
|
use prometeu_hal::log::{LogLevel, LogSource};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct SystemRunningStep;
|
||||||
|
|
||||||
|
impl SystemRunningStep {
|
||||||
|
pub fn on_enter(&mut self, ctx: &mut PrometeuContext) {
|
||||||
|
ctx.os.log(LogLevel::Info, LogSource::Hub, 0, "Entering SystemRunning".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_update(&mut self, ctx: &mut PrometeuContext) -> Option<FirmwareState> {
|
||||||
|
let outcome = ctx.hub.update_system_profile(ctx.os, ctx.vm, ctx.signals, ctx.hw);
|
||||||
|
if let Some(report) = outcome.crash {
|
||||||
|
return Some(FirmwareState::AppCrashes(AppCrashesStep { report }));
|
||||||
|
}
|
||||||
|
|
||||||
|
if !outcome.focused_window_active {
|
||||||
|
return Some(FirmwareState::HubHome(HubHomeStep));
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_exit(&mut self, _ctx: &mut PrometeuContext) {}
|
||||||
|
}
|
||||||
@ -10,6 +10,7 @@ pub(crate) mod firmware_step_launch_hub;
|
|||||||
pub(crate) mod firmware_step_load_cartridge;
|
pub(crate) mod firmware_step_load_cartridge;
|
||||||
pub(crate) mod firmware_step_reset;
|
pub(crate) mod firmware_step_reset;
|
||||||
pub(crate) mod firmware_step_splash_screen;
|
pub(crate) mod firmware_step_splash_screen;
|
||||||
|
pub(crate) mod firmware_step_system_running;
|
||||||
mod prometeu_context;
|
mod prometeu_context;
|
||||||
|
|
||||||
pub use boot_target::BootTarget;
|
pub use boot_target::BootTarget;
|
||||||
|
|||||||
@ -1,15 +1,21 @@
|
|||||||
use crate::VirtualMachineRuntime;
|
|
||||||
use crate::programs::prometeu_hub::window_manager::WindowManager;
|
use crate::programs::prometeu_hub::window_manager::WindowManager;
|
||||||
use prometeu_hal::HardwareBridge;
|
use crate::{CrashReport, VirtualMachineRuntime};
|
||||||
use prometeu_hal::color::Color;
|
use prometeu_hal::color::Color;
|
||||||
use prometeu_hal::log::{LogLevel, LogSource};
|
use prometeu_hal::log::{LogLevel, LogSource};
|
||||||
use prometeu_hal::window::Rect;
|
use prometeu_hal::window::Rect;
|
||||||
|
use prometeu_hal::{HardwareBridge, InputSignals};
|
||||||
|
use prometeu_vm::VirtualMachine;
|
||||||
|
|
||||||
/// PrometeuHub: Launcher and system UI environment.
|
/// PrometeuHub: Launcher and system UI environment.
|
||||||
pub struct PrometeuHub {
|
pub struct PrometeuHub {
|
||||||
pub window_manager: WindowManager,
|
pub window_manager: WindowManager,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct SystemProfileUpdate {
|
||||||
|
pub crash: Option<CrashReport>,
|
||||||
|
pub focused_window_active: bool,
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for PrometeuHub {
|
impl Default for PrometeuHub {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::new()
|
Self::new()
|
||||||
@ -75,4 +81,29 @@ impl PrometeuHub {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn update_system_profile(
|
||||||
|
&mut self,
|
||||||
|
os: &mut VirtualMachineRuntime,
|
||||||
|
vm: &mut VirtualMachine,
|
||||||
|
signals: &InputSignals,
|
||||||
|
hw: &mut dyn HardwareBridge,
|
||||||
|
) -> SystemProfileUpdate {
|
||||||
|
let mut crash = None;
|
||||||
|
|
||||||
|
self.gui_update(os, hw);
|
||||||
|
|
||||||
|
if let Some(focused_id) = self.window_manager.focused {
|
||||||
|
if hw.pad().start().down {
|
||||||
|
self.window_manager.remove_window(focused_id);
|
||||||
|
} else {
|
||||||
|
crash = os.tick(vm, signals, hw);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.render(os, hw);
|
||||||
|
hw.gfx_mut().present();
|
||||||
|
|
||||||
|
SystemProfileUpdate { crash, focused_window_active: self.window_manager.focused.is_some() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -84,6 +84,42 @@ impl VirtualMachineRuntime {
|
|||||||
fn int_arg_to_u16_status(value: i64) -> Result<u16, ComposerOpStatus> {
|
fn int_arg_to_u16_status(value: i64) -> Result<u16, ComposerOpStatus> {
|
||||||
u16::try_from(value).map_err(|_| ComposerOpStatus::ArgRangeInvalid)
|
u16::try_from(value).map_err(|_| ComposerOpStatus::ArgRangeInvalid)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_game_profile_syscall(syscall: Syscall) -> bool {
|
||||||
|
matches!(
|
||||||
|
syscall,
|
||||||
|
Syscall::GfxClear
|
||||||
|
| Syscall::GfxFillRect
|
||||||
|
| Syscall::GfxDrawLine
|
||||||
|
| Syscall::GfxDrawCircle
|
||||||
|
| Syscall::GfxDrawDisc
|
||||||
|
| Syscall::GfxDrawSquare
|
||||||
|
| Syscall::GfxDrawText
|
||||||
|
| Syscall::GfxClear565
|
||||||
|
| Syscall::ComposerBindScene
|
||||||
|
| Syscall::ComposerUnbindScene
|
||||||
|
| Syscall::ComposerSetCamera
|
||||||
|
| Syscall::ComposerEmitSprite
|
||||||
|
| Syscall::AssetLoad
|
||||||
|
| Syscall::AssetStatus
|
||||||
|
| Syscall::AssetCommit
|
||||||
|
| Syscall::AssetCancel
|
||||||
|
| Syscall::BankInfo
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ensure_game_profile_syscall(&self, syscall: Syscall) -> Result<(), VmFault> {
|
||||||
|
if Self::is_game_profile_syscall(syscall)
|
||||||
|
&& self.current_cartridge_app_mode != AppMode::Game
|
||||||
|
{
|
||||||
|
return Err(VmFault::Trap(
|
||||||
|
TRAP_INVALID_SYSCALL,
|
||||||
|
format!("{} is only available to the Game profile", syscall.name()),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NativeInterface for VirtualMachineRuntime {
|
impl NativeInterface for VirtualMachineRuntime {
|
||||||
@ -108,6 +144,8 @@ impl NativeInterface for VirtualMachineRuntime {
|
|||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.ensure_game_profile_syscall(syscall)?;
|
||||||
|
|
||||||
let hw = ctx.require_hw()?;
|
let hw = ctx.require_hw()?;
|
||||||
|
|
||||||
match syscall {
|
match syscall {
|
||||||
|
|||||||
@ -64,12 +64,16 @@ impl FsBackend for MemFsBackend {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cartridge_with_program(program: Vec<u8>, capabilities: u64) -> Cartridge {
|
fn cartridge_with_program_and_mode(
|
||||||
|
program: Vec<u8>,
|
||||||
|
capabilities: u64,
|
||||||
|
app_mode: AppMode,
|
||||||
|
) -> Cartridge {
|
||||||
Cartridge {
|
Cartridge {
|
||||||
app_id: 42,
|
app_id: 42,
|
||||||
title: "Test Cart".into(),
|
title: "Test Cart".into(),
|
||||||
app_version: "1.0.0".into(),
|
app_version: "1.0.0".into(),
|
||||||
app_mode: AppMode::Game,
|
app_mode,
|
||||||
capabilities,
|
capabilities,
|
||||||
program,
|
program,
|
||||||
assets: AssetsPayloadSource::empty(),
|
assets: AssetsPayloadSource::empty(),
|
||||||
@ -78,6 +82,10 @@ fn cartridge_with_program(program: Vec<u8>, capabilities: u64) -> Cartridge {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn cartridge_with_program(program: Vec<u8>, capabilities: u64) -> Cartridge {
|
||||||
|
cartridge_with_program_and_mode(program, capabilities, AppMode::Game)
|
||||||
|
}
|
||||||
|
|
||||||
fn serialized_single_function_module(code: Vec<u8>, syscalls: Vec<SyscallDecl>) -> Vec<u8> {
|
fn serialized_single_function_module(code: Vec<u8>, syscalls: Vec<SyscallDecl>) -> Vec<u8> {
|
||||||
serialized_single_function_module_with_consts(code, vec![], syscalls)
|
serialized_single_function_module_with_consts(code, vec![], syscalls)
|
||||||
}
|
}
|
||||||
@ -260,6 +268,130 @@ fn tick_returns_error_when_vm_ends_slice_with_trap() {
|
|||||||
assert!(matches!(runtime.last_crash_report, Some(CrashReport::VmTrap { .. })));
|
assert!(matches!(runtime.last_crash_report, Some(CrashReport::VmTrap { .. })));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tick_system_profile_rejects_gfx_game_surface() {
|
||||||
|
let mut runtime = VirtualMachineRuntime::new(None);
|
||||||
|
let mut vm = VirtualMachine::default();
|
||||||
|
let mut hardware = Hardware::new();
|
||||||
|
let signals = InputSignals::default();
|
||||||
|
let code = assemble("PUSH_I32 0\nHOSTCALL 0\nHALT").expect("assemble");
|
||||||
|
let program = serialized_single_function_module(
|
||||||
|
code,
|
||||||
|
vec![SyscallDecl {
|
||||||
|
module: "gfx".into(),
|
||||||
|
name: "clear".into(),
|
||||||
|
version: 1,
|
||||||
|
arg_slots: 1,
|
||||||
|
ret_slots: 0,
|
||||||
|
}],
|
||||||
|
);
|
||||||
|
let cartridge = cartridge_with_program_and_mode(program, caps::GFX, AppMode::System);
|
||||||
|
|
||||||
|
runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize");
|
||||||
|
let report = runtime.tick(&mut vm, &signals, &mut hardware).expect("system gfx call must trap");
|
||||||
|
|
||||||
|
match report {
|
||||||
|
CrashReport::VmTrap { trap } => {
|
||||||
|
assert_eq!(trap.code, prometeu_bytecode::TRAP_INVALID_SYSCALL);
|
||||||
|
assert!(trap.message.contains("GfxClear is only available to the Game profile"));
|
||||||
|
}
|
||||||
|
other => panic!("expected VmTrap crash report, got {:?}", other),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tick_system_profile_rejects_composer_game_surface() {
|
||||||
|
let mut runtime = VirtualMachineRuntime::new(None);
|
||||||
|
let mut vm = VirtualMachine::default();
|
||||||
|
let mut hardware = Hardware::new();
|
||||||
|
let signals = InputSignals::default();
|
||||||
|
let code = assemble("PUSH_I32 0\nHOSTCALL 0\nHALT").expect("assemble");
|
||||||
|
let program = serialized_single_function_module(
|
||||||
|
code,
|
||||||
|
vec![SyscallDecl {
|
||||||
|
module: "composer".into(),
|
||||||
|
name: "bind_scene".into(),
|
||||||
|
version: 1,
|
||||||
|
arg_slots: 1,
|
||||||
|
ret_slots: 1,
|
||||||
|
}],
|
||||||
|
);
|
||||||
|
let cartridge = cartridge_with_program_and_mode(program, caps::GFX, AppMode::System);
|
||||||
|
|
||||||
|
runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize");
|
||||||
|
let report =
|
||||||
|
runtime.tick(&mut vm, &signals, &mut hardware).expect("system composer call must trap");
|
||||||
|
|
||||||
|
match report {
|
||||||
|
CrashReport::VmTrap { trap } => {
|
||||||
|
assert_eq!(trap.code, prometeu_bytecode::TRAP_INVALID_SYSCALL);
|
||||||
|
assert!(
|
||||||
|
trap.message.contains("ComposerBindScene is only available to the Game profile")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
other => panic!("expected VmTrap crash report, got {:?}", other),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tick_system_profile_rejects_bank_game_surface() {
|
||||||
|
let mut runtime = VirtualMachineRuntime::new(None);
|
||||||
|
let mut vm = VirtualMachine::default();
|
||||||
|
let mut hardware = Hardware::new();
|
||||||
|
let signals = InputSignals::default();
|
||||||
|
let code = assemble("PUSH_I32 0\nHOSTCALL 0\nHALT").expect("assemble");
|
||||||
|
let program = serialized_single_function_module(
|
||||||
|
code,
|
||||||
|
vec![SyscallDecl {
|
||||||
|
module: "bank".into(),
|
||||||
|
name: "info".into(),
|
||||||
|
version: 1,
|
||||||
|
arg_slots: 1,
|
||||||
|
ret_slots: 2,
|
||||||
|
}],
|
||||||
|
);
|
||||||
|
let cartridge = cartridge_with_program_and_mode(program, caps::BANK, AppMode::System);
|
||||||
|
|
||||||
|
runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize");
|
||||||
|
let report =
|
||||||
|
runtime.tick(&mut vm, &signals, &mut hardware).expect("system bank call must trap");
|
||||||
|
|
||||||
|
match report {
|
||||||
|
CrashReport::VmTrap { trap } => {
|
||||||
|
assert_eq!(trap.code, prometeu_bytecode::TRAP_INVALID_SYSCALL);
|
||||||
|
assert!(trap.message.contains("BankInfo is only available to the Game profile"));
|
||||||
|
}
|
||||||
|
other => panic!("expected VmTrap crash report, got {:?}", other),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tick_system_profile_can_use_shared_log_transport() {
|
||||||
|
let mut runtime = VirtualMachineRuntime::new(None);
|
||||||
|
let mut vm = VirtualMachine::default();
|
||||||
|
let mut hardware = Hardware::new();
|
||||||
|
let signals = InputSignals::default();
|
||||||
|
let code = assemble("PUSH_I32 2\nPUSH_CONST 0\nHOSTCALL 0\nHALT").expect("assemble");
|
||||||
|
let program = serialized_single_function_module_with_consts(
|
||||||
|
code,
|
||||||
|
vec![ConstantPoolEntry::String("system log".into())],
|
||||||
|
vec![SyscallDecl {
|
||||||
|
module: "log".into(),
|
||||||
|
name: "write".into(),
|
||||||
|
version: 1,
|
||||||
|
arg_slots: 2,
|
||||||
|
ret_slots: 0,
|
||||||
|
}],
|
||||||
|
);
|
||||||
|
let cartridge = cartridge_with_program_and_mode(program, caps::LOG, AppMode::System);
|
||||||
|
|
||||||
|
runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize");
|
||||||
|
let report = runtime.tick(&mut vm, &signals, &mut hardware);
|
||||||
|
|
||||||
|
assert!(report.is_none(), "system log transport must remain internally shared");
|
||||||
|
assert!(vm.is_halted());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn tick_returns_panic_report_distinct_from_trap() {
|
fn tick_returns_panic_report_distinct_from_trap() {
|
||||||
let mut runtime = VirtualMachineRuntime::new(None);
|
let mut runtime = VirtualMachineRuntime::new(None);
|
||||||
|
|||||||
@ -1,31 +1,32 @@
|
|||||||
{"type":"meta","next_id":{"DSC":31,"AGD":31,"DEC":23,"PLN":46,"LSN":40,"CLSN":1}}
|
{"type":"meta","next_id":{"DSC":32,"AGD":32,"DEC":24,"PLN":51,"LSN":41,"CLSN":1}}
|
||||||
{"type":"discussion","id":"DSC-0023","status":"done","ticket":"perf-full-migration-to-atomic-telemetry","title":"Agenda - [PERF] Full Migration to Atomic Telemetry","created_at":"2026-04-10","updated_at":"2026-04-10","tags":["perf","runtime","telemetry"],"agendas":[{"id":"AGD-0021","file":"workflow/agendas/AGD-0021-full-migration-to-atomic-telemetry.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}],"decisions":[{"id":"DEC-0008","file":"workflow/decisions/DEC-0008-full-migration-to-atomic-telemetry.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10"}],"plans":[{"id":"PLN-0007","file":"workflow/plans/PLN-0007-full-migration-to-atomic-telemetry.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}],"lessons":[{"id":"LSN-0028","file":"lessons/DSC-0023-perf-full-migration-to-atomic-telemetry/LSN-0028-converging-to-single-atomic-telemetry-source.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}]}
|
{"type":"discussion","id":"DSC-0023","status":"done","ticket":"perf-full-migration-to-atomic-telemetry","title":"Agenda - [PERF] Full Migration to Atomic Telemetry","created_at":"2026-04-10","updated_at":"2026-04-10","tags":["perf","runtime","telemetry"],"agendas":[{"id":"AGD-0021","file":"AGD-0021-full-migration-to-atomic-telemetry.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}],"decisions":[{"id":"DEC-0008","file":"DEC-0008-full-migration-to-atomic-telemetry.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10"}],"plans":[{"id":"PLN-0007","file":"PLN-0007-full-migration-to-atomic-telemetry.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}],"lessons":[{"id":"LSN-0028","file":"discussion/lessons/DSC-0023-perf-full-migration-to-atomic-telemetry/LSN-0028-converging-to-single-atomic-telemetry-source.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}]}
|
||||||
{"type":"discussion","id":"DSC-0020","status":"done","ticket":"jenkins-gitea-integration","title":"Jenkins Gitea Integration and Relocation","created_at":"2026-04-07","updated_at":"2026-04-07","tags":["ci","jenkins","gitea"],"agendas":[{"id":"AGD-0018","file":"workflow/agendas/AGD-0018-jenkins-gitea-integration-and-relocation.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}],"decisions":[{"id":"DEC-0003","file":"workflow/decisions/DEC-0003-jenkins-gitea-strategy.md","status":"accepted","created_at":"2026-04-07","updated_at":"2026-04-07"}],"plans":[{"id":"PLN-0003","file":"workflow/plans/PLN-0003-jenkins-gitea-execution.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}],"lessons":[{"id":"LSN-0021","file":"lessons/DSC-0020-jenkins-gitea-integration/LSN-0021-jenkins-gitea-integration.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}]}
|
{"type":"discussion","id":"DSC-0020","status":"done","ticket":"jenkins-gitea-integration","title":"Jenkins Gitea Integration and Relocation","created_at":"2026-04-07","updated_at":"2026-04-07","tags":["ci","jenkins","gitea"],"agendas":[{"id":"AGD-0018","file":"AGD-0018-jenkins-gitea-integration-and-relocation.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}],"decisions":[{"id":"DEC-0003","file":"DEC-0003-jenkins-gitea-strategy.md","status":"accepted","created_at":"2026-04-07","updated_at":"2026-04-07"}],"plans":[{"id":"PLN-0003","file":"PLN-0003-jenkins-gitea-execution.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}],"lessons":[{"id":"LSN-0021","file":"discussion/lessons/DSC-0020-jenkins-gitea-integration/LSN-0021-jenkins-gitea-integration.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}]}
|
||||||
{"type":"discussion","id":"DSC-0021","status":"done","ticket":"asset-entry-codec-enum-with-metadata","title":"Asset Entry Codec Enum Contract","created_at":"2026-04-09","updated_at":"2026-04-09","tags":["asset","runtime","codec","metadata"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0024","file":"lessons/DSC-0021-asset-entry-codec-enum-contract/LSN-0024-string-on-the-wire-enum-in-runtime.md","status":"done","created_at":"2026-04-09","updated_at":"2026-04-09"}]}
|
{"type":"discussion","id":"DSC-0021","status":"done","ticket":"asset-entry-codec-enum-with-metadata","title":"Asset Entry Codec Enum Contract","created_at":"2026-04-09","updated_at":"2026-04-09","tags":["asset","runtime","codec","metadata"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0024","file":"discussion/lessons/DSC-0021-asset-entry-codec-enum-contract/LSN-0024-string-on-the-wire-enum-in-runtime.md","status":"done","created_at":"2026-04-09","updated_at":"2026-04-09"}]}
|
||||||
{"type":"discussion","id":"DSC-0022","status":"done","ticket":"tile-bank-vs-glyph-bank-domain-naming","title":"Glyph Bank Domain Naming Contract","created_at":"2026-04-09","updated_at":"2026-04-10","tags":["gfx","runtime","naming","domain-model"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0025","file":"lessons/DSC-0022-glyph-bank-domain-naming/LSN-0025-rename-artifact-by-meaning-not-by-token.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}]}
|
{"type":"discussion","id":"DSC-0022","status":"done","ticket":"tile-bank-vs-glyph-bank-domain-naming","title":"Glyph Bank Domain Naming Contract","created_at":"2026-04-09","updated_at":"2026-04-10","tags":["gfx","runtime","naming","domain-model"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0025","file":"discussion/lessons/DSC-0022-glyph-bank-domain-naming/LSN-0025-rename-artifact-by-meaning-not-by-token.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}]}
|
||||||
{"type":"discussion","id":"DSC-0001","status":"done","ticket":"legacy-runtime-learn-import","title":"Import legacy runtime learn into discussion lessons","created_at":"2026-03-27","updated_at":"2026-03-27","tags":["migration","tech-debt"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0001","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0001-prometeu-learn-index.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0002","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0002-historical-asset-status-first-fault-and-return-contract.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0003","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0003-historical-audio-status-first-fault-and-return-contract.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0004","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0004-historical-cartridge-boot-protocol-and-manifest-authority.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0005","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0005-historical-game-memcard-slots-surface-and-semantics.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0006","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0006-historical-gfx-status-first-fault-and-return-contract.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0007","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0007-historical-retired-fault-and-input-decisions.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0008","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0008-historical-vm-core-and-assets.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0009","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0009-mental-model-asset-management.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0010","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0010-mental-model-audio.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0011","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0011-mental-model-gfx.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0012","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0012-mental-model-input.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0013","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0013-mental-model-observability-and-debugging.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0014","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0014-mental-model-portability-and-cross-platform.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0015","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0015-mental-model-save-memory-and-memcard.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0016","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0016-mental-model-status-first-and-fault-thinking.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0017","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0017-mental-model-time-and-cycles.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0018","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0018-mental-model-touch.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"}]}
|
{"type":"discussion","id":"DSC-0001","status":"done","ticket":"legacy-runtime-learn-import","title":"Import legacy runtime learn into discussion lessons","created_at":"2026-03-27","updated_at":"2026-03-27","tags":["migration","tech-debt"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0001","file":"discussion/lessons/DSC-0001-runtime-learn-legacy-import/LSN-0001-prometeu-learn-index.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0002","file":"discussion/lessons/DSC-0001-runtime-learn-legacy-import/LSN-0002-historical-asset-status-first-fault-and-return-contract.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0003","file":"discussion/lessons/DSC-0001-runtime-learn-legacy-import/LSN-0003-historical-audio-status-first-fault-and-return-contract.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0004","file":"discussion/lessons/DSC-0001-runtime-learn-legacy-import/LSN-0004-historical-cartridge-boot-protocol-and-manifest-authority.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0005","file":"discussion/lessons/DSC-0001-runtime-learn-legacy-import/LSN-0005-historical-game-memcard-slots-surface-and-semantics.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0006","file":"discussion/lessons/DSC-0001-runtime-learn-legacy-import/LSN-0006-historical-gfx-status-first-fault-and-return-contract.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0007","file":"discussion/lessons/DSC-0001-runtime-learn-legacy-import/LSN-0007-historical-retired-fault-and-input-decisions.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0008","file":"discussion/lessons/DSC-0001-runtime-learn-legacy-import/LSN-0008-historical-vm-core-and-assets.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0009","file":"discussion/lessons/DSC-0001-runtime-learn-legacy-import/LSN-0009-mental-model-asset-management.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0010","file":"discussion/lessons/DSC-0001-runtime-learn-legacy-import/LSN-0010-mental-model-audio.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0011","file":"discussion/lessons/DSC-0001-runtime-learn-legacy-import/LSN-0011-mental-model-gfx.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0012","file":"discussion/lessons/DSC-0001-runtime-learn-legacy-import/LSN-0012-mental-model-input.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0013","file":"discussion/lessons/DSC-0001-runtime-learn-legacy-import/LSN-0013-mental-model-observability-and-debugging.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0014","file":"discussion/lessons/DSC-0001-runtime-learn-legacy-import/LSN-0014-mental-model-portability-and-cross-platform.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0015","file":"discussion/lessons/DSC-0001-runtime-learn-legacy-import/LSN-0015-mental-model-save-memory-and-memcard.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0016","file":"discussion/lessons/DSC-0001-runtime-learn-legacy-import/LSN-0016-mental-model-status-first-and-fault-thinking.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0017","file":"discussion/lessons/DSC-0001-runtime-learn-legacy-import/LSN-0017-mental-model-time-and-cycles.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0018","file":"discussion/lessons/DSC-0001-runtime-learn-legacy-import/LSN-0018-mental-model-touch.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"}]}
|
||||||
{"type":"discussion","id":"DSC-0002","status":"done","ticket":"runtime-edge-test-plan","title":"Agenda - Runtime Edge Test Plan","created_at":"2026-03-27","updated_at":"2026-04-21","tags":[],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0037","file":"lessons/DSC-0002-runtime-edge-test-plan/LSN-0037-domain-gates-must-be-owned-by-the-repository.md","status":"done","created_at":"2026-04-21","updated_at":"2026-04-21"}]}
|
{"type":"discussion","id":"DSC-0002","status":"done","ticket":"runtime-edge-test-plan","title":"Agenda - Runtime Edge Test Plan","created_at":"2026-03-27","updated_at":"2026-04-21","tags":[],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0037","file":"discussion/lessons/DSC-0002-runtime-edge-test-plan/LSN-0037-domain-gates-must-be-owned-by-the-repository.md","status":"done","created_at":"2026-04-21","updated_at":"2026-04-21"}]}
|
||||||
{"type":"discussion","id":"DSC-0003","status":"open","ticket":"packed-cartridge-loader-pmc","title":"Agenda - Packed Cartridge Loader PMC","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0002","file":"workflow/agendas/AGD-0002-packed-cartridge-loader-pmc.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]}
|
{"type":"discussion","id":"DSC-0003","status":"open","ticket":"packed-cartridge-loader-pmc","title":"Agenda - Packed Cartridge Loader PMC","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0002","file":"AGD-0002-packed-cartridge-loader-pmc.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]}
|
||||||
{"type":"discussion","id":"DSC-0004","status":"open","ticket":"system-run-cart","title":"Agenda - System Run Cart","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0003","file":"workflow/agendas/AGD-0003-system-run-cart.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]}
|
{"type":"discussion","id":"DSC-0004","status":"open","ticket":"system-run-cart","title":"Agenda - System Run Cart","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0003","file":"AGD-0003-system-run-cart.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]}
|
||||||
{"type":"discussion","id":"DSC-0005","status":"open","ticket":"system-fault-semantics-and-control-surface","title":"Agenda - System Fault Semantics and Control Surface","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0004","file":"workflow/agendas/AGD-0004-system-fault-semantics-and-control-surface.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]}
|
{"type":"discussion","id":"DSC-0005","status":"open","ticket":"system-fault-semantics-and-control-surface","title":"Agenda - System Fault Semantics and Control Surface","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0004","file":"AGD-0004-system-fault-semantics-and-control-surface.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]}
|
||||||
{"type":"discussion","id":"DSC-0006","status":"open","ticket":"vm-owned-random-service","title":"Agenda - VM-Owned Random Service","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0005","file":"workflow/agendas/AGD-0005-vm-owned-random-service.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]}
|
{"type":"discussion","id":"DSC-0006","status":"open","ticket":"vm-owned-random-service","title":"Agenda - VM-Owned Random Service","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0005","file":"AGD-0005-vm-owned-random-service.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]}
|
||||||
{"type":"discussion","id":"DSC-0007","status":"open","ticket":"app-home-filesystem-surface-and-semantics","title":"Agenda - App Home Filesystem Surface and Semantics","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0006","file":"workflow/agendas/AGD-0006-app-home-filesystem-surface-and-semantics.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]}
|
{"type":"discussion","id":"DSC-0007","status":"open","ticket":"app-home-filesystem-surface-and-semantics","title":"Agenda - App Home Filesystem Surface and Semantics","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0006","file":"AGD-0006-app-home-filesystem-surface-and-semantics.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]}
|
||||||
{"type":"discussion","id":"DSC-0008","status":"done","ticket":"perf-runtime-telemetry-hot-path","title":"Agenda - [PERF] Runtime Telemetry Hot Path","created_at":"2026-03-27","updated_at":"2026-04-10","tags":[],"agendas":[{"id":"AGD-0007","file":"workflow/agendas/AGD-0007-perf-runtime-telemetry-hot-path.md","status":"done","created_at":"2026-03-27","updated_at":"2026-04-10"}],"decisions":[{"id":"DEC-0005","file":"workflow/decisions/DEC-0005-perf-push-based-telemetry-model.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10"}],"plans":[{"id":"PLN-0005","file":"workflow/plans/PLN-0005-perf-push-based-telemetry-implementation.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}],"lessons":[{"id":"LSN-0026","file":"lessons/DSC-0008-perf-runtime-telemetry-hot-path/LSN-0026-push-based-telemetry-model.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}]}
|
{"type":"discussion","id":"DSC-0008","status":"done","ticket":"perf-runtime-telemetry-hot-path","title":"Agenda - [PERF] Runtime Telemetry Hot Path","created_at":"2026-03-27","updated_at":"2026-04-10","tags":[],"agendas":[{"id":"AGD-0007","file":"AGD-0007-perf-runtime-telemetry-hot-path.md","status":"done","created_at":"2026-03-27","updated_at":"2026-04-10"}],"decisions":[{"id":"DEC-0005","file":"DEC-0005-perf-push-based-telemetry-model.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10"}],"plans":[{"id":"PLN-0005","file":"PLN-0005-perf-push-based-telemetry-implementation.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}],"lessons":[{"id":"LSN-0026","file":"discussion/lessons/DSC-0008-perf-runtime-telemetry-hot-path/LSN-0026-push-based-telemetry-model.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}]}
|
||||||
{"type":"discussion","id":"DSC-0009","status":"open","ticket":"perf-async-background-work-lanes-for-assets-and-fs","title":"Agenda - [PERF] Async Background Work Lanes for Assets and FS","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0008","file":"workflow/agendas/AGD-0008-perf-async-background-work-lanes-for-assets-and-fs.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]}
|
{"type":"discussion","id":"DSC-0009","status":"open","ticket":"perf-async-background-work-lanes-for-assets-and-fs","title":"Agenda - [PERF] Async Background Work Lanes for Assets and FS","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0008","file":"AGD-0008-perf-async-background-work-lanes-for-assets-and-fs.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]}
|
||||||
{"type":"discussion","id":"DSC-0010","status":"done","ticket":"perf-host-desktop-frame-pacing-and-presentation","title":"Agenda - [PERF] Host Desktop Frame Pacing and Presentation","created_at":"2026-03-27","updated_at":"2026-04-20","tags":[],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0036","file":"lessons/DSC-0010-perf-host-desktop-frame-pacing-and-presentation/LSN-0036-frame-publication-and-host-invalidation-must-be-separate.md","status":"done","created_at":"2026-04-20","updated_at":"2026-04-20"}]}
|
{"type":"discussion","id":"DSC-0010","status":"done","ticket":"perf-host-desktop-frame-pacing-and-presentation","title":"Agenda - [PERF] Host Desktop Frame Pacing and Presentation","created_at":"2026-03-27","updated_at":"2026-04-20","tags":[],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0036","file":"discussion/lessons/DSC-0010-perf-host-desktop-frame-pacing-and-presentation/LSN-0036-frame-publication-and-host-invalidation-must-be-separate.md","status":"done","created_at":"2026-04-20","updated_at":"2026-04-20"}]}
|
||||||
{"type":"discussion","id":"DSC-0011","status":"open","ticket":"perf-gfx-render-pipeline-and-dirty-regions","title":"Agenda - [PERF] GFX Render Pipeline and Dirty Regions","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0010","file":"workflow/agendas/AGD-0010-perf-gfx-render-pipeline-and-dirty-regions.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]}
|
{"type":"discussion","id":"DSC-0011","status":"open","ticket":"perf-gfx-render-pipeline-and-dirty-regions","title":"Agenda - [PERF] GFX Render Pipeline and Dirty Regions","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0010","file":"AGD-0010-perf-gfx-render-pipeline-and-dirty-regions.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]}
|
||||||
{"type":"discussion","id":"DSC-0012","status":"done","ticket":"perf-runtime-introspection-syscalls","title":"Agenda - [PERF] Runtime Introspection Syscalls","created_at":"2026-03-27","updated_at":"2026-04-19","tags":["perf","runtime","syscall","telemetry","debug","asset"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0034","file":"lessons/DSC-0012-perf-runtime-introspection-syscalls/LSN-0034-host-owned-debug-boundaries.md","status":"done","created_at":"2026-04-19","updated_at":"2026-04-19"}]}
|
{"type":"discussion","id":"DSC-0012","status":"done","ticket":"perf-runtime-introspection-syscalls","title":"Agenda - [PERF] Runtime Introspection Syscalls","created_at":"2026-03-27","updated_at":"2026-04-19","tags":["perf","runtime","syscall","telemetry","debug","asset"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0034","file":"discussion/lessons/DSC-0012-perf-runtime-introspection-syscalls/LSN-0034-host-owned-debug-boundaries.md","status":"done","created_at":"2026-04-19","updated_at":"2026-04-19"}]}
|
||||||
{"type":"discussion","id":"DSC-0013","status":"done","ticket":"perf-host-debug-overlay-isolation","title":"Agenda - [PERF] Host Debug Overlay Isolation","created_at":"2026-03-27","updated_at":"2026-04-10","tags":[],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0027","file":"lessons/DSC-0013-perf-host-debug-overlay-isolation/LSN-0027-host-debug-overlay-isolation.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}]}
|
{"type":"discussion","id":"DSC-0013","status":"done","ticket":"perf-host-debug-overlay-isolation","title":"Agenda - [PERF] Host Debug Overlay Isolation","created_at":"2026-03-27","updated_at":"2026-04-10","tags":[],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0027","file":"discussion/lessons/DSC-0013-perf-host-debug-overlay-isolation/LSN-0027-host-debug-overlay-isolation.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}]}
|
||||||
{"type":"discussion","id":"DSC-0024","status":"done","ticket":"generic-memory-bank-slot-contract","title":"Agenda - Generic Memory Bank Slot Contract","created_at":"2026-04-10","updated_at":"2026-04-10","tags":["runtime","asset","memory-bank","slots","host"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0029","file":"lessons/DSC-0024-generic-memory-bank-slot-contract/LSN-0029-slot-first-bank-telemetry-belongs-in-asset-manager.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}]}
|
{"type":"discussion","id":"DSC-0024","status":"done","ticket":"generic-memory-bank-slot-contract","title":"Agenda - Generic Memory Bank Slot Contract","created_at":"2026-04-10","updated_at":"2026-04-10","tags":["runtime","asset","memory-bank","slots","host"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0029","file":"discussion/lessons/DSC-0024-generic-memory-bank-slot-contract/LSN-0029-slot-first-bank-telemetry-belongs-in-asset-manager.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}]}
|
||||||
{"type":"discussion","id":"DSC-0025","status":"done","ticket":"scene-bank-and-viewport-cache-refactor","title":"Scene Bank and Viewport Cache Refactor","created_at":"2026-04-11","updated_at":"2026-04-14","tags":["gfx","tilemap","runtime","render"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0030","file":"lessons/DSC-0025-scene-bank-and-viewport-cache-refactor/LSN-0030-canonical-scene-cache-and-resolver-split.md","status":"done","created_at":"2026-04-14","updated_at":"2026-04-14"}]}
|
{"type":"discussion","id":"DSC-0025","status":"done","ticket":"scene-bank-and-viewport-cache-refactor","title":"Scene Bank and Viewport Cache Refactor","created_at":"2026-04-11","updated_at":"2026-04-14","tags":["gfx","tilemap","runtime","render"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0030","file":"discussion/lessons/DSC-0025-scene-bank-and-viewport-cache-refactor/LSN-0030-canonical-scene-cache-and-resolver-split.md","status":"done","created_at":"2026-04-14","updated_at":"2026-04-14"}]}
|
||||||
{"type":"discussion","id":"DSC-0026","status":"done","ticket":"render-all-scene-cache-and-camera-integration","title":"Integrate render_all with Scene Cache and Camera","created_at":"2026-04-14","updated_at":"2026-04-18","tags":["gfx","runtime","render","camera","scene"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0031","file":"lessons/DSC-0026-render-all-scene-cache-and-camera-integration/LSN-0031-frame-composition-belongs-above-the-render-backend.md","status":"done","created_at":"2026-04-18","updated_at":"2026-04-18"}]}
|
{"type":"discussion","id":"DSC-0026","status":"done","ticket":"render-all-scene-cache-and-camera-integration","title":"Integrate render_all with Scene Cache and Camera","created_at":"2026-04-14","updated_at":"2026-04-18","tags":["gfx","runtime","render","camera","scene"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0031","file":"discussion/lessons/DSC-0026-render-all-scene-cache-and-camera-integration/LSN-0031-frame-composition-belongs-above-the-render-backend.md","status":"done","created_at":"2026-04-18","updated_at":"2026-04-18"}]}
|
||||||
{"type":"discussion","id":"DSC-0027","status":"done","ticket":"frame-composer-public-syscall-surface","title":"Agenda - FrameComposer Public Syscall Surface","created_at":"2026-04-17","updated_at":"2026-04-18","tags":["gfx","runtime","syscall","abi","frame-composer","scene","camera","sprites"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0032","file":"lessons/DSC-0027-frame-composer-public-syscall-surface/LSN-0032-public-abi-must-follow-the-canonical-service-boundary.md","status":"done","created_at":"2026-04-18","updated_at":"2026-04-18"}]}
|
{"type":"discussion","id":"DSC-0027","status":"done","ticket":"frame-composer-public-syscall-surface","title":"Agenda - FrameComposer Public Syscall Surface","created_at":"2026-04-17","updated_at":"2026-04-18","tags":["gfx","runtime","syscall","abi","frame-composer","scene","camera","sprites"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0032","file":"discussion/lessons/DSC-0027-frame-composer-public-syscall-surface/LSN-0032-public-abi-must-follow-the-canonical-service-boundary.md","status":"done","created_at":"2026-04-18","updated_at":"2026-04-18"}]}
|
||||||
{"type":"discussion","id":"DSC-0028","status":"done","ticket":"deferred-overlay-and-primitive-composition","title":"Deferred Overlay and Primitive Composition over FrameComposer","created_at":"2026-04-18","updated_at":"2026-04-18","tags":["gfx","runtime","render","frame-composer","overlay","primitives","hud"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0033","file":"lessons/DSC-0028-deferred-overlay-and-primitive-composition/LSN-0033-debug-primitives-should-be-a-final-overlay-not-part-of-game-composition.md","status":"done","created_at":"2026-04-18","updated_at":"2026-04-18"}]}
|
{"type":"discussion","id":"DSC-0028","status":"done","ticket":"deferred-overlay-and-primitive-composition","title":"Deferred Overlay and Primitive Composition over FrameComposer","created_at":"2026-04-18","updated_at":"2026-04-18","tags":["gfx","runtime","render","frame-composer","overlay","primitives","hud"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0033","file":"discussion/lessons/DSC-0028-deferred-overlay-and-primitive-composition/LSN-0033-debug-primitives-should-be-a-final-overlay-not-part-of-game-composition.md","status":"done","created_at":"2026-04-18","updated_at":"2026-04-18"}]}
|
||||||
{"type":"discussion","id":"DSC-0029","status":"done","ticket":"scene-bank-glyph-runtime-binding-leak","title":"Scene Bank Glyph Runtime Binding Leak","created_at":"2026-04-24","updated_at":"2026-04-24","tags":["gfx","runtime","asset","scene","glyph","format","architecture"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0038","file":"lessons/DSC-0029-scene-bank-glyph-runtime-binding-leak/LSN-0038-cold-scene-dependencies-must-bind-by-asset-identity.md","status":"done","created_at":"2026-04-24","updated_at":"2026-04-24"}]}
|
{"type":"discussion","id":"DSC-0029","status":"done","ticket":"scene-bank-glyph-runtime-binding-leak","title":"Scene Bank Glyph Runtime Binding Leak","created_at":"2026-04-24","updated_at":"2026-04-24","tags":["gfx","runtime","asset","scene","glyph","format","architecture"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0038","file":"discussion/lessons/DSC-0029-scene-bank-glyph-runtime-binding-leak/LSN-0038-cold-scene-dependencies-must-bind-by-asset-identity.md","status":"done","created_at":"2026-04-24","updated_at":"2026-04-24"}]}
|
||||||
{"type":"discussion","id":"DSC-0014","status":"done","ticket":"perf-vm-allocation-and-copy-pressure","title":"Agenda - [PERF] VM Allocation and Copy Pressure","created_at":"2026-03-27","updated_at":"2026-04-20","tags":[],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0035","file":"lessons/DSC-0014-perf-vm-allocation-and-copy-pressure/LSN-0035-first-materialization-is-not-the-same-as-hot-path-copy-pressure.md","status":"done","created_at":"2026-04-20","updated_at":"2026-04-20"}]}
|
{"type":"discussion","id":"DSC-0014","status":"done","ticket":"perf-vm-allocation-and-copy-pressure","title":"Agenda - [PERF] VM Allocation and Copy Pressure","created_at":"2026-03-27","updated_at":"2026-04-20","tags":[],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0035","file":"discussion/lessons/DSC-0014-perf-vm-allocation-and-copy-pressure/LSN-0035-first-materialization-is-not-the-same-as-hot-path-copy-pressure.md","status":"done","created_at":"2026-04-20","updated_at":"2026-04-20"}]}
|
||||||
{"type":"discussion","id":"DSC-0015","status":"abandoned","ticket":"perf-cartridge-boot-and-program-ownership","title":"Agenda - [PERF] Cartridge Boot and Program Ownership","created_at":"2026-03-27","updated_at":"2026-04-20","tags":[],"agendas":[{"id":"AGD-0014","file":"workflow/agendas/AGD-0014-perf-cartridge-boot-and-program-ownership.md","status":"abandoned","created_at":"2026-03-27","updated_at":"2026-04-20","_override_reason":"User explicitly chose to close the discussion without decision because FS->memory copy for the program is already acceptable."}],"decisions":[],"plans":[],"lessons":[],"_override_reason":"User explicitly chose to abandon the discussion without creating a decision because FS->memory copy for the program is already acceptable."}
|
{"type":"discussion","id":"DSC-0015","status":"abandoned","ticket":"perf-cartridge-boot-and-program-ownership","title":"Agenda - [PERF] Cartridge Boot and Program Ownership","created_at":"2026-03-27","updated_at":"2026-04-20","tags":[],"agendas":[{"id":"AGD-0014","file":"AGD-0014-perf-cartridge-boot-and-program-ownership.md","status":"abandoned","created_at":"2026-03-27","updated_at":"2026-04-20","_override_reason":"User explicitly chose to close the discussion without decision because FS->memory copy for the program is already acceptable."}],"decisions":[],"plans":[],"lessons":[],"_override_reason":"User explicitly chose to abandon the discussion without creating a decision because FS->memory copy for the program is already acceptable."}
|
||||||
{"type":"discussion","id":"DSC-0016","status":"done","ticket":"tilemap-empty-cell-vs-tile-id-zero","title":"Tilemap Empty Cell vs Tile ID Zero","created_at":"2026-03-27","updated_at":"2026-04-09","tags":[],"agendas":[{"id":"AGD-0015","file":"workflow/agendas/AGD-0015-tilemap-empty-cell-vs-tile-id-zero.md","status":"done","created_at":"2026-03-27","updated_at":"2026-04-09"}],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0022","file":"lessons/DSC-0016-tilemap-empty-cell-semantics/LSN-0022-tilemap-empty-cell-convergence.md","status":"done","created_at":"2026-04-09","updated_at":"2026-04-09"}]}
|
{"type":"discussion","id":"DSC-0016","status":"done","ticket":"tilemap-empty-cell-vs-tile-id-zero","title":"Tilemap Empty Cell vs Tile ID Zero","created_at":"2026-03-27","updated_at":"2026-04-09","tags":[],"agendas":[{"id":"AGD-0015","file":"AGD-0015-tilemap-empty-cell-vs-tile-id-zero.md","status":"done","created_at":"2026-03-27","updated_at":"2026-04-09"}],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0022","file":"discussion/lessons/DSC-0016-tilemap-empty-cell-semantics/LSN-0022-tilemap-empty-cell-convergence.md","status":"done","created_at":"2026-04-09","updated_at":"2026-04-09"}]}
|
||||||
{"type":"discussion","id":"DSC-0017","status":"done","ticket":"asset-entry-metadata-normalization-contract","title":"Asset Entry Metadata Normalization Contract","created_at":"2026-03-27","updated_at":"2026-04-09","tags":[],"agendas":[{"id":"AGD-0016","file":"workflow/agendas/AGD-0016-asset-entry-metadata-normalization-contract.md","status":"done","created_at":"2026-03-27","updated_at":"2026-04-09"}],"decisions":[{"id":"DEC-0004","file":"workflow/decisions/DEC-0004-asset-entry-metadata-normalization-contract.md","status":"accepted","created_at":"2026-04-09","updated_at":"2026-04-09"}],"plans":[],"lessons":[{"id":"LSN-0023","file":"lessons/DSC-0017-asset-metadata-normalization/LSN-0023-typed-asset-metadata-helpers.md","status":"done","created_at":"2026-04-09","updated_at":"2026-04-09"}]}
|
{"type":"discussion","id":"DSC-0017","status":"done","ticket":"asset-entry-metadata-normalization-contract","title":"Asset Entry Metadata Normalization Contract","created_at":"2026-03-27","updated_at":"2026-04-09","tags":[],"agendas":[{"id":"AGD-0016","file":"AGD-0016-asset-entry-metadata-normalization-contract.md","status":"done","created_at":"2026-03-27","updated_at":"2026-04-09"}],"decisions":[{"id":"DEC-0004","file":"DEC-0004-asset-entry-metadata-normalization-contract.md","status":"accepted","created_at":"2026-04-09","updated_at":"2026-04-09"}],"plans":[],"lessons":[{"id":"LSN-0023","file":"discussion/lessons/DSC-0017-asset-metadata-normalization/LSN-0023-typed-asset-metadata-helpers.md","status":"done","created_at":"2026-04-09","updated_at":"2026-04-09"}]}
|
||||||
{"type":"discussion","id":"DSC-0018","status":"done","ticket":"asset-load-asset-id-int-contract","title":"Asset Load Asset ID Int Contract","created_at":"2026-03-27","updated_at":"2026-03-27","tags":["asset","runtime","abi"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0019","file":"lessons/DSC-0018-asset-load-asset-id-int-contract/LSN-0019-asset-load-id-abi-convergence.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"}]}
|
{"type":"discussion","id":"DSC-0018","status":"done","ticket":"asset-load-asset-id-int-contract","title":"Asset Load Asset ID Int Contract","created_at":"2026-03-27","updated_at":"2026-03-27","tags":["asset","runtime","abi"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0019","file":"discussion/lessons/DSC-0018-asset-load-asset-id-int-contract/LSN-0019-asset-load-id-abi-convergence.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"}]}
|
||||||
{"type":"discussion","id":"DSC-0019","status":"done","ticket":"jenkinsfile-correction","title":"Jenkinsfile Correction and Relocation","created_at":"2026-04-07","updated_at":"2026-04-07","tags":["ci","jenkins"],"agendas":[{"id":"AGD-0017","file":"workflow/agendas/AGD-0017-jenkinsfile-correction.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}],"decisions":[{"id":"DEC-0002","file":"workflow/decisions/DEC-0002-jenkinsfile-strategy.md","status":"accepted","created_at":"2026-04-07","updated_at":"2026-04-07"}],"plans":[{"id":"PLN-0002","file":"workflow/plans/PLN-0002-jenkinsfile-execution.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}],"lessons":[{"id":"LSN-0020","file":"lessons/DSC-0019-jenkins-ci-standardization/LSN-0020-jenkins-standard-relocation.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}]}
|
{"type":"discussion","id":"DSC-0019","status":"done","ticket":"jenkinsfile-correction","title":"Jenkinsfile Correction and Relocation","created_at":"2026-04-07","updated_at":"2026-04-07","tags":["ci","jenkins"],"agendas":[{"id":"AGD-0017","file":"AGD-0017-jenkinsfile-correction.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}],"decisions":[{"id":"DEC-0002","file":"DEC-0002-jenkinsfile-strategy.md","status":"accepted","created_at":"2026-04-07","updated_at":"2026-04-07"}],"plans":[{"id":"PLN-0002","file":"PLN-0002-jenkinsfile-execution.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}],"lessons":[{"id":"LSN-0020","file":"discussion/lessons/DSC-0019-jenkins-ci-standardization/LSN-0020-jenkins-standard-relocation.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}]}
|
||||||
{"type":"discussion","id":"DSC-0030","status":"done","ticket":"internal-viewport-270p","title":"Agenda - Internal Viewport 270p (480x270)","created_at":"2026-04-27","updated_at":"2026-04-28","tags":["gfx","runtime","viewport","resolution","frame-composer","host"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0039","file":"lessons/DSC-0030-internal-viewport-270p/LSN-0039-resolution-baseline-changes-are-runtime-wide-contracts.md","status":"done","created_at":"2026-04-28","updated_at":"2026-04-28"}]}
|
{"type":"discussion","id":"DSC-0030","status":"done","ticket":"internal-viewport-270p","title":"Agenda - Internal Viewport 270p (480x270)","created_at":"2026-04-27","updated_at":"2026-04-28","tags":["gfx","runtime","viewport","resolution","frame-composer","host"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0039","file":"discussion/lessons/DSC-0030-internal-viewport-270p/LSN-0039-resolution-baseline-changes-are-runtime-wide-contracts.md","status":"done","created_at":"2026-04-28","updated_at":"2026-04-28"}]}
|
||||||
|
{"type":"discussion","id":"DSC-0031","status":"done","ticket":"runtime-mode-separation-game-system","title":"Agenda - Runtime Mode Separation: Game and System","created_at":"2026-05-11","updated_at":"2026-05-14","tags":["runtime","firmware","hub","system-apps","game-mode","scheduler"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0040","file":"discussion/lessons/DSC-0031-runtime-mode-separation-game-system/LSN-0040-system-pipeline-separation.md","status":"done","created_at":"2026-05-14","updated_at":"2026-05-14"}]}
|
||||||
|
|||||||
@ -0,0 +1,91 @@
|
|||||||
|
---
|
||||||
|
id: LSN-0040
|
||||||
|
discussion: DSC-0031
|
||||||
|
decision: DEC-0023
|
||||||
|
title: System Pipeline Separation
|
||||||
|
status: done
|
||||||
|
created: 2026-05-14
|
||||||
|
tags: [runtime, firmware, hub, system-apps, game-mode, abi]
|
||||||
|
---
|
||||||
|
|
||||||
|
# System Pipeline Separation
|
||||||
|
|
||||||
|
`AppMode` is the profile discriminator. `Game` and `System` can share the VM
|
||||||
|
and syscall transport machinery internally, but they must not share the same
|
||||||
|
author-facing runtime profile.
|
||||||
|
|
||||||
|
The important boundary is not the instruction used to cross into host services.
|
||||||
|
The boundary is the profile contract selected by `manifest.json app_mode`:
|
||||||
|
|
||||||
|
- `Game` uses the game pipeline and owns game-facing surfaces such as GFX,
|
||||||
|
frame composition, asset/bank access, sprites, game input, and memcard-style
|
||||||
|
persistence.
|
||||||
|
- `System` uses a Runtime/Hub pipeline oriented around system UI and app
|
||||||
|
hosting.
|
||||||
|
- Future system APIs may use syscalls as transport, but the public contract is a
|
||||||
|
dedicated `System` ABI and stdlib/framework.
|
||||||
|
|
||||||
|
## What Changed
|
||||||
|
|
||||||
|
The cartridge and boot specs now state that `app_mode` is the discriminator and
|
||||||
|
that no separate manifest `target` field is involved.
|
||||||
|
|
||||||
|
Firmware now routes loaded `System` cartridges into `SystemRunning` instead of
|
||||||
|
`GameRunning` or an implicit HubHome fallback. That state delegates focused
|
||||||
|
system app updates to `PrometeuHub::update_system_profile`, which gives the Hub
|
||||||
|
domain a named system-profile pipeline entry point.
|
||||||
|
|
||||||
|
The game-running path remains isolated. `GameRunningStep` still owns the game
|
||||||
|
tick and presentation behavior.
|
||||||
|
|
||||||
|
Runtime syscall dispatch now rejects representative game-only surfaces when the
|
||||||
|
current cartridge profile is `System`. GFX, composer, asset/bank surfaces are
|
||||||
|
guarded as game-profile syscalls. Memcard calls keep their existing
|
||||||
|
status-first `AccessDenied` contract for non-game profiles. Log transport
|
||||||
|
remains internally shared, while future author-facing APIs are still expected to
|
||||||
|
resolve through profile-specific stdlibs.
|
||||||
|
|
||||||
|
## Evidence
|
||||||
|
|
||||||
|
The implementation evidence for this separation was:
|
||||||
|
|
||||||
|
- `cargo test -p prometeu-firmware`: 8 passed.
|
||||||
|
- `cargo test -p prometeu-system`: 48 passed.
|
||||||
|
- `cargo test -p prometeu-host-desktop-winit`: 25 passed, 5 ignored.
|
||||||
|
|
||||||
|
The firmware tests prove that:
|
||||||
|
|
||||||
|
- `AppMode::Game` still reaches `GameRunning`;
|
||||||
|
- `AppMode::System` reaches `SystemRunning`;
|
||||||
|
- a focused system app ticks through the Hub system-profile pipeline.
|
||||||
|
|
||||||
|
The runtime tests prove that:
|
||||||
|
|
||||||
|
- `System` profile cartridges cannot call representative game GFX, composer, or
|
||||||
|
bank surfaces;
|
||||||
|
- non-game memcard access remains status-first instead of crashing;
|
||||||
|
- log transport can remain shared internally.
|
||||||
|
|
||||||
|
## How To Think About It
|
||||||
|
|
||||||
|
Do not model system apps as games inside windows. Model them as apps hosted by
|
||||||
|
the Runtime/Hub environment.
|
||||||
|
|
||||||
|
The first split is deliberately small: it creates a stable place for system UI
|
||||||
|
execution without freezing the final WindowManager, lifecycle, component tree,
|
||||||
|
filesystem, or public PBS `System` ABI. Those are follow-up contracts.
|
||||||
|
|
||||||
|
When extending this area, ask which profile owns the author-facing capability
|
||||||
|
before deciding which syscall or internal runtime service carries it. Syscalls
|
||||||
|
are a transport mechanism. Profile ABI is the contract.
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- `DEC-0023` - System Pipeline Separation.
|
||||||
|
- `PLN-0046` - Profile Separation Specification.
|
||||||
|
- `PLN-0047` - Firmware Runtime Profile Dispatch.
|
||||||
|
- `PLN-0048` - Minimal System Hub Pipeline.
|
||||||
|
- `PLN-0049` - System Profile ABI Gates.
|
||||||
|
- `docs/specs/runtime/13-cartridge.md`
|
||||||
|
- `docs/specs/runtime/14-boot-profiles.md`
|
||||||
|
- `docs/specs/runtime/16-host-abi-and-syscalls.md`
|
||||||
@ -75,6 +75,9 @@ If validation fails, cartridge loading is rejected before execution.
|
|||||||
- `Game`
|
- `Game`
|
||||||
- `System`
|
- `System`
|
||||||
|
|
||||||
|
`app_mode` is the runtime profile discriminator. No separate manifest `target`
|
||||||
|
field participates in profile selection.
|
||||||
|
|
||||||
Cartridge boot is protocol-driven, not manifest-driven.
|
Cartridge boot is protocol-driven, not manifest-driven.
|
||||||
|
|
||||||
The executable entrypoint of a valid cartridge is always `func_id = 0`.
|
The executable entrypoint of a valid cartridge is always `func_id = 0`.
|
||||||
@ -85,6 +88,37 @@ This means:
|
|||||||
- firmware/runtime loads the cartridge and starts execution from `func_id = 0`;
|
- firmware/runtime loads the cartridge and starts execution from `func_id = 0`;
|
||||||
- nominal exports may exist for linking or introspection, but they do not hold boot authority.
|
- nominal exports may exist for linking or introspection, but they do not hold boot authority.
|
||||||
|
|
||||||
|
## 5.1 Runtime Profiles
|
||||||
|
|
||||||
|
`Game` and `System` are distinct runtime profiles.
|
||||||
|
|
||||||
|
`Game` cartridges use the game pipeline. The game profile owns the public game
|
||||||
|
surfaces for frame composition, GFX, game input, banks, sprites, and
|
||||||
|
memory-card-style persistence.
|
||||||
|
|
||||||
|
`System` cartridges use a Runtime/Hub pipeline dedicated to system UI and app
|
||||||
|
hosting. The system profile must not inherit the game stdlib or game ABI as its
|
||||||
|
public authoring surface. In particular, `System` must not expose
|
||||||
|
`FrameComposer`, `Gfx`, game `Input`, banks, sprites, or game memory cards as
|
||||||
|
public profile APIs.
|
||||||
|
|
||||||
|
Future `System` APIs may be transported through syscalls, but syscall transport
|
||||||
|
does not define the conceptual ABI boundary. The author-facing contract for
|
||||||
|
system apps is a dedicated `System` profile ABI and stdlib/framework.
|
||||||
|
|
||||||
|
The following contracts are intentionally deferred from this cartridge chapter:
|
||||||
|
|
||||||
|
- complete WindowManager design;
|
||||||
|
- component tree and lifecycle semantics;
|
||||||
|
- rich invalidation and transitions;
|
||||||
|
- public PBS `System` ABI;
|
||||||
|
- multitasking and scheduler semantics;
|
||||||
|
- detailed `System` filesystem semantics.
|
||||||
|
|
||||||
|
The positive filesystem contract for `System` remains owned by the dedicated
|
||||||
|
filesystem discussion/spec work. The only profile rule fixed here is negative:
|
||||||
|
`System` does not use game memory cards as its public storage model.
|
||||||
|
|
||||||
## 6 Assets and Preload
|
## 6 Assets and Preload
|
||||||
|
|
||||||
`assets.pa` is the runtime-facing asset artifact consumed by the current runtime.
|
`assets.pa` is the runtime-facing asset artifact consumed by the current runtime.
|
||||||
|
|||||||
@ -40,11 +40,18 @@ When the boot target is `Cartridge`:
|
|||||||
|
|
||||||
For a cartridge boot target:
|
For a cartridge boot target:
|
||||||
|
|
||||||
- `Game` cartridges transition into game-running flow;
|
- `Game` cartridges transition into the game-running pipeline;
|
||||||
- `System` cartridges are initialized and integrated into the Hub/window path.
|
- `System` cartridges transition into a Runtime/Hub pipeline dedicated to
|
||||||
|
system UI and app hosting.
|
||||||
|
|
||||||
This preserves the distinction between machine firmware state and app execution mode.
|
This preserves the distinction between machine firmware state and app execution mode.
|
||||||
|
|
||||||
|
The `System` route must not be treated as the game pipeline running inside a
|
||||||
|
window. It is a separate runtime profile path oriented around Hub ownership and
|
||||||
|
WindowManager integration. The initial implementation may be minimal, but the
|
||||||
|
routing boundary is normative: `System` does not enter `GameRunning` as its
|
||||||
|
profile pipeline.
|
||||||
|
|
||||||
## 4 Host CLI Relationship
|
## 4 Host CLI Relationship
|
||||||
|
|
||||||
Typical host-facing boot intents are:
|
Typical host-facing boot intents are:
|
||||||
@ -67,6 +74,10 @@ Boot target selection feeds into firmware states such as:
|
|||||||
- `GameRunning`
|
- `GameRunning`
|
||||||
- `AppCrashes`
|
- `AppCrashes`
|
||||||
|
|
||||||
|
Additional firmware states may represent dedicated `System` profile execution.
|
||||||
|
Such states are part of the Runtime/Hub route and do not change the `Game`
|
||||||
|
pipeline contract.
|
||||||
|
|
||||||
Boot target is not itself a firmware state. It is an input to the firmware state machine.
|
Boot target is not itself a firmware state. It is an input to the firmware state machine.
|
||||||
|
|
||||||
## 6 Debug Boot
|
## 6 Debug Boot
|
||||||
|
|||||||
@ -52,6 +52,12 @@ This identity is:
|
|||||||
|
|
||||||
Input queries are VM-owned intrinsic calls in v1 and are outside the syscall identity space.
|
Input queries are VM-owned intrinsic calls in v1 and are outside the syscall identity space.
|
||||||
|
|
||||||
|
Syscall identity is transport, not the conceptual boundary for runtime
|
||||||
|
profiles. `Game` and `System` may both use syscall transport internally or at
|
||||||
|
their ABI edge, but that does not make their public authoring surfaces the same
|
||||||
|
ABI. `System` APIs, when exposed to PBS or other guest languages, belong to a
|
||||||
|
dedicated `System` profile ABI and stdlib/framework.
|
||||||
|
|
||||||
## 3 Syscall Resolution
|
## 3 Syscall Resolution
|
||||||
|
|
||||||
The host maintains a registry:
|
The host maintains a registry:
|
||||||
@ -173,6 +179,9 @@ Return shape must also follow the transversal status-first policy in [`16a-sysca
|
|||||||
The game memcard profile uses module `mem` with status-first return shapes.
|
The game memcard profile uses module `mem` with status-first return shapes.
|
||||||
`mem` is a domain layer backed by runtime `fs` (it is not a separate storage backend).
|
`mem` is a domain layer backed by runtime `fs` (it is not a separate storage backend).
|
||||||
|
|
||||||
|
The `mem` module is a game profile surface. It is not the public storage model
|
||||||
|
for `System` profile apps.
|
||||||
|
|
||||||
Canonical operations in v1 are:
|
Canonical operations in v1 are:
|
||||||
|
|
||||||
- `mem.slot_count() -> (status, count)`
|
- `mem.slot_count() -> (status, count)`
|
||||||
@ -215,6 +224,9 @@ For `asset.load`:
|
|||||||
|
|
||||||
The canonical frame-orchestration public ABI uses module `composer`.
|
The canonical frame-orchestration public ABI uses module `composer`.
|
||||||
|
|
||||||
|
The `composer` module is a game profile surface. It is not inherited by
|
||||||
|
`System` profile apps as their public UI/composition API.
|
||||||
|
|
||||||
Canonical operations in v1 are:
|
Canonical operations in v1 are:
|
||||||
|
|
||||||
- `composer.bind_scene(bank_id) -> (status)`
|
- `composer.bind_scene(bank_id) -> (status)`
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user