aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/fparkan-terrain-format/src/lib.rs68
-rw-r--r--docs/tomes/05-render.md7
2 files changed, 73 insertions, 2 deletions
diff --git a/crates/fparkan-terrain-format/src/lib.rs b/crates/fparkan-terrain-format/src/lib.rs
index 0051b82..ce97d62 100644
--- a/crates/fparkan-terrain-format/src/lib.rs
+++ b/crates/fparkan-terrain-format/src/lib.rs
@@ -198,6 +198,19 @@ pub struct TerrainSlotRenderDispatch {
pub pair_count: u16,
}
+/// One pair consumed by the original terrain material-batch dispatcher.
+///
+/// These four bytes live in the type-11 stream. `material_lookup` is handed to
+/// `GetShade`'s material lookup, while bit `0x10` of `flags` creates a batch
+/// boundary. Other flag bits remain raw.
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub struct TerrainMaterialPair {
+ /// Lookup id supplied to the shade material manager.
+ pub material_lookup: u16,
+ /// Opaque pair flags; bit `0x10` is a proven batch boundary.
+ pub flags: u16,
+}
+
impl TerrainSlotTable {
/// Returns the render-dispatch header for one decoded slot record.
#[must_use]
@@ -238,6 +251,29 @@ pub struct LandMeshDocument {
pub faces: Vec<TerrainFace28>,
}
+impl LandMeshDocument {
+ /// Resolves the type-11 material pairs addressed by one 68-byte slot.
+ ///
+ /// Returns `None` when the slot is absent or its pair range does not fit
+ /// the retained type-11 stream.
+ #[must_use]
+ pub fn slot_material_pairs(&self, slot_index: usize) -> Option<Vec<TerrainMaterialPair>> {
+ let dispatch = self.slots.render_dispatch(slot_index)?;
+ let start = usize::from(dispatch.pair_table_index);
+ let end = start.checked_add(usize::from(dispatch.pair_count))?;
+ let pairs = self.accelerator.get(start..end)?;
+ Some(
+ pairs
+ .iter()
+ .map(|raw| TerrainMaterialPair {
+ material_lookup: u16::from_le_bytes([raw[0], raw[1]]),
+ flags: u16::from_le_bytes([raw[2], raw[3]]),
+ })
+ .collect(),
+ )
+ }
+}
+
/// Decoded `Land.map` document.
#[derive(Clone, Debug, PartialEq)]
pub struct LandMapDocument {
@@ -1508,6 +1544,38 @@ mod tests {
}
#[test]
+ fn slot_material_pairs_use_type11_pair_table_and_preserve_flags() {
+ let nres =
+ decode_nres(&minimal_land_msh(&face([0, 1, 2], [None, None, None]))).expect("nres");
+ let mut document = decode_land_msh(&nres).expect("land mesh");
+ document.slots = TerrainSlotTable {
+ header_raw: SLOT_HEADER_ZERO.to_vec(),
+ slots_raw: vec![[0_u8; SLOT_STRIDE]],
+ };
+ document.slots.slots_raw[0][..2].copy_from_slice(&1_u16.to_le_bytes());
+ document.slots.slots_raw[0][2..4].copy_from_slice(&2_u16.to_le_bytes());
+ document.accelerator = vec![[0, 0, 0, 0], [2, 1, 0, 0], [3, 2, 0x10, 0]];
+
+ assert_eq!(
+ document.slot_material_pairs(0),
+ Some(vec![
+ TerrainMaterialPair {
+ material_lookup: 0x0102,
+ flags: 0,
+ },
+ TerrainMaterialPair {
+ material_lookup: 0x0203,
+ flags: 0x0010,
+ },
+ ])
+ );
+ assert_eq!(document.slot_material_pairs(1), None);
+
+ document.slots.slots_raw[0][2..4].copy_from_slice(&3_u16.to_le_bytes());
+ assert_eq!(document.slot_material_pairs(0), None);
+ }
+
+ #[test]
fn terrain_material_tag_decodes_two_sidecar_selectors() {
let mut raw_face = face([0, 1, 2], [None, None, None]);
raw_face[4..6].copy_from_slice(&0x0102_u16.to_le_bytes());
diff --git a/docs/tomes/05-render.md b/docs/tomes/05-render.md
index 29319ed..5dd2e0c 100644
--- a/docs/tomes/05-render.md
+++ b/docs/tomes/05-render.md
@@ -1582,8 +1582,11 @@ preceding slot `+60` call sets its visibility mode. Slot `+16` consumes the
second word as the count of adjacent `u16` pairs and the first as the pair-table
index, then groups the pairs by material lookup before constructing draw work.
`TerrainSlotTable::render_dispatch` exposes exactly those two proven disk
-fields. The pair payload, its material key, and the final blend operation remain
-unassigned.
+fields, while `LandMeshDocument::slot_material_pairs` decodes the selected
+type-11 entries as `{ material_lookup: u16, flags: u16 }`. Flag `0x0010` is a
+proven batch boundary in `GetShade`; the lookup's mapping to the two WEAR
+tables, the meanings of its remaining flags, and the final blend operation
+remain unassigned.
The static Vulkan bridge now carries each contiguous face run as a separate
draw range keyed by the original packed `material_tag`; it neither reorders