1use std::path::Path;
2use std::{cell::Cell, iter, marker::PhantomData, sync::Arc};
3
4use glam::{DVec3, IVec3};
5use rustc_hash::FxHashSet;
6use smallvec::SmallVec;
7use steel_math::lerp2;
8use steel_registry::biome::BiomeRef;
9use steel_registry::blocks::block_state_ext::BlockStateExt;
10use steel_registry::carver::ConfiguredCarverKind;
11use steel_registry::{REGISTRY, RegistryEntry, RegistryExt, vanilla_biomes};
12use steel_utils::random::{
13 Random, RandomSource, RandomSplitter, legacy_random::LegacyRandom, xoroshiro::Xoroshiro,
14};
15use steel_utils::{BlockPos, BlockStateId, ChunkPos, DowncastType, DowncastTypeKey, Identifier};
16use steel_worldgen::density::{ColumnCache, DimensionNoises, NoiseSettings};
17use steel_worldgen::density_functions::{
18 end::EndNoises, nether::NetherNoises, overworld::OverworldNoises,
19};
20use steel_worldgen::noise_parameters::get_noise_parameters;
21use steel_worldgen::surface::{
22 SurfaceBiomeProvider, SurfaceConditionNoiseCache, SurfaceRuleContext,
23};
24
25use crate::chunk::Chunk;
26use crate::chunk::heightmap::{Heightmap, HeightmapType};
27use crate::worldgen::carver::{
28 CarveRun, CarverBlockIds, CarvingContext, CarvingMask, PreliminarySurfaceCorners, SourceChunk,
29};
30use crate::worldgen::feature::FeatureDecorationRunner;
31use crate::worldgen::generator::surface_biomes::{
32 FuzzedBiomeColumn, SurfaceBiomeClasses, SurfaceQuartBiomes,
33};
34use crate::worldgen::generator::{
35 ChunkGenerator, GenerationChunk, TerrainPhase, worldgen_region_random_from_splitter,
36};
37use crate::worldgen::region::WorldGenRegion;
38use crate::worldgen::structure::{StructureGenerator, create_structures};
39use crate::worldgen::surface::SurfaceSystem;
40use steel_worldgen::biomes::BiomeSourceKind;
41use steel_worldgen::biomes::obfuscate_biome_seed;
42use steel_worldgen::noise::Beardifier;
43use steel_worldgen::noise::NoiseChunk;
44use steel_worldgen::noise::OreVeinifier;
45use steel_worldgen::noise::{Aquifer, AquiferResult, LazyAquifer, preliminary_surface_level};
46use steel_worldgen::noise::{CornerColumnStore, PreliminarySurfaceStore};
47use steel_worldgen::structure::GenerationContext;
48use steel_worldgen::surface_partial::PartialSurfaceOutcome;
49
50const CARVER_SOURCE_CHUNK_COUNT: usize = 17 * 17;
51
52pub trait VanillaPostNoiseStateType: DimensionNoises + 'static {
57 type State: DowncastType + Send + Sync;
59
60 fn wrap_post_noise_state(
62 aquifer: Aquifer<Self>,
63 material_ore_vein_values: Arc<[f32]>,
64 ) -> Self::State;
65
66 fn post_noise_aquifer(state: &mut Self::State) -> &mut Aquifer<Self>;
68
69 fn material_ore_vein_values(state: &mut Self::State) -> Arc<[f32]>;
71}
72
73#[doc(hidden)]
75pub struct SteelPostNoiseState<N: DimensionNoises> {
76 aquifer: Aquifer<N>,
77 material_ore_vein_values: Arc<[f32]>,
78}
79
80unsafe impl DowncastType for SteelPostNoiseState<OverworldNoises> {
82 const TYPE_KEY: DowncastTypeKey =
83 DowncastTypeKey::new("steel:worldgen_state/post_noise_overworld");
84}
85
86unsafe impl DowncastType for SteelPostNoiseState<NetherNoises> {
88 const TYPE_KEY: DowncastTypeKey =
89 DowncastTypeKey::new("steel:worldgen_state/post_noise_nether");
90}
91
92unsafe impl DowncastType for SteelPostNoiseState<EndNoises> {
94 const TYPE_KEY: DowncastTypeKey = DowncastTypeKey::new("steel:worldgen_state/post_noise_end");
95}
96
97macro_rules! impl_post_noise_state_type {
98 ($noises:ty) => {
99 impl VanillaPostNoiseStateType for $noises {
100 type State = SteelPostNoiseState<Self>;
101
102 fn wrap_post_noise_state(
103 aquifer: Aquifer<Self>,
104 material_ore_vein_values: Arc<[f32]>,
105 ) -> Self::State {
106 SteelPostNoiseState {
107 aquifer,
108 material_ore_vein_values,
109 }
110 }
111
112 fn post_noise_aquifer(state: &mut Self::State) -> &mut Aquifer<Self> {
113 &mut state.aquifer
114 }
115
116 fn material_ore_vein_values(state: &mut Self::State) -> Arc<[f32]> {
117 Arc::clone(&state.material_ore_vein_values)
118 }
119 }
120 };
121}
122
123impl_post_noise_state_type!(OverworldNoises);
124impl_post_noise_state_type!(NetherNoises);
125impl_post_noise_state_type!(EndNoises);
126
127pub struct VanillaGenerator<N: DimensionNoises> {
136 biome_source: BiomeSourceKind,
138 uniform_carver_biome: Option<BiomeRef>,
141 noises: Box<N>,
144 splitter: RandomSplitter,
146 preliminary_surface: Arc<PreliminarySurfaceStore>,
148 corner_columns: CornerColumnStore,
150 ore_veinifier: Option<OreVeinifier>,
152 surface_system: SurfaceSystem,
154 surface_extension_biomes: SurfaceExtensionBiomes,
156 surface_biome_classes: Option<SurfaceBiomeClasses>,
158 default_block_id: BlockStateId,
160 biome_zoom_seed: i64,
162 seed: i64,
164 structure_generator: StructureGenerator,
166 feature_runner: FeatureDecorationRunner,
168 _phantom: PhantomData<N>,
169}
170
171#[derive(Clone, Copy)]
172struct SurfaceExtensionBiomes {
173 eroded_badlands: bool,
174 frozen_ocean: bool,
175}
176
177impl SurfaceExtensionBiomes {
178 fn from_possible(possible_biomes: &FxHashSet<Identifier>) -> Self {
179 Self {
180 eroded_badlands: possible_biomes.contains(&vanilla_biomes::ERODED_BADLANDS.key),
181 frozen_ocean: possible_biomes.contains(&vanilla_biomes::FROZEN_OCEAN.key)
182 || possible_biomes.contains(&vanilla_biomes::DEEP_FROZEN_OCEAN.key),
183 }
184 }
185
186 const fn needs_surface_biome(self) -> bool {
187 self.eroded_badlands || self.frozen_ocean
188 }
189}
190
191impl<N: DimensionNoises> VanillaGenerator<N> {
192 #[must_use]
197 pub fn new(
198 world_path: Option<&Path>,
199 biome_source: BiomeSourceKind,
200 seed: u64,
201 thread_pool: &rayon::ThreadPool,
202 ) -> Self {
203 let splitter = if N::Settings::LEGACY_RANDOM_SOURCE {
205 LegacyRandom::from_seed(seed).next_positional()
206 } else {
207 Xoroshiro::from_seed(seed).next_positional()
208 };
209 let noise_params = get_noise_parameters();
210 let noises = N::create(seed, &splitter, &noise_params);
211
212 let ore_veinifier = if N::Settings::MATERIAL_ORE_VEINS_ENABLED {
213 Some(OreVeinifier::new(&splitter))
214 } else {
215 None
216 };
217
218 let default_block_id = N::Settings::default_block_id();
219 let surface_system = SurfaceSystem::new(
220 &splitter,
221 &noise_params,
222 N::surface_noise_ids(),
223 N::surface_gradient_ids(),
224 default_block_id,
225 N::Settings::SEA_LEVEL,
226 );
227
228 let biome_zoom_seed = obfuscate_biome_seed(seed as i64);
229
230 let possible_biome_refs = thread_pool.install(|| biome_source.possible_biome_refs());
233 let possible_biomes = biome_source.possible_biomes();
234 let surface_extension_biomes = SurfaceExtensionBiomes::from_possible(&possible_biomes);
235 let structure_generator =
236 StructureGenerator::vanilla(seed as i64, world_path, &biome_source, thread_pool);
237 let feature_runner = FeatureDecorationRunner::new(&possible_biome_refs, ®ISTRY);
238 let uniform_carver_biome = Self::uniform_carver_biome(&possible_biomes);
239
240 Self {
241 biome_source,
242 uniform_carver_biome,
243 noises: Box::new(noises),
244 splitter,
245 preliminary_surface: Arc::default(),
246 corner_columns: CornerColumnStore::default(),
247 ore_veinifier,
248 surface_system,
249 surface_extension_biomes,
250 surface_biome_classes: SurfaceBiomeClasses::new::<N>(),
251 default_block_id,
252 biome_zoom_seed,
253 seed: seed as i64,
254 structure_generator,
255 feature_runner,
256 _phantom: PhantomData,
257 }
258 }
259
260 fn uniform_carver_biome(possible_biomes: &FxHashSet<Identifier>) -> Option<BiomeRef> {
261 let mut possible_biomes = possible_biomes.iter();
262 let first_key = possible_biomes.next()?;
263 let first = REGISTRY.biomes.by_key(first_key)?;
264
265 possible_biomes
266 .all(|key| {
267 REGISTRY
268 .biomes
269 .by_key(key)
270 .is_some_and(|biome| biome.carvers == first.carvers)
271 })
272 .then_some(first)
273 }
274}
275
276impl<N: VanillaPostNoiseStateType> VanillaGenerator<N> {
277 fn preliminary_surface_corners(
278 &self,
279 chunk: GenerationChunk<'_, TerrainPhase>,
280 chunk_min_x: i32,
281 chunk_min_z: i32,
282 ) -> PreliminarySurfaceCorners {
283 let noises = &*self.noises;
284 if let Some(corners) = chunk.with_post_noise_state_mut::<N::State, _>(|state| {
285 let aquifer = N::post_noise_aquifer(state);
286 PreliminarySurfaceCorners {
287 nw: aquifer.preliminary_surface_level(noises, chunk_min_x, chunk_min_z),
288 ne: aquifer.preliminary_surface_level(noises, chunk_min_x + 16, chunk_min_z),
289 sw: aquifer.preliminary_surface_level(noises, chunk_min_x, chunk_min_z + 16),
290 se: aquifer.preliminary_surface_level(noises, chunk_min_x + 16, chunk_min_z + 16),
291 }
292 }) {
293 return corners;
294 }
295
296 let mut cache = N::ColumnCache::default();
297 PreliminarySurfaceCorners {
298 nw: preliminary_surface_level::<N>(noises, &mut cache, chunk_min_x, chunk_min_z),
299 ne: preliminary_surface_level::<N>(noises, &mut cache, chunk_min_x + 16, chunk_min_z),
300 sw: preliminary_surface_level::<N>(noises, &mut cache, chunk_min_x, chunk_min_z + 16),
301 se: preliminary_surface_level::<N>(
302 noises,
303 &mut cache,
304 chunk_min_x + 16,
305 chunk_min_z + 16,
306 ),
307 }
308 }
309}
310
311impl<N: VanillaPostNoiseStateType> ChunkGenerator for VanillaGenerator<N> {
312 fn min_y(&self) -> i32 {
313 N::Settings::MIN_Y
314 }
315
316 fn gen_depth(&self) -> i32 {
317 N::Settings::HEIGHT
318 }
319
320 fn noise_biome(&self, quart_x: i32, quart_y: i32, quart_z: i32) -> BiomeRef {
321 self.biome_source
322 .chunk_sampler()
323 .sample(quart_x, quart_y, quart_z)
324 }
325
326 fn initial_spawn_search_origin(&self) -> steel_utils::BlockPos {
327 self.biome_source.initial_spawn_search_origin()
328 }
329
330 fn structure_generator(&self) -> Option<&StructureGenerator> {
331 Some(&self.structure_generator)
332 }
333
334 fn create_structures(&self, chunk: &Chunk) {
335 let pos = chunk.pos();
336 let chunk_x = pos.0.x;
337 let chunk_z = pos.0.y;
338
339 let mut sampler = self.biome_source.chunk_sampler();
340 let chunk_min_x = chunk_x * 16;
341 let chunk_min_z = chunk_z * 16;
342
343 let mut height_cache = N::ColumnCache::default();
344 let sea_level = N::Settings::SEA_LEVEL;
345
346 let mut aquifer = LazyAquifer::new(
351 chunk_min_x,
352 chunk_min_z,
353 &self.splitter,
354 &*self.noises,
355 &self.preliminary_surface,
356 );
357 let mut surface_y_cache: Option<i32> = None;
358 let mut height_cache_grid_ready = false;
359 let mut ctx = GenerationContext::<'_, '_, N>::new(
360 self.seed,
361 chunk_x,
362 chunk_z,
363 sea_level,
364 &self.noises,
365 &self.splitter,
366 &self.preliminary_surface,
367 self.structure_generator.template_pools(),
368 self.structure_generator.templates(),
369 &mut sampler,
370 &mut height_cache,
371 &mut aquifer,
372 &mut surface_y_cache,
373 &mut height_cache_grid_ready,
374 );
375
376 create_structures(&self.structure_generator, chunk, &mut ctx);
377 }
378
379 fn create_biomes(&self, chunk: &Chunk) {
380 let pos = chunk.pos();
381 let min_y = chunk.min_y();
382 let section_count = chunk.sections().sections.len();
383
384 let chunk_x = pos.0.x;
385 let chunk_z = pos.0.y;
386
387 let mut sampler = self.biome_source.chunk_sampler();
388 sampler.init_grid(chunk_x * 16, chunk_z * 16);
394
395 for section_index in 0..section_count {
400 let section_y = (min_y / 16) + section_index as i32;
401 let section = &chunk.sections().sections[section_index];
402 let mut section_guard = section.write();
403
404 for local_quart_x in 0..4i32 {
405 let quart_x = chunk_x * 4 + local_quart_x;
406
407 for local_quart_y in 0..4i32 {
408 let quart_y = section_y * 4 + local_quart_y;
409
410 for local_quart_z in 0..4i32 {
411 let quart_z = chunk_z * 4 + local_quart_z;
412
413 let biome = sampler.sample(quart_x, quart_y, quart_z);
414 let biome_id = biome.id() as u16;
415
416 section_guard.biomes.set(
417 local_quart_x as usize,
418 local_quart_y as usize,
419 local_quart_z as usize,
420 biome_id,
421 );
422 }
423 }
424 }
425 }
426
427 chunk.mark_dirty();
428 }
429
430 #[expect(
431 clippy::too_many_lines,
432 reason = "mirrors Vanilla's terrain fill stage"
433 )]
434 fn fill_from_noise(
435 &self,
436 chunk: GenerationChunk<'_, TerrainPhase>,
437 beardifier: Option<&Beardifier>,
438 ) {
439 let pos = chunk.pos();
440 let chunk_min_x = pos.0.x * 16;
441 let chunk_min_z = pos.0.y * 16;
442
443 let min_y = N::Settings::MIN_Y;
444 let height = N::Settings::HEIGHT;
445
446 let mut noise_chunk = NoiseChunk::<N>::new(chunk_min_x, chunk_min_z);
447 let noises = &*self.noises;
448
449 let mut column_cache = N::ColumnCache::default();
450 column_cache.init_grid(chunk_min_x, chunk_min_z, noises);
451
452 let default_block_id = self.default_block_id;
453 let mut aquifer = Aquifer::<N>::new(
454 chunk_min_x,
455 chunk_min_z,
456 min_y,
457 height,
458 &self.splitter,
459 noises,
460 &self.preliminary_surface,
461 column_cache.clone(),
463 );
464
465 let mut pending_writes: Vec<(usize, BlockStateId)> = Vec::new();
468 let mut prev_x: usize = usize::MAX;
469 let mut prev_z: usize = usize::MAX;
470
471 let mut ocean_floor_complete = false;
472 let mut world_surface_complete = false;
473
474 let mut ocean_floor_wg =
475 Heightmap::new(HeightmapType::OceanFloorWg, min_y, N::Settings::HEIGHT);
476 let mut world_surface_wg =
477 Heightmap::new(HeightmapType::WorldSurfaceWg, min_y, N::Settings::HEIGHT);
478
479 let material_value_count = N::material_ore_vein_value_count();
480 let material_value_height = if material_value_count == 0 {
481 0
482 } else {
483 N::material_ore_vein_value_height()
484 };
485
486 let unsampled_air_min_y = aquifer
487 .unsampled_air_min_y()
488 .max(min_y + material_value_height as i32);
489
490 let mut material_ore_vein_values: Arc<[f32]> = iter::repeat_n(
492 0.0_f32,
493 16 * 16 * material_value_height * material_value_count,
494 )
495 .collect();
496
497 let material_values = Arc::make_mut(&mut material_ore_vein_values);
498 let mut material_cache = N::ColumnCache::default();
499 noise_chunk.fill(
500 noises,
501 &mut column_cache,
502 beardifier,
503 unsampled_air_min_y,
504 &self.corner_columns,
505 |local_x, world_y, local_z, density, interpolated, _| {
506 if local_x != prev_x || local_z != prev_z {
508 if !pending_writes.is_empty() {
509 chunk.write_noise_column(prev_x, prev_z, &pending_writes);
510 pending_writes.clear();
511 }
512 prev_x = local_x;
513 prev_z = local_z;
514 ocean_floor_complete = false;
515 world_surface_complete = false;
516 }
517
518 let relative_y = (world_y - min_y) as usize;
519 let world_x = chunk_min_x + local_x as i32;
520 let world_z = chunk_min_z + local_z as i32;
521
522 if material_value_count != 0 && relative_y < material_value_height {
523 let offset =
524 (relative_y * 16 * 16 + local_z * 16 + local_x) * material_value_count;
525 noises.fill_material_ore_vein_values(
526 &mut material_cache,
527 interpolated,
528 world_x,
529 world_y,
530 world_z,
531 &mut material_values[offset..offset + material_value_count],
532 );
533 }
534
535 let substance = aquifer.compute_substance(
536 noises,
537 world_x,
538 world_y,
539 world_z,
540 f64::from(density),
541 );
542 match substance {
543 AquiferResult::Solid => {
544 pending_writes.push((relative_y, default_block_id));
545 ocean_floor_complete = ocean_floor_complete
546 || ocean_floor_wg.update_for_initial_fill(
547 local_x,
548 world_y,
549 local_z,
550 default_block_id,
551 );
552 world_surface_complete = world_surface_complete
553 || world_surface_wg.update_for_initial_fill(
554 local_x,
555 world_y,
556 local_z,
557 default_block_id,
558 );
559 }
560 AquiferResult::Fluid(id) => {
561 pending_writes.push((relative_y, id));
562 ocean_floor_complete = ocean_floor_complete
563 || ocean_floor_wg
564 .update_for_initial_fill(local_x, world_y, local_z, id);
565 world_surface_complete = world_surface_complete
566 || world_surface_wg
567 .update_for_initial_fill(local_x, world_y, local_z, id);
568
569 if aquifer.should_schedule_fluid_update() && id.has_fluid() {
570 chunk.mark_pos_for_postprocessing(BlockPos::new(
571 world_x, world_y, world_z,
572 ));
573 }
574 }
575 AquiferResult::Air => {}
576 }
577 },
578 );
579
580 if !pending_writes.is_empty() {
582 chunk.write_noise_column(prev_x, prev_z, &pending_writes);
583 }
584 chunk.replace_noise_heightmaps(ocean_floor_wg, world_surface_wg);
585 chunk.install_post_noise_state(N::wrap_post_noise_state(aquifer, material_ore_vein_values));
586 }
587
588 #[expect(clippy::too_many_lines, reason = "splitting would hurt readability")]
589 fn build_surface(
590 &self,
591 chunk: GenerationChunk<'_, TerrainPhase>,
592 neighbor_biomes: &dyn Fn(IVec3) -> u16,
593 ) {
594 let min_y = N::Settings::MIN_Y;
595 let pos = chunk.pos();
596 let chunk_min_x = pos.0.x * 16;
597 let chunk_min_z = pos.0.y * 16;
598 let surface_rule_block_states = N::surface_rule_block_states();
599 let surface_rule_uses_biome = N::surface_rule_uses_biome();
600 let surface_rule_uses_preliminary_surface = N::surface_rule_uses_preliminary_surface();
601 let surface_rule_uses_surface_secondary = N::surface_rule_uses_surface_secondary();
602 let surface_rule_uses_steep = N::surface_rule_uses_steep();
603 let material_ore_vein_values = chunk
604 .with_post_noise_state_mut::<N::State, _>(N::material_ore_vein_values)
605 .unwrap_or_else(|| Arc::from([]));
606 let material_ore_vein_value_count = N::material_ore_vein_value_count();
607 let material_ore_vein_count = material_ore_vein_value_count / 2;
608 let mut material_cache = N::ColumnCache::default();
609 let lazy_surface_rule_biome =
610 surface_rule_uses_biome && surface_rule_uses_preliminary_surface;
611 let surface_needs_min_surface_level =
612 surface_rule_uses_preliminary_surface || self.surface_extension_biomes.frozen_ocean;
613 let surface_needs_biomes =
614 surface_rule_uses_biome || self.surface_extension_biomes.needs_surface_biome();
615 let chunk_quart_x = pos.0.x * 4;
616 let chunk_quart_z = pos.0.y * 4;
617
618 chunk.prime_world_surface_heightmap();
619
620 let preliminary_surface_corners = surface_needs_min_surface_level
622 .then(|| self.preliminary_surface_corners(chunk, chunk_min_x, chunk_min_z));
623
624 let eroded_badlands_id = (*vanilla_biomes::ERODED_BADLANDS).id() as u16;
625 let frozen_ocean_id = (*vanilla_biomes::FROZEN_OCEAN).id() as u16;
626 let deep_frozen_ocean_id = (*vanilla_biomes::DEEP_FROZEN_OCEAN).id() as u16;
627
628 let biome_data = surface_needs_biomes.then(|| chunk.read_all_biomes());
630 let mut quart_biomes = biome_data.as_deref().map(|biome_data| {
631 SurfaceQuartBiomes::new(
632 biome_data,
633 chunk.section_count(),
634 min_y,
635 chunk_quart_x,
636 chunk_quart_z,
637 neighbor_biomes,
638 )
639 });
640
641 let mut pending_writes: Vec<(usize, BlockStateId)> = Vec::new();
642 let mut column_buf: Vec<BlockStateId> = Vec::new();
643 let mut material_ore_vein_results: SmallVec<[Option<BlockStateId>; 2]> = SmallVec::new();
644 let condition_noise_values = N::surface_noise_ids()
645 .iter()
646 .map(|_| Cell::new(0.0))
647 .collect::<Vec<_>>();
648 let condition_noise_initialized = N::surface_noise_ids()
649 .iter()
650 .map(|_| Cell::new(false))
651 .collect::<Vec<_>>();
652 let condition_noise_cache =
653 SurfaceConditionNoiseCache::new(&condition_noise_values, &condition_noise_initialized);
654
655 for local_x in 0..16usize {
656 for local_z in 0..16usize {
657 let block_x = chunk_min_x + local_x as i32;
658 let block_z = chunk_min_z + local_z as i32;
659
660 let mut start_height = chunk.world_surface_height_at(local_x, local_z);
662
663 let mut biome_col = quart_biomes.as_mut().map(|quart_biomes| {
665 FuzzedBiomeColumn::new(
666 quart_biomes,
667 self.surface_biome_classes.as_ref(),
668 self.biome_zoom_seed,
669 block_x,
670 block_z,
671 )
672 });
673
674 let surface_biome_id = if self.surface_extension_biomes.needs_surface_biome() {
676 biome_col
677 .as_mut()
678 .map(|biome_col| biome_col.get(start_height))
679 } else {
680 None
681 };
682 if self.surface_extension_biomes.eroded_badlands
683 && surface_biome_id == Some(eroded_badlands_id)
684 {
685 start_height = self.surface_system.eroded_badlands_extension(
686 chunk,
687 local_x,
688 local_z,
689 block_x,
690 block_z,
691 start_height,
692 min_y,
693 );
694 }
695
696 chunk.read_column_into(local_x, local_z, &mut column_buf);
699
700 let surface_depth = self.surface_system.get_surface_depth(block_x, block_z);
702
703 let surface_secondary = if surface_rule_uses_surface_secondary {
704 self.surface_system.get_surface_secondary(block_x, block_z)
705 } else {
706 0.0
707 };
708 condition_noise_cache.reset();
709
710 let min_surface_level = if let Some(corners) = preliminary_surface_corners {
711 let t_x = f64::from(local_x as u8) / 16.0;
713 let t_z = f64::from(local_z as u8) / 16.0;
714 let interp = lerp2(
715 t_x,
716 t_z,
717 f64::from(corners.nw),
718 f64::from(corners.ne),
719 f64::from(corners.sw),
720 f64::from(corners.se),
721 );
722 interp.floor() as i32 + surface_depth - 8
723 } else {
724 0
725 };
726
727 let steep = surface_rule_uses_steep && {
730 let z_north = local_z.saturating_sub(1);
731 let z_south = (local_z + 1).min(15);
732 let h_north = chunk.world_surface_height_at(local_x, z_north) - 1;
733 let h_south = chunk.world_surface_height_at(local_x, z_south) - 1;
734 if h_south >= h_north + 4 {
735 true
736 } else {
737 let x_west = local_x.saturating_sub(1);
738 let x_east = (local_x + 1).min(15);
739 let h_west = chunk.world_surface_height_at(x_west, local_z) - 1;
740 let h_east = chunk.world_surface_height_at(x_east, local_z) - 1;
741 h_west >= h_east + 4
742 }
743 };
744
745 let mut stone_depth_above: i32 = 0;
746 let mut water_height: i32 = i32::MIN;
747 let mut next_ceiling_stone_y: i32 = i32::MAX;
748 pending_writes.clear();
749
750 for y in (min_y..=start_height).rev() {
751 let relative_y = (y - min_y) as usize;
752 let state = column_buf[relative_y];
753
754 if state.is_air() {
755 stone_depth_above = 0;
756 water_height = i32::MIN;
757 continue;
758 }
759
760 if state.get_block().config.liquid {
761 if water_height == i32::MIN {
762 water_height = y + 1;
763 }
764 continue;
765 }
766
767 if next_ceiling_stone_y >= y {
769 next_ceiling_stone_y = i32::MIN;
770 for la_y in (min_y - 1..y).rev() {
771 if la_y < min_y {
772 next_ceiling_stone_y = la_y + 1;
773 break;
774 }
775 let la_rel = (la_y - min_y) as usize;
776 let la_state = column_buf[la_rel];
777 if la_state.is_air() || la_state.get_block().config.liquid {
779 next_ceiling_stone_y = la_y + 1;
780 break;
781 }
782 }
783 }
784
785 stone_depth_above += 1;
786 let stone_depth_below = y - next_ceiling_stone_y + 1;
787
788 let outcome = if y < min_surface_level && preliminary_surface_corners.is_some()
791 {
792 biome_col
793 .as_mut()
794 .map_or(PartialSurfaceOutcome::Evaluate, |col| {
795 col.below_preliminary_outcome(y)
796 })
797 } else {
798 PartialSurfaceOutcome::Evaluate
799 };
800 match outcome {
801 PartialSurfaceOutcome::Keep => continue,
802 PartialSurfaceOutcome::Place(index) => {
803 pending_writes.push((relative_y, surface_rule_block_states[index]));
804 continue;
805 }
806 PartialSurfaceOutcome::Evaluate => {}
807 }
808
809 material_ore_vein_results.clear();
810 material_ore_vein_results.resize(material_ore_vein_count, None);
811 if let Some(ore_veinifier) = &self.ore_veinifier {
812 let offset = (relative_y * 16 * 16 + local_z * 16 + local_x)
813 * material_ore_vein_value_count;
814 if let Some(values) = material_ore_vein_values
815 .get(offset..offset + material_ore_vein_value_count)
816 {
817 N::fill_prefilled_material_ore_vein_results(
818 &self.noises,
819 &mut material_cache,
820 ore_veinifier,
821 values,
822 block_x,
823 y,
824 block_z,
825 &mut material_ore_vein_results,
826 );
827 }
828 }
829
830 let eager_biome_id = if surface_rule_uses_biome && !lazy_surface_rule_biome {
831 biome_col
832 .as_mut()
833 .map(|biome_col| biome_col.get_for_surface_rule(y))
834 } else {
835 None
836 };
837 let biome_provider = if lazy_surface_rule_biome {
838 biome_col
839 .as_mut()
840 .map(|biome_col| biome_col as &mut dyn SurfaceBiomeProvider)
841 } else {
842 None
843 };
844
845 let mut ctx = SurfaceRuleContext::new(
846 block_x,
847 block_z,
848 surface_depth,
849 surface_secondary,
850 min_surface_level,
851 steep,
852 y,
853 stone_depth_above,
854 stone_depth_below,
855 water_height,
856 eager_biome_id,
857 biome_provider,
858 &self.surface_system,
859 &condition_noise_cache,
860 surface_rule_block_states,
861 )
862 .with_ore_vein_results(&material_ore_vein_results);
863
864 if let Some(new_block) = N::try_apply_surface_rule(&mut ctx) {
865 pending_writes.push((relative_y, new_block));
866 }
867 }
868
869 if !pending_writes.is_empty() {
871 chunk.write_column(local_x, local_z, &pending_writes);
872 for &(relative_y, state) in &pending_writes {
873 column_buf[relative_y] = state;
874 }
875 }
876
877 if self.surface_extension_biomes.frozen_ocean
879 && let Some(surface_biome_id) = surface_biome_id
880 .filter(|id| *id == frozen_ocean_id || *id == deep_frozen_ocean_id)
881 {
882 pending_writes.clear();
883 self.surface_system.collect_frozen_ocean_extension_writes(
884 surface_biome_id,
885 block_x,
886 block_z,
887 start_height,
888 min_surface_level,
889 min_y,
890 &column_buf,
891 &mut pending_writes,
892 );
893 if !pending_writes.is_empty() {
894 chunk.write_column(local_x, local_z, &pending_writes);
895 }
896 }
897 }
898 }
899 }
900
901 #[expect(
902 clippy::too_many_lines,
903 reason = "keeps retained aquifer state, source carvers, and biome sampling in one flow"
904 )]
905 fn apply_carvers(&self, chunk: GenerationChunk<'_, TerrainPhase>) {
906 if self
907 .uniform_carver_biome
908 .is_some_and(|biome| biome.carvers.is_empty())
909 {
910 chunk.clear_post_noise_state();
911 return;
912 }
913
914 chunk.consume_post_noise_state::<N::State, _>(|mut retained_state| {
915 chunk.prime_world_surface_heightmap();
916
917 let pos = chunk.pos();
918 let chunk_min_x = pos.0.x * 16;
919 let chunk_min_z = pos.0.y * 16;
920 let min_y = N::Settings::MIN_Y;
921 let height = N::Settings::HEIGHT;
922 let noises = &*self.noises;
923
924 let material_ore_vein_values = retained_state
925 .as_deref_mut()
926 .map_or_else(|| Arc::from([]), N::material_ore_vein_values);
927
928 let mut rebuilt_aquifer = None;
929 let aquifer = if let Some(state) = retained_state {
930 N::post_noise_aquifer(state)
931 } else {
932 let mut column_cache = N::ColumnCache::default();
933 if N::Settings::AQUIFERS_ENABLED {
934 column_cache.init_grid(chunk_min_x, chunk_min_z, noises);
935 }
936 rebuilt_aquifer.insert(Aquifer::<N>::new(
937 chunk_min_x,
938 chunk_min_z,
939 min_y,
940 height,
941 &self.splitter,
942 noises,
943 &self.preliminary_surface,
944 column_cache,
945 ))
946 };
947
948 let psl_corners = PreliminarySurfaceCorners {
951 nw: aquifer.preliminary_surface_level(noises, chunk_min_x, chunk_min_z),
952 ne: aquifer.preliminary_surface_level(noises, chunk_min_x + 16, chunk_min_z),
953 sw: aquifer.preliminary_surface_level(noises, chunk_min_x, chunk_min_z + 16),
954 se: aquifer.preliminary_surface_level(noises, chunk_min_x + 16, chunk_min_z + 16),
955 };
956
957 let mut ctx = CarvingContext {
958 noises,
959 min_y,
960 gen_depth: height,
961 sea_level: N::Settings::SEA_LEVEL,
962 surface_system: &self.surface_system,
963 aquifer,
964 default_block_id: self.default_block_id,
965 psl_corners,
966 chunk_min_x,
967 chunk_min_z,
968 material_ore_vein_values,
969 ore_veinifier: self.ore_veinifier.as_ref(),
970 material_cache: N::ColumnCache::default(),
971 };
972
973 let ids = CarverBlockIds::load();
974
975 let mut biome_sampler = self.biome_source.chunk_sampler();
978 let mut source_biomes: SmallVec<[SourceChunk; CARVER_SOURCE_CHUNK_COUNT]> =
979 SmallVec::new();
980 for dx in -8i32..=8 {
981 for dz in -8i32..=8 {
982 let sx = pos.0.x + dx;
983 let sz = pos.0.y + dz;
984 let biome = if let Some(biome) = self.uniform_carver_biome {
985 biome
986 } else {
987 let qx = (sx * 16) >> 2;
988 let qz = (sz * 16) >> 2;
989 biome_sampler.sample(qx, 0, qz)
990 };
991 source_biomes.push(SourceChunk {
992 pos: ChunkPos::new(sx, sz),
993 biome,
994 });
995 }
996 }
997
998 let mut random = LegacyRandom::from_seed(0);
1002 let seed_i64 = self.seed;
1003
1004 let biome_zoom_seed = self.biome_zoom_seed;
1005 let mut biome_getter = |pos: BlockPos| -> u16 {
1009 fuzzed_biome_at_block(biome_zoom_seed, pos, |q_pos| {
1010 biome_sampler.sample(q_pos.x, q_pos.y, q_pos.z).id() as u16
1011 })
1012 };
1013
1014 let context_min_y = chunk.min_y().max(min_y);
1015 let context_height = chunk.height().min(height);
1016 let mut mask = CarvingMask::for_worldgen_context(context_min_y, context_height);
1017 let mut run = CarveRun {
1018 ctx: &mut ctx,
1019 noises,
1020 chunk,
1021 chunk_min_x,
1022 chunk_min_z,
1023 biome_getter: &mut biome_getter,
1024 mask: &mut mask,
1025 ids,
1026 };
1027
1028 run.run_all(&source_biomes, seed_i64, &mut random);
1029 if !run.mask.is_empty() {
1030 run.apply_carving_mask();
1031 }
1032 });
1033 }
1034
1035 fn create_worldgen_region_random(&self, _world_seed: i64, center: ChunkPos) -> RandomSource {
1036 worldgen_region_random_from_splitter(&self.splitter, center)
1037 }
1038
1039 fn apply_biome_decorations(&self, region: &mut WorldGenRegion<'_>) {
1040 self.feature_runner
1041 .decorate(region, ®ISTRY, self.seed, self.biome_zoom_seed);
1042 }
1043}
1044
1045impl<N, F> CarveRun<'_, '_, N, F>
1046where
1047 N: DimensionNoises,
1048 F: FnMut(BlockPos) -> u16,
1049{
1050 fn run_all(&mut self, source_biomes: &[SourceChunk], seed_i64: i64, random: &mut LegacyRandom) {
1054 for source in source_biomes {
1055 for (index, carver_key) in source.biome.carvers.iter().enumerate() {
1056 let Some(carver) = REGISTRY.configured_carvers.by_key(carver_key) else {
1057 panic!(
1058 "biome {} references unknown configured carver {}",
1059 source.biome.key, carver_key
1060 );
1061 };
1062 let index_i64 = index as i64;
1063 random.set_large_feature_seed(
1064 seed_i64.wrapping_add(index_i64),
1065 source.pos.0.x,
1066 source.pos.0.y,
1067 );
1068
1069 let probability = carver.base().probability;
1070 if random.next_f32() > probability {
1071 continue;
1072 }
1073
1074 match &carver.kind {
1075 ConfiguredCarverKind::Cave(cfg) => {
1076 self.carve_cave(cfg, source.pos, random);
1077 }
1078 ConfiguredCarverKind::Canyon(cfg) => {
1079 self.carve_canyon(cfg, source.pos, random);
1080 }
1081 }
1082 }
1083 }
1084 }
1085}
1086
1087#[inline]
1091pub(super) const fn lcg_next(mut rval: i64, c: i64) -> i64 {
1092 rval = rval.wrapping_mul(
1093 rval.wrapping_mul(6_364_136_223_846_793_005)
1094 .wrapping_add(1_442_695_040_888_963_407),
1095 );
1096 rval = rval.wrapping_add(c);
1097 rval
1098}
1099
1100#[inline]
1102pub(super) fn get_fiddle(rval: i64) -> f64 {
1103 let uniform = ((rval >> 24).rem_euclid(1024)) as f64 / 1024.0;
1104 (uniform - 0.5) * 0.9
1105}
1106
1107pub(crate) fn fuzzed_biome_at_block<F: FnMut(IVec3) -> u16>(
1118 biome_zoom_seed: i64,
1119 pos: BlockPos,
1120 mut quart_biome: F,
1121) -> u16 {
1122 let abs = pos.0 - IVec3::splat(2);
1123 let parent = IVec3::new(abs.x >> 2, abs.y >> 2, abs.z >> 2);
1124 let fract = DVec3::new(
1125 f64::from(abs.x & 3),
1126 f64::from(abs.y & 3),
1127 f64::from(abs.z & 3),
1128 ) / 4.0;
1129
1130 let mut min_i = 0usize;
1131 let mut min_dist = f64::INFINITY;
1132
1133 for i in 0..8usize {
1134 let x_even = (i & 4) == 0;
1135 let y_even = (i & 2) == 0;
1136 let z_even = (i & 1) == 0;
1137 let cx = if x_even { parent.x } else { parent.x + 1 };
1138 let cy = if y_even { parent.y } else { parent.y + 1 };
1139 let cz = if z_even { parent.z } else { parent.z + 1 };
1140 let dx = if x_even { fract.x } else { fract.x - 1.0 };
1141 let dy = if y_even { fract.y } else { fract.y - 1.0 };
1142 let dz = if z_even { fract.z } else { fract.z - 1.0 };
1143
1144 let mut rval = lcg_next(biome_zoom_seed, i64::from(cx));
1147 rval = lcg_next(rval, i64::from(cy));
1148 rval = lcg_next(rval, i64::from(cz));
1149 rval = lcg_next(rval, i64::from(cx));
1150 rval = lcg_next(rval, i64::from(cy));
1151 rval = lcg_next(rval, i64::from(cz));
1152 let fx = get_fiddle(rval);
1153 rval = lcg_next(rval, biome_zoom_seed);
1154 let fy = get_fiddle(rval);
1155 rval = lcg_next(rval, biome_zoom_seed);
1156 let fz = get_fiddle(rval);
1157
1158 let dx = dx + fx;
1159 let dy = dy + fy;
1160 let dz = dz + fz;
1161 let dist = dz * dz + dy * dy + dx * dx;
1162 if min_dist > dist {
1163 min_i = i;
1164 min_dist = dist;
1165 }
1166 }
1167
1168 let b = IVec3::new(
1169 if (min_i & 4) == 0 {
1170 parent.x
1171 } else {
1172 parent.x + 1
1173 },
1174 if (min_i & 2) == 0 {
1175 parent.y
1176 } else {
1177 parent.y + 1
1178 },
1179 if (min_i & 1) == 0 {
1180 parent.z
1181 } else {
1182 parent.z + 1
1183 },
1184 );
1185 quart_biome(b)
1186}
1187
1188#[cfg(test)]
1189mod tests {
1190 use std::sync::Weak;
1191
1192 use glam::IVec3;
1193 use steel_registry::{init_vanilla_registry, vanilla_dimension_types};
1194 use steel_worldgen::biomes::BiomeSourceKind;
1195
1196 use crate::behavior::init_behaviors;
1197 use crate::chunk::{
1198 Chunk,
1199 heightmap::{ChunkHeightmaps, Heightmap, HeightmapType},
1200 section::{ChunkSection, Sections},
1201 };
1202 use crate::worldgen::generator::{
1203 ChunkGenerator as _, GenerationChunk, TerrainPhase, context::OverworldGenerator,
1204 };
1205
1206 fn make_overworld_chunk() -> Chunk {
1207 let dimension = &vanilla_dimension_types::OVERWORLD;
1208 let sections = (0..dimension.height / 16)
1209 .map(|_| ChunkSection::new_empty())
1210 .collect::<Vec<_>>()
1211 .into_boxed_slice();
1212 Chunk::new(
1213 Sections::from_owned(sections),
1214 steel_utils::ChunkPos::new(0, 0),
1215 dimension.min_y,
1216 dimension.height,
1217 Weak::new(),
1218 )
1219 }
1220
1221 fn self_neighbor_biome(chunk: &Chunk, quart: IVec3) -> u16 {
1222 let sections = chunk.sections();
1223 let min_quart_y = chunk.min_y() >> 2;
1224 let quart_y =
1225 (quart.y - min_quart_y).clamp(0, (sections.sections.len() * 4) as i32 - 1) as usize;
1226 sections.sections[quart_y / 4].read().biomes.get(
1227 (quart.x & 3) as usize,
1228 quart_y % 4,
1229 (quart.z & 3) as usize,
1230 )
1231 }
1232
1233 fn blocks(chunk: &Chunk) -> Vec<steel_utils::BlockStateId> {
1234 let mut blocks = Vec::with_capacity((chunk.height() * 16 * 16) as usize);
1235 for relative_y in 0..chunk.height() as usize {
1236 for z in 0..16 {
1237 for x in 0..16 {
1238 let Some(state) = chunk.get_relative_block(x, relative_y, z) else {
1239 panic!("test coordinates must stay inside the chunk");
1240 };
1241 blocks.push(state);
1242 }
1243 }
1244 }
1245 blocks
1246 }
1247
1248 fn has_overworld_post_noise_state(chunk: &Chunk) -> bool {
1249 chunk
1250 .with_transient_generation_state_mut::<
1251 super::SteelPostNoiseState<super::OverworldNoises>,
1252 _,
1253 >(|_| ())
1254 .is_some()
1255 }
1256
1257 #[test]
1258 fn retained_and_rebuilt_aquifers_produce_identical_carver_output() {
1259 use std::panic;
1260 use std::thread;
1261
1262 let result = thread::Builder::new()
1265 .stack_size(16 * 1024 * 1024)
1266 .spawn(retained_and_rebuilt_aquifers_produce_identical_carver_output_inner)
1267 .expect("Failed to spawn test thread")
1268 .join();
1269
1270 if let Err(payload) = result {
1271 panic::resume_unwind(payload);
1272 }
1273 }
1274
1275 fn retained_and_rebuilt_aquifers_produce_identical_carver_output_inner() {
1276 init_vanilla_registry();
1277 init_behaviors();
1278 let pool = rayon::ThreadPoolBuilder::new()
1279 .num_threads(1)
1280 .build()
1281 .expect("test generation pool should build");
1282 let generator = OverworldGenerator::new(None, BiomeSourceKind::overworld(0), 0, &pool);
1283 let warm = make_overworld_chunk();
1284 let cold = make_overworld_chunk();
1285
1286 for chunk in [&warm, &cold] {
1287 generator.create_biomes(chunk);
1288 generator.fill_from_noise(GenerationChunk::<TerrainPhase>::for_test(chunk), None);
1289
1290 let mut expected = ChunkHeightmaps::empty();
1291 expected.prime_from_sections(
1292 HeightmapType::worldgen_types(),
1293 chunk.min_y(),
1294 chunk.height(),
1295 &chunk.sections().sections,
1296 );
1297 let actual = chunk.heightmaps.read();
1298 for &heightmap_type in HeightmapType::worldgen_types() {
1299 assert_eq!(
1300 actual.get(heightmap_type).map(Heightmap::raw_data),
1301 expected.get(heightmap_type).map(Heightmap::raw_data),
1302 "{heightmap_type:?} must match the generated blocks",
1303 );
1304 }
1305 }
1306 assert!(has_overworld_post_noise_state(&warm));
1307 assert!(has_overworld_post_noise_state(&cold));
1308
1309 cold.clear_transient_generation_state();
1310 for chunk in [&warm, &cold] {
1311 generator.build_surface(GenerationChunk::<TerrainPhase>::for_test(chunk), &|quart| {
1312 self_neighbor_biome(chunk, quart)
1313 });
1314 }
1315 assert!(has_overworld_post_noise_state(&warm));
1316 assert!(!has_overworld_post_noise_state(&cold));
1317
1318 generator.apply_carvers(GenerationChunk::<TerrainPhase>::for_test(&warm));
1319 generator.apply_carvers(GenerationChunk::<TerrainPhase>::for_test(&cold));
1320 assert!(!has_overworld_post_noise_state(&warm));
1321 assert!(!has_overworld_post_noise_state(&cold));
1322
1323 assert_eq!(blocks(&warm), blocks(&cold));
1324 assert_eq!(&*warm.postprocessing.lock(), &*cold.postprocessing.lock());
1325 for x in 0..16 {
1326 for z in 0..16 {
1327 assert_eq!(
1328 warm.generation_height_at(HeightmapType::WorldSurfaceWg, x, z),
1329 cold.generation_height_at(HeightmapType::WorldSurfaceWg, x, z)
1330 );
1331 }
1332 }
1333 }
1334}