// file: crates/apps/game-snake-poc-wasm/src/runtime.rs // version: 3 const FIXED_STEP_MILLIS: u64 = 16; /// WebAssembly-owned Snake game state consumed by the direct browser host. #[wasm_bindgen::prelude::wasm_bindgen] pub struct SnakeWasmGame { pending_input: engine_v1_common::InputState, provenance: engine_v1_platform_api::RuntimeProvenance, runner: engine_v1_common::FixedStepRunner, state: game_snake_poc::SnakeState, } #[wasm_bindgen::prelude::wasm_bindgen] impl SnakeWasmGame { /// Creates a fresh Snake POC session using the shared gameplay crate. #[wasm_bindgen::prelude::wasm_bindgen(constructor)] pub fn new() -> Self { return Self { pending_input: engine_v1_common::InputState::none(), provenance: engine_v1_platform_api::RuntimeProvenance::new( engine_v1_platform_api::DeviceClass::Unknown, engine_v1_platform_api::ExecutionModel::Wasm, engine_v1_platform_api::InputProfile::Unknown, engine_v1_platform_api::PlatformFamily::Web, engine_v1_platform_api::RuntimeHost::Browser, ), runner: engine_v1_common::FixedStepRunner::new(std::time::Duration::from_millis(FIXED_STEP_MILLIS)), state: game_snake_poc::SnakeState::new(), }; } /// Updates host-observed runtime provenance while preserving WebAssembly execution. pub fn configure_runtime_provenance(&mut self, platform_family: &str, runtime_host: &str, device_class: &str, input_profile: &str) -> bool { let platform_family = match parse_platform_family(platform_family) { Some(value) => value, None => return false, }; let runtime_host = match parse_runtime_host(runtime_host) { Some(value) => value, None => return false, }; let device_class = match parse_device_class(device_class) { Some(value) => value, None => return false, }; let input_profile = match parse_input_profile(input_profile) { Some(value) => value, None => return false, }; self.provenance = engine_v1_platform_api::RuntimeProvenance::new( device_class, engine_v1_platform_api::ExecutionModel::Wasm, input_profile, platform_family, runtime_host, ); return true; } /// Returns the current physical device class label. pub fn provenance_device_class(&self) -> String { return device_class_label(self.provenance.device_class()).to_string(); } /// Returns the current execution model label. pub fn provenance_execution_model(&self) -> String { return execution_model_label(self.provenance.execution_model()).to_string(); } /// Returns the current primary input profile label. pub fn provenance_input_profile(&self) -> String { return input_profile_label(self.provenance.input_profile()).to_string(); } /// Returns the current platform family label. pub fn provenance_platform_family(&self) -> String { return platform_family_label(self.provenance.platform_family()).to_string(); } /// Returns the current runtime host label. pub fn provenance_runtime_host(&self) -> String { return runtime_host_label(self.provenance.runtime_host()).to_string(); } /// Queues a logical left direction for the next deterministic update. pub fn left(&mut self) { self.queue_direction(engine_v1_common::GameAction::Left); return; } /// Queues a logical right direction for the next deterministic update. pub fn right(&mut self) { self.queue_direction(engine_v1_common::GameAction::Right); return; } /// Queues a logical upward direction for the next deterministic update. pub fn up(&mut self) { self.queue_direction(engine_v1_common::GameAction::Up); return; } /// Queues a logical downward direction for the next deterministic update. pub fn down(&mut self) { self.queue_direction(engine_v1_common::GameAction::Down); return; } /// Advances one deterministic update and consumes the queued direction. pub fn tick(&mut self) { let input = self.pending_input; self.pending_input = engine_v1_common::InputState::none(); self.runner.tick(&mut self.state, input); return; } /// Returns the current gameplay score. pub fn score(&self) -> u32 { return self.state.score().min(u32::MAX as u64) as u32; } /// Returns the current Snake length. pub fn length(&self) -> u32 { return self.state.length().min(u32::MAX as usize) as u32; } /// Returns the current scene background red channel. pub fn background_red(&self) -> u8 { return self.scene().background().red(); } /// Returns the current scene background green channel. pub fn background_green(&self) -> u8 { return self.scene().background().green(); } /// Returns the current scene background blue channel. pub fn background_blue(&self) -> u8 { return self.scene().background().blue(); } /// Returns the number of occupied rectangle slots in the current scene. pub fn rectangle_count(&self) -> u32 { let scene = self.scene(); let mut count = 0_u32; for slot in scene.rectangles() { if slot.is_some() { count = count.saturating_add(1); } } return count; } /// Returns one rectangle's normalized left coordinate or `-1.0` if absent. pub fn rectangle_x(&self, index: u32) -> f32 { return match self.rectangle(index) { Some(rectangle) => rectangle.rect().x(), None => -1.0, }; } /// Returns one rectangle's normalized top coordinate or `-1.0` if absent. pub fn rectangle_y(&self, index: u32) -> f32 { return match self.rectangle(index) { Some(rectangle) => rectangle.rect().y(), None => -1.0, }; } /// Returns one rectangle's normalized width or `0.0` if absent. pub fn rectangle_width(&self, index: u32) -> f32 { return match self.rectangle(index) { Some(rectangle) => rectangle.rect().width(), None => 0.0, }; } /// Returns one rectangle's normalized height or `0.0` if absent. pub fn rectangle_height(&self, index: u32) -> f32 { return match self.rectangle(index) { Some(rectangle) => rectangle.rect().height(), None => 0.0, }; } /// Returns one rectangle's red channel or zero if absent. pub fn rectangle_red(&self, index: u32) -> u8 { return match self.rectangle(index) { Some(rectangle) => rectangle.color().red(), None => 0, }; } /// Returns one rectangle's green channel or zero if absent. pub fn rectangle_green(&self, index: u32) -> u8 { return match self.rectangle(index) { Some(rectangle) => rectangle.color().green(), None => 0, }; } /// Returns one rectangle's blue channel or zero if absent. pub fn rectangle_blue(&self, index: u32) -> u8 { return match self.rectangle(index) { Some(rectangle) => rectangle.color().blue(), None => 0, }; } } impl Default for SnakeWasmGame { fn default() -> Self { return Self::new(); } } impl SnakeWasmGame { fn queue_direction(&mut self, action: engine_v1_common::GameAction) { self.pending_input = engine_v1_common::InputState::none().with_action(action, true); return; } fn scene(&self) -> engine_v1_common::EngineScene { return engine_v1_common::EngineGame::scene(&self.state); } fn rectangle(&self, requested_index: u32) -> Option { let scene = self.scene(); let mut occupied_index = 0_u32; for slot in scene.rectangles() { match slot { Some(rectangle) => { if occupied_index == requested_index { return Some(*rectangle); } occupied_index = occupied_index.saturating_add(1); }, None => {}, } } return None; } } fn parse_platform_family(label: &str) -> Option { return match label { "android" => Some(engine_v1_platform_api::PlatformFamily::Android), "desktop" => Some(engine_v1_platform_api::PlatformFamily::Desktop), "web" => Some(engine_v1_platform_api::PlatformFamily::Web), _ => None, }; } fn parse_runtime_host(label: &str) -> Option { return match label { "browser" => Some(engine_v1_platform_api::RuntimeHost::Browser), "native" => Some(engine_v1_platform_api::RuntimeHost::Native), "tauri-webview" => Some(engine_v1_platform_api::RuntimeHost::TauriWebView), _ => None, }; } fn parse_device_class(label: &str) -> Option { return match label { "desktop" => Some(engine_v1_platform_api::DeviceClass::Desktop), "phone" => Some(engine_v1_platform_api::DeviceClass::Phone), "tablet" => Some(engine_v1_platform_api::DeviceClass::Tablet), "unknown" => Some(engine_v1_platform_api::DeviceClass::Unknown), _ => None, }; } fn parse_input_profile(label: &str) -> Option { return match label { "gamepad" => Some(engine_v1_platform_api::InputProfile::Gamepad), "keyboard-mouse" => Some(engine_v1_platform_api::InputProfile::KeyboardMouse), "mixed" => Some(engine_v1_platform_api::InputProfile::Mixed), "touch" => Some(engine_v1_platform_api::InputProfile::Touch), "unknown" => Some(engine_v1_platform_api::InputProfile::Unknown), _ => None, }; } fn device_class_label(value: engine_v1_platform_api::DeviceClass) -> &'static str { return match value { engine_v1_platform_api::DeviceClass::Desktop => "desktop", engine_v1_platform_api::DeviceClass::Phone => "phone", engine_v1_platform_api::DeviceClass::Tablet => "tablet", engine_v1_platform_api::DeviceClass::Unknown => "unknown", }; } fn execution_model_label(value: engine_v1_platform_api::ExecutionModel) -> &'static str { return match value { engine_v1_platform_api::ExecutionModel::Native => "native", engine_v1_platform_api::ExecutionModel::Wasm => "wasm", }; } fn input_profile_label(value: engine_v1_platform_api::InputProfile) -> &'static str { return match value { engine_v1_platform_api::InputProfile::Gamepad => "gamepad", engine_v1_platform_api::InputProfile::KeyboardMouse => "keyboard-mouse", engine_v1_platform_api::InputProfile::Mixed => "mixed", engine_v1_platform_api::InputProfile::Touch => "touch", engine_v1_platform_api::InputProfile::Unknown => "unknown", }; } fn platform_family_label(value: engine_v1_platform_api::PlatformFamily) -> &'static str { return match value { engine_v1_platform_api::PlatformFamily::Android => "android", engine_v1_platform_api::PlatformFamily::Desktop => "desktop", engine_v1_platform_api::PlatformFamily::Web => "web", }; } fn runtime_host_label(value: engine_v1_platform_api::RuntimeHost) -> &'static str { return match value { engine_v1_platform_api::RuntimeHost::Browser => "browser", engine_v1_platform_api::RuntimeHost::Native => "native", engine_v1_platform_api::RuntimeHost::TauriWebView => "tauri-webview", }; } #[cfg(test)] #[path = "../unit_tests/runtime.rs"] mod tests;