Skip to main content

steel_worldgen/
surface.rs

1//! Surface rule context types used by both generated code and runtime.
2
3use std::cell::Cell;
4
5use crate::BlockStateId;
6
7/// Context data passed to transpiled surface rule functions.
8///
9/// This is a flat struct holding all the values a surface rule condition might need.
10/// The `SurfaceContext` in steel-core populates this and passes it to the generated
11/// `try_apply_surface_rule()` function.
12pub struct SurfaceRuleContext<'a> {
13    /// World X coordinate.
14    pub block_x: i32,
15    /// World Z coordinate.
16    pub block_z: i32,
17    /// Noise-based surface layer thickness (typically 3-6 blocks).
18    pub surface_depth: i32,
19    /// Surface secondary noise value for depth variation.
20    pub surface_secondary: f64,
21    /// Minimum surface level from preliminary surface interpolation.
22    pub min_surface_level: i32,
23    /// Whether this column has a steep slope.
24    pub steep: bool,
25    /// World Y coordinate.
26    pub block_y: i32,
27    /// How many solid blocks above the current position.
28    pub stone_depth_above: i32,
29    /// How many solid blocks below until the next cavity.
30    pub stone_depth_below: i32,
31    /// Y of water surface above this block, or `i32::MIN` if no water.
32    pub water_height: i32,
33    /// Cached numeric biome ID at the current position, if already known.
34    biome_id: Option<u16>,
35    /// Lazy biome lookup for rules that need the biome at this Y position.
36    biome_provider: Option<&'a mut dyn SurfaceBiomeProvider>,
37    /// Reference to the surface system for noise lookups and band generation.
38    pub system: &'a dyn SurfaceNoiseProvider,
39    /// Lazily populated column cache for surface condition noise values.
40    condition_noises: &'a SurfaceConditionNoiseCache<'a>,
41    /// Pre-resolved block states returned by generated surface rules.
42    block_states: &'a [BlockStateId],
43    /// Lazily computed temperature condition value.
44    cold_enough_to_snow: Option<bool>,
45}
46
47impl<'a> SurfaceRuleContext<'a> {
48    /// Creates a surface rule context for one block position.
49    #[expect(
50        clippy::too_many_arguments,
51        reason = "surface rule context mirrors vanilla's flat condition input"
52    )]
53    pub fn new(
54        block_x: i32,
55        block_z: i32,
56        surface_depth: i32,
57        surface_secondary: f64,
58        min_surface_level: i32,
59        steep: bool,
60        block_y: i32,
61        stone_depth_above: i32,
62        stone_depth_below: i32,
63        water_height: i32,
64        biome_id: Option<u16>,
65        biome_provider: Option<&'a mut dyn SurfaceBiomeProvider>,
66        system: &'a dyn SurfaceNoiseProvider,
67        condition_noises: &'a SurfaceConditionNoiseCache<'a>,
68        block_states: &'a [BlockStateId],
69    ) -> Self {
70        Self {
71            block_x,
72            block_z,
73            surface_depth,
74            surface_secondary,
75            min_surface_level,
76            steep,
77            block_y,
78            stone_depth_above,
79            stone_depth_below,
80            water_height,
81            biome_id,
82            biome_provider,
83            system,
84            condition_noises,
85            block_states,
86            cold_enough_to_snow: None,
87        }
88    }
89
90    /// Returns a column-cached surface condition noise value.
91    #[must_use]
92    pub fn condition_noise(&self, noise_index: usize) -> f64 {
93        self.condition_noises
94            .get(noise_index, self.system, self.block_x, self.block_z)
95    }
96
97    /// Returns an uncached surface condition noise value sampled at this block.
98    #[must_use]
99    pub fn condition_noise_3d(&self, noise_index: usize) -> f64 {
100        self.system
101            .condition_noise_3d(noise_index, self.block_x, self.block_y, self.block_z)
102    }
103
104    /// Returns a pre-resolved block state emitted by the generated surface rule.
105    #[must_use]
106    pub const fn block_state(&self, block_state_index: usize) -> BlockStateId {
107        self.block_states[block_state_index]
108    }
109
110    /// Returns a biome ID already supplied by the caller.
111    #[must_use]
112    pub const fn known_biome_id(&self) -> Option<u16> {
113        self.biome_id
114    }
115
116    /// Returns the current biome ID if this context was built with one.
117    #[must_use]
118    pub fn biome_id(&mut self) -> Option<u16> {
119        if self.biome_id.is_some() {
120            return self.biome_id;
121        }
122
123        let provider = self.biome_provider.as_mut()?;
124        let biome_id = provider.biome_id(self.block_y);
125        self.biome_id = Some(biome_id);
126        Some(biome_id)
127    }
128
129    /// Lazily evaluates the vanilla temperature surface condition.
130    #[must_use]
131    pub fn cold_enough_to_snow(&mut self) -> bool {
132        if let Some(value) = self.cold_enough_to_snow {
133            return value;
134        }
135
136        let Some(biome_id) = self.biome_id() else {
137            return false;
138        };
139
140        let value =
141            self.system
142                .cold_enough_to_snow(biome_id, self.block_x, self.block_y, self.block_z);
143        self.cold_enough_to_snow = Some(value);
144        value
145    }
146}
147
148/// Supplies vanilla-fuzzed biome IDs to generated surface rules on demand.
149pub trait SurfaceBiomeProvider {
150    /// Returns the biome ID for the given block Y in the current X/Z column.
151    fn biome_id(&mut self, block_y: i32) -> u16;
152}
153
154/// Lazily caches x/z-only surface condition noise values for one column.
155pub struct SurfaceConditionNoiseCache<'a> {
156    values: &'a [Cell<f64>],
157    initialized: &'a [Cell<bool>],
158}
159
160impl<'a> SurfaceConditionNoiseCache<'a> {
161    /// Creates a cache backed by caller-owned reusable storage.
162    #[must_use]
163    pub fn new(values: &'a [Cell<f64>], initialized: &'a [Cell<bool>]) -> Self {
164        debug_assert_eq!(values.len(), initialized.len());
165        Self {
166            values,
167            initialized,
168        }
169    }
170
171    /// Clears populated markers before reusing the cache for another column.
172    pub fn reset(&self) {
173        for initialized in self.initialized {
174            initialized.set(false);
175        }
176    }
177
178    /// Returns a cached noise value, computing it if this column has not used it yet.
179    #[must_use]
180    pub fn get(
181        &self,
182        noise_index: usize,
183        system: &dyn SurfaceNoiseProvider,
184        x: i32,
185        z: i32,
186    ) -> f64 {
187        if self.initialized[noise_index].get() {
188            return self.values[noise_index].get();
189        }
190
191        let value = system.condition_noise(noise_index, x, z);
192        self.values[noise_index].set(value);
193        self.initialized[noise_index].set(true);
194        value
195    }
196}
197
198/// Trait for providing noise values and clay band data to surface rules.
199///
200/// Implemented by `SurfaceSystem` in steel-core. The transpiled code calls these
201/// methods through the `SurfaceRuleContext.system` field.
202pub trait SurfaceNoiseProvider {
203    /// Sample a surface condition noise at (x, z). The noise is identified by
204    /// its index in the dimension's `surface_noise_ids()` list.
205    fn condition_noise(&self, noise_index: usize, x: i32, z: i32) -> f64;
206
207    /// Sample a surface condition noise at (x, y, z). The noise is identified by
208    /// its index in the dimension's `surface_noise_ids()` list.
209    fn condition_noise_3d(&self, noise_index: usize, x: i32, y: i32, z: i32) -> f64;
210
211    /// Get the badlands clay band block at position (x, y, z).
212    fn get_band(&self, x: i32, y: i32, z: i32) -> BlockStateId;
213
214    /// Evaluates whether the biome temperature is cold enough for snow.
215    fn cold_enough_to_snow(&self, biome_id: u16, block_x: i32, block_y: i32, block_z: i32) -> bool;
216
217    /// Evaluate a vertical gradient condition using positional random.
218    ///
219    /// Returns true if the random value at `(block_x, block_y, block_z)` falls
220    /// within the gradient between `true_at_and_below` and `false_at_and_above`.
221    fn vertical_gradient(
222        &self,
223        gradient_index: usize,
224        block_x: i32,
225        block_y: i32,
226        block_z: i32,
227        true_at_and_below: i32,
228        false_at_and_above: i32,
229    ) -> bool;
230}