GFX Status-First Surface and Fault Matrix
This commit is contained in:
parent
5df15d0c6d
commit
fd5a5cd22c
@ -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];
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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<String, VmFault> {
|
||||
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<f64, VmFault> {
|
||||
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),
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -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`.
|
||||
@ -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:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user