Skip to main content

steel_worldgen/
surface_partial.rs

1//! resolve surface rules below the preliminary surface from y and biome sets
2//! anything undecidable stays opaque and falls back to the full rule
3
4/// A surface rule tree reduced to what is decidable below the preliminary surface.
5#[derive(Debug)]
6pub enum PartialSurfaceRule {
7    /// Place the rule's block state at this index.
8    Block(usize),
9    /// Children in order; the first that places a block wins.
10    Sequence(&'static [Self]),
11    /// Run the rule when the condition holds.
12    Condition(PartialSurfaceCondition, &'static Self),
13    /// A result that depends on more than Y and biome (e.g. badlands bands).
14    Opaque,
15}
16
17/// A surface condition reduced to what is decidable below the preliminary surface.
18#[derive(Debug)]
19pub enum PartialSurfaceCondition {
20    /// `block_y >= min_surface_level`: false for every block resolved here.
21    AbovePreliminarySurface,
22    /// Membership in the rule's `biome_is` set at this index.
23    BiomeSet(usize),
24    /// `vertical_gradient`: true at and below the first Y, false at and above
25    /// the second, a positional random draw in between.
26    VerticalGradient {
27        /// Highest Y where the condition always holds.
28        true_at_and_below: i32,
29        /// Lowest Y where the condition never holds.
30        false_at_and_above: i32,
31    },
32    /// `y_above` without stone depth or surface-depth scaling.
33    YAtLeast(i32),
34    /// Logical negation.
35    Not(&'static Self),
36    /// Depends on more than Y and biome (noise, stone depth, water, ...).
37    Opaque,
38}
39
40/// What a surface rule does to a default block, when decidable.
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42pub enum PartialSurfaceOutcome {
43    /// The rule places nothing.
44    Keep,
45    /// The rule places the block state at this index.
46    Place(usize),
47    /// The full rule must run for this block.
48    Evaluate,
49}
50
51impl PartialSurfaceRule {
52    /// resolve below the preliminary surface using y and biome memberships
53    /// first placing child wins; unknown conditions matter only if their body can place
54    #[must_use]
55    pub fn resolve(&self, block_y: i32, biome_set_mask: u64) -> PartialSurfaceOutcome {
56        match self {
57            Self::Block(index) => PartialSurfaceOutcome::Place(*index),
58            Self::Sequence(rules) => rules
59                .iter()
60                .map(|rule| rule.resolve(block_y, biome_set_mask))
61                .find(|outcome| *outcome != PartialSurfaceOutcome::Keep)
62                .unwrap_or(PartialSurfaceOutcome::Keep),
63            Self::Condition(condition, then_run) => {
64                match condition.resolve(block_y, biome_set_mask) {
65                    Some(false) => PartialSurfaceOutcome::Keep,
66                    Some(true) => then_run.resolve(block_y, biome_set_mask),
67                    None => match then_run.resolve(block_y, biome_set_mask) {
68                        PartialSurfaceOutcome::Keep => PartialSurfaceOutcome::Keep,
69                        _ => PartialSurfaceOutcome::Evaluate,
70                    },
71                }
72            }
73            Self::Opaque => PartialSurfaceOutcome::Evaluate,
74        }
75    }
76}
77
78impl PartialSurfaceCondition {
79    /// `Some` when the condition is decided by `block_y` and the biome sets.
80    #[must_use]
81    pub fn resolve(&self, block_y: i32, biome_set_mask: u64) -> Option<bool> {
82        match self {
83            Self::AbovePreliminarySurface => Some(false),
84            Self::BiomeSet(index) => Some(biome_set_mask & 1 << index != 0),
85            Self::VerticalGradient {
86                true_at_and_below,
87                false_at_and_above,
88            } => {
89                if block_y <= *true_at_and_below {
90                    Some(true)
91                } else if block_y >= *false_at_and_above {
92                    Some(false)
93                } else {
94                    None
95                }
96            }
97            Self::YAtLeast(anchor) => Some(block_y >= *anchor),
98            Self::Not(condition) => condition
99                .resolve(block_y, biome_set_mask)
100                .map(|value| !value),
101            Self::Opaque => None,
102        }
103    }
104}
105
106#[cfg(test)]
107mod tests {
108    use super::{
109        PartialSurfaceCondition as C, PartialSurfaceOutcome as O, PartialSurfaceRule as R,
110    };
111
112    /// Shaped like the 26.x overworld rule's top level.
113    static RULE: R = R::Sequence(&[
114        R::Condition(
115            C::VerticalGradient {
116                true_at_and_below: -64,
117                false_at_and_above: -59,
118            },
119            &R::Block(0),
120        ),
121        R::Condition(C::AbovePreliminarySurface, &R::Block(1)),
122        R::Condition(
123            C::BiomeSet(3),
124            &R::Sequence(&[R::Condition(C::Opaque, &R::Block(2))]),
125        ),
126        R::Condition(
127            C::VerticalGradient {
128                true_at_and_below: 0,
129                false_at_and_above: 8,
130            },
131            &R::Block(4),
132        ),
133    ]);
134
135    #[test]
136    fn undecidable_branches_force_evaluation_only_when_they_could_place() {
137        let sulfur = 1 << 3;
138        assert_eq!(RULE.resolve(-64, 0), O::Place(0));
139        assert_eq!(RULE.resolve(-62, 0), O::Evaluate); // bedrock gradient draw
140        assert_eq!(RULE.resolve(-30, 0), O::Place(4));
141        assert_eq!(RULE.resolve(4, 0), O::Evaluate); // deepslate gradient draw
142        assert_eq!(RULE.resolve(40, 0), O::Keep);
143        assert_eq!(RULE.resolve(40, sulfur), O::Evaluate); // sulfur noise
144        assert_eq!(RULE.resolve(-64, sulfur), O::Place(0)); // bedrock comes first
145    }
146}