From 060b77b506e3ace37b1c9f85ff4d232d8a9549d4 Mon Sep 17 00:00:00 2001
From: feldenr <135638674+feldenr@users.noreply.github.com>
Date: Tue, 16 Jun 2026 21:12:17 +0200
Subject: [PATCH] feat: rethink cave generation - latitude extends underground,
Deep Dark legendary
The previous implementation forced a uniform cave-biome slab (lush/dripstone/
deep_dark) across all of Y<30, which overrode the latitude logic for a huge
volume and made the Deep Dark far too common.
New 3-zone underground model keeps the latitude surface biome as the default
at every depth (mining under a desert feels like the desert) and only carves
rare cave features on top:
- Near surface (Y >= 0): latitude biome as-is, no override.
- Mid caves (-30 <= Y < 0): lush/dripstone pockets (~8%) matching the climate
(humid/warm bands -> lush, dry/cold bands -> dripstone).
- Deep zone (Y < -30): legendary Deep Dark pockets (~1%, very low-frequency
noise so they form large rare regions suitable for Ancient Cities); the
surface latitude biome otherwise.
Deep Dark is removed from the biome band pools and driven solely by its own
noise, restoring it to a rare, legendary discovery. Cave biomes are now
resolved once and declared in possibleBiomes. BiomeBand.underground() removed
(caves are climate-driven, not band-pool-driven).
Validated by GameTest (BOP + Tectonic): spawn=birch_forest | north=100% |
south=100% | swamp=1.26%.
---
.../customoregen/worldgen/BiomeBand.java | 17 ---
.../worldgen/LatitudeBiomeSource.java | 129 +++++++++++++++---
2 files changed, 112 insertions(+), 34 deletions(-)
diff --git a/src/main/java/net/mcreator/customoregen/worldgen/BiomeBand.java b/src/main/java/net/mcreator/customoregen/worldgen/BiomeBand.java
index 293fe6e6..63be6e17 100644
--- a/src/main/java/net/mcreator/customoregen/worldgen/BiomeBand.java
+++ b/src/main/java/net/mcreator/customoregen/worldgen/BiomeBand.java
@@ -115,23 +115,6 @@ public enum BiomeBand {
}
}
- public List> underground() {
- switch (this) {
- case FROZEN:
- return List.of(Biomes.DRIPSTONE_CAVES, Biomes.DEEP_DARK);
- case COLD:
- return List.of(Biomes.DRIPSTONE_CAVES);
- case TEMPERATE:
- return List.of(Biomes.LUSH_CAVES, Biomes.DRIPSTONE_CAVES);
- case WARM:
- return List.of(Biomes.LUSH_CAVES);
- case HOT:
- return List.of(Biomes.LUSH_CAVES, Biomes.DEEP_DARK);
- default:
- return List.of(Biomes.LUSH_CAVES);
- }
- }
-
/** Resolve a list of (possibly absent) biome keys into actual holders, skipping missing ones. */
public static List> resolve(HolderGetter getter, List> keys) {
List> holders = new ArrayList<>();
diff --git a/src/main/java/net/mcreator/customoregen/worldgen/LatitudeBiomeSource.java b/src/main/java/net/mcreator/customoregen/worldgen/LatitudeBiomeSource.java
index 224b2d3e..9f947aa4 100644
--- a/src/main/java/net/mcreator/customoregen/worldgen/LatitudeBiomeSource.java
+++ b/src/main/java/net/mcreator/customoregen/worldgen/LatitudeBiomeSource.java
@@ -36,10 +36,16 @@ import java.util.stream.Stream;
* selector noise picks among the band's biomes, producing extremely large biomes that
* encourage exploration and long-distance travel (railways, roads, nether hubs).
*
+ * Underground philosophy: the surface latitude biome extends downward by default, so
+ * mining under a desert feels like being in the desert. Cave biomes (lush / dripstone) only
+ * appear as rare climatic pockets in the mid underground, and the Deep Dark is a legendary
+ * (~1%) feature of the deep zone. This keeps the latitude logic coherent at every depth
+ * instead of overriding it with a uniform cave-biome slab.
+ *
* Surface biomes come from dedicated climate tags (see {@code data/custom_ore_gen/tags/
* worldgen/biome/latitude_*_surface.json}), which support optional Biomes O' Plenty biomes
- * via {@code "required": false} without ever creating unbound holders. Ocean and underground
- * (cave) pools are vanilla-only.
+ * via {@code "required": false} without ever creating unbound holders. Ocean pools are
+ * vanilla-only.
*/
public class LatitudeBiomeSource extends BiomeSource {
@@ -49,7 +55,7 @@ public class LatitudeBiomeSource extends BiomeSource {
).apply(instance, instance.stable(LatitudeBiomeSource::new)));
// ------------------------------------------------------------------
- // Tunable constants
+ // Surface / climate tunables
// ------------------------------------------------------------------
/** Number of blocks for the temperature to go from 0 (equator) to +-1 (pole). */
@@ -76,8 +82,34 @@ public class LatitudeBiomeSource extends BiomeSource {
/** Above this moisture value a wet biome (swamp/mangrove) overrides the surface. ~8% of land. */
private static final double MOISTURE_THRESHOLD = 0.55;
- /** Below this block Y we resolve underground (cave) biomes. */
- private static final int UNDERGROUND_Y = 30;
+ // ------------------------------------------------------------------
+ // Underground tunables (3-zone model)
+ // ------------------------------------------------------------------
+
+ /** Top of the deep zone (Y < this = deep underground). Deep Dark can live here. */
+ private static final int DEEP_ZONE_TOP = -30;
+
+ /** Top of the mid-cave zone (DEEP_ZONE_TOP ≤ Y < this = mid caves, lush/dripstone pockets). */
+ private static final int MID_CAVE_TOP = 0;
+
+ /** Frequency of the mid-cave pocket noise (lush/dripstone). */
+ private static final double CAVE_SCALE = 0.003;
+
+ /** Above this value a lush/dripstone pocket overrides the surface biome. ~8% of the mid zone. */
+ private static final double CAVE_THRESHOLD = 0.55;
+
+ /** Frequency of the Deep Dark noise (very low = large, rare regions). */
+ private static final double DEEP_DARK_SCALE = 0.0011;
+
+ /** Vertical frequency of the Deep Dark noise (keeps it coherent in tall sections). */
+ private static final double DEEP_DARK_SCALE_Y = 0.012;
+
+ /** Above this value the Deep Dark overrides. Tuned for ~1% of the deep zone (legendary). */
+ private static final double DEEP_DARK_THRESHOLD = 0.88;
+
+ // ------------------------------------------------------------------
+ // Spawn safe zone
+ // ------------------------------------------------------------------
/** Half-size of the guaranteed safe spawn square around the origin (plains/forest). */
private static final int SPAWN_SAFE_RADIUS = 96;
@@ -96,6 +128,8 @@ public class LatitudeBiomeSource extends BiomeSource {
private final ImprovedNoise selectorNoise;
private final ImprovedNoise oceanNoise;
private final ImprovedNoise moistureNoise;
+ private final ImprovedNoise caveNoise;
+ private final ImprovedNoise deepDarkNoise;
/** Vanilla-only pools resolved eagerly (safe: vanilla biomes always exist and are bound). */
private final EnumMap resolvedVanilla;
@@ -109,6 +143,11 @@ public class LatitudeBiomeSource extends BiomeSource {
private final Holder swampBiome;
private final Holder mangroveBiome;
+ /** Cave biomes resolved eagerly (vanilla, always present). Lush = humid bands, Dripstone = dry bands. */
+ private final Holder lushCavesBiome;
+ private final Holder dripstoneCavesBiome;
+ private final Holder deepDarkBiome;
+
/** Mod-aware surface pools resolved lazily from climate tags (safe: tags are bound by then). */
private volatile EnumMap>> surfaceFromTag;
@@ -121,19 +160,25 @@ public class LatitudeBiomeSource extends BiomeSource {
this.selectorNoise = new ImprovedNoise(RandomSource.create(seed ^ 0x4C415449L));
this.oceanNoise = new ImprovedNoise(RandomSource.create(seed ^ 0x4F434541L));
this.moistureNoise = new ImprovedNoise(RandomSource.create(seed ^ 0x57455421L));
+ this.caveNoise = new ImprovedNoise(RandomSource.create(seed ^ 0x43415645L));
+ this.deepDarkNoise = new ImprovedNoise(RandomSource.create(seed ^ 0x44454550L));
this.resolvedVanilla = new EnumMap<>(BiomeBand.class);
Set> all = new HashSet<>();
for (BiomeBand band : BiomeBand.values()) {
List> surface = BiomeBand.resolve(biomeGetter, band.surfaceVanilla());
List> ocean = BiomeBand.resolve(biomeGetter, band.ocean());
- List> underground = BiomeBand.resolve(biomeGetter, band.underground());
- resolvedVanilla.put(band, new ResolvedBand(surface, ocean, underground));
+ resolvedVanilla.put(band, new ResolvedBand(surface, ocean));
all.addAll(surface);
all.addAll(ocean);
- all.addAll(underground);
}
+
+ // Cave biomes (vanilla) declared so the source advertises them as possible.
+ this.lushCavesBiome = resolveAndAdd(biomeGetter, Biomes.LUSH_CAVES, all);
+ this.dripstoneCavesBiome = resolveAndAdd(biomeGetter, Biomes.DRIPSTONE_CAVES, all);
+ this.deepDarkBiome = resolveAndAdd(biomeGetter, Biomes.DEEP_DARK, all);
+
this.possibleBiomes = all;
List> safe = new ArrayList<>();
@@ -151,6 +196,14 @@ public class LatitudeBiomeSource extends BiomeSource {
this.fallback = plains != null ? plains : (possibleBiomes.isEmpty() ? null : possibleBiomes.iterator().next());
}
+ private static Holder resolveAndAdd(HolderGetter getter, net.minecraft.resources.ResourceKey key, Set> into) {
+ Holder holder = getter.get(key).orElse(null);
+ if (holder != null) {
+ into.add(holder);
+ }
+ return holder;
+ }
+
@Override
protected MapCodec extends BiomeSource> codec() {
return CODEC;
@@ -182,19 +235,20 @@ public class LatitudeBiomeSource extends BiomeSource {
return pickBiome(spawnSafeBiomes, blockX, blockZ, SPAWN_SELECTOR_SCALE);
}
- // Underground (caves) layer.
- if (blockY < UNDERGROUND_Y && !pool.underground().isEmpty()) {
- return pickBiome(pool.underground(), blockX, blockZ, 0.004);
- }
+ // Determine the column's base biome: ocean mask, rare wet pocket, or latitude surface biome.
+ Holder base = baseSurfaceBiome(band, pool, blockX, blockZ);
- // Land vs ocean mask.
+ // Underground cave overrides (3-zone model) extend latitude rather than replace it.
+ return applyCaveOverrides(base, band, blockX, blockY, blockZ);
+ }
+
+ /** Resolve the surface biome for a column: ocean, swamp/mangrove pocket, or the band's surface tag. */
+ private Holder baseSurfaceBiome(BiomeBand band, ResolvedBand pool, int blockX, int blockZ) {
double oceanValue = oceanNoise.noise(blockX * OCEAN_NOISE_SCALE, 0.0, blockZ * OCEAN_NOISE_SCALE);
if (oceanValue > OCEAN_THRESHOLD && !pool.ocean().isEmpty()) {
return pickBiome(pool.ocean(), blockX, blockZ, 0.002);
}
- // Rare wet pockets: high moisture carves a swamp (temperate) or mangrove swamp (warm/hot),
- // matching vanilla where these are uncommon humid biomes rather than equal-weighted surface ones.
double moisture = moistureNoise.noise(blockX * MOISTURE_SCALE, 0.0, blockZ * MOISTURE_SCALE);
if (moisture > MOISTURE_THRESHOLD) {
if (band == BiomeBand.TEMPERATE && swampBiome != null) {
@@ -205,7 +259,6 @@ public class LatitudeBiomeSource extends BiomeSource {
}
}
- // Surface biome from the (mod-aware) climate tag, with a vanilla fallback.
List> surface = resolveSurfaceTag(band);
if (surface == null || surface.isEmpty()) {
surface = pool.surface();
@@ -216,6 +269,48 @@ public class LatitudeBiomeSource extends BiomeSource {
return pickBiome(surface, blockX, blockZ, SURFACE_SELECTOR_SCALE);
}
+ /**
+ * Underground overrides keep the latitude biome by default and only carve rare cave features:
+ *
+ * - Deep zone (Y < {@value #DEEP_ZONE_TOP}): legendary Deep Dark pockets (~1%), else surface biome.
+ * - Mid caves (DEEP_ZONE_TOP ≤ Y < {@value #MID_CAVE_TOP}): lush/dripstone pockets (~8%) matching the climate.
+ * - Near surface: no override, latitude biome as-is.
+ *
+ */
+ private Holder applyCaveOverrides(Holder surfaceBiome, BiomeBand band, int blockX, int blockY, int blockZ) {
+ // Deep zone: legendary Deep Dark.
+ if (blockY < DEEP_ZONE_TOP) {
+ if (deepDarkBiome != null) {
+ double dd = deepDarkNoise.noise(blockX * DEEP_DARK_SCALE, blockY * DEEP_DARK_SCALE_Y, blockZ * DEEP_DARK_SCALE);
+ if (dd > DEEP_DARK_THRESHOLD) {
+ return deepDarkBiome;
+ }
+ }
+ return surfaceBiome;
+ }
+
+ // Mid caves: rare lush/dripstone pockets by climate.
+ if (blockY < MID_CAVE_TOP) {
+ Holder caveBiome = caveBiomeFor(band);
+ if (caveBiome != null) {
+ double cave = caveNoise.noise(blockX * CAVE_SCALE, 0.0, blockZ * CAVE_SCALE);
+ if (cave > CAVE_THRESHOLD) {
+ return caveBiome;
+ }
+ }
+ }
+
+ return surfaceBiome;
+ }
+
+ /** Lush caves for humid/warm bands, dripstone for dry/cold bands. */
+ private Holder caveBiomeFor(BiomeBand band) {
+ if (band == BiomeBand.FROZEN || band == BiomeBand.COLD) {
+ return dripstoneCavesBiome;
+ }
+ return lushCavesBiome;
+ }
+
/** Lazily resolve surface biomes from climate tags. Tags are bound after the registry freezes,
* so this is safe and never creates unbound holders. */
private List> resolveSurfaceTag(BiomeBand band) {
@@ -258,6 +353,6 @@ public class LatitudeBiomeSource extends BiomeSource {
return value < min ? min : (value > max ? max : value);
}
- private record ResolvedBand(List> surface, List> ocean, List> underground) {
+ private record ResolvedBand(List> surface, List> ocean) {
}
}