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    /// Results of material ore-vein rules at this exact block position.
44    ore_vein_results: &'a [Option<BlockStateId>],
45    /// Lazily computed temperature condition value.
46    cold_enough_to_snow: Option<bool>,
47}
48
49impl<'a> SurfaceRuleContext<'a> {
50    /// Creates a surface rule context for one block position.
51    #[expect(
52        clippy::too_many_arguments,
53        reason = "surface rule context mirrors vanilla's flat condition input"
54    )]
55    pub fn new(
56        block_x: i32,
57        block_z: i32,
58        surface_depth: i32,
59        surface_secondary: f64,
60        min_surface_level: i32,
61        steep: bool,
62        block_y: i32,
63        stone_depth_above: i32,
64        stone_depth_below: i32,
65        water_height: i32,
66        biome_id: Option<u16>,
67        biome_provider: Option<&'a mut dyn SurfaceBiomeProvider>,
68        system: &'a dyn SurfaceNoiseProvider,
69        condition_noises: &'a SurfaceConditionNoiseCache<'a>,
70        block_states: &'a [BlockStateId],
71    ) -> Self {
72        Self {
73            block_x,
74            block_z,
75            surface_depth,
76            surface_secondary,
77            min_surface_level,
78            steep,
79            block_y,
80            stone_depth_above,
81            stone_depth_below,
82            water_height,
83            biome_id,
84            biome_provider,
85            system,
86            condition_noises,
87            block_states,
88            ore_vein_results: &[],
89            cold_enough_to_snow: None,
90        }
91    }
92
93    /// Supplies the already-evaluated ore-vein nodes for this material-rule evaluation.
94    #[must_use]
95    pub const fn with_ore_vein_results(mut self, results: &'a [Option<BlockStateId>]) -> Self {
96        self.ore_vein_results = results;
97        self
98    }
99
100    /// Returns a column-cached surface condition noise value.
101    #[must_use]
102    pub fn condition_noise(&self, noise_index: usize) -> f64 {
103        self.condition_noises
104            .get(noise_index, self.system, self.block_x, self.block_z)
105    }
106
107    /// Returns an uncached surface condition noise value sampled at this block.
108    #[must_use]
109    pub fn condition_noise_3d(&self, noise_index: usize) -> f64 {
110        self.system
111            .condition_noise_3d(noise_index, self.block_x, self.block_y, self.block_z)
112    }
113
114    /// Returns a pre-resolved block state emitted by the generated surface rule.
115    #[must_use]
116    pub const fn block_state(&self, block_state_index: usize) -> BlockStateId {
117        self.block_states[block_state_index]
118    }
119
120    /// Returns the result of an ore-vein node, if that node matched.
121    #[must_use]
122    pub fn ore_vein_result(&self, ore_vein_index: usize) -> Option<BlockStateId> {
123        match self.ore_vein_results.get(ore_vein_index) {
124            Some(result) => *result,
125            None => None,
126        }
127    }
128
129    /// Returns a biome ID already supplied by the caller.
130    #[must_use]
131    pub const fn known_biome_id(&self) -> Option<u16> {
132        self.biome_id
133    }
134
135    /// Returns the current biome ID if this context was built with one.
136    #[must_use]
137    pub fn biome_id(&mut self) -> Option<u16> {
138        if self.biome_id.is_some() {
139            return self.biome_id;
140        }
141
142        let provider = self.biome_provider.as_mut()?;
143        let biome_id = provider.biome_id(self.block_y);
144        self.biome_id = Some(biome_id);
145        Some(biome_id)
146    }
147
148    /// Lazily evaluates the vanilla temperature surface condition.
149    #[must_use]
150    pub fn cold_enough_to_snow(&mut self) -> bool {
151        if let Some(value) = self.cold_enough_to_snow {
152            return value;
153        }
154
155        let Some(biome_id) = self.biome_id() else {
156            return false;
157        };
158
159        let value =
160            self.system
161                .cold_enough_to_snow(biome_id, self.block_x, self.block_y, self.block_z);
162        self.cold_enough_to_snow = Some(value);
163        value
164    }
165}
166
167/// Supplies vanilla-fuzzed biome IDs to generated surface rules on demand.
168pub trait SurfaceBiomeProvider {
169    /// Returns the biome ID for the given block Y in the current X/Z column.
170    fn biome_id(&mut self, block_y: i32) -> u16;
171}
172
173/// Lazily caches x/z-only surface condition noise values for one column.
174pub struct SurfaceConditionNoiseCache<'a> {
175    values: &'a [Cell<f64>],
176    initialized: &'a [Cell<bool>],
177}
178
179impl<'a> SurfaceConditionNoiseCache<'a> {
180    /// Creates a cache backed by caller-owned reusable storage.
181    #[must_use]
182    pub fn new(values: &'a [Cell<f64>], initialized: &'a [Cell<bool>]) -> Self {
183        debug_assert_eq!(values.len(), initialized.len());
184        Self {
185            values,
186            initialized,
187        }
188    }
189
190    /// Clears populated markers before reusing the cache for another column.
191    pub fn reset(&self) {
192        for initialized in self.initialized {
193            initialized.set(false);
194        }
195    }
196
197    /// Returns a cached noise value, computing it if this column has not used it yet.
198    #[must_use]
199    pub fn get(
200        &self,
201        noise_index: usize,
202        system: &(impl SurfaceNoiseProvider + ?Sized),
203        x: i32,
204        z: i32,
205    ) -> f64 {
206        if self.initialized[noise_index].get() {
207            return self.values[noise_index].get();
208        }
209
210        let value = system.condition_noise(noise_index, x, z);
211        self.values[noise_index].set(value);
212        self.initialized[noise_index].set(true);
213        value
214    }
215}
216
217/// Trait for providing noise values and clay band data to surface rules.
218///
219/// Implemented by `SurfaceSystem` in steel-core. The transpiled code calls these
220/// methods through the `SurfaceRuleContext.system` field.
221pub trait SurfaceNoiseProvider {
222    /// Sample a surface condition noise at (x, z). The noise is identified by
223    /// its index in the dimension's `surface_noise_ids()` list.
224    fn condition_noise(&self, noise_index: usize, x: i32, z: i32) -> f64;
225
226    /// Sample a surface condition noise at (x, y, z). The noise is identified by
227    /// its index in the dimension's `surface_noise_ids()` list.
228    fn condition_noise_3d(&self, noise_index: usize, x: i32, y: i32, z: i32) -> f64;
229
230    /// Get the badlands clay band block at position (x, y, z).
231    fn get_band(&self, x: i32, y: i32, z: i32) -> BlockStateId;
232
233    /// Evaluates whether the biome temperature is cold enough for snow.
234    fn cold_enough_to_snow(&self, biome_id: u16, block_x: i32, block_y: i32, block_z: i32) -> bool;
235
236    /// Evaluate a vertical gradient condition using positional random.
237    ///
238    /// Returns true if the random value at `(block_x, block_y, block_z)` falls
239    /// within the gradient between `true_at_and_below` and `false_at_and_above`.
240    fn vertical_gradient(
241        &self,
242        gradient_index: usize,
243        block_x: i32,
244        block_y: i32,
245        block_z: i32,
246        true_at_and_below: i32,
247        false_at_and_above: i32,
248    ) -> bool;
249}