dev/render-frame-packet-boundary #31

Merged
bquarkz merged 16 commits from dev/render-frame-packet-boundary into master 2026-05-25 18:41:23 +00:00
16 changed files with 297 additions and 69 deletions
Showing only changes of commit 7c35edcb84 - Show all commits

View File

@ -286,6 +286,9 @@ pub enum Capability {
None,
System,
Gfx,
Composer,
Gfx2d,
Gfxui,
Audio,
Fs,
Log,

View File

@ -117,6 +117,9 @@ fn normalize_capabilities(capabilities: &[Capability]) -> Result<CapFlags, Cartr
Capability::None => caps::NONE,
Capability::System => caps::SYSTEM,
Capability::Gfx => caps::GFX,
Capability::Composer => caps::COMPOSER,
Capability::Gfx2d => caps::GFX2D,
Capability::Gfxui => caps::GFXUI,
Capability::Audio => caps::AUDIO,
Capability::Fs => caps::FS,
Capability::Log => caps::LOG,

View File

@ -18,7 +18,7 @@ pub use resolver::{
///
/// Each Syscall has a unique 32-bit ID. The IDs are grouped by category:
/// - **0x0xxx**: System & OS Control
/// - **0x1xxx**: Graphics (GFX)
/// - **0x1xxx**: Game 2D primitives (`gfx2d`) and Shell UI primitives (`gfxui`)
/// - **0x11xx**: Frame Composer orchestration
/// - **0x2xxx**: Reserved for legacy input syscalls (disabled for v1 VM-owned input)
/// - **0x3xxx**: Audio (PCM & Mixing)
@ -37,6 +37,13 @@ pub enum Syscall {
GfxDrawDisc = 0x1005,
GfxDrawSquare = 0x1006,
GfxDrawText = 0x1008,
GfxUiClear = 0x1201,
GfxUiFillRect = 0x1202,
GfxUiDrawLine = 0x1203,
GfxUiDrawCircle = 0x1204,
GfxUiDrawDisc = 0x1205,
GfxUiDrawSquare = 0x1206,
GfxUiDrawText = 0x1208,
ComposerBindScene = 0x1101,
ComposerUnbindScene = 0x1102,
ComposerSetCamera = 0x1103,

View File

@ -2,10 +2,13 @@ use super::CapFlags;
pub const NONE: CapFlags = 0;
pub const SYSTEM: CapFlags = 1 << 0;
pub const GFX: CapFlags = 1 << 1;
pub const COMPOSER: CapFlags = 1 << 1;
pub const AUDIO: CapFlags = 1 << 2;
pub const FS: CapFlags = 1 << 3;
pub const LOG: CapFlags = 1 << 4;
pub const ASSET: CapFlags = 1 << 5;
pub const BANK: CapFlags = 1 << 6;
pub const ALL: CapFlags = SYSTEM | GFX | AUDIO | FS | LOG | ASSET | BANK;
pub const GFX2D: CapFlags = 1 << 7;
pub const GFXUI: CapFlags = 1 << 8;
pub const GFX: CapFlags = COMPOSER | GFX2D;
pub const ALL: CapFlags = SYSTEM | COMPOSER | AUDIO | FS | LOG | ASSET | BANK | GFX2D | GFXUI;

View File

@ -4,19 +4,19 @@ pub(crate) const ENTRIES: &[SyscallRegistryEntry] = &[
SyscallRegistryEntry::builder(Syscall::ComposerBindScene, "composer", "bind_scene")
.args(1)
.rets(1)
.caps(caps::GFX)
.caps(caps::COMPOSER)
.cost(5),
SyscallRegistryEntry::builder(Syscall::ComposerUnbindScene, "composer", "unbind_scene")
.rets(1)
.caps(caps::GFX)
.caps(caps::COMPOSER)
.cost(2),
SyscallRegistryEntry::builder(Syscall::ComposerSetCamera, "composer", "set_camera")
.args(2)
.caps(caps::GFX)
.caps(caps::COMPOSER)
.cost(2),
SyscallRegistryEntry::builder(Syscall::ComposerEmitSprite, "composer", "emit_sprite")
.args(9)
.rets(1)
.caps(caps::GFX)
.caps(caps::COMPOSER)
.cost(5),
];

View File

@ -1,32 +1,32 @@
use crate::syscalls::{Syscall, SyscallRegistryEntry, caps};
pub(crate) const ENTRIES: &[SyscallRegistryEntry] = &[
SyscallRegistryEntry::builder(Syscall::GfxClear, "gfx", "clear")
SyscallRegistryEntry::builder(Syscall::GfxClear, "gfx2d", "clear")
.args(1)
.caps(caps::GFX)
.caps(caps::GFX2D)
.cost(20),
SyscallRegistryEntry::builder(Syscall::GfxFillRect, "gfx", "fill_rect")
SyscallRegistryEntry::builder(Syscall::GfxFillRect, "gfx2d", "fill_rect")
.args(5)
.caps(caps::GFX)
.caps(caps::GFX2D)
.cost(20),
SyscallRegistryEntry::builder(Syscall::GfxDrawLine, "gfx", "draw_line")
SyscallRegistryEntry::builder(Syscall::GfxDrawLine, "gfx2d", "draw_line")
.args(5)
.caps(caps::GFX)
.caps(caps::GFX2D)
.cost(5),
SyscallRegistryEntry::builder(Syscall::GfxDrawCircle, "gfx", "draw_circle")
SyscallRegistryEntry::builder(Syscall::GfxDrawCircle, "gfx2d", "draw_circle")
.args(4)
.caps(caps::GFX)
.caps(caps::GFX2D)
.cost(5),
SyscallRegistryEntry::builder(Syscall::GfxDrawDisc, "gfx", "draw_disc")
SyscallRegistryEntry::builder(Syscall::GfxDrawDisc, "gfx2d", "draw_disc")
.args(5)
.caps(caps::GFX)
.caps(caps::GFX2D)
.cost(5),
SyscallRegistryEntry::builder(Syscall::GfxDrawSquare, "gfx", "draw_square")
SyscallRegistryEntry::builder(Syscall::GfxDrawSquare, "gfx2d", "draw_square")
.args(6)
.caps(caps::GFX)
.caps(caps::GFX2D)
.cost(5),
SyscallRegistryEntry::builder(Syscall::GfxDrawText, "gfx", "draw_text")
SyscallRegistryEntry::builder(Syscall::GfxDrawText, "gfx2d", "draw_text")
.args(4)
.caps(caps::GFX)
.caps(caps::GFX2D)
.cost(20),
];

View File

@ -0,0 +1,32 @@
use crate::syscalls::{Syscall, SyscallRegistryEntry, caps};
pub(crate) const ENTRIES: &[SyscallRegistryEntry] = &[
SyscallRegistryEntry::builder(Syscall::GfxUiClear, "gfxui", "clear")
.args(1)
.caps(caps::GFXUI)
.cost(20),
SyscallRegistryEntry::builder(Syscall::GfxUiFillRect, "gfxui", "fill_rect")
.args(5)
.caps(caps::GFXUI)
.cost(20),
SyscallRegistryEntry::builder(Syscall::GfxUiDrawLine, "gfxui", "draw_line")
.args(5)
.caps(caps::GFXUI)
.cost(5),
SyscallRegistryEntry::builder(Syscall::GfxUiDrawCircle, "gfxui", "draw_circle")
.args(4)
.caps(caps::GFXUI)
.cost(5),
SyscallRegistryEntry::builder(Syscall::GfxUiDrawDisc, "gfxui", "draw_disc")
.args(5)
.caps(caps::GFXUI)
.cost(5),
SyscallRegistryEntry::builder(Syscall::GfxUiDrawSquare, "gfxui", "draw_square")
.args(6)
.caps(caps::GFXUI)
.cost(5),
SyscallRegistryEntry::builder(Syscall::GfxUiDrawText, "gfxui", "draw_text")
.args(4)
.caps(caps::GFXUI)
.cost(20),
];

View File

@ -4,6 +4,7 @@ mod bank;
mod composer;
mod fs;
mod gfx;
mod gfxui;
mod log;
mod system;
@ -13,6 +14,7 @@ pub(crate) fn all_entries() -> impl Iterator<Item = &'static SyscallRegistryEntr
system::ENTRIES
.iter()
.chain(gfx::ENTRIES.iter())
.chain(gfxui::ENTRIES.iter())
.chain(composer::ENTRIES.iter())
.chain(audio::ENTRIES.iter())
.chain(fs::ENTRIES.iter())

View File

@ -21,6 +21,13 @@ impl Syscall {
0x1005 => Some(Self::GfxDrawDisc),
0x1006 => Some(Self::GfxDrawSquare),
0x1008 => Some(Self::GfxDrawText),
0x1201 => Some(Self::GfxUiClear),
0x1202 => Some(Self::GfxUiFillRect),
0x1203 => Some(Self::GfxUiDrawLine),
0x1204 => Some(Self::GfxUiDrawCircle),
0x1205 => Some(Self::GfxUiDrawDisc),
0x1206 => Some(Self::GfxUiDrawSquare),
0x1208 => Some(Self::GfxUiDrawText),
0x1101 => Some(Self::ComposerBindScene),
0x1102 => Some(Self::ComposerUnbindScene),
0x1103 => Some(Self::ComposerSetCamera),
@ -70,6 +77,13 @@ impl Syscall {
Self::GfxDrawDisc => "GfxDrawDisc",
Self::GfxDrawSquare => "GfxDrawSquare",
Self::GfxDrawText => "GfxDrawText",
Self::GfxUiClear => "GfxUiClear",
Self::GfxUiFillRect => "GfxUiFillRect",
Self::GfxUiDrawLine => "GfxUiDrawLine",
Self::GfxUiDrawCircle => "GfxUiDrawCircle",
Self::GfxUiDrawDisc => "GfxUiDrawDisc",
Self::GfxUiDrawSquare => "GfxUiDrawSquare",
Self::GfxUiDrawText => "GfxUiDrawText",
Self::ComposerBindScene => "ComposerBindScene",
Self::ComposerUnbindScene => "ComposerUnbindScene",
Self::ComposerSetCamera => "ComposerSetCamera",

View File

@ -39,23 +39,23 @@ fn every_syscall_has_metadata() {
#[test]
fn resolver_returns_expected_id_for_known_identity() {
let id = resolve_syscall("gfx", "clear", 1).expect("known identity must resolve");
let id = resolve_syscall("gfx2d", "clear", 1).expect("known identity must resolve");
assert_eq!(id.id, 0x1001);
assert_eq!(id.meta.module, "gfx");
assert_eq!(id.meta.module, "gfx2d");
assert_eq!(id.meta.name, "clear");
assert_eq!(id.meta.version, 1);
}
#[test]
fn resolver_rejects_unknown_identity() {
let res = resolve_syscall("gfx", "nonexistent", 1);
let res = resolve_syscall("gfx2d", "nonexistent", 1);
assert!(res.is_none());
let requested = [SyscallIdentity { module: "gfx", name: "nonexistent", version: 1 }];
let requested = [SyscallIdentity { module: "gfx2d", name: "nonexistent", version: 1 }];
let err = resolve_program_syscalls(&requested, 0).unwrap_err();
match err {
LoadError::UnknownSyscall { module, name, version } => {
assert_eq!(module, "gfx");
assert_eq!(module, "gfx2d");
assert_eq!(name, "nonexistent");
assert_eq!(version, 1);
}
@ -65,23 +65,23 @@ fn resolver_rejects_unknown_identity() {
#[test]
fn resolver_rejects_removed_legacy_gfx_set_sprite_identity() {
assert!(resolve_syscall("gfx", "set_sprite", 1).is_none());
assert!(resolve_syscall("gfx2d", "set_sprite", 1).is_none());
let requested = [SyscallIdentity { module: "gfx", name: "set_sprite", version: 1 }];
let requested = [SyscallIdentity { module: "gfx2d", name: "set_sprite", version: 1 }];
let err = resolve_program_syscalls(&requested, caps::ALL).unwrap_err();
assert_eq!(
err,
LoadError::UnknownSyscall { module: "gfx".into(), name: "set_sprite".into(), version: 1 }
LoadError::UnknownSyscall { module: "gfx2d".into(), name: "set_sprite".into(), version: 1 }
);
}
#[test]
fn resolver_enforces_capabilities() {
let requested = [SyscallIdentity { module: "gfx", name: "clear", version: 1 }];
let requested = [SyscallIdentity { module: "gfx2d", name: "clear", version: 1 }];
let err = resolve_program_syscalls(&requested, 0).unwrap_err();
match err {
LoadError::MissingCapability { required, provided, module, name, version } => {
assert_eq!(module, "gfx");
assert_eq!(module, "gfx2d");
assert_eq!(name, "clear");
assert_eq!(version, 1);
assert_ne!(required, 0);
@ -90,15 +90,30 @@ fn resolver_enforces_capabilities() {
_ => panic!("expected MissingCapability error"),
}
let ok = resolve_program_syscalls(&requested, caps::GFX).expect("must resolve with caps");
let ok = resolve_program_syscalls(&requested, caps::GFX2D).expect("must resolve with caps");
assert_eq!(ok.len(), 1);
assert_eq!(ok[0].id, 0x1001);
}
#[test]
fn resolver_exposes_split_render_capabilities() {
let composer = resolve_syscall("composer", "bind_scene", 1).expect("composer resolves");
assert_eq!(composer.meta.caps, caps::COMPOSER);
let gfx2d = resolve_syscall("gfx2d", "clear", 1).expect("gfx2d resolves");
assert_eq!(gfx2d.meta.caps, caps::GFX2D);
let gfxui = resolve_syscall("gfxui", "clear", 1).expect("gfxui resolves");
assert_eq!(gfxui.id, 0x1201);
assert_eq!(gfxui.meta.caps, caps::GFXUI);
assert!(resolve_syscall("gfx", "clear", 1).is_none());
}
#[test]
fn declared_resolver_returns_expected_id_for_known_identity() {
let declared = [prometeu_bytecode::SyscallDecl {
module: "gfx".into(),
module: "gfx2d".into(),
name: "clear".into(),
version: 1,
arg_slots: 1,
@ -106,7 +121,7 @@ fn declared_resolver_returns_expected_id_for_known_identity() {
}];
let ok =
resolve_declared_program_syscalls(&declared, caps::GFX).expect("must resolve with ABI");
resolve_declared_program_syscalls(&declared, caps::GFX2D).expect("must resolve with ABI");
assert_eq!(ok.len(), 1);
assert_eq!(ok[0].id, 0x1001);
}
@ -114,18 +129,18 @@ fn declared_resolver_returns_expected_id_for_known_identity() {
#[test]
fn declared_resolver_rejects_unknown_identity() {
let declared = [prometeu_bytecode::SyscallDecl {
module: "gfx".into(),
module: "gfx2d".into(),
name: "nonexistent".into(),
version: 1,
arg_slots: 1,
ret_slots: 0,
}];
let err = resolve_declared_program_syscalls(&declared, caps::GFX).unwrap_err();
let err = resolve_declared_program_syscalls(&declared, caps::GFX2D).unwrap_err();
assert_eq!(
err,
DeclaredLoadError::UnknownSyscall {
module: "gfx".into(),
module: "gfx2d".into(),
name: "nonexistent".into(),
version: 1,
}
@ -135,7 +150,7 @@ fn declared_resolver_rejects_unknown_identity() {
#[test]
fn declared_resolver_rejects_missing_capability() {
let declared = [prometeu_bytecode::SyscallDecl {
module: "gfx".into(),
module: "gfx2d".into(),
name: "clear".into(),
version: 1,
arg_slots: 1,
@ -146,9 +161,9 @@ fn declared_resolver_rejects_missing_capability() {
assert_eq!(
err,
DeclaredLoadError::MissingCapability {
required: caps::GFX,
required: caps::GFX2D,
provided: caps::NONE,
module: "gfx".into(),
module: "gfx2d".into(),
name: "clear".into(),
version: 1,
}
@ -158,18 +173,18 @@ fn declared_resolver_rejects_missing_capability() {
#[test]
fn declared_resolver_rejects_abi_mismatch() {
let declared = [prometeu_bytecode::SyscallDecl {
module: "gfx".into(),
module: "gfx2d".into(),
name: "draw_line".into(),
version: 1,
arg_slots: 4,
ret_slots: 0,
}];
let err = resolve_declared_program_syscalls(&declared, caps::GFX).unwrap_err();
let err = resolve_declared_program_syscalls(&declared, caps::GFX2D).unwrap_err();
assert_eq!(
err,
DeclaredLoadError::AbiMismatch {
module: "gfx".into(),
module: "gfx2d".into(),
name: "draw_line".into(),
version: 1,
declared_arg_slots: 4,
@ -386,7 +401,7 @@ fn declared_resolver_accepts_mixed_status_first_surface_as_a_single_module() {
];
let resolved =
resolve_declared_program_syscalls(&declared, caps::GFX | caps::AUDIO | caps::ASSET)
resolve_declared_program_syscalls(&declared, caps::COMPOSER | caps::AUDIO | caps::ASSET)
.expect("mixed status-first surface must resolve together");
assert_eq!(resolved.len(), declared.len());

View File

@ -188,6 +188,17 @@ impl NativeInterface for VmRuntimeHost<'_> {
hw.gfx_mut().clear(color);
Ok(())
}
Syscall::GfxUiClear => {
if self.current_cartridge_app_mode != AppMode::Shell {
return Err(VmFault::Trap(
TRAP_INVALID_SYSCALL,
format!("{} is only available to the Shell profile", syscall.name()),
));
}
let color = self.get_color(expect_int(args, 0)?)?;
hw.gfx_mut().clear(color);
Ok(())
}
Syscall::GfxFillRect => {
let x = expect_int(args, 0)? as i32;
let y = expect_int(args, 1)? as i32;
@ -197,6 +208,21 @@ impl NativeInterface for VmRuntimeHost<'_> {
hw.gfx_mut().fill_rect(x, y, w, h, color);
Ok(())
}
Syscall::GfxUiFillRect => {
if self.current_cartridge_app_mode != AppMode::Shell {
return Err(VmFault::Trap(
TRAP_INVALID_SYSCALL,
format!("{} is only available to the Shell profile", syscall.name()),
));
}
let x = expect_int(args, 0)? as i32;
let y = expect_int(args, 1)? as i32;
let w = expect_int(args, 2)? as i32;
let h = expect_int(args, 3)? as i32;
let color = self.get_color(expect_int(args, 4)?)?;
hw.gfx_mut().fill_rect(x, y, w, h, color);
Ok(())
}
Syscall::GfxDrawLine => {
let x1 = expect_int(args, 0)? as i32;
let y1 = expect_int(args, 1)? as i32;
@ -206,6 +232,21 @@ impl NativeInterface for VmRuntimeHost<'_> {
hw.gfx_mut().draw_line(x1, y1, x2, y2, color);
Ok(())
}
Syscall::GfxUiDrawLine => {
if self.current_cartridge_app_mode != AppMode::Shell {
return Err(VmFault::Trap(
TRAP_INVALID_SYSCALL,
format!("{} is only available to the Shell profile", syscall.name()),
));
}
let x1 = expect_int(args, 0)? as i32;
let y1 = expect_int(args, 1)? as i32;
let x2 = expect_int(args, 2)? as i32;
let y2 = expect_int(args, 3)? as i32;
let color = self.get_color(expect_int(args, 4)?)?;
hw.gfx_mut().draw_line(x1, y1, x2, y2, color);
Ok(())
}
Syscall::GfxDrawCircle => {
let x = expect_int(args, 0)? as i32;
let y = expect_int(args, 1)? as i32;
@ -214,6 +255,20 @@ impl NativeInterface for VmRuntimeHost<'_> {
hw.gfx_mut().draw_circle(x, y, r, color);
Ok(())
}
Syscall::GfxUiDrawCircle => {
if self.current_cartridge_app_mode != AppMode::Shell {
return Err(VmFault::Trap(
TRAP_INVALID_SYSCALL,
format!("{} is only available to the Shell profile", syscall.name()),
));
}
let x = expect_int(args, 0)? as i32;
let y = expect_int(args, 1)? as i32;
let r = expect_int(args, 2)? as i32;
let color = self.get_color(expect_int(args, 3)?)?;
hw.gfx_mut().draw_circle(x, y, r, color);
Ok(())
}
Syscall::GfxDrawDisc => {
let x = expect_int(args, 0)? as i32;
let y = expect_int(args, 1)? as i32;
@ -223,6 +278,21 @@ impl NativeInterface for VmRuntimeHost<'_> {
hw.gfx_mut().draw_disc(x, y, r, border_color, fill_color);
Ok(())
}
Syscall::GfxUiDrawDisc => {
if self.current_cartridge_app_mode != AppMode::Shell {
return Err(VmFault::Trap(
TRAP_INVALID_SYSCALL,
format!("{} is only available to the Shell profile", syscall.name()),
));
}
let x = expect_int(args, 0)? as i32;
let y = expect_int(args, 1)? as i32;
let r = expect_int(args, 2)? as i32;
let border_color = self.get_color(expect_int(args, 3)?)?;
let fill_color = self.get_color(expect_int(args, 4)?)?;
hw.gfx_mut().draw_disc(x, y, r, border_color, fill_color);
Ok(())
}
Syscall::GfxDrawSquare => {
let x = expect_int(args, 0)? as i32;
let y = expect_int(args, 1)? as i32;
@ -233,6 +303,22 @@ impl NativeInterface for VmRuntimeHost<'_> {
hw.gfx_mut().draw_square(x, y, w, h, border_color, fill_color);
Ok(())
}
Syscall::GfxUiDrawSquare => {
if self.current_cartridge_app_mode != AppMode::Shell {
return Err(VmFault::Trap(
TRAP_INVALID_SYSCALL,
format!("{} is only available to the Shell profile", syscall.name()),
));
}
let x = expect_int(args, 0)? as i32;
let y = expect_int(args, 1)? as i32;
let w = expect_int(args, 2)? as i32;
let h = expect_int(args, 3)? as i32;
let border_color = self.get_color(expect_int(args, 4)?)?;
let fill_color = self.get_color(expect_int(args, 5)?)?;
hw.gfx_mut().draw_square(x, y, w, h, border_color, fill_color);
Ok(())
}
Syscall::GfxDrawText => {
let x = expect_int(args, 0)? as i32;
let y = expect_int(args, 1)? as i32;
@ -241,6 +327,20 @@ impl NativeInterface for VmRuntimeHost<'_> {
hw.gfx_mut().draw_text(x, y, &msg, color);
Ok(())
}
Syscall::GfxUiDrawText => {
if self.current_cartridge_app_mode != AppMode::Shell {
return Err(VmFault::Trap(
TRAP_INVALID_SYSCALL,
format!("{} is only available to the Shell profile", syscall.name()),
));
}
let x = expect_int(args, 0)? as i32;
let y = expect_int(args, 1)? as i32;
let msg = expect_string(args, 2, "message")?;
let color = self.get_color(expect_int(args, 3)?)?;
hw.gfx_mut().draw_text(x, y, &msg, color);
Ok(())
}
Syscall::ComposerBindScene => {
let scene_bank_id = match Self::int_arg_to_usize_status(expect_int(args, 0)?) {
Ok(id) => id,

View File

@ -195,7 +195,7 @@ fn initialize_vm_applies_cartridge_capabilities_before_loader_resolution() {
let program = serialized_single_function_module(
code,
vec![SyscallDecl {
module: "gfx".into(),
module: "gfx2d".into(),
name: "clear".into(),
version: 1,
arg_slots: 1,
@ -221,7 +221,7 @@ fn initialize_vm_succeeds_when_cartridge_capabilities_cover_hostcalls() {
let program = serialized_single_function_module(
code,
vec![SyscallDecl {
module: "gfx".into(),
module: "gfx2d".into(),
name: "clear".into(),
version: 1,
arg_slots: 1,
@ -253,7 +253,7 @@ fn tick_returns_error_when_vm_ends_slice_with_trap() {
let program = serialized_single_function_module(
code,
vec![SyscallDecl {
module: "gfx".into(),
module: "gfx2d".into(),
name: "clear".into(),
version: 1,
arg_slots: 1,
@ -304,7 +304,7 @@ fn tick_system_profile_rejects_gfx_game_surface() {
let program = serialized_single_function_module(
code,
vec![SyscallDecl {
module: "gfx".into(),
module: "gfx2d".into(),
name: "clear".into(),
version: 1,
arg_slots: 1,
@ -388,6 +388,55 @@ fn tick_system_profile_rejects_composer_game_surface() {
}
}
#[test]
fn tick_game_profile_rejects_gfxui_shell_surface() {
let mut runtime = VirtualMachineRuntime::new(None);
let mut log_service = LogService::new(4096);
let mut fs = VirtualFS::new();
let mut fs_state = FsState::Unmounted;
let mut memcard = MemcardService::new();
let mut open_files: HashMap<u32, String> = HashMap::new();
let mut next_handle = 1;
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: "gfxui".into(),
name: "clear".into(),
version: 1,
arg_slots: 1,
ret_slots: 0,
}],
);
let cartridge = cartridge_with_program_and_mode(program, caps::GFXUI, AppMode::Game);
runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize");
let report = runtime
.tick(
&mut log_service,
&mut fs,
&mut fs_state,
&mut memcard,
&mut open_files,
&mut next_handle,
&mut vm,
&signals,
&mut hardware,
)
.expect("game gfxui call must trap");
match report {
CrashReport::VmTrap { trap } => {
assert_eq!(trap.code, prometeu_bytecode::TRAP_INVALID_SYSCALL);
assert!(trap.message.contains("GfxUiClear is only available to the Shell profile"));
}
other => panic!("expected VmTrap crash report, got {:?}", other),
}
}
#[test]
fn tick_system_profile_rejects_bank_game_surface() {
let mut runtime = VirtualMachineRuntime::new(None);
@ -655,7 +704,7 @@ fn tick_draw_text_survives_no_scene_frame_path() {
code,
vec![ConstantPoolEntry::String("I".into())],
vec![SyscallDecl {
module: "gfx".into(),
module: "gfx2d".into(),
name: "draw_text".into(),
version: 1,
arg_slots: 4,
@ -696,7 +745,7 @@ fn initialize_vm_success_clears_previous_crash_report() {
let program = serialized_single_function_module(
code,
vec![SyscallDecl {
module: "gfx".into(),
module: "gfx2d".into(),
name: "clear".into(),
version: 1,
arg_slots: 1,
@ -856,7 +905,7 @@ fn initialize_vm_failure_clears_previous_identity_and_handles() {
let good_program = serialized_single_function_module(
assemble("PUSH_I32 0\nHOSTCALL 0\nHALT").expect("assemble"),
vec![SyscallDecl {
module: "gfx".into(),
module: "gfx2d".into(),
name: "clear".into(),
version: 1,
arg_slots: 1,
@ -878,7 +927,7 @@ fn initialize_vm_failure_clears_previous_identity_and_handles() {
let bad_program = serialized_single_function_module(
assemble("PUSH_I32 0\nHOSTCALL 0\nHALT").expect("assemble"),
vec![SyscallDecl {
module: "gfx".into(),
module: "gfx2d".into(),
name: "clear".into(),
version: 1,
arg_slots: 1,

View File

@ -2707,7 +2707,7 @@ mod tests {
let bytes = serialized_single_function_module(
code,
vec![SyscallDecl {
module: "gfx".into(),
module: "gfx2d".into(),
name: "clear".into(),
version: 1,
arg_slots: 1,
@ -2733,7 +2733,7 @@ mod tests {
let bytes = serialized_single_function_module(
code,
vec![SyscallDecl {
module: "gfx".into(),
module: "gfx2d".into(),
name: "clear".into(),
version: 1,
arg_slots: 1,
@ -2765,14 +2765,14 @@ mod tests {
code,
vec![
SyscallDecl {
module: "gfx".into(),
module: "gfx2d".into(),
name: "clear".into(),
version: 1,
arg_slots: 1,
ret_slots: 0,
},
SyscallDecl {
module: "gfx".into(),
module: "gfx2d".into(),
name: "draw_text".into(),
version: 1,
arg_slots: 4,
@ -2788,7 +2788,7 @@ mod tests {
Err(VmInitError::LoaderPatchFailed(
crate::vm_init_error::LoaderPatchError::UnusedSyscallDecl {
sysc_index: 1,
module: "gfx".into(),
module: "gfx2d".into(),
name: "draw_text".into(),
version: 1,
},
@ -2805,7 +2805,7 @@ mod tests {
let bytes = serialized_single_function_module(
code,
vec![SyscallDecl {
module: "gfx".into(),
module: "gfx2d".into(),
name: "clear".into(),
version: 1,
arg_slots: 1,
@ -2835,7 +2835,7 @@ mod tests {
let bytes = serialized_single_function_module(
code,
vec![SyscallDecl {
module: "gfx".into(),
module: "gfx2d".into(),
name: "missing".into(),
version: 1,
arg_slots: 0,
@ -2850,7 +2850,7 @@ mod tests {
Err(VmInitError::LoaderPatchFailed(
crate::vm_init_error::LoaderPatchError::ResolveFailed(
prometeu_hal::syscalls::DeclaredLoadError::UnknownSyscall {
module: "gfx".into(),
module: "gfx2d".into(),
name: "missing".into(),
version: 1,
},
@ -2868,7 +2868,7 @@ mod tests {
let bytes = serialized_single_function_module(
code,
vec![SyscallDecl {
module: "gfx".into(),
module: "gfx2d".into(),
name: "clear".into(),
version: 1,
arg_slots: 1,
@ -2883,9 +2883,9 @@ mod tests {
Err(VmInitError::LoaderPatchFailed(
crate::vm_init_error::LoaderPatchError::ResolveFailed(
prometeu_hal::syscalls::DeclaredLoadError::MissingCapability {
required: prometeu_hal::syscalls::caps::GFX,
required: prometeu_hal::syscalls::caps::GFX2D,
provided: prometeu_hal::syscalls::caps::NONE,
module: "gfx".into(),
module: "gfx2d".into(),
name: "clear".into(),
version: 1,
},
@ -2903,7 +2903,7 @@ mod tests {
let bytes = serialized_single_function_module(
code,
vec![SyscallDecl {
module: "gfx".into(),
module: "gfx2d".into(),
name: "draw_line".into(),
version: 1,
arg_slots: 4,
@ -2918,7 +2918,7 @@ mod tests {
Err(VmInitError::LoaderPatchFailed(
crate::vm_init_error::LoaderPatchError::ResolveFailed(
prometeu_hal::syscalls::DeclaredLoadError::AbiMismatch {
module: "gfx".into(),
module: "gfx2d".into(),
name: "draw_line".into(),
version: 1,
declared_arg_slots: 4,

View File

@ -42,14 +42,14 @@ pub fn generate() -> Result<()> {
let mut rom: Vec<u8> = Vec::new();
let syscalls = vec![
SyscallDecl {
module: "gfx".into(),
module: "gfx2d".into(),
name: "clear".into(),
version: 1,
arg_slots: 1,
ret_slots: 0,
},
SyscallDecl {
module: "gfx".into(),
module: "gfx2d".into(),
name: "draw_text".into(),
version: 1,
arg_slots: 4,

View File

@ -1,5 +1,5 @@
{"type":"meta","next_id":{"DSC":39,"AGD":39,"DEC":31,"PLN":87,"LSN":47,"CLSN":1}}
{"type":"discussion","id":"DSC-0038","status":"in_progress","ticket":"render-frame-packet-boundary","title":"Logical Render Pipelines and Command Packets","created_at":"2026-05-25","updated_at":"2026-05-25","tags":["gfx","renderer","runtime","frame-composer","architecture","ui","pipeline"],"agendas":[{"id":"AGD-0038","file":"AGD-0038-renderframepacket-boundary-for-classic-2d-renderer.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25"}],"decisions":[{"id":"DEC-0030","file":"DEC-0030-logical-render-pipeline-command-boundary.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25","ref_agenda":"AGD-0038"}],"plans":[{"id":"PLN-0073","file":"PLN-0073-render-contract-specs.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0074","file":"PLN-0074-hal-render-submission-types.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0075","file":"PLN-0075-rendermanager-core.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0076","file":"PLN-0076-capabilities-and-abi-domain-split.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0077","file":"PLN-0077-composer-buffer-and-game2d-packet.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0078","file":"PLN-0078-classic2d-game-renderer-consumer.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0079","file":"PLN-0079-gfx2d-primitive-domain.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0080","file":"PLN-0080-shell-ui-packet-and-gfxui-domain.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0081","file":"PLN-0081-shell-hub-migration.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0082","file":"PLN-0082-frame-publication-and-present-boundary.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0083","file":"PLN-0083-fade-removal.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0084","file":"PLN-0084-host-debug-overlay-removal.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0085","file":"PLN-0085-pbs-stdlib-and-syscall-declarations.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0086","file":"PLN-0086-end-to-end-render-boundary-validation.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]}],"lessons":[]}
{"type":"discussion","id":"DSC-0038","status":"in_progress","ticket":"render-frame-packet-boundary","title":"Logical Render Pipelines and Command Packets","created_at":"2026-05-25","updated_at":"2026-05-25","tags":["gfx","renderer","runtime","frame-composer","architecture","ui","pipeline"],"agendas":[{"id":"AGD-0038","file":"AGD-0038-renderframepacket-boundary-for-classic-2d-renderer.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25"}],"decisions":[{"id":"DEC-0030","file":"DEC-0030-logical-render-pipeline-command-boundary.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25","ref_agenda":"AGD-0038"}],"plans":[{"id":"PLN-0073","file":"PLN-0073-render-contract-specs.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0074","file":"PLN-0074-hal-render-submission-types.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0075","file":"PLN-0075-rendermanager-core.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0076","file":"PLN-0076-capabilities-and-abi-domain-split.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0077","file":"PLN-0077-composer-buffer-and-game2d-packet.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0078","file":"PLN-0078-classic2d-game-renderer-consumer.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0079","file":"PLN-0079-gfx2d-primitive-domain.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0080","file":"PLN-0080-shell-ui-packet-and-gfxui-domain.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0081","file":"PLN-0081-shell-hub-migration.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0082","file":"PLN-0082-frame-publication-and-present-boundary.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0083","file":"PLN-0083-fade-removal.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0084","file":"PLN-0084-host-debug-overlay-removal.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0085","file":"PLN-0085-pbs-stdlib-and-syscall-declarations.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0086","file":"PLN-0086-end-to-end-render-boundary-validation.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]}],"lessons":[]}
{"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"}]}
{"type":"discussion","id":"DSC-0034","status":"done","ticket":"system-os-domain-facades","title":"Agenda - SystemOS Domain Facades","created_at":"2026-05-15","updated_at":"2026-05-15","tags":["runtime","os","services","api-surface","lifecycle","fs"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0043","file":"discussion/lessons/DSC-0034-system-os-domain-facades/LSN-0043-systemos-domain-facades.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15"}]}
{"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"}]}

View File

@ -2,7 +2,7 @@
id: PLN-0076
ticket: render-frame-packet-boundary
title: Capabilities and ABI Domain Split
status: open
status: done
created: 2026-05-25
ref_decisions: [DEC-0030]
tags: [gfx, renderer, runtime, frame-composer, architecture, ui, pipeline]