GFX Status-First Surface and Fault Matrix
This commit is contained in:
parent
5df15d0c6d
commit
fd5a5cd22c
@ -11,6 +11,15 @@ pub enum BlendMode {
|
|||||||
Full,
|
Full,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
#[repr(i32)]
|
||||||
|
pub enum GfxOpStatus {
|
||||||
|
Ok = 0,
|
||||||
|
AssetNotFound = 1,
|
||||||
|
InvalidSpriteIndex = 2,
|
||||||
|
ArgRangeInvalid = 3,
|
||||||
|
}
|
||||||
|
|
||||||
pub trait GfxBridge {
|
pub trait GfxBridge {
|
||||||
fn size(&self) -> (usize, usize);
|
fn size(&self) -> (usize, usize);
|
||||||
fn front_buffer(&self) -> &[u16];
|
fn front_buffer(&self) -> &[u16];
|
||||||
|
|||||||
@ -29,7 +29,7 @@ pub mod window;
|
|||||||
|
|
||||||
pub use asset_bridge::AssetBridge;
|
pub use asset_bridge::AssetBridge;
|
||||||
pub use audio_bridge::{AudioBridge, AudioOpStatus, LoopMode};
|
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 hardware_bridge::HardwareBridge;
|
||||||
pub use host_context::{HostContext, HostContextProvider};
|
pub use host_context::{HostContext, HostContextProvider};
|
||||||
pub use host_return::HostReturn;
|
pub use host_return::HostReturn;
|
||||||
|
|||||||
@ -170,10 +170,42 @@ fn declared_resolver_rejects_abi_mismatch() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn status_first_syscall_signatures_are_pinned() {
|
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);
|
let set_sprite = meta_for(Syscall::GfxSetSprite);
|
||||||
assert_eq!(set_sprite.arg_slots, 10);
|
assert_eq!(set_sprite.arg_slots, 10);
|
||||||
assert_eq!(set_sprite.ret_slots, 1);
|
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);
|
let audio_play_sample = meta_for(Syscall::AudioPlaySample);
|
||||||
assert_eq!(audio_play_sample.arg_slots, 5);
|
assert_eq!(audio_play_sample.arg_slots, 5);
|
||||||
assert_eq!(audio_play_sample.ret_slots, 1);
|
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::tile::Tile;
|
||||||
use prometeu_hal::vm_fault::VmFault;
|
use prometeu_hal::vm_fault::VmFault;
|
||||||
use prometeu_hal::{
|
use prometeu_hal::{
|
||||||
AudioOpStatus, HostContext, HostReturn, NativeInterface, SyscallId, expect_bool, expect_int,
|
AudioOpStatus, GfxOpStatus, HostContext, HostReturn, NativeInterface, SyscallId, expect_bool,
|
||||||
|
expect_int,
|
||||||
};
|
};
|
||||||
|
|
||||||
impl VirtualMachineRuntime {
|
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> {
|
fn syscall_log_write(&mut self, level_val: i64, tag: u16, msg: String) -> Result<(), VmFault> {
|
||||||
let level = match level_val {
|
let level = match level_val {
|
||||||
0 => LogLevel::Trace,
|
0 => LogLevel::Trace,
|
||||||
@ -139,13 +135,7 @@ impl NativeInterface for VirtualMachineRuntime {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Syscall::GfxSetSprite => {
|
Syscall::GfxSetSprite => {
|
||||||
let asset_name = match args
|
let asset_name = expect_string(args, 0, "asset_name")?;
|
||||||
.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 index = expect_int(args, 1)? as usize;
|
let index = expect_int(args, 1)? as usize;
|
||||||
let x = expect_int(args, 2)? as i32;
|
let x = expect_int(args, 2)? as i32;
|
||||||
let y = expect_int(args, 3)? 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;
|
let priority = expect_int(args, 9)? as u8;
|
||||||
|
|
||||||
if index >= 512 {
|
if index >= 512 {
|
||||||
ret.push_int(Self::GFX_STATUS_INVALID_SPRITE_INDEX);
|
ret.push_int(GfxOpStatus::InvalidSpriteIndex as i64);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
if palette_id >= 64 || priority >= 5 {
|
if palette_id >= 64 || priority >= 5 {
|
||||||
ret.push_int(Self::GFX_STATUS_INVALID_ARG_RANGE);
|
ret.push_int(GfxOpStatus::ArgRangeInvalid as i64);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
let Some(bank_id) = hw.assets().find_slot_by_name(&asset_name, BankType::TILES) else {
|
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(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -179,19 +169,13 @@ impl NativeInterface for VirtualMachineRuntime {
|
|||||||
flip_y,
|
flip_y,
|
||||||
priority,
|
priority,
|
||||||
};
|
};
|
||||||
ret.push_int(Self::GFX_STATUS_OK);
|
ret.push_int(GfxOpStatus::Ok as i64);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Syscall::GfxDrawText => {
|
Syscall::GfxDrawText => {
|
||||||
let x = expect_int(args, 0)? as i32;
|
let x = expect_int(args, 0)? as i32;
|
||||||
let y = expect_int(args, 1)? as i32;
|
let y = expect_int(args, 1)? as i32;
|
||||||
let msg = match args
|
let msg = expect_string(args, 2, "message")?;
|
||||||
.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 color = self.get_color(expect_int(args, 3)?);
|
let color = self.get_color(expect_int(args, 3)?);
|
||||||
hw.gfx_mut().draw_text(x, y, &msg, color);
|
hw.gfx_mut().draw_text(x, y, &msg, color);
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -536,14 +520,14 @@ impl NativeInterface for VirtualMachineRuntime {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn expect_string(args: &[Value], index: usize, field: &str) -> Result<String, VmFault> {
|
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()),
|
Value::String(value) => Ok(value.clone()),
|
||||||
_ => Err(VmFault::Trap(TRAP_TYPE, format!("Expected string {}", field))),
|
_ => Err(VmFault::Trap(TRAP_TYPE, format!("Expected string {}", field))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn expect_number(args: &[Value], index: usize, field: &str) -> Result<f64, VmFault> {
|
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::Float(f) => Ok(*f),
|
||||||
Value::Int32(i) => Ok(*i as f64),
|
Value::Int32(i) => Ok(*i as f64),
|
||||||
Value::Int64(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 crate::fs::{FsBackend, FsEntry, FsError};
|
||||||
use prometeu_hal::AudioOpStatus;
|
use prometeu_hal::AudioOpStatus;
|
||||||
use prometeu_hal::asset::AssetOpStatus;
|
use prometeu_hal::asset::AssetOpStatus;
|
||||||
|
use prometeu_hal::GfxOpStatus;
|
||||||
use prometeu_hal::InputSignals;
|
use prometeu_hal::InputSignals;
|
||||||
use prometeu_hal::cartridge::Cartridge;
|
use prometeu_hal::cartridge::Cartridge;
|
||||||
use prometeu_hal::syscalls::caps;
|
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);
|
let report = runtime.tick(&mut vm, &signals, &mut hardware);
|
||||||
assert!(report.is_none(), "operational error must not crash");
|
assert!(report.is_none(), "operational error must not crash");
|
||||||
assert!(vm.is_halted());
|
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]
|
#[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`
|
### 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`.
|
`gfx.set_sprite` returns `status:int`.
|
||||||
|
|
||||||
Minimum status table:
|
Minimum status table:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user