From 998252aa25d2fb5a05d487b39460d8c60c9d50eb Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Mon, 9 Mar 2026 06:59:28 +0000 Subject: [PATCH] PR003: implement audio status-first returns for play operations --- crates/console/prometeu-drivers/src/audio.rs | 25 +++---- .../console/prometeu-hal/src/audio_bridge.rs | 15 +++- crates/console/prometeu-hal/src/lib.rs | 2 +- .../src/syscalls/domains/audio.rs | 4 +- .../src/virtual_machine_runtime/dispatch.rs | 75 ++++++++++++++----- docs/runtime/specs/05-audio-peripheral.md | 32 ++++++++ 6 files changed, 117 insertions(+), 36 deletions(-) diff --git a/crates/console/prometeu-drivers/src/audio.rs b/crates/console/prometeu-drivers/src/audio.rs index f92e5bc8..f02ad497 100644 --- a/crates/console/prometeu-drivers/src/audio.rs +++ b/crates/console/prometeu-drivers/src/audio.rs @@ -1,4 +1,4 @@ -use prometeu_hal::AudioBridge; +use prometeu_hal::{AudioBridge, AudioOpStatus}; use std::sync::Arc; /// Maximum number of simultaneous audio voices supported by the hardware. @@ -113,7 +113,7 @@ impl AudioBridge for Audio { pitch: f64, priority: u8, loop_mode: prometeu_hal::LoopMode, - ) { + ) -> AudioOpStatus { let lm = match loop_mode { prometeu_hal::LoopMode::Off => LoopMode::Off, prometeu_hal::LoopMode::On => LoopMode::On, @@ -129,7 +129,7 @@ impl AudioBridge for Audio { pitch: f64, priority: u8, loop_mode: prometeu_hal::LoopMode, - ) { + ) -> AudioOpStatus { let lm = match loop_mode { prometeu_hal::LoopMode::Off => LoopMode::Off, prometeu_hal::LoopMode::On => LoopMode::On, @@ -177,9 +177,9 @@ impl Audio { pitch: f64, priority: u8, loop_mode: LoopMode, - ) { + ) -> AudioOpStatus { if voice_id >= MAX_CHANNELS { - return; + return AudioOpStatus::VoiceInvalid; } // Resolve the sample from the hardware pools @@ -193,14 +193,10 @@ impl Audio { // "[Audio] Resolved sample from bank {} sample {}. Playing on voice {}.", // bank_id, sample_id, voice_id // ); - self.play_sample(s, voice_id, volume, pan, pitch, priority, loop_mode); + self.play_sample(s, voice_id, volume, pan, pitch, priority, loop_mode) + } else { + AudioOpStatus::SampleNotFound } - // else { - // eprintln!( - // "[Audio] Failed to resolve sample from bank {} sample {}.", - // bank_id, sample_id - // ); - // } } #[allow(clippy::too_many_arguments)] @@ -213,9 +209,9 @@ impl Audio { pitch: f64, priority: u8, loop_mode: LoopMode, - ) { + ) -> AudioOpStatus { if voice_id >= MAX_CHANNELS { - return; + return AudioOpStatus::VoiceInvalid; } // Update local state @@ -240,6 +236,7 @@ impl Audio { priority, loop_mode, }); + AudioOpStatus::Ok } pub fn stop(&mut self, voice_id: usize) { diff --git a/crates/console/prometeu-hal/src/audio_bridge.rs b/crates/console/prometeu-hal/src/audio_bridge.rs index e831640e..f09febb3 100644 --- a/crates/console/prometeu-hal/src/audio_bridge.rs +++ b/crates/console/prometeu-hal/src/audio_bridge.rs @@ -7,6 +7,17 @@ pub enum LoopMode { On, } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(i32)] +pub enum AudioOpStatus { + Ok = 0, + VoiceInvalid = 1, + SampleNotFound = 2, + ArgRangeInvalid = 3, + AssetNotFound = 4, + NoEffect = 5, +} + pub trait AudioBridge { #[allow(clippy::too_many_arguments)] fn play( @@ -19,7 +30,7 @@ pub trait AudioBridge { pitch: f64, priority: u8, loop_mode: LoopMode, - ); + ) -> AudioOpStatus; #[allow(clippy::too_many_arguments)] fn play_sample( &mut self, @@ -30,7 +41,7 @@ pub trait AudioBridge { pitch: f64, priority: u8, loop_mode: LoopMode, - ); + ) -> AudioOpStatus; fn stop(&mut self, voice_id: usize); fn set_volume(&mut self, voice_id: usize, volume: u8); fn set_pan(&mut self, voice_id: usize, pan: u8); diff --git a/crates/console/prometeu-hal/src/lib.rs b/crates/console/prometeu-hal/src/lib.rs index 30019385..ce27f6ba 100644 --- a/crates/console/prometeu-hal/src/lib.rs +++ b/crates/console/prometeu-hal/src/lib.rs @@ -28,7 +28,7 @@ pub mod vm_fault; pub mod window; pub use asset_bridge::AssetBridge; -pub use audio_bridge::{AudioBridge, LoopMode}; +pub use audio_bridge::{AudioBridge, AudioOpStatus, LoopMode}; pub use gfx_bridge::{BlendMode, GfxBridge}; pub use hardware_bridge::HardwareBridge; pub use host_context::{HostContext, HostContextProvider}; diff --git a/crates/console/prometeu-hal/src/syscalls/domains/audio.rs b/crates/console/prometeu-hal/src/syscalls/domains/audio.rs index ca1dc7ce..5fd1abad 100644 --- a/crates/console/prometeu-hal/src/syscalls/domains/audio.rs +++ b/crates/console/prometeu-hal/src/syscalls/domains/audio.rs @@ -8,7 +8,7 @@ pub(crate) const ENTRIES: &[SyscallRegistryEntry] = &[ "play_sample", 1, 5, - 0, + 1, caps::AUDIO, Determinism::Deterministic, false, @@ -20,7 +20,7 @@ pub(crate) const ENTRIES: &[SyscallRegistryEntry] = &[ "play", 1, 7, - 0, + 1, caps::AUDIO, Determinism::Deterministic, false, 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 160ae61d..a84d1459 100644 --- a/crates/console/prometeu-system/src/virtual_machine_runtime/dispatch.rs +++ b/crates/console/prometeu-system/src/virtual_machine_runtime/dispatch.rs @@ -7,7 +7,9 @@ use prometeu_hal::sprite::Sprite; use prometeu_hal::syscalls::Syscall; use prometeu_hal::tile::Tile; use prometeu_hal::vm_fault::VmFault; -use prometeu_hal::{HostContext, HostReturn, NativeInterface, SyscallId, expect_bool, expect_int}; +use prometeu_hal::{ + AudioOpStatus, HostContext, HostReturn, NativeInterface, SyscallId, expect_bool, expect_int, +}; impl VirtualMachineRuntime { const GFX_STATUS_OK: i64 = 0; @@ -201,22 +203,36 @@ impl NativeInterface for VirtualMachineRuntime { Ok(()) } Syscall::AudioPlaySample => { - let sample_id = expect_int(args, 0)? as u32; - let voice_id = expect_int(args, 1)? as usize; - let volume = expect_int(args, 2)? as u8; - let pan = expect_int(args, 3)? as u8; + let sample_id_raw = expect_int(args, 0)?; + let voice_id_raw = expect_int(args, 1)?; + let volume_raw = expect_int(args, 2)?; + let pan_raw = expect_int(args, 3)?; let pitch = expect_number(args, 4, "pitch")?; - hw.audio_mut().play( + if sample_id_raw < 0 + || sample_id_raw > u16::MAX as i64 + || voice_id_raw < 0 + || voice_id_raw >= 16 + || !(0..=255).contains(&volume_raw) + || !(0..=255).contains(&pan_raw) + || !pitch.is_finite() + || pitch <= 0.0 + { + ret.push_int(AudioOpStatus::ArgRangeInvalid as i64); + return Ok(()); + } + + let status = hw.audio_mut().play( 0, - sample_id as u16, - voice_id, - volume, - pan, + sample_id_raw as u16, + voice_id_raw as usize, + volume_raw as u8, + pan_raw as u8, pitch, 0, prometeu_hal::LoopMode::Off, ); + ret.push_int(status as i64); Ok(()) } Syscall::AudioPlay => { @@ -227,19 +243,44 @@ impl NativeInterface for VirtualMachineRuntime { Value::String(s) => s.clone(), _ => return Err(VmFault::Trap(TRAP_TYPE, "Expected string asset_name".into())), }; - let sample_id = expect_int(args, 1)? as u16; - let voice_id = expect_int(args, 2)? as usize; - let volume = expect_int(args, 3)? as u8; - let pan = expect_int(args, 4)? as u8; + let sample_id_raw = expect_int(args, 1)?; + let voice_id_raw = expect_int(args, 2)?; + let volume_raw = expect_int(args, 3)?; + let pan_raw = expect_int(args, 4)?; let pitch = expect_number(args, 5, "pitch")?; let loop_mode = match expect_int(args, 6)? { 0 => prometeu_hal::LoopMode::Off, _ => prometeu_hal::LoopMode::On, }; - let bank_id = - hw.assets().find_slot_by_name(&asset_name, BankType::SOUNDS).unwrap_or(0); - hw.audio_mut().play(bank_id, sample_id, voice_id, volume, pan, pitch, 0, loop_mode); + if sample_id_raw < 0 + || sample_id_raw > u16::MAX as i64 + || voice_id_raw < 0 + || voice_id_raw >= 16 + || !(0..=255).contains(&volume_raw) + || !(0..=255).contains(&pan_raw) + || !pitch.is_finite() + || pitch <= 0.0 + { + ret.push_int(AudioOpStatus::ArgRangeInvalid as i64); + return Ok(()); + } + + let Some(bank_id) = hw.assets().find_slot_by_name(&asset_name, BankType::SOUNDS) else { + ret.push_int(AudioOpStatus::AssetNotFound as i64); + return Ok(()); + }; + let status = hw.audio_mut().play( + bank_id, + sample_id_raw as u16, + voice_id_raw as usize, + volume_raw as u8, + pan_raw as u8, + pitch, + 0, + loop_mode, + ); + ret.push_int(status as i64); Ok(()) } Syscall::FsOpen => { diff --git a/docs/runtime/specs/05-audio-peripheral.md b/docs/runtime/specs/05-audio-peripheral.md index 6a987df4..e475acef 100644 --- a/docs/runtime/specs/05-audio-peripheral.md +++ b/docs/runtime/specs/05-audio-peripheral.md @@ -174,3 +174,35 @@ voices_active: 9 mix_cycles: 410 audio_commands: 6 ``` + +## 11 Syscall Return and Fault Policy + +`audio` follows status-first policy for operations with operational failure modes. + +Fault boundary: + +- `Trap`: structural ABI misuse (type/arity/capability/shape mismatch); +- `status`: operational failure; +- `Panic`: internal invariant break only. + +### 11.1 MVP return shape + +In the current MVP: + +- `audio.play` returns `status:int`; +- `audio.play_sample` returns `status:int`. + +### 11.2 Minimum status table for `play`/`play_sample` + +- `0` = `OK` +- `1` = `VOICE_INVALID` +- `2` = `SAMPLE_NOT_FOUND` +- `3` = `ARG_RANGE_INVALID` +- `4` = `ASSET_NOT_FOUND` +- `5` = `NO_EFFECT` + +Operational rules: + +- no fallback to default bank when an asset cannot be resolved; +- no silent no-op for invalid `voice_id`; +- invalid numeric ranges (e.g. `volume`, `pan`, `pitch`) must return explicit status.