From 7c35edcb847d33afa13e70fd0ad000ea1b4e4cde Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Mon, 25 May 2026 14:02:37 +0100 Subject: [PATCH] implements PLN-0076 render capability split --- crates/console/prometeu-hal/src/cartridge.rs | 3 + .../prometeu-hal/src/cartridge_loader.rs | 3 + crates/console/prometeu-hal/src/syscalls.rs | 9 +- .../console/prometeu-hal/src/syscalls/caps.rs | 7 +- .../src/syscalls/domains/composer.rs | 8 +- .../prometeu-hal/src/syscalls/domains/gfx.rs | 28 ++--- .../src/syscalls/domains/gfxui.rs | 32 ++++++ .../prometeu-hal/src/syscalls/domains/mod.rs | 2 + .../prometeu-hal/src/syscalls/registry.rs | 14 +++ .../prometeu-hal/src/syscalls/tests.rs | 61 +++++++---- .../src/services/vm_runtime/dispatch.rs | 100 ++++++++++++++++++ .../src/services/vm_runtime/tests.rs | 65 ++++++++++-- .../prometeu-vm/src/virtual_machine.rs | 26 ++--- crates/tools/pbxgen-stress/src/lib.rs | 4 +- discussion/index.ndjson | 2 +- ...-0076-capabilities-and-abi-domain-split.md | 2 +- 16 files changed, 297 insertions(+), 69 deletions(-) create mode 100644 crates/console/prometeu-hal/src/syscalls/domains/gfxui.rs diff --git a/crates/console/prometeu-hal/src/cartridge.rs b/crates/console/prometeu-hal/src/cartridge.rs index 42c7f735..972c755f 100644 --- a/crates/console/prometeu-hal/src/cartridge.rs +++ b/crates/console/prometeu-hal/src/cartridge.rs @@ -286,6 +286,9 @@ pub enum Capability { None, System, Gfx, + Composer, + Gfx2d, + Gfxui, Audio, Fs, Log, diff --git a/crates/console/prometeu-hal/src/cartridge_loader.rs b/crates/console/prometeu-hal/src/cartridge_loader.rs index 2b287f96..2fe4f836 100644 --- a/crates/console/prometeu-hal/src/cartridge_loader.rs +++ b/crates/console/prometeu-hal/src/cartridge_loader.rs @@ -117,6 +117,9 @@ fn normalize_capabilities(capabilities: &[Capability]) -> Result 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, diff --git a/crates/console/prometeu-hal/src/syscalls.rs b/crates/console/prometeu-hal/src/syscalls.rs index 4098ed40..73ae59cf 100644 --- a/crates/console/prometeu-hal/src/syscalls.rs +++ b/crates/console/prometeu-hal/src/syscalls.rs @@ -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, diff --git a/crates/console/prometeu-hal/src/syscalls/caps.rs b/crates/console/prometeu-hal/src/syscalls/caps.rs index 3230b1ce..8e2e0611 100644 --- a/crates/console/prometeu-hal/src/syscalls/caps.rs +++ b/crates/console/prometeu-hal/src/syscalls/caps.rs @@ -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; diff --git a/crates/console/prometeu-hal/src/syscalls/domains/composer.rs b/crates/console/prometeu-hal/src/syscalls/domains/composer.rs index 32a53a95..cdeb4d2d 100644 --- a/crates/console/prometeu-hal/src/syscalls/domains/composer.rs +++ b/crates/console/prometeu-hal/src/syscalls/domains/composer.rs @@ -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), ]; diff --git a/crates/console/prometeu-hal/src/syscalls/domains/gfx.rs b/crates/console/prometeu-hal/src/syscalls/domains/gfx.rs index d45bc619..9f531993 100644 --- a/crates/console/prometeu-hal/src/syscalls/domains/gfx.rs +++ b/crates/console/prometeu-hal/src/syscalls/domains/gfx.rs @@ -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), ]; diff --git a/crates/console/prometeu-hal/src/syscalls/domains/gfxui.rs b/crates/console/prometeu-hal/src/syscalls/domains/gfxui.rs new file mode 100644 index 00000000..59e96c45 --- /dev/null +++ b/crates/console/prometeu-hal/src/syscalls/domains/gfxui.rs @@ -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), +]; diff --git a/crates/console/prometeu-hal/src/syscalls/domains/mod.rs b/crates/console/prometeu-hal/src/syscalls/domains/mod.rs index 00e896f1..9a2b6428 100644 --- a/crates/console/prometeu-hal/src/syscalls/domains/mod.rs +++ b/crates/console/prometeu-hal/src/syscalls/domains/mod.rs @@ -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 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", diff --git a/crates/console/prometeu-hal/src/syscalls/tests.rs b/crates/console/prometeu-hal/src/syscalls/tests.rs index 97677f16..64e7434a 100644 --- a/crates/console/prometeu-hal/src/syscalls/tests.rs +++ b/crates/console/prometeu-hal/src/syscalls/tests.rs @@ -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()); diff --git a/crates/console/prometeu-system/src/services/vm_runtime/dispatch.rs b/crates/console/prometeu-system/src/services/vm_runtime/dispatch.rs index 7501ced7..ef13ad2c 100644 --- a/crates/console/prometeu-system/src/services/vm_runtime/dispatch.rs +++ b/crates/console/prometeu-system/src/services/vm_runtime/dispatch.rs @@ -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, diff --git a/crates/console/prometeu-system/src/services/vm_runtime/tests.rs b/crates/console/prometeu-system/src/services/vm_runtime/tests.rs index 61ba95ed..7b0ec1fd 100644 --- a/crates/console/prometeu-system/src/services/vm_runtime/tests.rs +++ b/crates/console/prometeu-system/src/services/vm_runtime/tests.rs @@ -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 = 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, diff --git a/crates/console/prometeu-vm/src/virtual_machine.rs b/crates/console/prometeu-vm/src/virtual_machine.rs index 09282fe9..7cbd7a0a 100644 --- a/crates/console/prometeu-vm/src/virtual_machine.rs +++ b/crates/console/prometeu-vm/src/virtual_machine.rs @@ -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, diff --git a/crates/tools/pbxgen-stress/src/lib.rs b/crates/tools/pbxgen-stress/src/lib.rs index ca304c0c..97e1773a 100644 --- a/crates/tools/pbxgen-stress/src/lib.rs +++ b/crates/tools/pbxgen-stress/src/lib.rs @@ -42,14 +42,14 @@ pub fn generate() -> Result<()> { let mut rom: Vec = 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, diff --git a/discussion/index.ndjson b/discussion/index.ndjson index bae22d2d..660dc750 100644 --- a/discussion/index.ndjson +++ b/discussion/index.ndjson @@ -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"}]} diff --git a/discussion/workflow/plans/PLN-0076-capabilities-and-abi-domain-split.md b/discussion/workflow/plans/PLN-0076-capabilities-and-abi-domain-split.md index 6105c803..b62ce076 100644 --- a/discussion/workflow/plans/PLN-0076-capabilities-and-abi-domain-split.md +++ b/discussion/workflow/plans/PLN-0076-capabilities-and-abi-domain-split.md @@ -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]