Compare commits

..

10 Commits

Author SHA1 Message Date
9cf9fa6163
implements PLN-0109
All checks were successful
Intrepid/Prometeu/Runtime/pipeline/head This commit looks good
Intrepid/Prometeu/Runtime/pipeline/pr-master This commit looks good
2026-06-15 10:47:34 +01:00
117a18ee72
implements PLN-0108 2026-06-15 06:04:29 +01:00
f45b3a38cb
implements PLN-0107 2026-06-15 06:01:49 +01:00
5381ae02b1
implements PLN-0106 2026-06-15 06:00:39 +01:00
1d6b799960
implements PLN-0105 2026-06-15 05:59:06 +01:00
d0a2682366
implements PLN-0104 2026-06-15 05:56:15 +01:00
c62aef51ba
implements PLN-0103 2026-06-15 05:52:48 +01:00
d2bace2612
implements PLN-0102 2026-06-15 05:51:07 +01:00
6f3df74e79
implements PLN-0101 2026-06-15 05:48:24 +01:00
18cfeedbff
implements PLN-0100 2026-06-15 05:46:28 +01:00
31 changed files with 757 additions and 415 deletions

View File

@ -8,8 +8,8 @@ use prometeu_hal::scene_viewport_resolver::{
}; };
use prometeu_hal::sprite::Sprite; use prometeu_hal::sprite::Sprite;
use prometeu_hal::{ use prometeu_hal::{
BoundScenePacket, Camera2D, ComposerFramePacket, Game2DFramePacket, GameSpritePacket, BoundScenePacket, Camera2D, ComposerFramePacket, ComposerOpStatus, Game2DFrameComposer,
GfxBridge, HudPacket, Game2DFramePacket, GameSpritePacket, GfxBridge, HudPacket,
}; };
use std::sync::Arc; use std::sync::Arc;
@ -452,6 +452,41 @@ impl FrameComposer {
} }
} }
impl Game2DFrameComposer for FrameComposer {
fn begin_frame(&mut self) {
FrameComposer::begin_frame(self);
}
fn bind_scene(&mut self, scene_bank_id: usize) -> ComposerOpStatus {
if FrameComposer::bind_scene(self, scene_bank_id) {
ComposerOpStatus::Ok
} else {
ComposerOpStatus::SceneUnavailable
}
}
fn unbind_scene(&mut self) {
FrameComposer::unbind_scene(self);
}
fn set_camera(&mut self, x: i32, y: i32) -> ComposerOpStatus {
FrameComposer::set_camera(self, x, y);
ComposerOpStatus::Ok
}
fn emit_sprite(&mut self, sprite: Sprite) -> ComposerOpStatus {
if FrameComposer::emit_sprite(self, sprite) {
ComposerOpStatus::Ok
} else {
ComposerOpStatus::SpriteOverflow
}
}
fn close_game2d_packet(&self) -> Game2DFramePacket {
FrameComposer::close_game2d_packet(self)
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -896,6 +931,45 @@ mod tests {
assert!(packet.gfx2d.is_empty()); assert!(packet.gfx2d.is_empty());
} }
#[test]
fn game2d_frame_composer_facade_closes_equivalent_packet() {
let mut frame_composer = FrameComposer::new(
Hardware::W,
Hardware::H,
Arc::new(MemoryBanks::default()),
make_glyph_slot_index(&[]),
);
let composer: &mut dyn Game2DFrameComposer = &mut frame_composer;
assert_eq!(composer.set_camera(10, 20), ComposerOpStatus::Ok);
composer.begin_frame();
assert_eq!(
composer.emit_sprite(Sprite {
glyph: Glyph { glyph_id: 7, palette_id: 3 },
x: 4,
y: 5,
layer: 2,
bank_id: 1,
active: false,
flip_x: true,
flip_y: false,
priority: 9,
}),
ComposerOpStatus::Ok
);
let packet = composer.close_game2d_packet();
assert_eq!(packet.composer.bound_scene, None);
assert_eq!(packet.composer.camera, Camera2D { x: 10, y: 20 });
assert_eq!(packet.composer.sprites.len(), 1);
assert_eq!(packet.composer.sprites[0].glyph_id, 7);
assert_eq!(packet.composer.sprites[0].palette_id, 3);
assert_eq!(packet.composer.sprites[0].layer, 2);
assert!(packet.composer.sprites[0].flip_x);
assert!(packet.gfx2d.is_empty());
}
#[test] #[test]
fn composer_buffer_preserves_integer_layer_ordering() { fn composer_buffer_preserves_integer_layer_ordering() {
let mut frame_composer = FrameComposer::new( let mut frame_composer = FrameComposer::new(

View File

@ -10,8 +10,13 @@ use crate::pad::Pad;
use crate::touch::Touch; use crate::touch::Touch;
use prometeu_hal::cartridge::AssetsPayloadSource; use prometeu_hal::cartridge::AssetsPayloadSource;
use prometeu_hal::sprite::Sprite; use prometeu_hal::sprite::Sprite;
use prometeu_hal::{AssetBridge, AudioBridge, GfxBridge, HardwareBridge, PadBridge, TouchBridge}; use prometeu_hal::{
use prometeu_hal::{Game2DFramePacket, RenderSubmission, RenderSubmissionPacket}; AssetBridge, AudioBridge, InputPlatform, NoopTelemetryPlatform, PadBridge, RenderBackend,
RuntimePlatform, TelemetryPlatform, TouchBridge,
};
use prometeu_hal::{
Game2DFrameComposer, Game2DFramePacket, RenderSubmission, RenderSubmissionPacket,
};
use prometeu_hal::{RenderSubmissionSink, RenderSubmitError}; use prometeu_hal::{RenderSubmissionSink, RenderSubmitError};
use std::sync::Arc; use std::sync::Arc;
@ -48,32 +53,8 @@ impl Default for Hardware {
} }
} }
impl HardwareBridge for Hardware { impl Hardware {
fn begin_frame(&mut self) { fn consume_render_submission(&mut self, submission: &RenderSubmission) {
self.frame_composer.begin_frame();
}
fn bind_scene(&mut self, scene_bank_id: usize) -> bool {
self.frame_composer.bind_scene(scene_bank_id)
}
fn unbind_scene(&mut self) {
self.frame_composer.unbind_scene();
}
fn set_camera(&mut self, x: i32, y: i32) {
self.frame_composer.set_camera(x, y);
}
fn emit_sprite(&mut self, sprite: Sprite) -> bool {
self.frame_composer.emit_sprite(sprite)
}
fn close_game2d_packet(&self) -> Game2DFramePacket {
self.frame_composer.close_game2d_packet()
}
fn publish_render_submission(&mut self, submission: &RenderSubmission) {
match &submission.packet { match &submission.packet {
RenderSubmissionPacket::Game2D(packet) => { RenderSubmissionPacket::Game2D(packet) => {
if packet.composer.bound_scene.is_none() { if packet.composer.bound_scene.is_none() {
@ -89,7 +70,19 @@ impl HardwareBridge for Hardware {
} }
self.gfx.present(); self.gfx.present();
} }
}
impl RenderSubmissionSink for Hardware {
fn submit_render_submission(
&mut self,
submission: RenderSubmission,
) -> Result<(), RenderSubmitError> {
self.consume_render_submission(&submission);
Ok(())
}
}
impl RenderBackend for Hardware {
fn render_frame(&mut self) { fn render_frame(&mut self) {
if self.frame_composer.active_scene_id().is_none() { if self.frame_composer.active_scene_id().is_none() {
let packet = self.frame_composer.close_game2d_packet(); let packet = self.frame_composer.close_game2d_packet();
@ -98,28 +91,52 @@ impl HardwareBridge for Hardware {
self.frame_composer.render_frame(&mut self.gfx); self.frame_composer.render_frame(&mut self.gfx);
} }
} }
}
fn has_glyph_bank(&self, bank_id: usize) -> bool { impl Game2DFrameComposer for Hardware {
self.gfx.glyph_banks.glyph_bank_slot(bank_id).is_some() fn begin_frame(&mut self) {
self.frame_composer.begin_frame();
} }
fn gfx(&self) -> &dyn GfxBridge { fn bind_scene(&mut self, scene_bank_id: usize) -> prometeu_hal::ComposerOpStatus {
&self.gfx if self.frame_composer.bind_scene(scene_bank_id) {
} prometeu_hal::ComposerOpStatus::Ok
fn gfx_mut(&mut self) -> &mut dyn GfxBridge { } else {
&mut self.gfx prometeu_hal::ComposerOpStatus::SceneUnavailable
}
} }
fn audio(&self) -> &dyn AudioBridge { fn unbind_scene(&mut self) {
&self.audio self.frame_composer.unbind_scene();
}
fn audio_mut(&mut self) -> &mut dyn AudioBridge {
&mut self.audio
} }
fn set_camera(&mut self, x: i32, y: i32) -> prometeu_hal::ComposerOpStatus {
self.frame_composer.set_camera(x, y);
prometeu_hal::ComposerOpStatus::Ok
}
fn emit_sprite(&mut self, sprite: Sprite) -> prometeu_hal::ComposerOpStatus {
if self.gfx.glyph_banks.glyph_bank_slot(sprite.bank_id as usize).is_none() {
return prometeu_hal::ComposerOpStatus::BankInvalid;
}
if self.frame_composer.emit_sprite(sprite) {
prometeu_hal::ComposerOpStatus::Ok
} else {
prometeu_hal::ComposerOpStatus::SpriteOverflow
}
}
fn close_game2d_packet(&self) -> Game2DFramePacket {
self.frame_composer.close_game2d_packet()
}
}
impl InputPlatform for Hardware {
fn pad(&self) -> &dyn PadBridge { fn pad(&self) -> &dyn PadBridge {
&self.pad &self.pad
} }
fn pad_mut(&mut self) -> &mut dyn PadBridge { fn pad_mut(&mut self) -> &mut dyn PadBridge {
&mut self.pad &mut self.pad
} }
@ -127,25 +144,53 @@ impl HardwareBridge for Hardware {
fn touch(&self) -> &dyn TouchBridge { fn touch(&self) -> &dyn TouchBridge {
&self.touch &self.touch
} }
fn touch_mut(&mut self) -> &mut dyn TouchBridge { fn touch_mut(&mut self) -> &mut dyn TouchBridge {
&mut self.touch &mut self.touch
} }
}
static NOOP_TELEMETRY: NoopTelemetryPlatform = NoopTelemetryPlatform;
impl RuntimePlatform for Hardware {
fn render_submission_sink(&mut self) -> &mut dyn RenderSubmissionSink {
self
}
fn render_backend(&mut self) -> &mut dyn RenderBackend {
self
}
fn game2d_frame_composer(&mut self) -> &mut dyn Game2DFrameComposer {
self
}
fn audio(&self) -> &dyn AudioBridge {
&self.audio
}
fn audio_mut(&mut self) -> &mut dyn AudioBridge {
&mut self.audio
}
fn input(&self) -> &dyn InputPlatform {
self
}
fn input_mut(&mut self) -> &mut dyn InputPlatform {
self
}
fn assets(&self) -> &dyn AssetBridge { fn assets(&self) -> &dyn AssetBridge {
&self.assets &self.assets
} }
fn assets_mut(&mut self) -> &mut dyn AssetBridge { fn assets_mut(&mut self) -> &mut dyn AssetBridge {
&mut self.assets &mut self.assets
} }
}
impl RenderSubmissionSink for Hardware { fn telemetry(&self) -> &dyn TelemetryPlatform {
fn submit_render_submission( &NOOP_TELEMETRY
&mut self,
submission: RenderSubmission,
) -> Result<(), RenderSubmitError> {
self.publish_render_submission(&submission);
Ok(())
} }
} }
@ -294,15 +339,22 @@ mod tests {
let mut glyph_slots = [None; 16]; let mut glyph_slots = [None; 16];
glyph_slots[0] = Some(0); glyph_slots[0] = Some(0);
hardware.assets.glyph_asset_slot_index().rebuild_from_slots(&glyph_slots); hardware.assets.glyph_asset_slot_index().rebuild_from_slots(&glyph_slots);
assert!(hardware.bind_scene(0)); assert_eq!(
Game2DFrameComposer::bind_scene(&mut hardware, 0),
prometeu_hal::ComposerOpStatus::Ok
);
let mut packet = hardware.close_game2d_packet(); let mut packet = Game2DFrameComposer::close_game2d_packet(&hardware);
packet.gfx2d = vec![Gfx2dCommand::FillRect { packet.gfx2d = vec![Gfx2dCommand::FillRect {
rect: Rect { x: 0, y: 0, w: 1, h: 1 }, rect: Rect { x: 0, y: 0, w: 1, h: 1 },
color: Color::BLUE, color: Color::BLUE,
}]; }];
hardware.publish_render_submission(&RenderSubmission::game2d(FrameId::ZERO, packet)); RenderSubmissionSink::submit_render_submission(
&mut hardware,
RenderSubmission::game2d(FrameId::ZERO, packet),
)
.expect("local sink should publish Game2D packet");
assert_eq!(hardware.gfx.front_buffer()[0], Color::BLUE.raw()); assert_eq!(hardware.gfx.front_buffer()[0], Color::BLUE.raw());
} }
@ -317,7 +369,10 @@ mod tests {
let mut glyph_slots = [None; 16]; let mut glyph_slots = [None; 16];
glyph_slots[0] = Some(0); glyph_slots[0] = Some(0);
hardware.assets.glyph_asset_slot_index().rebuild_from_slots(&glyph_slots); hardware.assets.glyph_asset_slot_index().rebuild_from_slots(&glyph_slots);
assert!(hardware.bind_scene(0)); assert_eq!(
Game2DFrameComposer::bind_scene(&mut hardware, 0),
prometeu_hal::ComposerOpStatus::Ok
);
let packet = Game2DFramePacket::new( let packet = Game2DFramePacket::new(
ComposerFramePacket { ComposerFramePacket {
@ -339,8 +394,12 @@ mod tests {
Vec::new(), Vec::new(),
); );
hardware.begin_frame(); Game2DFrameComposer::begin_frame(&mut hardware);
hardware.publish_render_submission(&RenderSubmission::game2d(FrameId::ZERO, packet)); RenderSubmissionSink::submit_render_submission(
&mut hardware,
RenderSubmission::game2d(FrameId::ZERO, packet),
)
.expect("local sink should publish Game2D packet");
assert_eq!(hardware.gfx.front_buffer()[0], Color::RED.raw()); assert_eq!(hardware.gfx.front_buffer()[0], Color::RED.raw());
} }

View File

@ -5,6 +5,7 @@ mod gfx;
pub mod hardware; pub mod hardware;
mod memory_banks; mod memory_banks;
mod pad; mod pad;
mod test_platform;
mod touch; mod touch;
pub use crate::asset::AssetManager; pub use crate::asset::AssetManager;
@ -16,3 +17,4 @@ pub use crate::memory_banks::{
SceneBankPoolAccess, SceneBankPoolInstaller, SoundBankPoolAccess, SoundBankPoolInstaller, SceneBankPoolAccess, SceneBankPoolInstaller, SoundBankPoolAccess, SoundBankPoolInstaller,
}; };
pub use crate::pad::Pad; pub use crate::pad::Pad;
pub use crate::test_platform::TestPlatform;

View File

@ -0,0 +1,100 @@
use crate::hardware::Hardware;
use crate::memory_banks::MemoryBanks;
use prometeu_hal::{
AssetBridge, AudioBridge, Game2DFrameComposer, InputPlatform, RenderBackend,
RenderSubmissionSink, RuntimePlatform, TelemetryPlatform,
};
use std::sync::Arc;
pub struct TestPlatform {
hardware: Hardware,
}
impl Default for TestPlatform {
fn default() -> Self {
Self::new()
}
}
impl TestPlatform {
pub fn new() -> Self {
Self { hardware: Hardware::new() }
}
pub fn new_with_memory_banks(memory_banks: Arc<MemoryBanks>) -> Self {
Self { hardware: Hardware::new_with_memory_banks(memory_banks) }
}
pub fn local_hardware(&self) -> &Hardware {
&self.hardware
}
pub fn local_hardware_mut(&mut self) -> &mut Hardware {
&mut self.hardware
}
}
impl RuntimePlatform for TestPlatform {
fn render_submission_sink(&mut self) -> &mut dyn RenderSubmissionSink {
self.hardware.render_submission_sink()
}
fn render_backend(&mut self) -> &mut dyn RenderBackend {
self.hardware.render_backend()
}
fn game2d_frame_composer(&mut self) -> &mut dyn Game2DFrameComposer {
self.hardware.game2d_frame_composer()
}
fn audio(&self) -> &dyn AudioBridge {
self.hardware.audio()
}
fn audio_mut(&mut self) -> &mut dyn AudioBridge {
self.hardware.audio_mut()
}
fn input(&self) -> &dyn InputPlatform {
self.hardware.input()
}
fn input_mut(&mut self) -> &mut dyn InputPlatform {
self.hardware.input_mut()
}
fn assets(&self) -> &dyn AssetBridge {
self.hardware.assets()
}
fn assets_mut(&mut self) -> &mut dyn AssetBridge {
self.hardware.assets_mut()
}
fn telemetry(&self) -> &dyn TelemetryPlatform {
self.hardware.telemetry()
}
}
#[cfg(test)]
mod tests {
use super::*;
use prometeu_hal::{FrameId, Game2DFramePacket, RenderSubmission};
#[test]
fn test_platform_initializes_required_facades() {
let mut platform = TestPlatform::new();
platform.game2d_frame_composer().begin_frame();
platform
.render_submission_sink()
.submit_render_submission(RenderSubmission::game2d(
FrameId::ZERO,
Game2DFramePacket::default(),
))
.expect("test platform render sink should accept local submission");
assert!(!platform.audio().is_playing(0));
assert!(!platform.input().pad().any());
assert!(platform.telemetry().telemetry_snapshot().is_none());
}
}

View File

@ -3,7 +3,7 @@ use crate::firmware::firmware_state::{FirmwareState, LoadCartridgeStep, ResetSte
use crate::firmware::prometeu_context::PrometeuContext; use crate::firmware::prometeu_context::PrometeuContext;
use prometeu_hal::cartridge::Cartridge; use prometeu_hal::cartridge::Cartridge;
use prometeu_hal::telemetry::CertificationConfig; use prometeu_hal::telemetry::CertificationConfig;
use prometeu_hal::{HardwareBridge, InputSignals}; use prometeu_hal::{InputSignals, RuntimePlatform};
use prometeu_system::{PrometeuHub, SystemOS}; use prometeu_system::{PrometeuHub, SystemOS};
use prometeu_vm::VirtualMachine; use prometeu_vm::VirtualMachine;
@ -54,24 +54,24 @@ impl Firmware {
/// ///
/// This method is called exactly once per Host frame (60Hz). /// This method is called exactly once per Host frame (60Hz).
/// It updates peripheral signals and delegates the logic to the current state. /// It updates peripheral signals and delegates the logic to the current state.
pub fn tick(&mut self, signals: &InputSignals, hw: &mut dyn HardwareBridge) { pub fn tick(&mut self, signals: &InputSignals, platform: &mut dyn RuntimePlatform) {
// 0. Process asset commits at the beginning of the frame boundary. // 0. Process asset commits at the beginning of the frame boundary.
hw.assets_mut().apply_commits(); platform.assets_mut().apply_commits();
// 1. Update the peripheral state using the latest signals from the Host. // 1. Update the peripheral state using the latest signals from the Host.
// This ensures input is consistent throughout the entire update. // This ensures input is consistent throughout the entire update.
hw.pad_mut().begin_frame(signals); platform.input_mut().pad_mut().begin_frame(signals);
hw.touch_mut().begin_frame(signals); platform.input_mut().touch_mut().begin_frame(signals);
// 2. State machine lifecycle management. // 2. State machine lifecycle management.
if !self.state_initialized { if !self.state_initialized {
self.on_enter(signals, hw); self.on_enter(signals, platform);
self.state_initialized = true; self.state_initialized = true;
} }
// 3. Update the current state and check for transitions. // 3. Update the current state and check for transitions.
if let Some(next_state) = self.on_update(signals, hw) { if let Some(next_state) = self.on_update(signals, platform) {
self.change_state(next_state, signals, hw); self.change_state(next_state, signals, platform);
} }
} }
@ -80,26 +80,26 @@ impl Firmware {
&mut self, &mut self,
new_state: FirmwareState, new_state: FirmwareState,
signals: &InputSignals, signals: &InputSignals,
hw: &mut dyn HardwareBridge, platform: &mut dyn RuntimePlatform,
) { ) {
self.on_exit(signals, hw); self.on_exit(signals, platform);
self.state = new_state; self.state = new_state;
self.state_initialized = false; self.state_initialized = false;
// Enter the new state immediately to avoid "empty" frames during transitions. // Enter the new state immediately to avoid "empty" frames during transitions.
self.on_enter(signals, hw); self.on_enter(signals, platform);
self.state_initialized = true; self.state_initialized = true;
} }
/// Dispatches the `on_enter` event to the current state implementation. /// Dispatches the `on_enter` event to the current state implementation.
fn on_enter(&mut self, signals: &InputSignals, hw: &mut dyn HardwareBridge) { fn on_enter(&mut self, signals: &InputSignals, platform: &mut dyn RuntimePlatform) {
let mut req = PrometeuContext { let mut req = PrometeuContext {
vm: &mut self.vm, vm: &mut self.vm,
os: &mut self.os, os: &mut self.os,
hub: &mut self.hub, hub: &mut self.hub,
boot_target: &self.boot_target, boot_target: &self.boot_target,
signals, signals,
hw, platform,
}; };
match &mut self.state { match &mut self.state {
FirmwareState::Reset(s) => s.on_enter(&mut req), FirmwareState::Reset(s) => s.on_enter(&mut req),
@ -118,7 +118,7 @@ impl Firmware {
fn on_update( fn on_update(
&mut self, &mut self,
signals: &InputSignals, signals: &InputSignals,
hw: &mut dyn HardwareBridge, platform: &mut dyn RuntimePlatform,
) -> Option<FirmwareState> { ) -> Option<FirmwareState> {
let mut req = PrometeuContext { let mut req = PrometeuContext {
vm: &mut self.vm, vm: &mut self.vm,
@ -126,7 +126,7 @@ impl Firmware {
hub: &mut self.hub, hub: &mut self.hub,
boot_target: &self.boot_target, boot_target: &self.boot_target,
signals, signals,
hw, platform,
}; };
match &mut self.state { match &mut self.state {
FirmwareState::Reset(s) => s.on_update(&mut req), FirmwareState::Reset(s) => s.on_update(&mut req),
@ -141,14 +141,14 @@ impl Firmware {
} }
/// Dispatches the `on_exit` event to the current state implementation. /// Dispatches the `on_exit` event to the current state implementation.
fn on_exit(&mut self, signals: &InputSignals, hw: &mut dyn HardwareBridge) { fn on_exit(&mut self, signals: &InputSignals, platform: &mut dyn RuntimePlatform) {
let mut req = PrometeuContext { let mut req = PrometeuContext {
vm: &mut self.vm, vm: &mut self.vm,
os: &mut self.os, os: &mut self.os,
hub: &mut self.hub, hub: &mut self.hub,
boot_target: &self.boot_target, boot_target: &self.boot_target,
signals, signals,
hw, platform,
}; };
match &mut self.state { match &mut self.state {
FirmwareState::Reset(s) => s.on_exit(&mut req), FirmwareState::Reset(s) => s.on_exit(&mut req),
@ -174,7 +174,7 @@ mod tests {
use crate::firmware::firmware_state::HubHomeStep; use crate::firmware::firmware_state::HubHomeStep;
use prometeu_bytecode::assembler::assemble; use prometeu_bytecode::assembler::assemble;
use prometeu_bytecode::model::{BytecodeModule, FunctionMeta, SyscallDecl}; use prometeu_bytecode::model::{BytecodeModule, FunctionMeta, SyscallDecl};
use prometeu_drivers::hardware::Hardware; use prometeu_drivers::TestPlatform;
use prometeu_hal::app_mode::AppMode; use prometeu_hal::app_mode::AppMode;
use prometeu_hal::cartridge::AssetsPayloadSource; use prometeu_hal::cartridge::AssetsPayloadSource;
use prometeu_hal::color::Color; use prometeu_hal::color::Color;
@ -267,28 +267,28 @@ mod tests {
} }
} }
fn load_shell_running_firmware() -> (Firmware, Hardware, InputSignals, TaskId) { fn load_shell_running_firmware() -> (Firmware, TestPlatform, InputSignals, TaskId) {
let mut firmware = Firmware::new(None); let mut firmware = Firmware::new(None);
let mut hardware = Hardware::new(); let mut platform = TestPlatform::new();
let signals = InputSignals::default(); let signals = InputSignals::default();
firmware.load_cartridge(valid_cartridge(AppMode::Shell)); firmware.load_cartridge(valid_cartridge(AppMode::Shell));
firmware.tick(&signals, &mut hardware); firmware.tick(&signals, &mut platform);
let task_id = match &firmware.state { let task_id = match &firmware.state {
FirmwareState::ShellRunning(step) => step.task_id, FirmwareState::ShellRunning(step) => step.task_id,
other => panic!("expected ShellRunning state, got {:?}", other), other => panic!("expected ShellRunning state, got {:?}", other),
}; };
(firmware, hardware, signals, task_id) (firmware, platform, signals, task_id)
} }
fn hub_home_firmware() -> (Firmware, Hardware) { fn hub_home_firmware() -> (Firmware, TestPlatform) {
let mut firmware = Firmware::new(None); let mut firmware = Firmware::new(None);
firmware.state = FirmwareState::HubHome(HubHomeStep); firmware.state = FirmwareState::HubHome(HubHomeStep);
firmware.state_initialized = false; firmware.state_initialized = false;
(firmware, Hardware::new()) (firmware, TestPlatform::new())
} }
fn focus_window(firmware: &mut Firmware, owner: WindowOwner) { fn focus_window(firmware: &mut Firmware, owner: WindowOwner) {
@ -306,11 +306,11 @@ mod tests {
#[test] #[test]
fn load_cartridge_transitions_to_app_crashes_when_vm_init_fails() { fn load_cartridge_transitions_to_app_crashes_when_vm_init_fails() {
let mut firmware = Firmware::new(None); let mut firmware = Firmware::new(None);
let mut hardware = Hardware::new(); let mut platform = TestPlatform::new();
let signals = InputSignals::default(); let signals = InputSignals::default();
firmware.load_cartridge(invalid_game_cartridge()); firmware.load_cartridge(invalid_game_cartridge());
firmware.tick(&signals, &mut hardware); firmware.tick(&signals, &mut platform);
match &firmware.state { match &firmware.state {
FirmwareState::AppCrashes(step) => match &step.report { FirmwareState::AppCrashes(step) => match &step.report {
@ -326,18 +326,18 @@ mod tests {
#[test] #[test]
fn game_running_transitions_to_app_crashes_when_runtime_surfaces_trap() { fn game_running_transitions_to_app_crashes_when_runtime_surfaces_trap() {
let mut firmware = Firmware::new(None); let mut firmware = Firmware::new(None);
let mut hardware = Hardware::new(); let mut platform = TestPlatform::new();
let signals = InputSignals::default(); let signals = InputSignals::default();
firmware.load_cartridge(trapping_game_cartridge()); firmware.load_cartridge(trapping_game_cartridge());
firmware.tick(&signals, &mut hardware); firmware.tick(&signals, &mut platform);
let task_id = match &firmware.state { let task_id = match &firmware.state {
FirmwareState::GameRunning(step) => step.task_id, FirmwareState::GameRunning(step) => step.task_id,
other => panic!("expected GameRunning state, got {:?}", other), other => panic!("expected GameRunning state, got {:?}", other),
}; };
firmware.tick(&signals, &mut hardware); firmware.tick(&signals, &mut platform);
match &firmware.state { match &firmware.state {
FirmwareState::AppCrashes(step) => match &step.report { FirmwareState::AppCrashes(step) => match &step.report {
@ -359,11 +359,11 @@ mod tests {
#[test] #[test]
fn reset_routes_hub_boot_target_to_splash_screen() { fn reset_routes_hub_boot_target_to_splash_screen() {
let mut firmware = Firmware::new(None); let mut firmware = Firmware::new(None);
let mut hardware = Hardware::new(); let mut platform = TestPlatform::new();
let signals = InputSignals::default(); let signals = InputSignals::default();
firmware.boot_target = BootTarget::Hub; firmware.boot_target = BootTarget::Hub;
firmware.tick(&signals, &mut hardware); firmware.tick(&signals, &mut platform);
assert!(matches!(firmware.state, FirmwareState::SplashScreen(_))); assert!(matches!(firmware.state, FirmwareState::SplashScreen(_)));
} }
@ -371,12 +371,12 @@ mod tests {
#[test] #[test]
fn reset_routes_cartridge_boot_target_to_launch_hub() { fn reset_routes_cartridge_boot_target_to_launch_hub() {
let mut firmware = Firmware::new(None); let mut firmware = Firmware::new(None);
let mut hardware = Hardware::new(); let mut platform = TestPlatform::new();
let signals = InputSignals::default(); let signals = InputSignals::default();
firmware.boot_target = firmware.boot_target =
BootTarget::Cartridge { path: "missing-cart".into(), debug: false, debug_port: 7777 }; BootTarget::Cartridge { path: "missing-cart".into(), debug: false, debug_port: 7777 };
firmware.tick(&signals, &mut hardware); firmware.tick(&signals, &mut platform);
assert!(matches!(firmware.state, FirmwareState::LaunchHub(_))); assert!(matches!(firmware.state, FirmwareState::LaunchHub(_)));
} }
@ -384,11 +384,11 @@ mod tests {
#[test] #[test]
fn load_cartridge_routes_system_apps_to_system_running() { fn load_cartridge_routes_system_apps_to_system_running() {
let mut firmware = Firmware::new(None); let mut firmware = Firmware::new(None);
let mut hardware = Hardware::new(); let mut platform = TestPlatform::new();
let signals = InputSignals::default(); let signals = InputSignals::default();
firmware.load_cartridge(valid_cartridge(AppMode::Shell)); firmware.load_cartridge(valid_cartridge(AppMode::Shell));
firmware.tick(&signals, &mut hardware); firmware.tick(&signals, &mut platform);
assert!(matches!(firmware.state, FirmwareState::ShellRunning(_))); assert!(matches!(firmware.state, FirmwareState::ShellRunning(_)));
assert!(firmware.os.windows().focused_window().is_some()); assert!(firmware.os.windows().focused_window().is_some());
@ -398,18 +398,18 @@ mod tests {
#[test] #[test]
fn system_running_ticks_focused_system_app_through_hub_pipeline() { fn system_running_ticks_focused_system_app_through_hub_pipeline() {
let mut firmware = Firmware::new(None); let mut firmware = Firmware::new(None);
let mut hardware = Hardware::new(); let mut platform = TestPlatform::new();
let signals = InputSignals::default(); let signals = InputSignals::default();
firmware.load_cartridge(valid_cartridge(AppMode::Shell)); firmware.load_cartridge(valid_cartridge(AppMode::Shell));
firmware.tick(&signals, &mut hardware); firmware.tick(&signals, &mut platform);
let task_id = match &firmware.state { let task_id = match &firmware.state {
FirmwareState::ShellRunning(step) => step.task_id, FirmwareState::ShellRunning(step) => step.task_id,
other => panic!("expected ShellRunning state, got {:?}", other), other => panic!("expected ShellRunning state, got {:?}", other),
}; };
let tick_index_before_system_update = firmware.os.vm().tick_index(); let tick_index_before_system_update = firmware.os.vm().tick_index();
firmware.tick(&signals, &mut hardware); firmware.tick(&signals, &mut platform);
assert!(matches!(firmware.state, FirmwareState::ShellRunning(_))); assert!(matches!(firmware.state, FirmwareState::ShellRunning(_)));
assert!(firmware.os.windows().focused_window_belongs_to_task(task_id)); assert!(firmware.os.windows().focused_window_belongs_to_task(task_id));
@ -418,10 +418,10 @@ mod tests {
#[test] #[test]
fn hub_home_does_not_launch_shell_before_home_input_is_armed() { fn hub_home_does_not_launch_shell_before_home_input_is_armed() {
let (mut firmware, mut hardware) = hub_home_firmware(); let (mut firmware, mut platform) = hub_home_firmware();
let signals = InputSignals { f_signal: true, x_pos: 112, y_pos: 108, ..Default::default() }; let signals = InputSignals { f_signal: true, x_pos: 112, y_pos: 108, ..Default::default() };
firmware.tick(&signals, &mut hardware); firmware.tick(&signals, &mut platform);
assert!(matches!(firmware.state, FirmwareState::HubHome(_))); assert!(matches!(firmware.state, FirmwareState::HubHome(_)));
assert_eq!(firmware.os.windows().window_count(), 0); assert_eq!(firmware.os.windows().window_count(), 0);
@ -429,11 +429,11 @@ mod tests {
#[test] #[test]
fn hub_home_click_launches_shell_a_as_native_shell_task_window() { fn hub_home_click_launches_shell_a_as_native_shell_task_window() {
let (mut firmware, mut hardware) = hub_home_firmware(); let (mut firmware, mut platform) = hub_home_firmware();
firmware.tick(&InputSignals::default(), &mut hardware); firmware.tick(&InputSignals::default(), &mut platform);
let signals = InputSignals { f_signal: true, x_pos: 112, y_pos: 108, ..Default::default() }; let signals = InputSignals { f_signal: true, x_pos: 112, y_pos: 108, ..Default::default() };
firmware.tick(&signals, &mut hardware); firmware.tick(&signals, &mut platform);
let task_id = match &firmware.state { let task_id = match &firmware.state {
FirmwareState::ShellRunning(step) => step.task_id, FirmwareState::ShellRunning(step) => step.task_id,
@ -452,11 +452,11 @@ mod tests {
#[test] #[test]
fn hub_home_click_launches_shell_b_as_native_shell_task_window() { fn hub_home_click_launches_shell_b_as_native_shell_task_window() {
let (mut firmware, mut hardware) = hub_home_firmware(); let (mut firmware, mut platform) = hub_home_firmware();
firmware.tick(&InputSignals::default(), &mut hardware); firmware.tick(&InputSignals::default(), &mut platform);
let signals = InputSignals { f_signal: true, x_pos: 256, y_pos: 108, ..Default::default() }; let signals = InputSignals { f_signal: true, x_pos: 256, y_pos: 108, ..Default::default() };
firmware.tick(&signals, &mut hardware); firmware.tick(&signals, &mut platform);
let task_id = match &firmware.state { let task_id = match &firmware.state {
FirmwareState::ShellRunning(step) => step.task_id, FirmwareState::ShellRunning(step) => step.task_id,
@ -475,10 +475,10 @@ mod tests {
#[test] #[test]
fn shell_running_start_close_uses_lifecycle_and_returns_hub_home() { fn shell_running_start_close_uses_lifecycle_and_returns_hub_home() {
let (mut firmware, mut hardware, _signals, task_id) = load_shell_running_firmware(); let (mut firmware, mut platform, _signals, task_id) = load_shell_running_firmware();
let close_signals = InputSignals { start_signal: true, ..Default::default() }; let close_signals = InputSignals { start_signal: true, ..Default::default() };
firmware.tick(&close_signals, &mut hardware); firmware.tick(&close_signals, &mut platform);
assert!(matches!(firmware.state, FirmwareState::HubHome(_))); assert!(matches!(firmware.state, FirmwareState::HubHome(_)));
assert_eq!(firmware.os.lifecycle().task_state(task_id), Some(TaskState::Closed)); assert_eq!(firmware.os.lifecycle().task_state(task_id), Some(TaskState::Closed));
@ -490,11 +490,11 @@ mod tests {
#[test] #[test]
fn shell_running_close_click_uses_lifecycle_and_returns_hub_home() { fn shell_running_close_click_uses_lifecycle_and_returns_hub_home() {
let (mut firmware, mut hardware, _signals, task_id) = load_shell_running_firmware(); let (mut firmware, mut platform, _signals, task_id) = load_shell_running_firmware();
let close_signals = let close_signals =
InputSignals { f_signal: true, x_pos: 420, y_pos: 24, ..Default::default() }; InputSignals { f_signal: true, x_pos: 420, y_pos: 24, ..Default::default() };
firmware.tick(&close_signals, &mut hardware); firmware.tick(&close_signals, &mut platform);
assert!(matches!(firmware.state, FirmwareState::HubHome(_))); assert!(matches!(firmware.state, FirmwareState::HubHome(_)));
assert_eq!(firmware.os.lifecycle().task_state(task_id), Some(TaskState::Closed)); assert_eq!(firmware.os.lifecycle().task_state(task_id), Some(TaskState::Closed));
@ -506,10 +506,10 @@ mod tests {
#[test] #[test]
fn shell_running_closes_task_and_returns_hub_home_without_focused_window() { fn shell_running_closes_task_and_returns_hub_home_without_focused_window() {
let (mut firmware, mut hardware, signals, task_id) = load_shell_running_firmware(); let (mut firmware, mut platform, signals, task_id) = load_shell_running_firmware();
firmware.os.windows().remove_all_windows(); firmware.os.windows().remove_all_windows();
firmware.tick(&signals, &mut hardware); firmware.tick(&signals, &mut platform);
assert!(matches!(firmware.state, FirmwareState::HubHome(_))); assert!(matches!(firmware.state, FirmwareState::HubHome(_)));
assert_eq!(firmware.os.lifecycle().task_state(task_id), Some(TaskState::Closed)); assert_eq!(firmware.os.lifecycle().task_state(task_id), Some(TaskState::Closed));
@ -521,10 +521,10 @@ mod tests {
#[test] #[test]
fn shell_running_closes_task_and_returns_hub_home_for_focused_hub_window() { fn shell_running_closes_task_and_returns_hub_home_for_focused_hub_window() {
let (mut firmware, mut hardware, signals, task_id) = load_shell_running_firmware(); let (mut firmware, mut platform, signals, task_id) = load_shell_running_firmware();
focus_window(&mut firmware, WindowOwner::Hub); focus_window(&mut firmware, WindowOwner::Hub);
firmware.tick(&signals, &mut hardware); firmware.tick(&signals, &mut platform);
assert!(matches!(firmware.state, FirmwareState::HubHome(_))); assert!(matches!(firmware.state, FirmwareState::HubHome(_)));
assert_eq!(firmware.os.lifecycle().task_state(task_id), Some(TaskState::Closed)); assert_eq!(firmware.os.lifecycle().task_state(task_id), Some(TaskState::Closed));
@ -536,10 +536,10 @@ mod tests {
#[test] #[test]
fn shell_running_closes_task_and_returns_hub_home_for_focused_overlay_window() { fn shell_running_closes_task_and_returns_hub_home_for_focused_overlay_window() {
let (mut firmware, mut hardware, signals, task_id) = load_shell_running_firmware(); let (mut firmware, mut platform, signals, task_id) = load_shell_running_firmware();
focus_window(&mut firmware, WindowOwner::Overlay); focus_window(&mut firmware, WindowOwner::Overlay);
firmware.tick(&signals, &mut hardware); firmware.tick(&signals, &mut platform);
assert!(matches!(firmware.state, FirmwareState::HubHome(_))); assert!(matches!(firmware.state, FirmwareState::HubHome(_)));
assert_eq!(firmware.os.lifecycle().task_state(task_id), Some(TaskState::Closed)); assert_eq!(firmware.os.lifecycle().task_state(task_id), Some(TaskState::Closed));
@ -551,10 +551,10 @@ mod tests {
#[test] #[test]
fn shell_running_closes_task_and_returns_hub_home_for_other_task_window() { fn shell_running_closes_task_and_returns_hub_home_for_other_task_window() {
let (mut firmware, mut hardware, signals, task_id) = load_shell_running_firmware(); let (mut firmware, mut platform, signals, task_id) = load_shell_running_firmware();
focus_window(&mut firmware, WindowOwner::Task(TaskId(task_id.0 + 1))); focus_window(&mut firmware, WindowOwner::Task(TaskId(task_id.0 + 1)));
firmware.tick(&signals, &mut hardware); firmware.tick(&signals, &mut platform);
assert!(matches!(firmware.state, FirmwareState::HubHome(_))); assert!(matches!(firmware.state, FirmwareState::HubHome(_)));
assert_eq!(firmware.os.lifecycle().task_state(task_id), Some(TaskState::Closed)); assert_eq!(firmware.os.lifecycle().task_state(task_id), Some(TaskState::Closed));
@ -567,11 +567,11 @@ mod tests {
#[test] #[test]
fn load_cartridge_routes_game_apps_to_game_running() { fn load_cartridge_routes_game_apps_to_game_running() {
let mut firmware = Firmware::new(None); let mut firmware = Firmware::new(None);
let mut hardware = Hardware::new(); let mut platform = TestPlatform::new();
let signals = InputSignals::default(); let signals = InputSignals::default();
firmware.load_cartridge(valid_cartridge(AppMode::Game)); firmware.load_cartridge(valid_cartridge(AppMode::Game));
firmware.tick(&signals, &mut hardware); firmware.tick(&signals, &mut platform);
assert!(matches!(firmware.state, FirmwareState::GameRunning(_))); assert!(matches!(firmware.state, FirmwareState::GameRunning(_)));
assert_eq!(firmware.os.windows().window_count(), 0); assert_eq!(firmware.os.windows().window_count(), 0);

View File

@ -21,13 +21,16 @@ impl AppCrashesStep {
pub fn on_update(&mut self, ctx: &mut PrometeuContext) -> Option<FirmwareState> { pub fn on_update(&mut self, ctx: &mut PrometeuContext) -> Option<FirmwareState> {
// Update peripherals for input on the crash screen // Update peripherals for input on the crash screen
ctx.hw.pad_mut().begin_frame(ctx.signals); ctx.platform.input_mut().pad_mut().begin_frame(ctx.signals);
let packet = ShellUiFramePacket::new(vec![GfxUiCommand::Clear { color: Color::RED }]); let packet = ShellUiFramePacket::new(vec![GfxUiCommand::Clear { color: Color::RED }]);
ctx.hw.publish_render_submission(&RenderSubmission::shell_ui(FrameId::ZERO, packet)); ctx.platform
.render_submission_sink()
.submit_render_submission(RenderSubmission::shell_ui(FrameId::ZERO, packet))
.expect("firmware crash render submission should publish");
// If START is pressed, return to the Hub // If START is pressed, return to the Hub
if ctx.hw.pad().start().down { if ctx.platform.input().pad().start().down {
return Some(FirmwareState::LaunchHub(LaunchHubStep)); return Some(FirmwareState::LaunchHub(LaunchHubStep));
} }

View File

@ -36,7 +36,7 @@ impl GameRunningStep {
return Some(FirmwareState::AppCrashes(AppCrashesStep { report })); return Some(FirmwareState::AppCrashes(AppCrashesStep { report }));
} }
let result = ctx.os.vm().tick(ctx.vm, ctx.signals, ctx.hw); let result = ctx.os.vm().tick(ctx.vm, ctx.signals, ctx.platform);
if let Some(report) = result { if let Some(report) = result {
let _ = ctx.os.lifecycle().crash_task(self.task_id, Some(&report)); let _ = ctx.os.lifecycle().crash_task(self.task_id, Some(&report));

View File

@ -16,7 +16,7 @@ impl HubHomeStep {
} }
pub fn on_update(&mut self, ctx: &mut PrometeuContext) -> Option<FirmwareState> { pub fn on_update(&mut self, ctx: &mut PrometeuContext) -> Option<FirmwareState> {
let outcome = ctx.hub.update_shell_profile(ctx.os, ctx.vm, ctx.signals, ctx.hw); let outcome = ctx.hub.update_shell_profile(ctx.os, ctx.vm, ctx.signals, ctx.platform);
if let Some(report) = outcome.crash { if let Some(report) = outcome.crash {
return Some(FirmwareState::AppCrashes(AppCrashesStep { report })); return Some(FirmwareState::AppCrashes(AppCrashesStep { report }));
} }

View File

@ -30,7 +30,7 @@ impl LoadCartridgeStep {
); );
// Initialize Asset Manager // Initialize Asset Manager
ctx.hw.assets_mut().initialize_for_cartridge( ctx.platform.assets_mut().initialize_for_cartridge(
self.cartridge.asset_table.clone(), self.cartridge.asset_table.clone(),
self.cartridge.preload.clone(), self.cartridge.preload.clone(),
self.cartridge.assets.clone(), self.cartridge.assets.clone(),

View File

@ -59,7 +59,7 @@ impl ShellRunningStep {
} }
} }
let outcome = ctx.hub.update_shell_profile(ctx.os, ctx.vm, ctx.signals, ctx.hw); let outcome = ctx.hub.update_shell_profile(ctx.os, ctx.vm, ctx.signals, ctx.platform);
if let Some(report) = outcome.crash { if let Some(report) = outcome.crash {
let _ = ctx.os.lifecycle().crash_task(self.task_id, Some(&report)); let _ = ctx.os.lifecycle().crash_task(self.task_id, Some(&report));

View File

@ -14,7 +14,7 @@ impl SplashScreenStep {
pub fn on_enter(&mut self, ctx: &mut PrometeuContext) { pub fn on_enter(&mut self, ctx: &mut PrometeuContext) {
ctx.os.log(LogLevel::Info, LogSource::Pos, 0, "Showing SplashScreen".to_string()); ctx.os.log(LogLevel::Info, LogSource::Pos, 0, "Showing SplashScreen".to_string());
// Play sound on enter // Play sound on enter
// ctx.hw.audio_mut().play(0, 0, 0, 255, 127, 1.0, 0, LoopMode::Off); // ctx.platform.audio_mut().play(0, 0, 0, 255, 127, 1.0, 0, LoopMode::Off);
} }
pub fn on_update(&mut self, ctx: &mut PrometeuContext) -> Option<FirmwareState> { pub fn on_update(&mut self, ctx: &mut PrometeuContext) -> Option<FirmwareState> {
@ -22,10 +22,10 @@ impl SplashScreenStep {
const TOTAL_DURATION: u32 = 240; // 4 seconds total (updated from 2s based on total_duration logic) const TOTAL_DURATION: u32 = 240; // 4 seconds total (updated from 2s based on total_duration logic)
// Update peripherals for input // Update peripherals for input
ctx.hw.pad_mut().begin_frame(ctx.signals); ctx.platform.input_mut().pad_mut().begin_frame(ctx.signals);
// Draw expanding square // Draw expanding square
let (sw, sh) = ctx.hw.gfx().size(); let (sw, sh) = ctx.platform.display_size();
let max_size = (sw.min(sh) as i32 / 2).max(1); let max_size = (sw.min(sh) as i32 / 2).max(1);
let current_size = if self.frame < ANIMATION_DURATION { let current_size = if self.frame < ANIMATION_DURATION {
@ -44,11 +44,14 @@ impl SplashScreenStep {
color: Color::WHITE, color: Color::WHITE,
}, },
]); ]);
ctx.hw.publish_render_submission(&RenderSubmission::shell_ui(FrameId::ZERO, packet)); ctx.platform
.render_submission_sink()
.submit_render_submission(RenderSubmission::shell_ui(FrameId::ZERO, packet))
.expect("firmware splash render submission should publish");
// Transition logic // Transition logic
// If any button is pressed at any time after the animation ends // If any button is pressed at any time after the animation ends
if self.frame >= ANIMATION_DURATION && ctx.hw.pad().any() { if self.frame >= ANIMATION_DURATION && ctx.platform.input().pad().any() {
return Some(FirmwareState::LaunchHub(LaunchHubStep)); return Some(FirmwareState::LaunchHub(LaunchHubStep));
} }

View File

@ -1,5 +1,5 @@
use crate::firmware::boot_target::BootTarget; use crate::firmware::boot_target::BootTarget;
use prometeu_hal::{HardwareBridge, InputSignals}; use prometeu_hal::{InputSignals, RuntimePlatform};
use prometeu_system::{PrometeuHub, SystemOS}; use prometeu_system::{PrometeuHub, SystemOS};
use prometeu_vm::VirtualMachine; use prometeu_vm::VirtualMachine;
@ -9,5 +9,5 @@ pub struct PrometeuContext<'a> {
pub hub: &'a mut PrometeuHub, pub hub: &'a mut PrometeuHub,
pub boot_target: &'a BootTarget, pub boot_target: &'a BootTarget,
pub signals: &'a InputSignals, pub signals: &'a InputSignals,
pub hw: &'a mut dyn HardwareBridge, pub platform: &'a mut dyn RuntimePlatform,
} }

View File

@ -1,39 +0,0 @@
use crate::asset_bridge::AssetBridge;
use crate::audio_bridge::AudioBridge;
use crate::gfx_bridge::GfxBridge;
use crate::pad_bridge::PadBridge;
use crate::render_submission::{Game2DFramePacket, RenderSubmission};
use crate::sprite::Sprite;
use crate::touch_bridge::TouchBridge;
/// Legacy migration scaffold for code that has not moved to `platform` facades yet.
///
/// `HardwareBridge` is intentionally not the final runtime-facing contract. New
/// platform boundaries belong in `crate::platform`, and this trait must be
/// removed after runtime, firmware, host, and tests finish migrating.
pub trait HardwareBridge {
fn begin_frame(&mut self);
fn bind_scene(&mut self, scene_bank_id: usize) -> bool;
fn unbind_scene(&mut self);
fn set_camera(&mut self, x: i32, y: i32);
fn emit_sprite(&mut self, sprite: Sprite) -> bool;
fn close_game2d_packet(&self) -> Game2DFramePacket;
fn publish_render_submission(&mut self, submission: &RenderSubmission);
fn render_frame(&mut self);
fn has_glyph_bank(&self, bank_id: usize) -> bool;
fn gfx(&self) -> &dyn GfxBridge;
fn gfx_mut(&mut self) -> &mut dyn GfxBridge;
fn audio(&self) -> &dyn AudioBridge;
fn audio_mut(&mut self) -> &mut dyn AudioBridge;
fn pad(&self) -> &dyn PadBridge;
fn pad_mut(&mut self) -> &mut dyn PadBridge;
fn touch(&self) -> &dyn TouchBridge;
fn touch_mut(&mut self) -> &mut dyn TouchBridge;
fn assets(&self) -> &dyn AssetBridge;
fn assets_mut(&mut self) -> &mut dyn AssetBridge;
}

View File

@ -1,19 +1,23 @@
use crate::hardware_bridge::HardwareBridge; use crate::platform::RuntimePlatform;
use crate::vm_fault::VmFault; use crate::vm_fault::VmFault;
pub struct HostContext<'a> { pub struct HostContext<'a> {
pub hw: Option<&'a mut dyn HardwareBridge>, pub platform: Option<&'a mut dyn RuntimePlatform>,
} }
impl<'a> HostContext<'a> { impl<'a> HostContext<'a> {
pub fn new(hw: Option<&'a mut dyn HardwareBridge>) -> Self { pub fn new(platform: Option<&'a mut dyn RuntimePlatform>) -> Self {
Self { hw } Self { platform }
}
pub fn new_platform(platform: Option<&'a mut dyn RuntimePlatform>) -> Self {
Self::new(platform)
} }
#[inline] #[inline]
pub fn require_hw(&mut self) -> Result<&mut dyn HardwareBridge, VmFault> { pub fn require_platform(&mut self) -> Result<&mut dyn RuntimePlatform, VmFault> {
match &mut self.hw { match &mut self.platform {
Some(hw) => Ok(*hw), Some(platform) => Ok(*platform),
None => Err(VmFault::Unavailable), None => Err(VmFault::Unavailable),
} }
} }

View File

@ -11,7 +11,6 @@ pub mod debugger_protocol;
pub mod gfx_bridge; pub mod gfx_bridge;
pub mod glyph; pub mod glyph;
pub mod glyph_bank; pub mod glyph_bank;
pub mod hardware_bridge;
pub mod host_context; pub mod host_context;
pub mod host_return; pub mod host_return;
pub mod input_signals; pub mod input_signals;
@ -40,7 +39,6 @@ pub use asset_bridge::AssetBridge;
pub use audio_bridge::{AudioBridge, AudioOpStatus, LoopMode}; pub use audio_bridge::{AudioBridge, AudioOpStatus, LoopMode};
pub use composer_status::ComposerOpStatus; pub use composer_status::ComposerOpStatus;
pub use gfx_bridge::{BlendMode, GfxBridge, GfxOpStatus}; pub use gfx_bridge::{BlendMode, GfxBridge, GfxOpStatus};
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;
pub use input_signals::InputSignals; pub use input_signals::InputSignals;
@ -49,8 +47,8 @@ pub use native_interface::{NativeInterface, SyscallId};
pub use pad_bridge::PadBridge; pub use pad_bridge::PadBridge;
pub use platform::{ pub use platform::{
AssetStoragePlatform, AudioPlatform, ClockPacingPlatform, Game2DFrameComposer, InputPlatform, AssetStoragePlatform, AudioPlatform, ClockPacingPlatform, Game2DFrameComposer, InputPlatform,
RenderBackend, RenderSubmissionSink, RenderSubmitError, RuntimePlatform, TelemetryPlatform, NoopTelemetryPlatform, RenderBackend, RenderSubmissionSink, RenderSubmitError, RuntimePlatform,
TestPlatform, TelemetryPlatform, TestPlatform,
}; };
pub use render_submission::{ pub use render_submission::{
BoundScenePacket, Camera2D, ComposerFramePacket, FrameId, Game2DFramePacket, GameSpritePacket, BoundScenePacket, Camera2D, ComposerFramePacket, FrameId, Game2DFramePacket, GameSpritePacket,

View File

@ -31,7 +31,6 @@ pub trait Game2DFrameComposer {
fn set_camera(&mut self, x: i32, y: i32) -> ComposerOpStatus; fn set_camera(&mut self, x: i32, y: i32) -> ComposerOpStatus;
fn emit_sprite(&mut self, sprite: Sprite) -> ComposerOpStatus; fn emit_sprite(&mut self, sprite: Sprite) -> ComposerOpStatus;
fn close_game2d_packet(&self) -> Game2DFramePacket; fn close_game2d_packet(&self) -> Game2DFramePacket;
fn has_glyph_bank(&self, bank_id: usize) -> bool;
} }
pub trait AudioPlatform: AudioBridge {} pub trait AudioPlatform: AudioBridge {}
@ -55,16 +54,25 @@ pub trait TelemetryPlatform {
} }
} }
#[derive(Debug, Default)]
pub struct NoopTelemetryPlatform;
impl TelemetryPlatform for NoopTelemetryPlatform {}
pub trait RuntimePlatform { pub trait RuntimePlatform {
fn display_size(&self) -> (usize, usize) {
(480, 270)
}
fn render_submission_sink(&mut self) -> &mut dyn RenderSubmissionSink; fn render_submission_sink(&mut self) -> &mut dyn RenderSubmissionSink;
fn render_backend(&mut self) -> &mut dyn RenderBackend; fn render_backend(&mut self) -> &mut dyn RenderBackend;
fn game2d_frame_composer(&mut self) -> &mut dyn Game2DFrameComposer; fn game2d_frame_composer(&mut self) -> &mut dyn Game2DFrameComposer;
fn audio(&self) -> &dyn AudioPlatform; fn audio(&self) -> &dyn AudioBridge;
fn audio_mut(&mut self) -> &mut dyn AudioPlatform; fn audio_mut(&mut self) -> &mut dyn AudioBridge;
fn input(&self) -> &dyn InputPlatform; fn input(&self) -> &dyn InputPlatform;
fn input_mut(&mut self) -> &mut dyn InputPlatform; fn input_mut(&mut self) -> &mut dyn InputPlatform;
fn assets(&self) -> &dyn AssetStoragePlatform; fn assets(&self) -> &dyn AssetBridge;
fn assets_mut(&mut self) -> &mut dyn AssetStoragePlatform; fn assets_mut(&mut self) -> &mut dyn AssetBridge;
fn telemetry(&self) -> &dyn TelemetryPlatform; fn telemetry(&self) -> &dyn TelemetryPlatform;
} }
@ -114,10 +122,6 @@ mod tests {
fn close_game2d_packet(&self) -> Game2DFramePacket { fn close_game2d_packet(&self) -> Game2DFramePacket {
Game2DFramePacket::default() Game2DFramePacket::default()
} }
fn has_glyph_bank(&self, _bank_id: usize) -> bool {
false
}
} }
#[test] #[test]

View File

@ -3,7 +3,7 @@ use crate::os::SystemOS;
use prometeu_hal::app_mode::AppMode; use prometeu_hal::app_mode::AppMode;
use prometeu_hal::cartridge::Cartridge; use prometeu_hal::cartridge::Cartridge;
use prometeu_hal::telemetry::{CertificationConfig, TelemetryFrame}; use prometeu_hal::telemetry::{CertificationConfig, TelemetryFrame};
use prometeu_hal::{HardwareBridge, InputSignals}; use prometeu_hal::{InputSignals, RuntimePlatform};
use prometeu_vm::VirtualMachine; use prometeu_vm::VirtualMachine;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
@ -25,7 +25,7 @@ impl<'a> VmFacade<'a> {
&mut self, &mut self,
vm: &mut VirtualMachine, vm: &mut VirtualMachine,
signals: &InputSignals, signals: &InputSignals,
hw: &mut dyn HardwareBridge, platform: &mut dyn RuntimePlatform,
) -> Option<CrashReport> { ) -> Option<CrashReport> {
self.os.vm_runtime.tick( self.os.vm_runtime.tick(
&mut self.os.log_service, &mut self.os.log_service,
@ -36,7 +36,7 @@ impl<'a> VmFacade<'a> {
&mut self.os.next_handle, &mut self.os.next_handle,
vm, vm,
signals, signals,
hw, platform,
) )
} }
@ -47,9 +47,9 @@ impl<'a> VmFacade<'a> {
pub fn debug_step_instruction( pub fn debug_step_instruction(
&mut self, &mut self,
vm: &mut VirtualMachine, vm: &mut VirtualMachine,
hw: &mut dyn HardwareBridge, platform: &mut dyn RuntimePlatform,
) -> Option<CrashReport> { ) -> Option<CrashReport> {
self.os.vm_runtime.debug_step_instruction(&mut self.os.log_service, vm, hw) self.os.vm_runtime.debug_step_instruction(&mut self.os.log_service, vm, platform)
} }
pub fn paused(&self) -> bool { pub fn paused(&self) -> bool {

View File

@ -2,7 +2,8 @@ use crate::{CrashReport, SystemOS};
use prometeu_hal::color::Color; use prometeu_hal::color::Color;
use prometeu_hal::primitives::Rect; use prometeu_hal::primitives::Rect;
use prometeu_hal::{ use prometeu_hal::{
FrameId, GfxUiCommand, HardwareBridge, InputSignals, RenderSubmission, ShellUiFramePacket, FrameId, GfxUiCommand, InputPlatform, InputSignals, RenderSubmission, RuntimePlatform,
ShellUiFramePacket,
}; };
use prometeu_vm::VirtualMachine; use prometeu_vm::VirtualMachine;
@ -100,31 +101,35 @@ impl PrometeuHub {
pub fn gui_update( pub fn gui_update(
&mut self, &mut self,
os: &mut SystemOS, os: &mut SystemOS,
hw: &mut dyn HardwareBridge, platform: &mut dyn RuntimePlatform,
) -> Option<SystemProfileAction> { ) -> Option<SystemProfileAction> {
let in_shell = os.windows().focused_window().is_some(); let in_shell = os.windows().focused_window().is_some();
if !in_shell { if !in_shell {
if !self.home_input_armed { if !self.home_input_armed {
self.home_input_armed = !activation_input_down(hw); self.home_input_armed = !activation_input_down(platform.input());
return None; return None;
} }
} else { } else {
self.home_input_armed = false; self.home_input_armed = false;
} }
if let Some(action) = let input = platform.input();
action_for_click(in_shell, hw.touch().x(), hw.touch().y(), hw.touch().f().pressed) if let Some(action) = action_for_click(
{ in_shell,
input.touch().x(),
input.touch().y(),
input.touch().f().pressed,
) {
return Some(action); return Some(action);
} }
None None
} }
pub fn render(&mut self, os: &mut SystemOS, hw: &mut dyn HardwareBridge) { pub fn render(&mut self, os: &mut SystemOS, platform: &mut dyn RuntimePlatform) {
let pointer_x = hw.touch().x(); let pointer_x = platform.input().touch().x();
let pointer_y = hw.touch().y(); let pointer_y = platform.input().touch().y();
let packet = if os.windows().window_count() == 0 { let packet = if os.windows().window_count() == 0 {
render_home_packet(pointer_x, pointer_y) render_home_packet(pointer_x, pointer_y)
@ -142,7 +147,10 @@ impl PrometeuHub {
} }
ShellUiFramePacket::new(commands) ShellUiFramePacket::new(commands)
}; };
hw.publish_render_submission(&RenderSubmission::shell_ui(FrameId::ZERO, packet)); platform
.render_submission_sink()
.submit_render_submission(RenderSubmission::shell_ui(FrameId::ZERO, packet))
.expect("hub render submission should publish");
} }
pub fn update_shell_profile( pub fn update_shell_profile(
@ -150,20 +158,20 @@ impl PrometeuHub {
os: &mut SystemOS, os: &mut SystemOS,
vm: &mut VirtualMachine, vm: &mut VirtualMachine,
signals: &InputSignals, signals: &InputSignals,
hw: &mut dyn HardwareBridge, platform: &mut dyn RuntimePlatform,
) -> SystemProfileUpdate { ) -> SystemProfileUpdate {
let mut crash = None; let mut crash = None;
let mut action = self.gui_update(os, hw); let mut action = self.gui_update(os, platform);
if os.windows().focused_window().is_some() { if os.windows().focused_window().is_some() {
if hw.pad().start().down { if platform.input().pad().start().down {
action = Some(SystemProfileAction::CloseShell); action = Some(SystemProfileAction::CloseShell);
} else if action != Some(SystemProfileAction::CloseShell) { } else if action != Some(SystemProfileAction::CloseShell) {
crash = os.vm().tick(vm, signals, hw); crash = os.vm().tick(vm, signals, platform);
} }
} }
self.render(os, hw); self.render(os, platform);
SystemProfileUpdate { crash, action } SystemProfileUpdate { crash, action }
} }
@ -188,8 +196,11 @@ fn rect_contains(rect: Rect, x: i32, y: i32) -> bool {
x >= rect.x && x < rect.x + rect.w && y >= rect.y && y < rect.y + rect.h x >= rect.x && x < rect.x + rect.w && y >= rect.y && y < rect.y + rect.h
} }
fn activation_input_down(hw: &dyn HardwareBridge) -> bool { fn activation_input_down(input: &dyn InputPlatform) -> bool {
hw.touch().f().down || hw.pad().a().down || hw.pad().b().down || hw.pad().start().down input.touch().f().down
|| input.pad().a().down
|| input.pad().b().down
|| input.pad().start().down
} }
fn render_home_packet(pointer_x: i32, pointer_y: i32) -> ShellUiFramePacket { fn render_home_packet(pointer_x: i32, pointer_y: i32) -> ShellUiFramePacket {

View File

@ -179,7 +179,7 @@ impl NativeInterface for VmRuntimeHost<'_> {
self.ensure_game_profile_syscall(syscall)?; self.ensure_game_profile_syscall(syscall)?;
let hw = ctx.require_hw()?; let platform = ctx.require_platform()?;
match syscall { match syscall {
Syscall::SystemHasCart => unreachable!(), Syscall::SystemHasCart => unreachable!(),
@ -187,7 +187,6 @@ impl NativeInterface for VmRuntimeHost<'_> {
Syscall::GfxClear => { Syscall::GfxClear => {
let color = self.get_color(expect_int(args, 0)?)?; let color = self.get_color(expect_int(args, 0)?)?;
self.gfx2d_commands.push(Gfx2dCommand::Clear { color }); self.gfx2d_commands.push(Gfx2dCommand::Clear { color });
hw.gfx_mut().clear(color);
Ok(()) Ok(())
} }
Syscall::GfxUiClear => { Syscall::GfxUiClear => {
@ -199,7 +198,6 @@ impl NativeInterface for VmRuntimeHost<'_> {
} }
let color = self.get_color(expect_int(args, 0)?)?; let color = self.get_color(expect_int(args, 0)?)?;
self.gfxui_commands.push(GfxUiCommand::Clear { color }); self.gfxui_commands.push(GfxUiCommand::Clear { color });
hw.gfx_mut().clear(color);
Ok(()) Ok(())
} }
Syscall::GfxFillRect => { Syscall::GfxFillRect => {
@ -210,7 +208,6 @@ impl NativeInterface for VmRuntimeHost<'_> {
let color = self.get_color(expect_int(args, 4)?)?; let color = self.get_color(expect_int(args, 4)?)?;
self.gfx2d_commands self.gfx2d_commands
.push(Gfx2dCommand::FillRect { rect: Rect { x, y, w, h }, color }); .push(Gfx2dCommand::FillRect { rect: Rect { x, y, w, h }, color });
hw.gfx_mut().fill_rect(x, y, w, h, color);
Ok(()) Ok(())
} }
Syscall::GfxUiFillRect => { Syscall::GfxUiFillRect => {
@ -227,7 +224,6 @@ impl NativeInterface for VmRuntimeHost<'_> {
let color = self.get_color(expect_int(args, 4)?)?; let color = self.get_color(expect_int(args, 4)?)?;
self.gfxui_commands self.gfxui_commands
.push(GfxUiCommand::FillRect { rect: Rect { x, y, w, h }, color }); .push(GfxUiCommand::FillRect { rect: Rect { x, y, w, h }, color });
hw.gfx_mut().fill_rect(x, y, w, h, color);
Ok(()) Ok(())
} }
Syscall::GfxDrawLine => { Syscall::GfxDrawLine => {
@ -243,7 +239,6 @@ impl NativeInterface for VmRuntimeHost<'_> {
y1: y2, y1: y2,
color, color,
}); });
hw.gfx_mut().draw_line(x1, y1, x2, y2, color);
Ok(()) Ok(())
} }
Syscall::GfxUiDrawLine => { Syscall::GfxUiDrawLine => {
@ -265,7 +260,6 @@ impl NativeInterface for VmRuntimeHost<'_> {
y1: y2, y1: y2,
color, color,
}); });
hw.gfx_mut().draw_line(x1, y1, x2, y2, color);
Ok(()) Ok(())
} }
Syscall::GfxDrawCircle => { Syscall::GfxDrawCircle => {
@ -274,7 +268,6 @@ impl NativeInterface for VmRuntimeHost<'_> {
let r = expect_int(args, 2)? as i32; let r = expect_int(args, 2)? as i32;
let color = self.get_color(expect_int(args, 3)?)?; let color = self.get_color(expect_int(args, 3)?)?;
self.gfx2d_commands.push(Gfx2dCommand::DrawCircle { x, y, radius: r, color }); self.gfx2d_commands.push(Gfx2dCommand::DrawCircle { x, y, radius: r, color });
hw.gfx_mut().draw_circle(x, y, r, color);
Ok(()) Ok(())
} }
Syscall::GfxUiDrawCircle => { Syscall::GfxUiDrawCircle => {
@ -289,7 +282,6 @@ impl NativeInterface for VmRuntimeHost<'_> {
let r = expect_int(args, 2)? as i32; let r = expect_int(args, 2)? as i32;
let color = self.get_color(expect_int(args, 3)?)?; let color = self.get_color(expect_int(args, 3)?)?;
self.gfxui_commands.push(GfxUiCommand::DrawCircle { x, y, radius: r, color }); self.gfxui_commands.push(GfxUiCommand::DrawCircle { x, y, radius: r, color });
hw.gfx_mut().draw_circle(x, y, r, color);
Ok(()) Ok(())
} }
Syscall::GfxDrawDisc => { Syscall::GfxDrawDisc => {
@ -305,7 +297,6 @@ impl NativeInterface for VmRuntimeHost<'_> {
border_color, border_color,
fill_color, fill_color,
}); });
hw.gfx_mut().draw_disc(x, y, r, border_color, fill_color);
Ok(()) Ok(())
} }
Syscall::GfxUiDrawDisc => { Syscall::GfxUiDrawDisc => {
@ -327,7 +318,6 @@ impl NativeInterface for VmRuntimeHost<'_> {
border_color, border_color,
fill_color, fill_color,
}); });
hw.gfx_mut().draw_disc(x, y, r, border_color, fill_color);
Ok(()) Ok(())
} }
Syscall::GfxDrawSquare => { Syscall::GfxDrawSquare => {
@ -342,7 +332,6 @@ impl NativeInterface for VmRuntimeHost<'_> {
border_color, border_color,
fill_color, fill_color,
}); });
hw.gfx_mut().draw_square(x, y, w, h, border_color, fill_color);
Ok(()) Ok(())
} }
Syscall::GfxUiDrawSquare => { Syscall::GfxUiDrawSquare => {
@ -363,7 +352,6 @@ impl NativeInterface for VmRuntimeHost<'_> {
border_color, border_color,
fill_color, fill_color,
}); });
hw.gfx_mut().draw_square(x, y, w, h, border_color, fill_color);
Ok(()) Ok(())
} }
Syscall::GfxDrawText => { Syscall::GfxDrawText => {
@ -372,7 +360,6 @@ impl NativeInterface for VmRuntimeHost<'_> {
let msg = expect_string(args, 2, "message")?; let msg = expect_string(args, 2, "message")?;
let color = self.get_color(expect_int(args, 3)?)?; let color = self.get_color(expect_int(args, 3)?)?;
self.gfx2d_commands.push(Gfx2dCommand::DrawText { x, y, text: msg.clone(), color }); self.gfx2d_commands.push(Gfx2dCommand::DrawText { x, y, text: msg.clone(), color });
hw.gfx_mut().draw_text(x, y, &msg, color);
Ok(()) Ok(())
} }
Syscall::GfxUiDrawText => { Syscall::GfxUiDrawText => {
@ -387,7 +374,6 @@ impl NativeInterface for VmRuntimeHost<'_> {
let msg = expect_string(args, 2, "message")?; let msg = expect_string(args, 2, "message")?;
let color = self.get_color(expect_int(args, 3)?)?; let color = self.get_color(expect_int(args, 3)?)?;
self.gfxui_commands.push(GfxUiCommand::DrawText { x, y, text: msg.clone(), color }); self.gfxui_commands.push(GfxUiCommand::DrawText { x, y, text: msg.clone(), color });
hw.gfx_mut().draw_text(x, y, &msg, color);
Ok(()) Ok(())
} }
Syscall::ComposerBindScene => { Syscall::ComposerBindScene => {
@ -399,23 +385,23 @@ impl NativeInterface for VmRuntimeHost<'_> {
} }
}; };
let bound = catch_unwind(AssertUnwindSafe(|| hw.bind_scene(scene_bank_id))) let status = catch_unwind(AssertUnwindSafe(|| {
.map_err(|payload| VmFault::Panic(Self::panic_payload_to_string(payload)))?; platform.game2d_frame_composer().bind_scene(scene_bank_id)
}))
.map_err(|payload| VmFault::Panic(Self::panic_payload_to_string(payload)))?;
let status =
if bound { ComposerOpStatus::Ok } else { ComposerOpStatus::SceneUnavailable };
ret.push_int(status as i64); ret.push_int(status as i64);
Ok(()) Ok(())
} }
Syscall::ComposerUnbindScene => { Syscall::ComposerUnbindScene => {
hw.unbind_scene(); platform.game2d_frame_composer().unbind_scene();
ret.push_int(ComposerOpStatus::Ok as i64); ret.push_int(ComposerOpStatus::Ok as i64);
Ok(()) Ok(())
} }
Syscall::ComposerSetCamera => { Syscall::ComposerSetCamera => {
let x = Self::int_arg_to_i32_trap(expect_int(args, 0)?, "camera x")?; let x = Self::int_arg_to_i32_trap(expect_int(args, 0)?, "camera x")?;
let y = Self::int_arg_to_i32_trap(expect_int(args, 1)?, "camera y")?; let y = Self::int_arg_to_i32_trap(expect_int(args, 1)?, "camera y")?;
hw.set_camera(x, y); platform.game2d_frame_composer().set_camera(x, y);
Ok(()) Ok(())
} }
Syscall::ComposerEmitSprite => { Syscall::ComposerEmitSprite => {
@ -463,12 +449,7 @@ impl NativeInterface for VmRuntimeHost<'_> {
} }
}; };
if !hw.has_glyph_bank(bank_id as usize) { let status = platform.game2d_frame_composer().emit_sprite(Sprite {
ret.push_int(ComposerOpStatus::BankInvalid as i64);
return Ok(());
}
let emitted = hw.emit_sprite(Sprite {
glyph: Glyph { glyph_id, palette_id }, glyph: Glyph { glyph_id, palette_id },
x, x,
y, y,
@ -479,8 +460,6 @@ impl NativeInterface for VmRuntimeHost<'_> {
flip_y, flip_y,
priority, priority,
}); });
let status =
if emitted { ComposerOpStatus::Ok } else { ComposerOpStatus::SpriteOverflow };
ret.push_int(status as i64); ret.push_int(status as i64);
Ok(()) Ok(())
} }
@ -507,7 +486,7 @@ impl NativeInterface for VmRuntimeHost<'_> {
return Ok(()); return Ok(());
} }
let status = hw.audio_mut().play( let status = platform.audio_mut().play(
0, 0,
sample_id_raw as u16, sample_id_raw as u16,
voice_id_raw as usize, voice_id_raw as usize,
@ -537,7 +516,8 @@ impl NativeInterface for VmRuntimeHost<'_> {
return Ok(()); return Ok(());
} }
if hw.assets().slot_info(SlotRef::audio(bank_id as usize)).asset_id.is_none() { if platform.assets().slot_info(SlotRef::audio(bank_id as usize)).asset_id.is_none()
{
ret.push_int(AudioOpStatus::BankInvalid as i64); ret.push_int(AudioOpStatus::BankInvalid as i64);
return Ok(()); return Ok(());
} }
@ -553,7 +533,7 @@ impl NativeInterface for VmRuntimeHost<'_> {
return Ok(()); return Ok(());
} }
let status = hw.audio_mut().play( let status = platform.audio_mut().play(
bank_id, bank_id,
sample_id_raw as u16, sample_id_raw as u16,
voice_id_raw as usize, voice_id_raw as usize,
@ -750,7 +730,7 @@ impl NativeInterface for VmRuntimeHost<'_> {
})?; })?;
let slot_index = expect_int(args, 1)? as usize; let slot_index = expect_int(args, 1)? as usize;
match hw.assets().load(asset_id, slot_index) { match platform.assets().load(asset_id, slot_index) {
Ok(handle) => { Ok(handle) => {
ret.push_int(AssetOpStatus::Ok as i64); ret.push_int(AssetOpStatus::Ok as i64);
ret.push_int(handle as i64); ret.push_int(handle as i64);
@ -764,16 +744,16 @@ impl NativeInterface for VmRuntimeHost<'_> {
} }
} }
Syscall::AssetStatus => { Syscall::AssetStatus => {
ret.push_int(hw.assets().status(expect_int(args, 0)? as u32) as i64); ret.push_int(platform.assets().status(expect_int(args, 0)? as u32) as i64);
Ok(()) Ok(())
} }
Syscall::AssetCommit => { Syscall::AssetCommit => {
let status = hw.assets().commit(expect_int(args, 0)? as u32); let status = platform.assets().commit(expect_int(args, 0)? as u32);
ret.push_int(status as i64); ret.push_int(status as i64);
Ok(()) Ok(())
} }
Syscall::AssetCancel => { Syscall::AssetCancel => {
let status = hw.assets().cancel(expect_int(args, 0)? as u32); let status = platform.assets().cancel(expect_int(args, 0)? as u32);
ret.push_int(status as i64); ret.push_int(status as i64);
Ok(()) Ok(())
} }
@ -783,7 +763,7 @@ impl NativeInterface for VmRuntimeHost<'_> {
1 => BankType::SOUNDS, 1 => BankType::SOUNDS,
_ => return Err(VmFault::Trap(TRAP_TYPE, "Invalid asset type".to_string())), _ => return Err(VmFault::Trap(TRAP_TYPE, "Invalid asset type".to_string())),
}; };
let telemetry = hw let telemetry = platform
.assets() .assets()
.bank_telemetry() .bank_telemetry()
.into_iter() .into_iter()

View File

@ -5,11 +5,10 @@ use prometeu_bytecode::TRAP_TYPE;
use prometeu_bytecode::Value; use prometeu_bytecode::Value;
use prometeu_bytecode::assembler::assemble; use prometeu_bytecode::assembler::assemble;
use prometeu_bytecode::model::{BytecodeModule, ConstantPoolEntry, FunctionMeta, SyscallDecl}; use prometeu_bytecode::model::{BytecodeModule, ConstantPoolEntry, FunctionMeta, SyscallDecl};
use prometeu_drivers::hardware::Hardware; use prometeu_drivers::TestPlatform;
use prometeu_drivers::{GlyphBankPoolInstaller, MemoryBanks, SceneBankPoolInstaller}; use prometeu_drivers::{GlyphBankPoolInstaller, MemoryBanks, SceneBankPoolInstaller};
use prometeu_hal::AudioOpStatus; use prometeu_hal::AudioOpStatus;
use prometeu_hal::ComposerOpStatus; use prometeu_hal::ComposerOpStatus;
use prometeu_hal::InputSignals;
use prometeu_hal::asset::{ use prometeu_hal::asset::{
AssetCodec, AssetEntry, AssetLoadError, AssetOpStatus, BankType, LoadStatus, AssetCodec, AssetEntry, AssetLoadError, AssetOpStatus, BankType, LoadStatus,
}; };
@ -22,6 +21,7 @@ use prometeu_hal::scene_layer::{ParallaxFactor, SceneLayer};
use prometeu_hal::syscalls::caps; use prometeu_hal::syscalls::caps;
use prometeu_hal::tile::Tile; use prometeu_hal::tile::Tile;
use prometeu_hal::tilemap::TileMap; use prometeu_hal::tilemap::TileMap;
use prometeu_hal::{InputSignals, RuntimePlatform};
use prometeu_vm::VmInitError; use prometeu_vm::VmInitError;
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
@ -247,7 +247,7 @@ fn tick_returns_error_when_vm_ends_slice_with_trap() {
let mut open_files: HashMap<u32, String> = HashMap::new(); let mut open_files: HashMap<u32, String> = HashMap::new();
let mut next_handle = 1; let mut next_handle = 1;
let mut vm = VirtualMachine::default(); let mut vm = VirtualMachine::default();
let mut hardware = Hardware::new(); let mut platform = TestPlatform::new();
let signals = InputSignals::default(); let signals = InputSignals::default();
let code = assemble("PUSH_BOOL 1\nHOSTCALL 0\nHALT").expect("assemble"); let code = assemble("PUSH_BOOL 1\nHOSTCALL 0\nHALT").expect("assemble");
let program = serialized_single_function_module( let program = serialized_single_function_module(
@ -274,7 +274,7 @@ fn tick_returns_error_when_vm_ends_slice_with_trap() {
&mut next_handle, &mut next_handle,
&mut vm, &mut vm,
&signals, &signals,
&mut hardware, &mut platform,
) )
.expect("trap must surface as runtime error"); .expect("trap must surface as runtime error");
@ -298,7 +298,7 @@ fn tick_system_profile_rejects_gfx_game_surface() {
let mut open_files: HashMap<u32, String> = HashMap::new(); let mut open_files: HashMap<u32, String> = HashMap::new();
let mut next_handle = 1; let mut next_handle = 1;
let mut vm = VirtualMachine::default(); let mut vm = VirtualMachine::default();
let mut hardware = Hardware::new(); let mut platform = TestPlatform::new();
let signals = InputSignals::default(); let signals = InputSignals::default();
let code = assemble("PUSH_I32 0\nHOSTCALL 0\nHALT").expect("assemble"); let code = assemble("PUSH_I32 0\nHOSTCALL 0\nHALT").expect("assemble");
let program = serialized_single_function_module( let program = serialized_single_function_module(
@ -324,7 +324,7 @@ fn tick_system_profile_rejects_gfx_game_surface() {
&mut next_handle, &mut next_handle,
&mut vm, &mut vm,
&signals, &signals,
&mut hardware, &mut platform,
) )
.expect("system gfx call must trap"); .expect("system gfx call must trap");
@ -347,7 +347,7 @@ fn tick_system_profile_rejects_composer_game_surface() {
let mut open_files: HashMap<u32, String> = HashMap::new(); let mut open_files: HashMap<u32, String> = HashMap::new();
let mut next_handle = 1; let mut next_handle = 1;
let mut vm = VirtualMachine::default(); let mut vm = VirtualMachine::default();
let mut hardware = Hardware::new(); let mut platform = TestPlatform::new();
let signals = InputSignals::default(); let signals = InputSignals::default();
let code = assemble("PUSH_I32 0\nHOSTCALL 0\nHALT").expect("assemble"); let code = assemble("PUSH_I32 0\nHOSTCALL 0\nHALT").expect("assemble");
let program = serialized_single_function_module( let program = serialized_single_function_module(
@ -373,7 +373,7 @@ fn tick_system_profile_rejects_composer_game_surface() {
&mut next_handle, &mut next_handle,
&mut vm, &mut vm,
&signals, &signals,
&mut hardware, &mut platform,
) )
.expect("system composer call must trap"); .expect("system composer call must trap");
@ -398,7 +398,7 @@ fn tick_game_profile_rejects_gfxui_shell_surface() {
let mut open_files: HashMap<u32, String> = HashMap::new(); let mut open_files: HashMap<u32, String> = HashMap::new();
let mut next_handle = 1; let mut next_handle = 1;
let mut vm = VirtualMachine::default(); let mut vm = VirtualMachine::default();
let mut hardware = Hardware::new(); let mut platform = TestPlatform::new();
let signals = InputSignals::default(); let signals = InputSignals::default();
let code = assemble("PUSH_I32 0\nHOSTCALL 0\nHALT").expect("assemble"); let code = assemble("PUSH_I32 0\nHOSTCALL 0\nHALT").expect("assemble");
let program = serialized_single_function_module( let program = serialized_single_function_module(
@ -424,7 +424,7 @@ fn tick_game_profile_rejects_gfxui_shell_surface() {
&mut next_handle, &mut next_handle,
&mut vm, &mut vm,
&signals, &signals,
&mut hardware, &mut platform,
) )
.expect("game gfxui call must trap"); .expect("game gfxui call must trap");
@ -447,7 +447,7 @@ fn tick_shell_profile_closes_gfxui_commands_into_shell_packet() {
let mut open_files: HashMap<u32, String> = HashMap::new(); let mut open_files: HashMap<u32, String> = HashMap::new();
let mut next_handle = 1; let mut next_handle = 1;
let mut vm = VirtualMachine::default(); let mut vm = VirtualMachine::default();
let mut hardware = Hardware::new(); let mut platform = TestPlatform::new();
let signals = InputSignals::default(); let signals = InputSignals::default();
let code = assemble("PUSH_I32 287454020\nHOSTCALL 0\nFRAME_SYNC\nHALT").expect("assemble"); let code = assemble("PUSH_I32 287454020\nHOSTCALL 0\nFRAME_SYNC\nHALT").expect("assemble");
let program = serialized_single_function_module( let program = serialized_single_function_module(
@ -472,7 +472,7 @@ fn tick_shell_profile_closes_gfxui_commands_into_shell_packet() {
&mut next_handle, &mut next_handle,
&mut vm, &mut vm,
&signals, &signals,
&mut hardware, &mut platform,
); );
assert!(report.is_none()); assert!(report.is_none());
@ -483,11 +483,110 @@ fn tick_shell_profile_closes_gfxui_commands_into_shell_packet() {
}; };
assert_eq!(packet.commands.len(), 1); assert_eq!(packet.commands.len(), 1);
assert!(matches!(packet.commands[0], prometeu_hal::GfxUiCommand::Clear { .. })); assert!(matches!(packet.commands[0], prometeu_hal::GfxUiCommand::Clear { .. }));
assert_eq!(hardware.gfx.front_buffer()[0], Color::from_raw(0x11223344).raw()); assert_eq!(platform.local_hardware().gfx.front_buffer()[0], Color::from_raw(0x11223344).raw());
assert_eq!(runtime.frame_scheduler.completed_game_frames(), 0); assert_eq!(runtime.frame_scheduler.completed_game_frames(), 0);
assert_eq!(runtime.frame_scheduler.active_game_frame_id(), None); assert_eq!(runtime.frame_scheduler.active_game_frame_id(), None);
} }
#[test]
fn tick_gfx2d_clear_buffers_without_immediate_pixel_mutation_before_frame_close() {
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<u32, String> = HashMap::new();
let mut next_handle = 1;
let mut vm = VirtualMachine::default();
let mut platform = TestPlatform::new();
let signals = InputSignals::default();
let code = assemble("PUSH_I32 287454020\nHOSTCALL 0\nJMP 0").expect("assemble");
let program = serialized_single_function_module(
code,
vec![SyscallDecl {
module: "gfx2d".into(),
name: "clear".into(),
version: 1,
arg_slots: 1,
ret_slots: 0,
}],
);
let cartridge = cartridge_with_program(program, caps::GFX);
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 platform,
);
assert!(report.is_none());
assert!(runtime.render_manager.latest_complete_submission().is_none());
assert!(matches!(
runtime.gfx2d_commands.first(),
Some(prometeu_hal::Gfx2dCommand::Clear { color })
if *color == Color::from_raw(0x11223344)
));
assert_ne!(platform.local_hardware().gfx.front_buffer()[0], Color::from_raw(0x11223344).raw());
}
#[test]
fn tick_gfx2d_draw_text_buffers_without_immediate_pixel_mutation_before_frame_close() {
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<u32, String> = HashMap::new();
let mut next_handle = 1;
let mut vm = VirtualMachine::default();
let mut platform = TestPlatform::new();
let signals = InputSignals::default();
let code =
assemble("PUSH_I32 0\nPUSH_I32 0\nPUSH_CONST 0\nPUSH_I32 287454020\nHOSTCALL 0\nJMP 0")
.expect("assemble");
let program = serialized_single_function_module_with_consts(
code,
vec![ConstantPoolEntry::String("I".into())],
vec![SyscallDecl {
module: "gfx2d".into(),
name: "draw_text".into(),
version: 1,
arg_slots: 4,
ret_slots: 0,
}],
);
let cartridge = cartridge_with_program(program, caps::GFX);
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 platform,
);
assert!(report.is_none());
assert!(runtime.render_manager.latest_complete_submission().is_none());
assert!(matches!(
runtime.gfx2d_commands.first(),
Some(prometeu_hal::Gfx2dCommand::DrawText { x: 0, y: 0, text, color })
if text == "I" && *color == Color::from_raw(0x11223344)
));
assert_ne!(platform.local_hardware().gfx.front_buffer()[0], Color::from_raw(0x11223344).raw());
}
#[test] #[test]
fn tick_system_profile_rejects_bank_game_surface() { fn tick_system_profile_rejects_bank_game_surface() {
let mut runtime = VirtualMachineRuntime::new(None); let mut runtime = VirtualMachineRuntime::new(None);
@ -498,7 +597,7 @@ fn tick_system_profile_rejects_bank_game_surface() {
let mut open_files: HashMap<u32, String> = HashMap::new(); let mut open_files: HashMap<u32, String> = HashMap::new();
let mut next_handle = 1; let mut next_handle = 1;
let mut vm = VirtualMachine::default(); let mut vm = VirtualMachine::default();
let mut hardware = Hardware::new(); let mut platform = TestPlatform::new();
let signals = InputSignals::default(); let signals = InputSignals::default();
let code = assemble("PUSH_I32 0\nHOSTCALL 0\nHALT").expect("assemble"); let code = assemble("PUSH_I32 0\nHOSTCALL 0\nHALT").expect("assemble");
let program = serialized_single_function_module( let program = serialized_single_function_module(
@ -524,7 +623,7 @@ fn tick_system_profile_rejects_bank_game_surface() {
&mut next_handle, &mut next_handle,
&mut vm, &mut vm,
&signals, &signals,
&mut hardware, &mut platform,
) )
.expect("system bank call must trap"); .expect("system bank call must trap");
@ -547,7 +646,7 @@ fn tick_system_profile_can_use_shared_log_transport() {
let mut open_files: HashMap<u32, String> = HashMap::new(); let mut open_files: HashMap<u32, String> = HashMap::new();
let mut next_handle = 1; let mut next_handle = 1;
let mut vm = VirtualMachine::default(); let mut vm = VirtualMachine::default();
let mut hardware = Hardware::new(); let mut platform = TestPlatform::new();
let signals = InputSignals::default(); let signals = InputSignals::default();
let code = assemble("PUSH_I32 2\nPUSH_CONST 0\nHOSTCALL 0\nHALT").expect("assemble"); let code = assemble("PUSH_I32 2\nPUSH_CONST 0\nHOSTCALL 0\nHALT").expect("assemble");
let program = serialized_single_function_module_with_consts( let program = serialized_single_function_module_with_consts(
@ -573,7 +672,7 @@ fn tick_system_profile_can_use_shared_log_transport() {
&mut next_handle, &mut next_handle,
&mut vm, &mut vm,
&signals, &signals,
&mut hardware, &mut platform,
); );
assert!(report.is_none(), "system log transport must remain internally shared"); assert!(report.is_none(), "system log transport must remain internally shared");
@ -590,7 +689,7 @@ fn tick_returns_panic_report_distinct_from_trap() {
let mut open_files: HashMap<u32, String> = HashMap::new(); let mut open_files: HashMap<u32, String> = HashMap::new();
let mut next_handle = 1; let mut next_handle = 1;
let mut vm = VirtualMachine::new(assemble("HOSTCALL 0\nHALT").expect("assemble"), vec![]); let mut vm = VirtualMachine::new(assemble("HOSTCALL 0\nHALT").expect("assemble"), vec![]);
let mut hardware = Hardware::new(); let mut platform = TestPlatform::new();
let signals = InputSignals::default(); let signals = InputSignals::default();
let report = runtime let report = runtime
@ -603,7 +702,7 @@ fn tick_returns_panic_report_distinct_from_trap() {
&mut next_handle, &mut next_handle,
&mut vm, &mut vm,
&signals, &signals,
&mut hardware, &mut platform,
) )
.expect("panic must surface as runtime error"); .expect("panic must surface as runtime error");
@ -635,12 +734,12 @@ fn tick_renders_bound_eight_pixel_scene_through_frame_composer_path() {
let banks = Arc::new(MemoryBanks::new()); let banks = Arc::new(MemoryBanks::new());
banks.install_glyph_bank(0, Arc::new(runtime_test_glyph_bank(TileSize::Size8, 2, Color::BLUE))); banks.install_glyph_bank(0, Arc::new(runtime_test_glyph_bank(TileSize::Size8, 2, Color::BLUE)));
banks.install_scene_bank(0, Arc::new(runtime_test_scene(0, 2, TileSize::Size8))); banks.install_scene_bank(0, Arc::new(runtime_test_scene(0, 2, TileSize::Size8)));
let mut hardware = Hardware::new_with_memory_banks(Arc::clone(&banks)); let mut platform = TestPlatform::new_with_memory_banks(Arc::clone(&banks));
let glyph_slot_index = hardware.assets.glyph_asset_slot_index(); let glyph_slot_index = platform.local_hardware().assets.glyph_asset_slot_index();
let mut slots = [None; 16]; let mut slots = [None; 16];
slots[0] = Some(0); slots[0] = Some(0);
glyph_slot_index.rebuild_from_slots(&slots); glyph_slot_index.rebuild_from_slots(&slots);
assert!(hardware.frame_composer.bind_scene(0)); assert!(platform.local_hardware_mut().frame_composer.bind_scene(0));
runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize"); runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize");
let report = runtime.tick( let report = runtime.tick(
@ -652,11 +751,11 @@ fn tick_renders_bound_eight_pixel_scene_through_frame_composer_path() {
&mut next_handle, &mut next_handle,
&mut vm, &mut vm,
&signals, &signals,
&mut hardware, &mut platform,
); );
assert!(report.is_none(), "frame render path must not crash"); assert!(report.is_none(), "frame render path must not crash");
assert_eq!(hardware.gfx.front_buffer()[0], Color::BLUE.raw()); assert_eq!(platform.local_hardware().gfx.front_buffer()[0], Color::BLUE.raw());
} }
#[test] #[test]
@ -708,8 +807,8 @@ fn tick_renders_scene_through_public_composer_syscalls() {
let banks = Arc::new(MemoryBanks::new()); let banks = Arc::new(MemoryBanks::new());
banks.install_glyph_bank(0, Arc::new(runtime_test_glyph_bank(TileSize::Size8, 2, Color::BLUE))); banks.install_glyph_bank(0, Arc::new(runtime_test_glyph_bank(TileSize::Size8, 2, Color::BLUE)));
banks.install_scene_bank(0, Arc::new(runtime_test_scene(0, 2, TileSize::Size8))); banks.install_scene_bank(0, Arc::new(runtime_test_scene(0, 2, TileSize::Size8)));
let mut hardware = Hardware::new_with_memory_banks(Arc::clone(&banks)); let mut platform = TestPlatform::new_with_memory_banks(Arc::clone(&banks));
let glyph_slot_index = hardware.assets.glyph_asset_slot_index(); let glyph_slot_index = platform.local_hardware().assets.glyph_asset_slot_index();
let mut slots = [None; 16]; let mut slots = [None; 16];
slots[0] = Some(0); slots[0] = Some(0);
glyph_slot_index.rebuild_from_slots(&slots); glyph_slot_index.rebuild_from_slots(&slots);
@ -724,12 +823,12 @@ fn tick_renders_scene_through_public_composer_syscalls() {
&mut next_handle, &mut next_handle,
&mut vm, &mut vm,
&signals, &signals,
&mut hardware, &mut platform,
); );
assert!(report.is_none(), "public composer path must not crash"); assert!(report.is_none(), "public composer path must not crash");
assert_eq!(vm.operand_stack_top(1), vec![Value::Int64(ComposerOpStatus::Ok as i64)]); assert_eq!(vm.operand_stack_top(1), vec![Value::Int64(ComposerOpStatus::Ok as i64)]);
assert_eq!(hardware.gfx.front_buffer()[0], Color::BLUE.raw()); assert_eq!(platform.local_hardware().gfx.front_buffer()[0], Color::BLUE.raw());
} }
#[test] #[test]
@ -759,7 +858,7 @@ fn tick_draw_text_survives_no_scene_frame_path() {
}], }],
); );
let cartridge = cartridge_with_program(program, caps::GFX); let cartridge = cartridge_with_program(program, caps::GFX);
let mut hardware = Hardware::new(); let mut platform = TestPlatform::new();
runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize"); runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize");
let report = runtime.tick( let report = runtime.tick(
@ -771,7 +870,7 @@ fn tick_draw_text_survives_no_scene_frame_path() {
&mut next_handle, &mut next_handle,
&mut vm, &mut vm,
&signals, &signals,
&mut hardware, &mut platform,
); );
assert!(report.is_none(), "no-scene overlay text must not crash"); assert!(report.is_none(), "no-scene overlay text must not crash");
@ -782,7 +881,7 @@ fn tick_draw_text_survives_no_scene_frame_path() {
}; };
assert_eq!(packet.gfx2d.len(), 1); assert_eq!(packet.gfx2d.len(), 1);
assert!(matches!(packet.gfx2d[0], prometeu_hal::Gfx2dCommand::DrawText { .. })); assert!(matches!(packet.gfx2d[0], prometeu_hal::Gfx2dCommand::DrawText { .. }));
assert_eq!(hardware.gfx.front_buffer()[0], Color::from_raw(0x11223344).raw()); assert_eq!(platform.local_hardware().gfx.front_buffer()[0], Color::from_raw(0x11223344).raw());
} }
#[test] #[test]
@ -808,7 +907,7 @@ fn tick_game_submissions_are_immutable_and_latest_complete_wins() {
}], }],
); );
let cartridge = cartridge_with_program(program, caps::GFX); let cartridge = cartridge_with_program(program, caps::GFX);
let mut hardware = Hardware::new(); let mut platform = TestPlatform::new();
runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize"); runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize");
let first_report = runtime.tick( let first_report = runtime.tick(
@ -820,7 +919,7 @@ fn tick_game_submissions_are_immutable_and_latest_complete_wins() {
&mut next_handle, &mut next_handle,
&mut vm, &mut vm,
&signals, &signals,
&mut hardware, &mut platform,
); );
assert!(first_report.is_none()); assert!(first_report.is_none());
let first_submission = let first_submission =
@ -834,7 +933,7 @@ fn tick_game_submissions_are_immutable_and_latest_complete_wins() {
first_packet.gfx2d[0], first_packet.gfx2d[0],
prometeu_hal::Gfx2dCommand::Clear { color } if color == Color::from_raw(0x11223344) prometeu_hal::Gfx2dCommand::Clear { color } if color == Color::from_raw(0x11223344)
)); ));
assert_eq!(hardware.gfx.front_buffer()[0], Color::from_raw(0x11223344).raw()); assert_eq!(platform.local_hardware().gfx.front_buffer()[0], Color::from_raw(0x11223344).raw());
assert_eq!(runtime.frame_scheduler.completed_game_frames(), 1); assert_eq!(runtime.frame_scheduler.completed_game_frames(), 1);
let second_packet = prometeu_hal::Game2DFramePacket::new( let second_packet = prometeu_hal::Game2DFramePacket::new(
@ -858,8 +957,11 @@ fn tick_game_submissions_are_immutable_and_latest_complete_wins() {
first_packet.gfx2d[0], first_packet.gfx2d[0],
prometeu_hal::Gfx2dCommand::Clear { color } if color == Color::from_raw(0x11223344) prometeu_hal::Gfx2dCommand::Clear { color } if color == Color::from_raw(0x11223344)
)); ));
prometeu_hal::HardwareBridge::publish_render_submission(&mut hardware, latest); platform
assert_eq!(hardware.gfx.front_buffer()[0], Color::from_raw(0x55667788).raw()); .render_submission_sink()
.submit_render_submission(latest.clone())
.expect("local sink should publish latest submission");
assert_eq!(platform.local_hardware().gfx.front_buffer()[0], Color::from_raw(0x55667788).raw());
} }
#[test] #[test]
@ -961,7 +1063,7 @@ fn tick_numeric_happy_path_records_zero_internal_allocations() {
let mut open_files: HashMap<u32, String> = HashMap::new(); let mut open_files: HashMap<u32, String> = HashMap::new();
let mut next_handle = 1; let mut next_handle = 1;
let mut vm = VirtualMachine::default(); let mut vm = VirtualMachine::default();
let mut hardware = Hardware::new(); let mut platform = TestPlatform::new();
let signals = InputSignals::default(); let signals = InputSignals::default();
let code = let code =
assemble("PUSH_I32 1\nPUSH_I32 2\nADD\nPOP_N 1\nFRAME_SYNC\nHALT").expect("assemble"); assemble("PUSH_I32 1\nPUSH_I32 2\nADD\nPOP_N 1\nFRAME_SYNC\nHALT").expect("assemble");
@ -978,7 +1080,7 @@ fn tick_numeric_happy_path_records_zero_internal_allocations() {
&mut next_handle, &mut next_handle,
&mut vm, &mut vm,
&signals, &signals,
&mut hardware, &mut platform,
); );
assert!(report.is_none()); assert!(report.is_none());
@ -997,7 +1099,7 @@ fn tick_already_materialized_string_path_records_zero_internal_allocations() {
let mut open_files: HashMap<u32, String> = HashMap::new(); let mut open_files: HashMap<u32, String> = HashMap::new();
let mut next_handle = 1; let mut next_handle = 1;
let mut vm = VirtualMachine::default(); let mut vm = VirtualMachine::default();
let mut hardware = Hardware::new(); let mut platform = TestPlatform::new();
let signals = InputSignals::default(); let signals = InputSignals::default();
let code = assemble("PUSH_CONST 0\nSET_GLOBAL 0\nGET_GLOBAL 0\nPOP_N 1\nFRAME_SYNC\nHALT") let code = assemble("PUSH_CONST 0\nSET_GLOBAL 0\nGET_GLOBAL 0\nPOP_N 1\nFRAME_SYNC\nHALT")
.expect("assemble"); .expect("assemble");
@ -1018,7 +1120,7 @@ fn tick_already_materialized_string_path_records_zero_internal_allocations() {
&mut next_handle, &mut next_handle,
&mut vm, &mut vm,
&signals, &signals,
&mut hardware, &mut platform,
); );
assert!(report.is_none()); assert!(report.is_none());
@ -1093,7 +1195,7 @@ fn tick_composer_bind_scene_operational_error_returns_status_not_crash() {
let mut open_files: HashMap<u32, String> = HashMap::new(); let mut open_files: HashMap<u32, String> = HashMap::new();
let mut next_handle = 1; let mut next_handle = 1;
let mut vm = VirtualMachine::default(); let mut vm = VirtualMachine::default();
let mut hardware = Hardware::new(); let mut platform = TestPlatform::new();
let signals = InputSignals::default(); let signals = InputSignals::default();
let code = assemble("PUSH_I32 99\nHOSTCALL 0\nHALT").expect("assemble"); let code = assemble("PUSH_I32 99\nHOSTCALL 0\nHALT").expect("assemble");
let program = serialized_single_function_module( let program = serialized_single_function_module(
@ -1118,7 +1220,7 @@ fn tick_composer_bind_scene_operational_error_returns_status_not_crash() {
&mut next_handle, &mut next_handle,
&mut vm, &mut vm,
&signals, &signals,
&mut hardware, &mut platform,
); );
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());
@ -1138,7 +1240,7 @@ fn tick_composer_emit_sprite_operational_error_returns_status_not_crash() {
let mut open_files: HashMap<u32, String> = HashMap::new(); let mut open_files: HashMap<u32, String> = HashMap::new();
let mut next_handle = 1; let mut next_handle = 1;
let mut vm = VirtualMachine::default(); let mut vm = VirtualMachine::default();
let mut hardware = Hardware::new(); let mut platform = TestPlatform::new();
let signals = InputSignals::default(); let signals = InputSignals::default();
let code = assemble( let code = assemble(
"PUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_BOOL 0\nPUSH_BOOL 0\nPUSH_I32 0\nHOSTCALL 0\nHALT", "PUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_BOOL 0\nPUSH_BOOL 0\nPUSH_I32 0\nHOSTCALL 0\nHALT",
@ -1166,7 +1268,7 @@ fn tick_composer_emit_sprite_operational_error_returns_status_not_crash() {
&mut next_handle, &mut next_handle,
&mut vm, &mut vm,
&signals, &signals,
&mut hardware, &mut platform,
); );
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());
@ -1183,7 +1285,7 @@ fn tick_composer_emit_sprite_invalid_layer_returns_status_not_crash() {
let mut open_files: HashMap<u32, String> = HashMap::new(); let mut open_files: HashMap<u32, String> = HashMap::new();
let mut next_handle = 1; let mut next_handle = 1;
let mut vm = VirtualMachine::default(); let mut vm = VirtualMachine::default();
let mut hardware = Hardware::new(); let mut platform = TestPlatform::new();
let signals = InputSignals::default(); let signals = InputSignals::default();
let code = assemble( let code = assemble(
"PUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 4\nPUSH_I32 0\nPUSH_BOOL 0\nPUSH_BOOL 0\nPUSH_I32 0\nHOSTCALL 0\nHALT", "PUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 4\nPUSH_I32 0\nPUSH_BOOL 0\nPUSH_BOOL 0\nPUSH_I32 0\nHOSTCALL 0\nHALT",
@ -1211,7 +1313,7 @@ fn tick_composer_emit_sprite_invalid_layer_returns_status_not_crash() {
&mut next_handle, &mut next_handle,
&mut vm, &mut vm,
&signals, &signals,
&mut hardware, &mut platform,
); );
assert!(report.is_none(), "invalid layer must not crash"); assert!(report.is_none(), "invalid layer must not crash");
assert!(vm.is_halted()); assert!(vm.is_halted());
@ -1228,7 +1330,7 @@ fn tick_composer_emit_sprite_invalid_range_returns_status_not_crash() {
let mut open_files: HashMap<u32, String> = HashMap::new(); let mut open_files: HashMap<u32, String> = HashMap::new();
let mut next_handle = 1; let mut next_handle = 1;
let mut vm = VirtualMachine::default(); let mut vm = VirtualMachine::default();
let mut hardware = Hardware::new(); let mut platform = TestPlatform::new();
let signals = InputSignals::default(); let signals = InputSignals::default();
let code = assemble( let code = assemble(
"PUSH_I32 0\nPUSH_I32 64\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_BOOL 0\nPUSH_BOOL 0\nPUSH_I32 0\nHOSTCALL 0\nHALT", "PUSH_I32 0\nPUSH_I32 64\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_BOOL 0\nPUSH_BOOL 0\nPUSH_I32 0\nHOSTCALL 0\nHALT",
@ -1247,7 +1349,7 @@ fn tick_composer_emit_sprite_invalid_range_returns_status_not_crash() {
let cartridge = cartridge_with_program(program, caps::GFX); let cartridge = cartridge_with_program(program, caps::GFX);
let asset_data = test_glyph_asset_data(); let asset_data = test_glyph_asset_data();
hardware.assets.initialize_for_cartridge( platform.local_hardware().assets.initialize_for_cartridge(
vec![test_glyph_asset_entry("tile_asset", asset_data.len())], vec![test_glyph_asset_entry("tile_asset", asset_data.len())],
vec![prometeu_hal::asset::PreloadEntry { asset_id: 7, slot: 0 }], vec![prometeu_hal::asset::PreloadEntry { asset_id: 7, slot: 0 }],
AssetsPayloadSource::from_bytes(asset_data), AssetsPayloadSource::from_bytes(asset_data),
@ -1263,7 +1365,7 @@ fn tick_composer_emit_sprite_invalid_range_returns_status_not_crash() {
&mut next_handle, &mut next_handle,
&mut vm, &mut vm,
&signals, &signals,
&mut hardware, &mut platform,
); );
assert!(report.is_none(), "invalid composer parameter range must not crash"); assert!(report.is_none(), "invalid composer parameter range must not crash");
assert!(vm.is_halted()); assert!(vm.is_halted());
@ -1283,7 +1385,7 @@ fn tick_audio_play_sample_operational_error_returns_status_not_crash() {
let mut open_files: HashMap<u32, String> = HashMap::new(); let mut open_files: HashMap<u32, String> = HashMap::new();
let mut next_handle = 1; let mut next_handle = 1;
let mut vm = VirtualMachine::default(); let mut vm = VirtualMachine::default();
let mut hardware = Hardware::new(); let mut platform = TestPlatform::new();
let signals = InputSignals::default(); let signals = InputSignals::default();
let code = assemble( let code = assemble(
"PUSH_I32 -1\nPUSH_I32 0\nPUSH_I32 255\nPUSH_I32 128\nPUSH_I32 1\nHOSTCALL 0\nHALT", "PUSH_I32 -1\nPUSH_I32 0\nPUSH_I32 255\nPUSH_I32 128\nPUSH_I32 1\nHOSTCALL 0\nHALT",
@ -1311,7 +1413,7 @@ fn tick_audio_play_sample_operational_error_returns_status_not_crash() {
&mut next_handle, &mut next_handle,
&mut vm, &mut vm,
&signals, &signals,
&mut hardware, &mut platform,
); );
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());
@ -1328,7 +1430,7 @@ fn tick_audio_play_voice_invalid_returns_status_not_crash() {
let mut open_files: HashMap<u32, String> = HashMap::new(); let mut open_files: HashMap<u32, String> = HashMap::new();
let mut next_handle = 1; let mut next_handle = 1;
let mut vm = VirtualMachine::default(); let mut vm = VirtualMachine::default();
let mut hardware = Hardware::new(); let mut platform = TestPlatform::new();
let signals = InputSignals::default(); let signals = InputSignals::default();
let code = assemble( let code = assemble(
"PUSH_I32 0\nPUSH_I32 0\nPUSH_I32 16\nPUSH_I32 255\nPUSH_I32 128\nPUSH_I32 1\nPUSH_I32 0\nHOSTCALL 0\nHALT", "PUSH_I32 0\nPUSH_I32 0\nPUSH_I32 16\nPUSH_I32 255\nPUSH_I32 128\nPUSH_I32 1\nPUSH_I32 0\nHOSTCALL 0\nHALT",
@ -1356,7 +1458,7 @@ fn tick_audio_play_voice_invalid_returns_status_not_crash() {
&mut next_handle, &mut next_handle,
&mut vm, &mut vm,
&signals, &signals,
&mut hardware, &mut platform,
); );
assert!(report.is_none(), "invalid voice must not crash"); assert!(report.is_none(), "invalid voice must not crash");
assert!(vm.is_halted()); assert!(vm.is_halted());
@ -1373,7 +1475,7 @@ fn tick_audio_play_missing_asset_returns_status_not_crash() {
let mut open_files: HashMap<u32, String> = HashMap::new(); let mut open_files: HashMap<u32, String> = HashMap::new();
let mut next_handle = 1; let mut next_handle = 1;
let mut vm = VirtualMachine::default(); let mut vm = VirtualMachine::default();
let mut hardware = Hardware::new(); let mut platform = TestPlatform::new();
let signals = InputSignals::default(); let signals = InputSignals::default();
let code = assemble( let code = assemble(
"PUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 255\nPUSH_I32 128\nPUSH_I32 1\nPUSH_I32 0\nHOSTCALL 0\nHALT", "PUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 255\nPUSH_I32 128\nPUSH_I32 1\nPUSH_I32 0\nHOSTCALL 0\nHALT",
@ -1401,7 +1503,7 @@ fn tick_audio_play_missing_asset_returns_status_not_crash() {
&mut next_handle, &mut next_handle,
&mut vm, &mut vm,
&signals, &signals,
&mut hardware, &mut platform,
); );
assert!(report.is_none(), "missing audio asset must not crash"); assert!(report.is_none(), "missing audio asset must not crash");
assert!(vm.is_halted()); assert!(vm.is_halted());
@ -1418,7 +1520,7 @@ fn tick_audio_play_type_mismatch_surfaces_trap_not_panic() {
let mut open_files: HashMap<u32, String> = HashMap::new(); let mut open_files: HashMap<u32, String> = HashMap::new();
let mut next_handle = 1; let mut next_handle = 1;
let mut vm = VirtualMachine::default(); let mut vm = VirtualMachine::default();
let mut hardware = Hardware::new(); let mut platform = TestPlatform::new();
let signals = InputSignals::default(); let signals = InputSignals::default();
let code = assemble( let code = assemble(
"PUSH_BOOL 1\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 255\nPUSH_I32 128\nPUSH_I32 1\nPUSH_I32 0\nHOSTCALL 0\nHALT", "PUSH_BOOL 1\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 255\nPUSH_I32 128\nPUSH_I32 1\nPUSH_I32 0\nHOSTCALL 0\nHALT",
@ -1447,7 +1549,7 @@ fn tick_audio_play_type_mismatch_surfaces_trap_not_panic() {
&mut next_handle, &mut next_handle,
&mut vm, &mut vm,
&signals, &signals,
&mut hardware, &mut platform,
) )
.expect("type mismatch must surface as trap"); .expect("type mismatch must surface as trap");
match report { match report {
@ -1470,7 +1572,7 @@ fn tick_composer_emit_sprite_type_mismatch_surfaces_trap_not_panic() {
let mut open_files: HashMap<u32, String> = HashMap::new(); let mut open_files: HashMap<u32, String> = HashMap::new();
let mut next_handle = 1; let mut next_handle = 1;
let mut vm = VirtualMachine::default(); let mut vm = VirtualMachine::default();
let mut hardware = Hardware::new(); let mut platform = TestPlatform::new();
let signals = InputSignals::default(); let signals = InputSignals::default();
let code = assemble( let code = assemble(
"PUSH_BOOL 1\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_BOOL 0\nPUSH_BOOL 0\nPUSH_I32 0\nHOSTCALL 0\nHALT", "PUSH_BOOL 1\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_BOOL 0\nPUSH_BOOL 0\nPUSH_I32 0\nHOSTCALL 0\nHALT",
@ -1499,7 +1601,7 @@ fn tick_composer_emit_sprite_type_mismatch_surfaces_trap_not_panic() {
&mut next_handle, &mut next_handle,
&mut vm, &mut vm,
&signals, &signals,
&mut hardware, &mut platform,
) )
.expect("type mismatch must surface as trap"); .expect("type mismatch must surface as trap");
match report { match report {

View File

@ -10,7 +10,7 @@ fn tick_asset_commit_operational_error_returns_status_not_crash() {
let mut open_files: HashMap<u32, String> = HashMap::new(); let mut open_files: HashMap<u32, String> = HashMap::new();
let mut next_handle = 1; let mut next_handle = 1;
let mut vm = VirtualMachine::default(); let mut vm = VirtualMachine::default();
let mut hardware = Hardware::new(); let mut platform = TestPlatform::new();
let signals = InputSignals::default(); let signals = InputSignals::default();
let code = assemble("PUSH_I32 999\nHOSTCALL 0\nHALT").expect("assemble"); let code = assemble("PUSH_I32 999\nHOSTCALL 0\nHALT").expect("assemble");
let program = serialized_single_function_module( let program = serialized_single_function_module(
@ -35,7 +35,7 @@ fn tick_asset_commit_operational_error_returns_status_not_crash() {
&mut next_handle, &mut next_handle,
&mut vm, &mut vm,
&signals, &signals,
&mut hardware, &mut platform,
); );
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());
@ -52,7 +52,7 @@ fn tick_asset_load_missing_asset_returns_status_and_zero_handle() {
let mut open_files: HashMap<u32, String> = HashMap::new(); let mut open_files: HashMap<u32, String> = HashMap::new();
let mut next_handle = 1; let mut next_handle = 1;
let mut vm = VirtualMachine::default(); let mut vm = VirtualMachine::default();
let mut hardware = Hardware::new(); let mut platform = TestPlatform::new();
let signals = InputSignals::default(); let signals = InputSignals::default();
let code = assemble("PUSH_I32 999\nPUSH_I32 0\nHOSTCALL 0\nHALT").expect("assemble"); let code = assemble("PUSH_I32 999\nPUSH_I32 0\nHOSTCALL 0\nHALT").expect("assemble");
let program = serialized_single_function_module( let program = serialized_single_function_module(
@ -77,7 +77,7 @@ fn tick_asset_load_missing_asset_returns_status_and_zero_handle() {
&mut next_handle, &mut next_handle,
&mut vm, &mut vm,
&signals, &signals,
&mut hardware, &mut platform,
); );
assert!(report.is_none(), "missing asset must not crash"); assert!(report.is_none(), "missing asset must not crash");
assert!(vm.is_halted()); assert!(vm.is_halted());
@ -97,10 +97,10 @@ fn tick_asset_load_invalid_slot_returns_status_and_zero_handle() {
let mut open_files: HashMap<u32, String> = HashMap::new(); let mut open_files: HashMap<u32, String> = HashMap::new();
let mut next_handle = 1; let mut next_handle = 1;
let mut vm = VirtualMachine::default(); let mut vm = VirtualMachine::default();
let mut hardware = Hardware::new(); let mut platform = TestPlatform::new();
let signals = InputSignals::default(); let signals = InputSignals::default();
let asset_data = test_glyph_asset_data(); let asset_data = test_glyph_asset_data();
hardware.assets.initialize_for_cartridge( platform.local_hardware().assets.initialize_for_cartridge(
vec![test_glyph_asset_entry("tile_asset", asset_data.len())], vec![test_glyph_asset_entry("tile_asset", asset_data.len())],
vec![], vec![],
AssetsPayloadSource::from_bytes(asset_data), AssetsPayloadSource::from_bytes(asset_data),
@ -128,7 +128,7 @@ fn tick_asset_load_invalid_slot_returns_status_and_zero_handle() {
&mut next_handle, &mut next_handle,
&mut vm, &mut vm,
&signals, &signals,
&mut hardware, &mut platform,
); );
assert!(report.is_none(), "invalid slot must not crash"); assert!(report.is_none(), "invalid slot must not crash");
assert!(vm.is_halted()); assert!(vm.is_halted());
@ -148,7 +148,7 @@ fn tick_asset_status_unknown_handle_returns_status_not_crash() {
let mut open_files: HashMap<u32, String> = HashMap::new(); let mut open_files: HashMap<u32, String> = HashMap::new();
let mut next_handle = 1; let mut next_handle = 1;
let mut vm = VirtualMachine::default(); let mut vm = VirtualMachine::default();
let mut hardware = Hardware::new(); let mut platform = TestPlatform::new();
let signals = InputSignals::default(); let signals = InputSignals::default();
let code = assemble("PUSH_I32 999\nHOSTCALL 0\nHALT").expect("assemble"); let code = assemble("PUSH_I32 999\nHOSTCALL 0\nHALT").expect("assemble");
let program = serialized_single_function_module( let program = serialized_single_function_module(
@ -173,7 +173,7 @@ fn tick_asset_status_unknown_handle_returns_status_not_crash() {
&mut next_handle, &mut next_handle,
&mut vm, &mut vm,
&signals, &signals,
&mut hardware, &mut platform,
); );
assert!(report.is_none(), "unknown asset handle must not crash"); assert!(report.is_none(), "unknown asset handle must not crash");
assert!(vm.is_halted()); assert!(vm.is_halted());
@ -190,10 +190,10 @@ fn tick_bank_info_returns_slot_summary_not_json() {
let mut open_files: HashMap<u32, String> = HashMap::new(); let mut open_files: HashMap<u32, String> = HashMap::new();
let mut next_handle = 1; let mut next_handle = 1;
let mut vm = VirtualMachine::default(); let mut vm = VirtualMachine::default();
let mut hardware = Hardware::new(); let mut platform = TestPlatform::new();
let signals = InputSignals::default(); let signals = InputSignals::default();
let asset_data = test_glyph_asset_data(); let asset_data = test_glyph_asset_data();
hardware.assets.initialize_for_cartridge( platform.local_hardware().assets.initialize_for_cartridge(
vec![test_glyph_asset_entry("tile_asset", asset_data.len())], vec![test_glyph_asset_entry("tile_asset", asset_data.len())],
vec![prometeu_hal::asset::PreloadEntry { asset_id: 7, slot: 0 }], vec![prometeu_hal::asset::PreloadEntry { asset_id: 7, slot: 0 }],
AssetsPayloadSource::from_bytes(asset_data), AssetsPayloadSource::from_bytes(asset_data),
@ -221,7 +221,7 @@ fn tick_bank_info_returns_slot_summary_not_json() {
&mut next_handle, &mut next_handle,
&mut vm, &mut vm,
&signals, &signals,
&mut hardware, &mut platform,
); );
assert!(report.is_none(), "bank summary must not crash"); assert!(report.is_none(), "bank summary must not crash");
assert!(vm.is_halted()); assert!(vm.is_halted());
@ -261,7 +261,7 @@ fn tick_asset_commit_invalid_transition_returns_status_not_crash() {
let mut open_files: HashMap<u32, String> = HashMap::new(); let mut open_files: HashMap<u32, String> = HashMap::new();
let mut next_handle = 1; let mut next_handle = 1;
let mut vm = VirtualMachine::default(); let mut vm = VirtualMachine::default();
let mut hardware = Hardware::new(); let mut platform = TestPlatform::new();
let signals = InputSignals::default(); let signals = InputSignals::default();
let code = assemble("PUSH_I32 1\nHOSTCALL 0\nPOP_N 1\nPUSH_I32 1\nHOSTCALL 1\nHALT") let code = assemble("PUSH_I32 1\nHOSTCALL 0\nPOP_N 1\nPUSH_I32 1\nHOSTCALL 1\nHALT")
.expect("assemble"); .expect("assemble");
@ -287,12 +287,13 @@ fn tick_asset_commit_invalid_transition_returns_status_not_crash() {
let cartridge = cartridge_with_program(program, caps::ASSET); let cartridge = cartridge_with_program(program, caps::ASSET);
let asset_data = test_glyph_asset_data(); let asset_data = test_glyph_asset_data();
hardware.assets.initialize_for_cartridge( platform.local_hardware().assets.initialize_for_cartridge(
vec![test_glyph_asset_entry("tile_asset", asset_data.len())], vec![test_glyph_asset_entry("tile_asset", asset_data.len())],
vec![], vec![],
AssetsPayloadSource::from_bytes(asset_data), AssetsPayloadSource::from_bytes(asset_data),
); );
let handle = hardware.assets.load(7, 0).expect("asset handle must be allocated"); let handle =
platform.local_hardware().assets.load(7, 0).expect("asset handle must be allocated");
runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize"); runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize");
let report = runtime.tick( let report = runtime.tick(
@ -304,7 +305,7 @@ fn tick_asset_commit_invalid_transition_returns_status_not_crash() {
&mut next_handle, &mut next_handle,
&mut vm, &mut vm,
&signals, &signals,
&mut hardware, &mut platform,
); );
assert!(report.is_none(), "invalid transition must not crash"); assert!(report.is_none(), "invalid transition must not crash");
assert!(vm.is_halted()); assert!(vm.is_halted());
@ -322,7 +323,7 @@ fn tick_asset_cancel_unknown_handle_returns_status_not_crash() {
let mut open_files: HashMap<u32, String> = HashMap::new(); let mut open_files: HashMap<u32, String> = HashMap::new();
let mut next_handle = 1; let mut next_handle = 1;
let mut vm = VirtualMachine::default(); let mut vm = VirtualMachine::default();
let mut hardware = Hardware::new(); let mut platform = TestPlatform::new();
let signals = InputSignals::default(); let signals = InputSignals::default();
let code = assemble("PUSH_I32 999\nHOSTCALL 0\nHALT").expect("assemble"); let code = assemble("PUSH_I32 999\nHOSTCALL 0\nHALT").expect("assemble");
let program = serialized_single_function_module( let program = serialized_single_function_module(
@ -347,7 +348,7 @@ fn tick_asset_cancel_unknown_handle_returns_status_not_crash() {
&mut next_handle, &mut next_handle,
&mut vm, &mut vm,
&signals, &signals,
&mut hardware, &mut platform,
); );
assert!(report.is_none(), "unknown handle cancel must not crash"); assert!(report.is_none(), "unknown handle cancel must not crash");
assert!(vm.is_halted()); assert!(vm.is_halted());
@ -364,7 +365,7 @@ fn tick_asset_cancel_invalid_transition_returns_status_not_crash() {
let mut open_files: HashMap<u32, String> = HashMap::new(); let mut open_files: HashMap<u32, String> = HashMap::new();
let mut next_handle = 1; let mut next_handle = 1;
let mut vm = VirtualMachine::default(); let mut vm = VirtualMachine::default();
let mut hardware = Hardware::new(); let mut platform = TestPlatform::new();
let signals = InputSignals::default(); let signals = InputSignals::default();
let code = assemble("PUSH_I32 1\nHOSTCALL 0\nHALT").expect("assemble"); let code = assemble("PUSH_I32 1\nHOSTCALL 0\nHALT").expect("assemble");
let program = serialized_single_function_module( let program = serialized_single_function_module(
@ -380,17 +381,18 @@ fn tick_asset_cancel_invalid_transition_returns_status_not_crash() {
let cartridge = cartridge_with_program(program, caps::ASSET); let cartridge = cartridge_with_program(program, caps::ASSET);
let asset_data = test_glyph_asset_data(); let asset_data = test_glyph_asset_data();
hardware.assets.initialize_for_cartridge( platform.local_hardware().assets.initialize_for_cartridge(
vec![test_glyph_asset_entry("tile_asset", asset_data.len())], vec![test_glyph_asset_entry("tile_asset", asset_data.len())],
vec![], vec![],
AssetsPayloadSource::from_bytes(asset_data), AssetsPayloadSource::from_bytes(asset_data),
); );
let handle = hardware.assets.load(7, 0).expect("asset handle must be allocated"); let handle =
platform.local_hardware().assets.load(7, 0).expect("asset handle must be allocated");
runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize"); runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize");
loop { loop {
match hardware.assets.status(handle) { match platform.local_hardware().assets.status(handle) {
LoadStatus::READY => break, LoadStatus::READY => break,
LoadStatus::PENDING | LoadStatus::LOADING => { LoadStatus::PENDING | LoadStatus::LOADING => {
std::thread::sleep(std::time::Duration::from_millis(1)); std::thread::sleep(std::time::Duration::from_millis(1));
@ -399,9 +401,9 @@ fn tick_asset_cancel_invalid_transition_returns_status_not_crash() {
} }
} }
assert_eq!(hardware.assets.commit(handle), AssetOpStatus::Ok); assert_eq!(platform.local_hardware().assets.commit(handle), AssetOpStatus::Ok);
hardware.assets.apply_commits(); platform.local_hardware().assets.apply_commits();
assert_eq!(hardware.assets.status(handle), LoadStatus::COMMITTED); assert_eq!(platform.local_hardware().assets.status(handle), LoadStatus::COMMITTED);
let report = runtime.tick( let report = runtime.tick(
&mut log_service, &mut log_service,
@ -412,7 +414,7 @@ fn tick_asset_cancel_invalid_transition_returns_status_not_crash() {
&mut next_handle, &mut next_handle,
&mut vm, &mut vm,
&signals, &signals,
&mut hardware, &mut platform,
); );
assert!(report.is_none(), "cancel after commit must not crash"); assert!(report.is_none(), "cancel after commit must not crash");
assert!(vm.is_halted()); assert!(vm.is_halted());
@ -429,7 +431,7 @@ fn tick_status_first_surface_smoke_across_composer_audio_and_asset() {
let mut open_files: HashMap<u32, String> = HashMap::new(); let mut open_files: HashMap<u32, String> = HashMap::new();
let mut next_handle = 1; let mut next_handle = 1;
let mut vm = VirtualMachine::default(); let mut vm = VirtualMachine::default();
let mut hardware = Hardware::new(); let mut platform = TestPlatform::new();
let signals = InputSignals::default(); let signals = InputSignals::default();
let code = assemble( let code = assemble(
"PUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_BOOL 0\nPUSH_BOOL 0\nPUSH_I32 0\nHOSTCALL 0\n\ "PUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_BOOL 0\nPUSH_BOOL 0\nPUSH_I32 0\nHOSTCALL 0\n\
@ -476,7 +478,7 @@ fn tick_status_first_surface_smoke_across_composer_audio_and_asset() {
&mut next_handle, &mut next_handle,
&mut vm, &mut vm,
&signals, &signals,
&mut hardware, &mut platform,
); );
assert!(report.is_none(), "mixed status-first surface must not crash"); assert!(report.is_none(), "mixed status-first surface must not crash");
assert!(vm.is_halted()); assert!(vm.is_halted());

View File

@ -11,7 +11,7 @@ fn tick_memcard_slot_roundtrip_for_game_profile() {
let mut next_handle = 1; let mut next_handle = 1;
runtime.mount_fs(&mut log_service, &mut fs, &mut fs_state, Box::new(MemFsBackend::default())); runtime.mount_fs(&mut log_service, &mut fs, &mut fs_state, Box::new(MemFsBackend::default()));
let mut vm = VirtualMachine::default(); let mut vm = VirtualMachine::default();
let mut hardware = Hardware::new(); let mut platform = TestPlatform::new();
let signals = InputSignals::default(); let signals = InputSignals::default();
let code = assemble( let code = assemble(
"PUSH_I32 0\nPUSH_I32 0\nPUSH_CONST 0\nHOSTCALL 0\nPOP_N 2\nPUSH_I32 0\nHOSTCALL 1\nPOP_N 1\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 10\nHOSTCALL 2\nHALT", "PUSH_I32 0\nPUSH_I32 0\nPUSH_CONST 0\nHOSTCALL 0\nPOP_N 2\nPUSH_I32 0\nHOSTCALL 1\nPOP_N 1\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 10\nHOSTCALL 2\nHALT",
@ -66,7 +66,7 @@ fn tick_memcard_slot_roundtrip_for_game_profile() {
&mut next_handle, &mut next_handle,
&mut vm, &mut vm,
&signals, &signals,
&mut hardware, &mut platform,
); );
assert!(report.is_none(), "memcard roundtrip must not crash"); assert!(report.is_none(), "memcard roundtrip must not crash");
assert!(vm.is_halted()); assert!(vm.is_halted());
@ -86,7 +86,7 @@ fn tick_memcard_access_is_denied_for_non_game_profile() {
let mut open_files: HashMap<u32, String> = HashMap::new(); let mut open_files: HashMap<u32, String> = HashMap::new();
let mut next_handle = 1; let mut next_handle = 1;
let mut vm = VirtualMachine::default(); let mut vm = VirtualMachine::default();
let mut hardware = Hardware::new(); let mut platform = TestPlatform::new();
let signals = InputSignals::default(); let signals = InputSignals::default();
let code = assemble("HOSTCALL 0\nHALT").expect("assemble"); let code = assemble("HOSTCALL 0\nHALT").expect("assemble");
let program = serialized_single_function_module( let program = serialized_single_function_module(
@ -121,7 +121,7 @@ fn tick_memcard_access_is_denied_for_non_game_profile() {
&mut next_handle, &mut next_handle,
&mut vm, &mut vm,
&signals, &signals,
&mut hardware, &mut platform,
); );
assert!(report.is_none(), "non-game memcard call must return status"); assert!(report.is_none(), "non-game memcard call must return status");
assert!(vm.is_halted()); assert!(vm.is_halted());

View File

@ -9,8 +9,8 @@ use crate::services::memcard::MemcardService;
use prometeu_hal::asset::{BankTelemetry, BankType}; use prometeu_hal::asset::{BankTelemetry, BankType};
use prometeu_hal::log::{LogLevel, LogService, LogSource}; use prometeu_hal::log::{LogLevel, LogService, LogSource};
use prometeu_hal::{ use prometeu_hal::{
FrameId, HardwareBridge, HostContext, InputSignals, RenderSubmission, RenderSubmissionPacket, FrameId, HostContext, InputSignals, RenderSubmission, RenderSubmissionPacket,
ShellUiFramePacket, RenderSubmissionSink, RuntimePlatform, ShellUiFramePacket,
}; };
use prometeu_vm::LogicalFrameEndingReason; use prometeu_vm::LogicalFrameEndingReason;
use std::collections::HashMap; use std::collections::HashMap;
@ -18,12 +18,14 @@ use std::panic::{AssertUnwindSafe, catch_unwind};
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
struct HardwareRenderSurface<'a> { struct HardwareRenderSurface<'a> {
hw: &'a mut dyn HardwareBridge, sink: &'a mut dyn RenderSubmissionSink,
} }
impl RenderSurface for HardwareRenderSurface<'_> { impl RenderSurface for HardwareRenderSurface<'_> {
fn consume_submission(&mut self, submission: &RenderSubmission) { fn consume_submission(&mut self, submission: &RenderSubmission) {
self.hw.publish_render_submission(submission); self.sink
.submit_render_submission(submission.clone())
.expect("local render submission sink should publish");
} }
} }
@ -51,9 +53,9 @@ impl VirtualMachineRuntime {
} }
fn bank_telemetry_summary( fn bank_telemetry_summary(
hw: &dyn HardwareBridge, platform: &dyn RuntimePlatform,
) -> (BankTelemetry, BankTelemetry, BankTelemetry) { ) -> (BankTelemetry, BankTelemetry, BankTelemetry) {
let telemetry = hw.assets().bank_telemetry(); let telemetry = platform.assets().bank_telemetry();
let glyph = let glyph =
telemetry.iter().find(|entry| entry.bank_type == BankType::GLYPH).cloned().unwrap_or( telemetry.iter().find(|entry| entry.bank_type == BankType::GLYPH).cloned().unwrap_or(
BankTelemetry { bank_type: BankType::GLYPH, used_slots: 0, total_slots: 0 }, BankTelemetry { bank_type: BankType::GLYPH, used_slots: 0, total_slots: 0 },
@ -74,10 +76,10 @@ impl VirtualMachineRuntime {
&mut self, &mut self,
log_service: &mut LogService, log_service: &mut LogService,
vm: &mut VirtualMachine, vm: &mut VirtualMachine,
hw: &mut dyn HardwareBridge, platform: &mut dyn RuntimePlatform,
) -> Option<CrashReport> { ) -> Option<CrashReport> {
let step_result = { let step_result = {
let mut ctx = HostContext::new(Some(hw)); let mut ctx = HostContext::new_platform(Some(platform));
let mut fs = VirtualFS::new(); let mut fs = VirtualFS::new();
let mut fs_state = FsState::Unmounted; let mut fs_state = FsState::Unmounted;
let mut memcard = MemcardService::new(); let mut memcard = MemcardService::new();
@ -131,7 +133,7 @@ impl VirtualMachineRuntime {
next_handle: &mut u32, next_handle: &mut u32,
vm: &mut VirtualMachine, vm: &mut VirtualMachine,
signals: &InputSignals, signals: &InputSignals,
hw: &mut dyn HardwareBridge, platform: &mut dyn RuntimePlatform,
) -> Option<CrashReport> { ) -> Option<CrashReport> {
let start = Instant::now(); let start = Instant::now();
self.tick_index += 1; self.tick_index += 1;
@ -141,7 +143,6 @@ impl VirtualMachineRuntime {
} }
self.update_fs(log_service, fs, fs_state); self.update_fs(log_service, fs, fs_state);
if !self.logical_frame_active { if !self.logical_frame_active {
if RenderManager::policy_for_app_mode(self.current_cartridge_app_mode) if RenderManager::policy_for_app_mode(self.current_cartridge_app_mode)
.uses_frame_scheduler() .uses_frame_scheduler()
@ -152,7 +153,7 @@ impl VirtualMachineRuntime {
} }
self.logical_frame_active = true; self.logical_frame_active = true;
self.logical_frame_remaining_cycles = Self::CYCLES_PER_LOGICAL_FRAME; self.logical_frame_remaining_cycles = Self::CYCLES_PER_LOGICAL_FRAME;
self.begin_logical_frame(signals, hw); self.begin_logical_frame(signals, platform);
if self.needs_prepare_entry_call || vm.call_stack_is_empty() { if self.needs_prepare_entry_call || vm.call_stack_is_empty() {
vm.prepare_boot_call(); vm.prepare_boot_call();
@ -180,7 +181,7 @@ impl VirtualMachineRuntime {
if budget > 0 { if budget > 0 {
let run_result = { let run_result = {
let mut ctx = HostContext::new(Some(hw)); let mut ctx = HostContext::new_platform(Some(platform));
let mut host = VmRuntimeHost { let mut host = VmRuntimeHost {
runtime: self, runtime: self,
log_service, log_service,
@ -248,7 +249,7 @@ impl VirtualMachineRuntime {
self.current_app_id, self.current_app_id,
); );
if self.current_cartridge_app_mode == AppMode::Game { if self.current_cartridge_app_mode == AppMode::Game {
let mut packet = hw.close_game2d_packet(); let mut packet = platform.game2d_frame_composer().close_game2d_packet();
packet.gfx2d = std::mem::take(&mut self.gfx2d_commands); packet.gfx2d = std::mem::take(&mut self.gfx2d_commands);
self.render_manager self.render_manager
.close_frame_with_packet(RenderSubmissionPacket::Game2D(packet)) .close_frame_with_packet(RenderSubmissionPacket::Game2D(packet))
@ -262,7 +263,8 @@ impl VirtualMachineRuntime {
} }
let render_outcome = catch_unwind(AssertUnwindSafe(|| { let render_outcome = catch_unwind(AssertUnwindSafe(|| {
let mut surface = HardwareRenderSurface { hw }; let mut surface =
HardwareRenderSurface { sink: platform.render_submission_sink() };
match self match self
.render_manager .render_manager
.active_render_policy() .active_render_policy()
@ -304,7 +306,8 @@ impl VirtualMachineRuntime {
} }
// 1. Snapshot full telemetry at logical frame end // 1. Snapshot full telemetry at logical frame end
let (glyph_bank, sound_bank, scene_bank) = Self::bank_telemetry_summary(hw); let (glyph_bank, sound_bank, scene_bank) =
Self::bank_telemetry_summary(platform);
self.atomic_telemetry self.atomic_telemetry
.glyph_slots_used .glyph_slots_used
.store(glyph_bank.used_slots as u32, Ordering::Relaxed); .store(glyph_bank.used_slots as u32, Ordering::Relaxed);
@ -395,7 +398,7 @@ impl VirtualMachineRuntime {
// 2. High-frequency telemetry update (only if inspection is active) // 2. High-frequency telemetry update (only if inspection is active)
if self.inspection_active { if self.inspection_active {
let (glyph_bank, sound_bank, scene_bank) = Self::bank_telemetry_summary(hw); let (glyph_bank, sound_bank, scene_bank) = Self::bank_telemetry_summary(platform);
self.atomic_telemetry self.atomic_telemetry
.glyph_slots_used .glyph_slots_used
.store(glyph_bank.used_slots as u32, Ordering::Relaxed); .store(glyph_bank.used_slots as u32, Ordering::Relaxed);
@ -432,10 +435,10 @@ impl VirtualMachineRuntime {
pub(crate) fn begin_logical_frame( pub(crate) fn begin_logical_frame(
&mut self, &mut self,
_signals: &InputSignals, _signals: &InputSignals,
hw: &mut dyn HardwareBridge, platform: &mut dyn RuntimePlatform,
) { ) {
hw.begin_frame(); platform.game2d_frame_composer().begin_frame();
hw.audio_mut().clear_commands(); platform.audio_mut().clear_commands();
self.logs_written_this_frame.clear(); self.logs_written_this_frame.clear();
} }
} }

View File

@ -1350,8 +1350,9 @@ fn input_touch_x(
ctx: &mut HostContext<'_>, ctx: &mut HostContext<'_>,
) -> Result<Vec<Value>, IntrinsicExecutionError> { ) -> Result<Vec<Value>, IntrinsicExecutionError> {
expect_touch_handle(args, "x")?; expect_touch_handle(args, "x")?;
let hw = ctx.require_hw().map_err(|_| IntrinsicExecutionError::HardwareUnavailable)?; let platform =
Ok(vec![Value::Int64(hw.touch().x() as i64)]) ctx.require_platform().map_err(|_| IntrinsicExecutionError::HardwareUnavailable)?;
Ok(vec![Value::Int64(platform.input().touch().x() as i64)])
} }
fn input_touch_y( fn input_touch_y(
@ -1359,8 +1360,9 @@ fn input_touch_y(
ctx: &mut HostContext<'_>, ctx: &mut HostContext<'_>,
) -> Result<Vec<Value>, IntrinsicExecutionError> { ) -> Result<Vec<Value>, IntrinsicExecutionError> {
expect_touch_handle(args, "y")?; expect_touch_handle(args, "y")?;
let hw = ctx.require_hw().map_err(|_| IntrinsicExecutionError::HardwareUnavailable)?; let platform =
Ok(vec![Value::Int64(hw.touch().y() as i64)]) ctx.require_platform().map_err(|_| IntrinsicExecutionError::HardwareUnavailable)?;
Ok(vec![Value::Int64(platform.input().touch().y() as i64)])
} }
fn input_button_pressed( fn input_button_pressed(
@ -1435,9 +1437,11 @@ fn resolve_button(
ctx: &mut HostContext<'_>, ctx: &mut HostContext<'_>,
) -> Result<prometeu_hal::button::Button, IntrinsicExecutionError> { ) -> Result<prometeu_hal::button::Button, IntrinsicExecutionError> {
let carrier = expect_builtin_carrier(args, 0)?; let carrier = expect_builtin_carrier(args, 0)?;
let hw = ctx.require_hw().map_err(|_| IntrinsicExecutionError::HardwareUnavailable)?; let platform =
ctx.require_platform().map_err(|_| IntrinsicExecutionError::HardwareUnavailable)?;
let input = platform.input();
if carrier == TOUCH_BUTTON_CARRIER { if carrier == TOUCH_BUTTON_CARRIER {
return Ok(*hw.touch().f()); return Ok(*input.touch().f());
} }
let Some(index) = carrier.checked_sub(PAD_BUTTON_BASE) else { let Some(index) = carrier.checked_sub(PAD_BUTTON_BASE) else {
@ -1448,18 +1452,18 @@ fn resolve_button(
}); });
}; };
let button = match index { let button = match index {
0 => *hw.pad().up(), 0 => *input.pad().up(),
1 => *hw.pad().down(), 1 => *input.pad().down(),
2 => *hw.pad().left(), 2 => *input.pad().left(),
3 => *hw.pad().right(), 3 => *input.pad().right(),
4 => *hw.pad().a(), 4 => *input.pad().a(),
5 => *hw.pad().b(), 5 => *input.pad().b(),
6 => *hw.pad().x(), 6 => *input.pad().x(),
7 => *hw.pad().y(), 7 => *input.pad().y(),
8 => *hw.pad().l(), 8 => *input.pad().l(),
9 => *hw.pad().r(), 9 => *input.pad().r(),
10 => *hw.pad().start(), 10 => *input.pad().start(),
11 => *hw.pad().select(), 11 => *input.pad().select(),
_ => { _ => {
return Err(IntrinsicExecutionError::InvalidBuiltinCarrier { return Err(IntrinsicExecutionError::InvalidBuiltinCarrier {
owner: "input.button", owner: "input.button",

View File

@ -1,5 +1,5 @@
use prometeu_drivers::hardware::Hardware;
use prometeu_firmware::{BootTarget, Firmware}; use prometeu_firmware::{BootTarget, Firmware};
use prometeu_hal::RuntimePlatform;
use prometeu_hal::cartridge_loader::CartridgeLoader; use prometeu_hal::cartridge_loader::CartridgeLoader;
use prometeu_hal::debugger_protocol::*; use prometeu_hal::debugger_protocol::*;
use prometeu_hal::telemetry::{CertificationConfig, TelemetryFrame}; use prometeu_hal::telemetry::{CertificationConfig, TelemetryFrame};
@ -99,7 +99,7 @@ impl HostDebugger {
/// Main maintenance method called by the HostRunner every iteration. /// Main maintenance method called by the HostRunner every iteration.
/// It handles new connections and processes incoming commands. /// It handles new connections and processes incoming commands.
pub fn check_commands(&mut self, firmware: &mut Firmware, hardware: &mut Hardware) { pub fn check_commands(&mut self, firmware: &mut Firmware, platform: &mut dyn RuntimePlatform) {
// 1. Accept new client connections. // 1. Accept new client connections.
if let Some(listener) = &self.listener if let Some(listener) = &self.listener
&& let Ok((stream, _addr)) = listener.accept() && let Ok((stream, _addr)) = listener.accept()
@ -154,7 +154,7 @@ impl HostDebugger {
continue; continue;
} }
if let Ok(cmd) = serde_json::from_str::<DebugCommand>(trimmed) { if let Ok(cmd) = serde_json::from_str::<DebugCommand>(trimmed) {
self.handle_command(cmd, firmware, hardware); self.handle_command(cmd, firmware, platform);
} }
} }
} }
@ -182,7 +182,7 @@ impl HostDebugger {
&mut self, &mut self,
cmd: DebugCommand, cmd: DebugCommand,
firmware: &mut Firmware, firmware: &mut Firmware,
hardware: &mut Hardware, platform: &mut dyn RuntimePlatform,
) { ) {
match cmd { match cmd {
DebugCommand::Ok | DebugCommand::Start => { DebugCommand::Ok | DebugCommand::Start => {
@ -201,7 +201,7 @@ impl HostDebugger {
DebugCommand::Step => { DebugCommand::Step => {
// Execute exactly one instruction and keep paused. // Execute exactly one instruction and keep paused.
firmware.os.vm().set_paused(true); firmware.os.vm().set_paused(true);
let _ = firmware.os.vm().debug_step_instruction(&mut firmware.vm, hardware); let _ = firmware.os.vm().debug_step_instruction(&mut firmware.vm, platform);
} }
DebugCommand::StepFrame => { DebugCommand::StepFrame => {
// Execute until the end of the current logical frame. // Execute until the end of the current logical frame.
@ -372,7 +372,7 @@ impl HostDebugger {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use prometeu_drivers::hardware::Hardware; use prometeu_drivers::TestPlatform;
use prometeu_hal::debugger_protocol::DebugCommand; use prometeu_hal::debugger_protocol::DebugCommand;
#[test] #[test]
@ -396,15 +396,19 @@ mod tests {
fn handle_command_updates_pause_and_step_flags_without_host_io() { fn handle_command_updates_pause_and_step_flags_without_host_io() {
let mut debugger = HostDebugger::new(); let mut debugger = HostDebugger::new();
let mut firmware = Firmware::new(None); let mut firmware = Firmware::new(None);
let mut hardware = Hardware::new(); let mut platform = TestPlatform::new();
debugger.handle_command(DebugCommand::Pause, &mut firmware, &mut hardware); debugger.handle_command(DebugCommand::Pause, &mut firmware, platform.local_hardware_mut());
assert!(firmware.os.vm().paused()); assert!(firmware.os.vm().paused());
debugger.handle_command(DebugCommand::Resume, &mut firmware, &mut hardware); debugger.handle_command(DebugCommand::Resume, &mut firmware, platform.local_hardware_mut());
assert!(!firmware.os.vm().paused()); assert!(!firmware.os.vm().paused());
debugger.handle_command(DebugCommand::StepFrame, &mut firmware, &mut hardware); debugger.handle_command(
DebugCommand::StepFrame,
&mut firmware,
platform.local_hardware_mut(),
);
assert!(!firmware.os.vm().paused()); assert!(!firmware.os.vm().paused());
assert!(firmware.os.vm().debug_step_requested()); assert!(firmware.os.vm().debug_step_requested());
} }
@ -413,12 +417,12 @@ mod tests {
fn handle_command_start_leaves_waiting_for_start_mode() { fn handle_command_start_leaves_waiting_for_start_mode() {
let mut debugger = HostDebugger::new(); let mut debugger = HostDebugger::new();
let mut firmware = Firmware::new(None); let mut firmware = Firmware::new(None);
let mut hardware = Hardware::new(); let mut platform = TestPlatform::new();
debugger.waiting_for_start = true; debugger.waiting_for_start = true;
firmware.os.vm().set_paused(true); firmware.os.vm().set_paused(true);
debugger.handle_command(DebugCommand::Start, &mut firmware, &mut hardware); debugger.handle_command(DebugCommand::Start, &mut firmware, platform.local_hardware_mut());
assert!(!debugger.waiting_for_start); assert!(!debugger.waiting_for_start);
assert!(!firmware.os.vm().paused()); assert!(!firmware.os.vm().paused());

View File

@ -1,4 +1,3 @@
use prometeu_drivers::hardware::Hardware;
use prometeu_hal::InputSignals; use prometeu_hal::InputSignals;
use winit::event::{ElementState, MouseButton, WindowEvent}; use winit::event::{ElementState, MouseButton, WindowEvent};
use winit::keyboard::{KeyCode, PhysicalKey}; use winit::keyboard::{KeyCode, PhysicalKey};
@ -14,7 +13,7 @@ struct FbViewport {
} }
impl FbViewport { impl FbViewport {
fn framebuffer_viewport(window: &Window) -> Option<FbViewport> { fn framebuffer_viewport(window: &Window, display_size: (usize, usize)) -> Option<FbViewport> {
let size = window.inner_size(); let size = window.inner_size();
let win_w = size.width as f32; let win_w = size.width as f32;
@ -24,8 +23,8 @@ impl FbViewport {
return None; return None;
} }
let fb_w = Hardware::W as f32; let fb_w = display_size.0 as f32;
let fb_h = Hardware::H as f32; let fb_h = display_size.1 as f32;
let scale_x = (win_w / fb_w).floor(); let scale_x = (win_w / fb_w).floor();
let scale_y = (win_h / fb_h).floor(); let scale_y = (win_h / fb_h).floor();
@ -44,17 +43,18 @@ impl FbViewport {
pub struct HostInputHandler { pub struct HostInputHandler {
pub signals: InputSignals, pub signals: InputSignals,
display_size: (usize, usize),
} }
impl Default for HostInputHandler { impl Default for HostInputHandler {
fn default() -> Self { fn default() -> Self {
Self::new() Self::new((480, 270))
} }
} }
impl HostInputHandler { impl HostInputHandler {
pub fn new() -> Self { pub fn new(display_size: (usize, usize)) -> Self {
Self { signals: InputSignals::default() } Self { signals: InputSignals::default(), display_size }
} }
pub fn handle_event(&mut self, event: &WindowEvent, window: &Window) { pub fn handle_event(&mut self, event: &WindowEvent, window: &Window) {
@ -87,7 +87,9 @@ impl HostInputHandler {
} }
WindowEvent::CursorMoved { position, .. } => { WindowEvent::CursorMoved { position, .. } => {
if let Some((x, y)) = window_to_fb(position.x as f32, position.y as f32, window) { if let Some((x, y)) =
window_to_fb(position.x as f32, position.y as f32, window, self.display_size)
{
self.signals.x_pos = x; self.signals.x_pos = x;
self.signals.y_pos = y; self.signals.y_pos = y;
} }
@ -107,8 +109,13 @@ impl HostInputHandler {
_ => {} _ => {}
} }
fn window_to_fb(wx: f32, wy: f32, window: &Window) -> Option<(i32, i32)> { fn window_to_fb(
let viewport = FbViewport::framebuffer_viewport(window)?; wx: f32,
wy: f32,
window: &Window,
display_size: (usize, usize),
) -> Option<(i32, i32)> {
let viewport = FbViewport::framebuffer_viewport(window, display_size)?;
let local_x = wx - viewport.x; let local_x = wx - viewport.x;
let local_y = wy - viewport.y; let local_y = wy - viewport.y;
@ -120,7 +127,10 @@ impl HostInputHandler {
let fb_x = (local_x / viewport.scale).floor() as i32; let fb_x = (local_x / viewport.scale).floor() as i32;
let fb_y = (local_y / viewport.scale).floor() as i32; let fb_y = (local_y / viewport.scale).floor() as i32;
Some((fb_x.clamp(0, Hardware::W as i32 - 1), fb_y.clamp(0, Hardware::H as i32 - 1))) Some((
fb_x.clamp(0, display_size.0 as i32 - 1),
fb_y.clamp(0, display_size.1 as i32 - 1),
))
} }
} }
} }

View File

@ -10,6 +10,7 @@ use pixels::{Pixels, PixelsBuilder, SurfaceTexture};
use prometeu_drivers::AudioCommand; use prometeu_drivers::AudioCommand;
use prometeu_drivers::hardware::Hardware; use prometeu_drivers::hardware::Hardware;
use prometeu_firmware::{BootTarget, Firmware, FirmwareState}; use prometeu_firmware::{BootTarget, Firmware, FirmwareState};
use prometeu_hal::RuntimePlatform;
use prometeu_hal::telemetry::CertificationConfig; use prometeu_hal::telemetry::CertificationConfig;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use winit::application::ApplicationHandler; use winit::application::ApplicationHandler;
@ -152,12 +153,15 @@ impl HostRunner {
firmware.os.fs().mount(Box::new(backend)); firmware.os.fs().mount(Box::new(backend));
} }
let hardware = Hardware::new();
let display_size = hardware.display_size();
Self { Self {
window: None, window: None,
pixels: None, pixels: None,
hardware: Hardware::new(), hardware,
firmware, firmware,
input: HostInputHandler::new(), input: HostInputHandler::new(display_size),
fs_root, fs_root,
log_sink: HostConsoleSink::new(), log_sink: HostConsoleSink::new(),
frame_target_dt: Duration::from_nanos(1_000_000_000 / target_fps), frame_target_dt: Duration::from_nanos(1_000_000_000 / target_fps),
@ -228,11 +232,11 @@ impl ApplicationHandler for HostRunner {
let size = window.inner_size(); let size = window.inner_size();
let surface_texture = SurfaceTexture::new(size.width, size.height, window); let surface_texture = SurfaceTexture::new(size.width, size.height, window);
let mut pixels = let (display_w, display_h) = self.hardware.display_size();
PixelsBuilder::new(Hardware::W as u32, Hardware::H as u32, surface_texture) let mut pixels = PixelsBuilder::new(display_w as u32, display_h as u32, surface_texture)
.present_mode(PresentMode::Fifo) // activate vsync .present_mode(PresentMode::Fifo) // activate vsync
.build() .build()
.expect("failed to create Pixels"); .expect("failed to create Pixels");
pixels.frame_mut().fill(0); pixels.frame_mut().fill(0);
@ -381,7 +385,7 @@ impl ApplicationHandler for HostRunner {
self.audio.update_stats(&mut self.stats); self.audio.update_stats(&mut self.stats);
// Update technical statistics displayed in the window title. // Update technical statistics displayed in the window title.
self.stats.update(now, self.window, &self.hardware, &mut self.firmware); self.stats.update(now, self.window, &mut self.firmware);
// Synchronize system logs to the host console. // Synchronize system logs to the host console.
let last_seq = self.log_sink.last_seq().unwrap_or(u64::MAX); let last_seq = self.log_sink.last_seq().unwrap_or(u64::MAX);

View File

@ -1,4 +1,3 @@
use prometeu_drivers::hardware::Hardware;
use prometeu_firmware::Firmware; use prometeu_firmware::Firmware;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use winit::window::Window; use winit::window::Window;
@ -60,13 +59,7 @@ impl HostStats {
} }
} }
pub fn update( pub fn update(&mut self, now: Instant, window: Option<&Window>, firmware: &mut Firmware) {
&mut self,
now: Instant,
window: Option<&Window>,
_hardware: &Hardware,
firmware: &mut Firmware,
) {
let stats_elapsed = now.duration_since(self.last_stats_update); let stats_elapsed = now.duration_since(self.last_stats_update);
if stats_elapsed >= Duration::from_secs(1) { if stats_elapsed >= Duration::from_secs(1) {
self.current_fps = self.frames_since_last_update as f64 / stats_elapsed.as_secs_f64(); self.current_fps = self.frames_since_last_update as f64 / stats_elapsed.as_secs_f64();

View File

@ -31,6 +31,13 @@ MUST contain a frame id, the active app mode, and one typed packet:
immutable. The runtime backpressure policy is latest-complete-submission-wins; immutable. The runtime backpressure policy is latest-complete-submission-wins;
it MUST NOT accumulate an unbounded frame queue. it MUST NOT accumulate an unbounded frame queue.
The platform layer is the runtime-facing implementation boundary for render
handoff. Runtime and firmware code publish completed frames through typed
platform services such as `RenderSubmissionSink`, and they access Game 2D
composition through `Game2DFrameComposer`. They MUST NOT depend on a monolithic
hardware bridge, mutable `Hardware`, mutable `Gfx`, or live `FrameComposer`
reference as the render handoff contract.
The current 2D graphics model is based on: The current 2D graphics model is based on:
- framebuffer - framebuffer
@ -127,6 +134,10 @@ to the logical/runtime side before handoff, or to an owning service. They MUST
NOT require the render consumer to hold mutable VM, `Hardware`, `Gfx`, or NOT require the render consumer to hold mutable VM, `Hardware`, `Gfx`, or
`FrameComposer` references. `FrameComposer` references.
Local host implementations may keep a concrete hardware object internally as a
platform implementation detail. That object is not part of the runtime-facing
render boundary; the boundary is the typed platform service set.
The runtime does not guarantee visual integrity if a developer or framework The runtime does not guarantee visual integrity if a developer or framework
replaces resources behind an in-flight submission in a way that violates asset replaces resources behind an in-flight submission in a way that violates asset
discipline. discipline.

View File

@ -123,9 +123,13 @@ The graphics system:
The platform layer: The platform layer:
- exposes typed service facades for render submission, render backend
execution, Game 2D composition, input, audio, assets, and telemetry
- consumes closed render submissions through a render-surface implementation - consumes closed render submissions through a render-surface implementation
- transports published RGBA8888 output into a host presentation surface without - transports published RGBA8888 output into a host presentation surface without
injecting host-owned debug overlay pixels injecting host-owned debug overlay pixels
- is the runtime-facing portability boundary; runtime and firmware code must not
depend on a monolithic hardware bridge or on a concrete hardware aggregate
The host presentation layer MUST treat RGBA8888 as the canonical logical The host presentation layer MUST treat RGBA8888 as the canonical logical
framebuffer format. RGB565 conversion is not part of the normal host framebuffer format. RGB565 conversion is not part of the normal host

View File

@ -50,6 +50,12 @@ Does not belong in `docs/runtime/specs/` as the primary canonical source:
Here, hardware is not a generic bucket for everything. In the current package, "hardware" means the machine's virtual peripherals: GFX, AUDIO, INPUT, TOUCH, and MEMCARD/save memory. VM, firmware, cartridge, and ABI sit in sibling categories rather than inside hardware. Here, hardware is not a generic bucket for everything. In the current package, "hardware" means the machine's virtual peripherals: GFX, AUDIO, INPUT, TOUCH, and MEMCARD/save memory. VM, firmware, cartridge, and ABI sit in sibling categories rather than inside hardware.
The implementation boundary for host portability is the platform layer. Runtime,
firmware, and host integration code use typed platform services for rendering,
input, audio, assets, and telemetry. A concrete local hardware aggregate may
exist inside a host or test platform, but it is not the normative runtime-facing
contract.
## Document Functions ## Document Functions
- `normative`: defines the technical contract, expected behavior, or implementation-facing surface. - `normative`: defines the technical contract, expected behavior, or implementation-facing surface.