From 5666c895427dea4fe5bfce6d12d2e7ced01d1046 Mon Sep 17 00:00:00 2001 From: LachyFS <100457804+LachyFS@users.noreply.github.com> Date: Wed, 1 Apr 2026 11:23:42 +1100 Subject: [PATCH] fix: add face validation, constants, and size checks to WASM API - Export FACE_POS_X..FACE_NEG_Z constants for JS consumers - Return errors for invalid face values instead of silent failures - Add ChunkNeighbors::size() and validate chunk/neighbor size match --- crates/urath-core/src/chunk.rs | 6 +++ crates/urath-wasm/src/lib.rs | 93 +++++++++++++++++++++++----------- 2 files changed, 70 insertions(+), 29 deletions(-) diff --git a/crates/urath-core/src/chunk.rs b/crates/urath-core/src/chunk.rs index ebe190a..40cae31 100644 --- a/crates/urath-core/src/chunk.rs +++ b/crates/urath-core/src/chunk.rs @@ -303,6 +303,12 @@ pub struct ChunkNeighbors { } impl ChunkNeighbors { + /// The chunk size this neighbor set was created for. + #[inline] + pub fn size(&self) -> usize { + self.size + } + /// Create neighbors with all faces treated as air. pub fn empty(size: usize) -> Self { Self { diff --git a/crates/urath-wasm/src/lib.rs b/crates/urath-wasm/src/lib.rs index 9dcc0f4..fc1224b 100644 --- a/crates/urath-wasm/src/lib.rs +++ b/crates/urath-wasm/src/lib.rs @@ -5,6 +5,51 @@ use urath::{ TerrainConfig, TerrainGenerator, }; +pub const FACE_POS_X: u8 = 0; +pub const FACE_NEG_X: u8 = 1; +pub const FACE_POS_Y: u8 = 2; +pub const FACE_NEG_Y: u8 = 3; +pub const FACE_POS_Z: u8 = 4; +pub const FACE_NEG_Z: u8 = 5; + +/// Face direction constants exported to JavaScript. +#[wasm_bindgen] +pub fn face_pos_x() -> u8 { + FACE_POS_X +} +#[wasm_bindgen] +pub fn face_neg_x() -> u8 { + FACE_NEG_X +} +#[wasm_bindgen] +pub fn face_pos_y() -> u8 { + FACE_POS_Y +} +#[wasm_bindgen] +pub fn face_neg_y() -> u8 { + FACE_NEG_Y +} +#[wasm_bindgen] +pub fn face_pos_z() -> u8 { + FACE_POS_Z +} +#[wasm_bindgen] +pub fn face_neg_z() -> u8 { + FACE_NEG_Z +} + +fn face_from_u8(val: u8) -> Result { + match val { + 0 => Ok(Face::PosX), + 1 => Ok(Face::NegX), + 2 => Ok(Face::PosY), + 3 => Ok(Face::NegY), + 4 => Ok(Face::PosZ), + 5 => Ok(Face::NegZ), + _ => Err(JsError::new(&format!("invalid face: {val}, must be 0-5"))), + } +} + /// WASM-exposed chunk that holds voxel data. #[wasm_bindgen] pub struct WasmChunk { @@ -73,18 +118,10 @@ impl WasmChunk { /// Extract the border slice for a given face direction. /// Returns a Uint16Array of size² elements. - pub fn extract_border(&self, face: u8) -> js_sys::Uint16Array { - let f = match face { - 0 => Face::PosX, - 1 => Face::NegX, - 2 => Face::PosY, - 3 => Face::NegY, - 4 => Face::PosZ, - 5 => Face::NegZ, - _ => return js_sys::Uint16Array::new_with_length(0), - }; + pub fn extract_border(&self, face: u8) -> Result { + let f = face_from_u8(face)?; let border = self.inner.extract_border(f); - js_sys::Uint16Array::from(&border[..]) + Ok(js_sys::Uint16Array::from(&border[..])) } /// True if all blocks are air. O(1). @@ -117,15 +154,7 @@ impl WasmChunkNeighbors { /// E.g., calling `set_neighbor(0, neighborChunk)` extracts the NegX (x=0) border /// from `neighborChunk` and uses it as the PosX neighbor data. pub fn set_neighbor(&mut self, face: u8, neighbor_chunk: &WasmChunk) -> Result<(), JsError> { - let face = match face { - 0 => Face::PosX, - 1 => Face::NegX, - 2 => Face::PosY, - 3 => Face::NegY, - 4 => Face::PosZ, - 5 => Face::NegZ, - _ => return Ok(()), - }; + let face = face_from_u8(face)?; let border = neighbor_chunk.inner.extract_border(face.opposite()); self.inner .set_face(face, border) @@ -135,15 +164,7 @@ impl WasmChunkNeighbors { /// Set neighbor border data directly from a Uint16Array (size² elements). /// Used by workers that don't have the neighbor WasmChunk. pub fn set_neighbor_border(&mut self, face: u8, data: &[u16]) -> Result<(), JsError> { - let face = match face { - 0 => Face::PosX, - 1 => Face::NegX, - 2 => Face::PosY, - 3 => Face::NegY, - 4 => Face::PosZ, - 5 => Face::NegZ, - _ => return Ok(()), - }; + let face = face_from_u8(face)?; self.inner .set_face(face, data.to_vec()) .map_err(|e| JsError::new(&e.to_string())) @@ -323,6 +344,13 @@ impl WasmGreedyMesher { chunk: &WasmChunk, neighbors: &WasmChunkNeighbors, ) -> Result { + if neighbors.inner.size() != chunk.inner.size() { + return Err(JsError::new(&format!( + "neighbor size {} does not match chunk size {}", + neighbors.inner.size(), + chunk.inner.size() + ))); + } let mut opaque = MeshOutput::with_capacity(4096); let mut transparent = MeshOutput::with_capacity(1024); self.inner @@ -385,6 +413,13 @@ impl WasmSurfaceNetsMesher { chunk: &WasmChunk, neighbors: &WasmChunkNeighbors, ) -> Result { + if neighbors.inner.size() != chunk.inner.size() { + return Err(JsError::new(&format!( + "neighbor size {} does not match chunk size {}", + neighbors.inner.size(), + chunk.inner.size() + ))); + } let mut opaque = MeshOutput::with_capacity(4096); let mut transparent = MeshOutput::new(); self.inner