From fd5a5cd22c846a542491516b52aa54744b2d152c Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Tue, 10 Mar 2026 09:32:46 +0000 Subject: [PATCH] GFX Status-First Surface and Fault Matrix --- crates/console/prometeu-hal/src/gfx_bridge.rs | 9 +++ crates/console/prometeu-hal/src/lib.rs | 2 +- .../prometeu-hal/src/syscalls/tests.rs | 32 +++++++++ .../src/virtual_machine_runtime/dispatch.rs | 36 +++------- .../src/virtual_machine_runtime/tests.rs | 66 ++++++++++++++++++- ...x-status-first-surface-and-fault-matrix.md | 47 ------------- docs/runtime/specs/04-gfx-peripheral.md | 19 ++++++ 7 files changed, 136 insertions(+), 75 deletions(-) delete mode 100644 docs/runtime/pull-requests/PR002-gfx-status-first-surface-and-fault-matrix.md diff --git a/crates/console/prometeu-hal/src/gfx_bridge.rs b/crates/console/prometeu-hal/src/gfx_bridge.rs index 33295999..b1fc5ca8 100644 --- a/crates/console/prometeu-hal/src/gfx_bridge.rs +++ b/crates/console/prometeu-hal/src/gfx_bridge.rs @@ -11,6 +11,15 @@ pub enum BlendMode { Full, } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(i32)] +pub enum GfxOpStatus { + Ok = 0, + AssetNotFound = 1, + InvalidSpriteIndex = 2, + ArgRangeInvalid = 3, +} + pub trait GfxBridge { fn size(&self) -> (usize, usize); fn front_buffer(&self) -> &[u16]; diff --git a/crates/console/prometeu-hal/src/lib.rs b/crates/console/prometeu-hal/src/lib.rs index ce27f6ba..b9c9681d 100644 --- a/crates/console/prometeu-hal/src/lib.rs +++ b/crates/console/prometeu-hal/src/lib.rs @@ -29,7 +29,7 @@ pub mod window; pub use asset_bridge::AssetBridge; pub use audio_bridge::{AudioBridge, AudioOpStatus, LoopMode}; -pub use gfx_bridge::{BlendMode, GfxBridge}; +pub use gfx_bridge::{BlendMode, GfxBridge, GfxOpStatus}; pub use hardware_bridge::HardwareBridge; pub use host_context::{HostContext, HostContextProvider}; pub use host_return::HostReturn; diff --git a/crates/console/prometeu-hal/src/syscalls/tests.rs b/crates/console/prometeu-hal/src/syscalls/tests.rs index 775ee9f8..4d44c4d9 100644 --- a/crates/console/prometeu-hal/src/syscalls/tests.rs +++ b/crates/console/prometeu-hal/src/syscalls/tests.rs @@ -170,10 +170,42 @@ fn declared_resolver_rejects_abi_mismatch() { #[test] fn status_first_syscall_signatures_are_pinned() { + let clear = meta_for(Syscall::GfxClear); + assert_eq!(clear.arg_slots, 1); + assert_eq!(clear.ret_slots, 0); + + let fill_rect = meta_for(Syscall::GfxFillRect); + assert_eq!(fill_rect.arg_slots, 5); + assert_eq!(fill_rect.ret_slots, 0); + + let draw_line = meta_for(Syscall::GfxDrawLine); + assert_eq!(draw_line.arg_slots, 5); + assert_eq!(draw_line.ret_slots, 0); + + let draw_circle = meta_for(Syscall::GfxDrawCircle); + assert_eq!(draw_circle.arg_slots, 4); + assert_eq!(draw_circle.ret_slots, 0); + + let draw_disc = meta_for(Syscall::GfxDrawDisc); + assert_eq!(draw_disc.arg_slots, 5); + assert_eq!(draw_disc.ret_slots, 0); + + let draw_square = meta_for(Syscall::GfxDrawSquare); + assert_eq!(draw_square.arg_slots, 6); + assert_eq!(draw_square.ret_slots, 0); + let set_sprite = meta_for(Syscall::GfxSetSprite); assert_eq!(set_sprite.arg_slots, 10); assert_eq!(set_sprite.ret_slots, 1); + let draw_text = meta_for(Syscall::GfxDrawText); + assert_eq!(draw_text.arg_slots, 4); + assert_eq!(draw_text.ret_slots, 0); + + let clear_565 = meta_for(Syscall::GfxClear565); + assert_eq!(clear_565.arg_slots, 1); + assert_eq!(clear_565.ret_slots, 0); + let audio_play_sample = meta_for(Syscall::AudioPlaySample); assert_eq!(audio_play_sample.arg_slots, 5); assert_eq!(audio_play_sample.ret_slots, 1); diff --git a/crates/console/prometeu-system/src/virtual_machine_runtime/dispatch.rs b/crates/console/prometeu-system/src/virtual_machine_runtime/dispatch.rs index 7d1c88e0..edefbca8 100644 --- a/crates/console/prometeu-system/src/virtual_machine_runtime/dispatch.rs +++ b/crates/console/prometeu-system/src/virtual_machine_runtime/dispatch.rs @@ -10,15 +10,11 @@ use prometeu_hal::syscalls::Syscall; use prometeu_hal::tile::Tile; use prometeu_hal::vm_fault::VmFault; use prometeu_hal::{ - AudioOpStatus, HostContext, HostReturn, NativeInterface, SyscallId, expect_bool, expect_int, + AudioOpStatus, GfxOpStatus, HostContext, HostReturn, NativeInterface, SyscallId, expect_bool, + expect_int, }; impl VirtualMachineRuntime { - const GFX_STATUS_OK: i64 = 0; - const GFX_STATUS_ASSET_NOT_FOUND: i64 = 1; - const GFX_STATUS_INVALID_SPRITE_INDEX: i64 = 2; - const GFX_STATUS_INVALID_ARG_RANGE: i64 = 3; - fn syscall_log_write(&mut self, level_val: i64, tag: u16, msg: String) -> Result<(), VmFault> { let level = match level_val { 0 => LogLevel::Trace, @@ -139,13 +135,7 @@ impl NativeInterface for VirtualMachineRuntime { Ok(()) } Syscall::GfxSetSprite => { - let asset_name = match args - .first() - .ok_or_else(|| VmFault::Panic("Missing asset_name".into()))? - { - Value::String(s) => s.clone(), - _ => return Err(VmFault::Trap(TRAP_TYPE, "Expected string asset_name".into())), - }; + let asset_name = expect_string(args, 0, "asset_name")?; let index = expect_int(args, 1)? as usize; let x = expect_int(args, 2)? as i32; let y = expect_int(args, 3)? as i32; @@ -157,15 +147,15 @@ impl NativeInterface for VirtualMachineRuntime { let priority = expect_int(args, 9)? as u8; if index >= 512 { - ret.push_int(Self::GFX_STATUS_INVALID_SPRITE_INDEX); + ret.push_int(GfxOpStatus::InvalidSpriteIndex as i64); return Ok(()); } if palette_id >= 64 || priority >= 5 { - ret.push_int(Self::GFX_STATUS_INVALID_ARG_RANGE); + ret.push_int(GfxOpStatus::ArgRangeInvalid as i64); return Ok(()); } let Some(bank_id) = hw.assets().find_slot_by_name(&asset_name, BankType::TILES) else { - ret.push_int(Self::GFX_STATUS_ASSET_NOT_FOUND); + ret.push_int(GfxOpStatus::AssetNotFound as i64); return Ok(()); }; @@ -179,19 +169,13 @@ impl NativeInterface for VirtualMachineRuntime { flip_y, priority, }; - ret.push_int(Self::GFX_STATUS_OK); + ret.push_int(GfxOpStatus::Ok as i64); Ok(()) } Syscall::GfxDrawText => { let x = expect_int(args, 0)? as i32; let y = expect_int(args, 1)? as i32; - let msg = match args - .get(2) - .ok_or_else(|| VmFault::Panic("Missing message".into()))? - { - Value::String(s) => s.clone(), - _ => return Err(VmFault::Trap(TRAP_TYPE, "Expected string message".into())), - }; + 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(()) @@ -536,14 +520,14 @@ impl NativeInterface for VirtualMachineRuntime { } fn expect_string(args: &[Value], index: usize, field: &str) -> Result { - match args.get(index).ok_or_else(|| VmFault::Panic(format!("Missing {}", field)))? { + match args.get(index).ok_or_else(|| VmFault::Trap(TRAP_TYPE, format!("Missing {}", field)))? { Value::String(value) => Ok(value.clone()), _ => Err(VmFault::Trap(TRAP_TYPE, format!("Expected string {}", field))), } } fn expect_number(args: &[Value], index: usize, field: &str) -> Result { - match args.get(index).ok_or_else(|| VmFault::Panic(format!("Missing {}", field)))? { + match args.get(index).ok_or_else(|| VmFault::Trap(TRAP_TYPE, format!("Missing {}", field)))? { Value::Float(f) => Ok(*f), Value::Int32(i) => Ok(*i as f64), Value::Int64(i) => Ok(*i as f64), diff --git a/crates/console/prometeu-system/src/virtual_machine_runtime/tests.rs b/crates/console/prometeu-system/src/virtual_machine_runtime/tests.rs index 93f523d4..3d7587da 100644 --- a/crates/console/prometeu-system/src/virtual_machine_runtime/tests.rs +++ b/crates/console/prometeu-system/src/virtual_machine_runtime/tests.rs @@ -7,6 +7,7 @@ use prometeu_drivers::hardware::Hardware; use crate::fs::{FsBackend, FsEntry, FsError}; use prometeu_hal::AudioOpStatus; use prometeu_hal::asset::AssetOpStatus; +use prometeu_hal::GfxOpStatus; use prometeu_hal::InputSignals; use prometeu_hal::cartridge::Cartridge; use prometeu_hal::syscalls::caps; @@ -353,7 +354,70 @@ fn tick_gfx_set_sprite_operational_error_returns_status_not_crash() { let report = runtime.tick(&mut vm, &signals, &mut hardware); assert!(report.is_none(), "operational error must not crash"); assert!(vm.is_halted()); - assert_eq!(vm.operand_stack_top(1), vec![Value::Int64(1)]); + assert_eq!(vm.operand_stack_top(1), vec![Value::Int64(GfxOpStatus::AssetNotFound as i64)]); +} + +#[test] +fn tick_gfx_set_sprite_invalid_index_returns_status_not_crash() { + let mut runtime = VirtualMachineRuntime::new(None); + let mut vm = VirtualMachine::default(); + let mut hardware = Hardware::new(); + let signals = InputSignals::default(); + let code = assemble( + "PUSH_CONST 0\nPUSH_I32 512\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_BOOL 1\nPUSH_BOOL 0\nPUSH_BOOL 0\nPUSH_I32 0\nHOSTCALL 0\nHALT", + ) + .expect("assemble"); + let program = serialized_single_function_module_with_consts( + code, + vec![ConstantPoolEntry::String("missing_tile_bank".into())], + vec![SyscallDecl { + module: "gfx".into(), + name: "set_sprite".into(), + version: 1, + arg_slots: 10, + ret_slots: 1, + }], + ); + let cartridge = cartridge_with_program(program, caps::GFX); + + runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize"); + let report = runtime.tick(&mut vm, &signals, &mut hardware); + assert!(report.is_none(), "invalid sprite index must not crash"); + assert!(vm.is_halted()); + assert_eq!( + vm.operand_stack_top(1), + vec![Value::Int64(GfxOpStatus::InvalidSpriteIndex as i64)] + ); +} + +#[test] +fn tick_gfx_set_sprite_invalid_range_returns_status_not_crash() { + let mut runtime = VirtualMachineRuntime::new(None); + let mut vm = VirtualMachine::default(); + let mut hardware = Hardware::new(); + let signals = InputSignals::default(); + let code = assemble( + "PUSH_CONST 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 64\nPUSH_BOOL 1\nPUSH_BOOL 0\nPUSH_BOOL 0\nPUSH_I32 0\nHOSTCALL 0\nHALT", + ) + .expect("assemble"); + let program = serialized_single_function_module_with_consts( + code, + vec![ConstantPoolEntry::String("missing_tile_bank".into())], + vec![SyscallDecl { + module: "gfx".into(), + name: "set_sprite".into(), + version: 1, + arg_slots: 10, + ret_slots: 1, + }], + ); + let cartridge = cartridge_with_program(program, caps::GFX); + + runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize"); + let report = runtime.tick(&mut vm, &signals, &mut hardware); + assert!(report.is_none(), "invalid gfx parameter range must not crash"); + assert!(vm.is_halted()); + assert_eq!(vm.operand_stack_top(1), vec![Value::Int64(GfxOpStatus::ArgRangeInvalid as i64)]); } #[test] diff --git a/docs/runtime/pull-requests/PR002-gfx-status-first-surface-and-fault-matrix.md b/docs/runtime/pull-requests/PR002-gfx-status-first-surface-and-fault-matrix.md deleted file mode 100644 index 99281496..00000000 --- a/docs/runtime/pull-requests/PR002-gfx-status-first-surface-and-fault-matrix.md +++ /dev/null @@ -1,47 +0,0 @@ -# PR002 - GFX Status-First Surface and Fault Matrix - -## Briefing - -A decision `008` fechou politica status-first em `gfx`, incluindo: - -- sem no-op silencioso para erro operacional; -- retorno `status` quando houver possibilidade real de falha; -- `Trap` apenas estrutural e `Panic` apenas invariante. - -## Alvo - -Implementar o contrato de `gfx` no runtime e atualizar spec do dominio. - -Arquivos principais: - -- `docs/runtime/specs/04-gfx-peripheral.md` -- `crates/console/prometeu-hal/src/syscalls/domains/gfx.rs` -- `crates/console/prometeu-system/src/virtual_machine_runtime/dispatch.rs` - -## Escopo Funcional - -- fechar matriz por syscall (`void` vs `status`); -- remover fallback implicito em `gfx.set_sprite`; -- mapear casos operacionais para status inteiros por operacao; -- remover `Panic` acidental por argumento de app no dominio `gfx`. - -## Fora de Escopo - -- redesign de pipeline grafico; -- mudancas de feature fora do conjunto atual de syscalls `gfx`. - -## Critérios de Aceite - -- metadata de syscall `gfx` (arg/ret slots) alinhada com matriz definida; -- `set_sprite` nao usa fallback implicito para banco default; -- casos operacionais invalidos retornam `status` e nao no-op silencioso; -- testes cobrindo pelo menos: - - sprite index invalido; - - asset ausente; - - faixas invalidas de parametro. - -## Tests - -- `cargo test -p prometeu-system` -- `cargo test -p prometeu-hal` -- testes de regressao especificos de `dispatch` e dominio `gfx`. diff --git a/docs/runtime/specs/04-gfx-peripheral.md b/docs/runtime/specs/04-gfx-peripheral.md index 072332e7..a95832f9 100644 --- a/docs/runtime/specs/04-gfx-peripheral.md +++ b/docs/runtime/specs/04-gfx-peripheral.md @@ -526,6 +526,25 @@ Fault boundary: ### 19.1 `gfx.set_sprite` +Return-shape matrix in v1: + +| Syscall | Return | Policy basis | +| ------------------ | ------------- | ---------------------------------------------------- | +| `gfx.clear` | `void` | no real operational failure path in v1 | +| `gfx.fill_rect` | `void` | no real operational failure path in v1 | +| `gfx.draw_line` | `void` | no real operational failure path in v1 | +| `gfx.draw_circle` | `void` | no real operational failure path in v1 | +| `gfx.draw_disc` | `void` | no real operational failure path in v1 | +| `gfx.draw_square` | `void` | no real operational failure path in v1 | +| `gfx.set_sprite` | `status:int` | operational rejection must be explicit | +| `gfx.draw_text` | `void` | no real operational failure path in v1 | +| `gfx.clear_565` | `void` | no real operational failure path in v1 | + +Only `gfx.set_sprite` is status-returning in v1. +All other `gfx` syscalls remain `void` unless a future domain revision introduces a real operational failure path. + +### 19.2 `gfx.set_sprite` + `gfx.set_sprite` returns `status:int`. Minimum status table: