feat(terrain): expose slot render dispatch

This commit is contained in:
2026-07-18 17:20:37 +04:00
parent 8b211f9a1c
commit 5577a04a40
2 changed files with 56 additions and 6 deletions
+47
View File
@@ -183,6 +183,33 @@ pub struct TerrainSlotTable {
pub slots_raw: Vec<[u8; SLOT_STRIDE]>, pub slots_raw: Vec<[u8; SLOT_STRIDE]>,
} }
/// The proven front fields of one 68-byte terrain slot record.
///
/// `Terrain.dll!CLandscape` supplies these fields to `GetShade`'s terrain
/// batch dispatcher: `pair_table_index` selects the pointer-table entry and
/// `pair_count` is the number of adjacent `u16` pairs to process. The pair
/// payload and the later material/blend interpretation remain external to the
/// disk record.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct TerrainSlotRenderDispatch {
/// Index into the runtime pair-pointer table.
pub pair_table_index: u16,
/// Number of adjacent `u16` pairs in that selected payload.
pub pair_count: u16,
}
impl TerrainSlotTable {
/// Returns the render-dispatch header for one decoded slot record.
#[must_use]
pub fn render_dispatch(&self, slot_index: usize) -> Option<TerrainSlotRenderDispatch> {
let raw = self.slots_raw.get(slot_index)?;
Some(TerrainSlotRenderDispatch {
pair_table_index: u16::from_le_bytes([raw[0], raw[1]]),
pair_count: u16::from_le_bytes([raw[2], raw[3]]),
})
}
}
/// Land mesh document. /// Land mesh document.
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct LandMeshDocument { pub struct LandMeshDocument {
@@ -1460,6 +1487,26 @@ mod tests {
); );
} }
#[test]
fn slot_render_dispatch_retains_pair_table_index_and_count() {
let mut raw = [0_u8; SLOT_STRIDE];
raw[..2].copy_from_slice(&7_u16.to_le_bytes());
raw[2..4].copy_from_slice(&3_u16.to_le_bytes());
let slots = TerrainSlotTable {
header_raw: SLOT_HEADER_ZERO.to_vec(),
slots_raw: vec![raw],
};
assert_eq!(
slots.render_dispatch(0),
Some(TerrainSlotRenderDispatch {
pair_table_index: 7,
pair_count: 3,
})
);
assert_eq!(slots.render_dispatch(1), None);
}
#[test] #[test]
fn terrain_material_tag_decodes_two_sidecar_selectors() { fn terrain_material_tag_decodes_two_sidecar_selectors() {
let mut raw_face = face([0, 1, 2], [None, None, None]); let mut raw_face = face([0, 1, 2], [None, None, None]);
+9 -6
View File
@@ -1575,12 +1575,15 @@ an overlay, blend, or microtexture shader.
The terrain render traversal is now located at `Terrain.dll` RVA `0x11340`. The terrain render traversal is now located at `Terrain.dll` RVA `0x11340`.
After visibility/cell work, it derives a 68-byte source-material record and After visibility/cell work, it derives a 68-byte source-material record and
dispatches renderer-context slot `+16` with four arguments: zero, the record's dispatches `Terrain!GetShade()` slot `+16` with four arguments: zero, the
16-bit word at `+2`, a pointer selected from a four-byte table by its word at record's word at `+2`, a pointer selected from a four-byte table by its word at
`+0`, and zero. A preceding renderer-context slot `+60` receives the current `+0`, and zero. `GetShade` is a Terrain singleton, not `niGet3DRender`; the
visibility mode. This is the concrete terrain-to-renderer handoff; it does not preceding slot `+60` call sets its visibility mode. Slot `+16` consumes the
call `GetMaterialPhase` directly and does not yet identify either word, the second word as the count of adjacent `u16` pairs and the first as the pair-table
four-byte payload, or the final blend operation. 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.
The static Vulkan bridge now carries each contiguous face run as a separate 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 draw range keyed by the original packed `material_tag`; it neither reorders