test+feat: depth-slice map validation + tune cave thresholds to target

Add geological depth slices to the automated GameTest: it now samples the
latitude world at three depths and renders one PNG per depth (a top-down
'view' of that Y layer):
  - latitude_map_surface.png  (Y=64)
  - latitude_map_mid_cave.png (Y=-15)
  - latitude_map_deep.png     (Y=-50)
plus a report section per slice and new assertions:
  - Deep Dark must stay rare in the deep zone (<8%)
  - cave biomes (lush/dripstone) must stay a minority in mid-cave (<25%)

Cave biome colors are now distinct (lush=green, dripstone=orange, deep_dark=
teal) so the slices are readable.

Tuned the cave thresholds using the test as a fast feedback loop (BiomeSource-
only sampling, ~30s per run, mods-independent):
  - CAVE_THRESHOLD 0.55 -> 0.38   (lush/dripstone pockets now ~6.5%)
  - DEEP_DARK_THRESHOLD 0.88 -> 0.55 (Deep Dark now legendary ~1.3%; was 0%)

Validated with BOP + Tectonic:
  spawn=birch_forest | north=100% | south=100% | swamp=1.26% |
  deepDark=1.30% | caveBiomes=6.5%
This commit is contained in:
feldenr
2026-06-16 21:26:32 +02:00
parent 060b77b506
commit fa8c40652b
2 changed files with 132 additions and 81 deletions
@@ -95,8 +95,8 @@ public class LatitudeBiomeSource extends BiomeSource {
/** 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;
/** Above this value a lush/dripstone pocket overrides the surface biome. ~5-8% of the mid zone. */
private static final double CAVE_THRESHOLD = 0.38;
/** Frequency of the Deep Dark noise (very low = large, rare regions). */
private static final double DEEP_DARK_SCALE = 0.0011;
@@ -104,8 +104,8 @@ public class LatitudeBiomeSource extends BiomeSource {
/** 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;
/** Above this value the Deep Dark overrides. Tuned for ~1-2% of the deep zone (legendary). */
private static final double DEEP_DARK_THRESHOLD = 0.55;
// ------------------------------------------------------------------
// Spawn safe zone
@@ -29,9 +29,9 @@ import java.util.Set;
* {@link GameTest} so it executes inside a real game server with the full biome registry.
*
* <p>Launch with {@code ./gradlew runGameTestServer}. The test samples a large grid through a
* freshly built {@link LatitudeBiomeSource}, renders a PNG map, writes a distribution report and
* asserts the climate invariants (frozen north, hot south, safe spawn, rare swamp). No manual
* in-game testing required.</p>
* freshly built {@link LatitudeBiomeSource} at several depths and renders one PNG map per depth
* (a "geological slice" view), writes a distribution report and asserts the climate + cave
* invariants. No manual in-game testing required.</p>
*/
@GameTestHolder(CustomOreGenMod.MODID)
@PrefixGameTestTemplate(false)
@@ -42,6 +42,11 @@ public class LatitudeGameTest {
private static final long SEED = 0L;
private static final Path OUT_DIR = Path.of("gametest-results", "latitude");
// Sampling depths (one PNG per depth = a top-down "slice").
private static final int SURFACE_Y = 64;
private static final int MID_CAVE_Y = -15; // -30 <= Y < 0 (lush/dripstone pockets)
private static final int DEEP_Y = -50; // Y < -30 (legendary Deep Dark)
private static final Map<ResourceKey<Biome>, Integer> COLORS = new HashMap<>();
static {
@@ -61,7 +66,10 @@ public class LatitudeGameTest {
Biomes.JUNGLE, Biomes.SPARSE_JUNGLE, Biomes.BAMBOO_JUNGLE, Biomes.WARM_OCEAN);
col(0x5B7A3A, Biomes.SWAMP);
col(0x3A5A3A, Biomes.MANGROVE_SWAMP);
col(0x6B4E3A, Biomes.LUSH_CAVES, Biomes.DRIPSTONE_CAVES, Biomes.DEEP_DARK);
// Cave biomes - distinct colors so the slices are readable.
col(0x4DB05A, Biomes.LUSH_CAVES); // lush green
col(0xC8782C, Biomes.DRIPSTONE_CAVES); // stalactite orange-brown
col(0x0A4D54, Biomes.DEEP_DARK); // dark sculk teal
}
private static void col(int rgb, ResourceKey<Biome>... keys) {
@@ -85,6 +93,85 @@ public class LatitudeGameTest {
LatitudeBiomeSource source = new LatitudeBiomeSource(SEED, getter);
Files.createDirectories(OUT_DIR);
// ---- Sample one slice per depth. Each slice produces a PNG + a biome-count map. ----
Slice surface = sampleSlice(source, SURFACE_Y, "latitude_map_surface.png");
Slice midCave = sampleSlice(source, MID_CAVE_Y, "latitude_map_mid_cave.png");
Slice deep = sampleSlice(source, DEEP_Y, "latitude_map_deep.png");
// ---- Surface climate assertions ----
ResourceKey<Biome> spawnKey = source.getNoiseBiome(0, SURFACE_Y >> 2, 0, null).unwrapKey().orElse(null);
Set<ResourceKey<Biome>> coldTag = resolveTagKeys(getter, BiomeBand.COLD.surfaceTag());
Set<ResourceKey<Biome>> hotTag = resolveTagKeys(getter, BiomeBand.HOT.surfaceTag());
Set<ResourceKey<Biome>> coldOceans = new HashSet<>(List.of(
Biomes.FROZEN_OCEAN, Biomes.DEEP_FROZEN_OCEAN, Biomes.COLD_OCEAN, Biomes.DEEP_COLD_OCEAN,
Biomes.FROZEN_RIVER));
Set<ResourceKey<Biome>> hotOceans = new HashSet<>(List.of(
Biomes.WARM_OCEAN, Biomes.LUKEWARM_OCEAN, Biomes.DEEP_LUKEWARM_OCEAN));
assertNotNull(spawnKey, "spawn biome resolved");
assertTrue(isSafeSpawn(spawnKey), "spawn must be a safe biome, was " + spawnKey);
Set<ResourceKey<Biome>> northExpected = new HashSet<>();
northExpected.addAll(coldTag);
northExpected.addAll(coldOceans);
double north = sliceBandRatio(surface, BiomeBand.FROZEN, northExpected);
assertTrue(north > 60.0, "FROZEN band should be >60%% cold/frozen (by tag), was %.1f%%".formatted(north));
Set<ResourceKey<Biome>> southExpected = new HashSet<>();
southExpected.addAll(hotTag);
southExpected.addAll(hotOceans);
southExpected.add(Biomes.MANGROVE_SWAMP);
double south = sliceBandRatio(surface, BiomeBand.HOT, southExpected);
assertTrue(south > 60.0, "HOT band should be >60%% warm/hot (by tag), was %.1f%%".formatted(south));
// Swamp stays rare at the surface in the temperate band.
double swampRatio = biomeRatioInBand(surface, BiomeBand.TEMPERATE, Biomes.SWAMP);
assertTrue(swampRatio < 15.0, "swamp must be <15%% of temperate band, was %.2f%%".formatted(swampRatio));
// ---- Cave assertions ----
// Deep Dark is legendary: a small fraction of the deep slice only.
double deepDarkRatio = biomeRatio(deep, Biomes.DEEP_DARK);
assertTrue(deepDarkRatio < 8.0,
"Deep Dark must be rare in the deep zone (<8%%), was %.2f%%".formatted(deepDarkRatio));
// Cave biomes (lush/dripstone) should be a minority even in the mid-cave slice - the latitude
// biome dominates underground by design.
double caveBiomeRatio = biomeRatio(midCave, Biomes.LUSH_CAVES, Biomes.DRIPSTONE_CAVES);
assertTrue(caveBiomeRatio < 25.0,
"cave biomes must stay a minority in mid-cave (<25%%), was %.2f%%".formatted(caveBiomeRatio));
// ---- Report ----
StringBuilder r = new StringBuilder();
r.append("Latitude GameTest Report (seed=").append(SEED).append(")\n\n");
r.append("Spawn @ (0,0): ").append(spawnKey).append("\n\n");
r.append("=== Depth slices (PNG) ===\n");
r.append(String.format(" Surface Y=%3d : latitude_map_surface.png%n", SURFACE_Y));
r.append(String.format(" Mid cave Y=%3d: latitude_map_mid_cave.png%n", MID_CAVE_Y));
r.append(String.format(" Deep Y=%3d : latitude_map_deep.png%n%n", DEEP_Y));
r.append("=== Surface top biomes (top 12) ===\n");
appendTop(r, surface.global, 12);
r.append("\n=== Mid cave (Y=").append(MID_CAVE_Y).append(") top biomes (top 10) ===\n");
appendTop(r, midCave.global, 10);
r.append("\n=== Deep (Y=").append(DEEP_Y).append(") top biomes (top 10) ===\n");
appendTop(r, deep.global, 10);
Files.writeString(OUT_DIR.resolve("latitude_report.txt"), r.toString());
// ---- One-line summary ----
CustomOreGenMod.LOGGER.info(String.format(
"Latitude game test OK: spawn=%s | north=%.0f%% | south=%.0f%% | swamp=%.2f%% | "
+ "deepDark=%.2f%% | caveBiomes=%.1f%%",
spawnKey, north, south, swampRatio, deepDarkRatio, caveBiomeRatio));
}
/** Sample the whole grid at a single Y and write a PNG; returns the slice's biome counts. */
private static Slice sampleSlice(LatitudeBiomeSource source, int blockY, String pngName) throws Exception {
int dim = (2 * RADIUS) / STEP + 1;
BufferedImage img = new BufferedImage(dim, dim, BufferedImage.TYPE_INT_RGB);
Map<ResourceKey<Biome>, Integer> global = new HashMap<>();
@@ -95,89 +182,31 @@ public class LatitudeGameTest {
for (int xi = 0; xi < dim; xi++) {
int blockX = -RADIUS + xi * STEP;
int blockZ = -RADIUS + zi * STEP;
Holder<Biome> holder = source.getNoiseBiome(blockX >> 2, 64 >> 2, blockZ >> 2, null);
Holder<Biome> holder = source.getNoiseBiome(blockX >> 2, blockY >> 2, blockZ >> 2, null);
ResourceKey<Biome> key = holder.unwrapKey().orElse(null);
global.merge(key, 1, Integer::sum);
perBand.get(BiomeBand.fromTemperature(blockZ / 16000.0)).merge(key, 1, Integer::sum);
int rgb = key == null ? 0x888888 : COLORS.getOrDefault(key, 0x888888);
img.setRGB(xi, dim - 1 - zi, rgb);
img.setRGB(xi, dim - 1 - zi, rgb); // flip Z so north is at the top
}
}
ImageIO.write(img, "png", OUT_DIR.resolve("latitude_map.png").toFile());
ResourceKey<Biome> spawnKey = source.getNoiseBiome(0, 64 >> 2, 0, null).unwrapKey().orElse(null);
// Resolve the three surface tags so assertions can use tag membership (the source of
// truth) instead of a hardcoded vanilla-only biome list. This correctly counts BOP biomes.
Set<ResourceKey<Biome>> coldTag = resolveTagKeys(getter, BiomeBand.COLD.surfaceTag());
Set<ResourceKey<Biome>> temperateTag = resolveTagKeys(getter, BiomeBand.TEMPERATE.surfaceTag());
Set<ResourceKey<Biome>> hotTag = resolveTagKeys(getter, BiomeBand.HOT.surfaceTag());
Set<ResourceKey<Biome>> coldOceans = new HashSet<>(List.of(
Biomes.FROZEN_OCEAN, Biomes.DEEP_FROZEN_OCEAN, Biomes.COLD_OCEAN, Biomes.DEEP_COLD_OCEAN,
Biomes.FROZEN_RIVER));
Set<ResourceKey<Biome>> hotOceans = new HashSet<>(List.of(
Biomes.WARM_OCEAN, Biomes.LUKEWARM_OCEAN, Biomes.DEEP_LUKEWARM_OCEAN));
StringBuilder r = new StringBuilder();
r.append("Latitude GameTest Report (seed=").append(SEED).append(")\n\n");
r.append("Spawn @ (0,0): ").append(spawnKey).append("\n\n");
int total = global.values().stream().mapToInt(Integer::intValue).sum();
r.append("Global biome distribution (top 20):\n");
global.entrySet().stream()
.sorted(Map.Entry.<ResourceKey<Biome>, Integer>comparingByValue().reversed())
.limit(20)
.forEach(e -> r.append(String.format(" %-45s %6.2f%%%n", e.getKey(), 100.0 * e.getValue() / total)));
// Per-band breakdown for diagnostics.
for (BiomeBand band : BiomeBand.values()) {
Map<ResourceKey<Biome>, Integer> m = perBand.get(band);
int bt = m.values().stream().mapToInt(Integer::intValue).sum();
if (bt == 0) continue;
r.append("\nBand ").append(band).append(" (").append(bt).append(" samples):\n");
m.entrySet().stream()
.sorted(Map.Entry.<ResourceKey<Biome>, Integer>comparingByValue().reversed())
.limit(8)
.forEach(e -> r.append(String.format(" %-43s %6.2f%%%n", e.getKey(), 100.0 * e.getValue() / bt)));
}
Files.writeString(OUT_DIR.resolve("latitude_report.txt"), r.toString());
// ---- Assertions (tag-based: correctly counts vanilla + BOP biomes) ----
assertNotNull(spawnKey, "spawn biome resolved");
assertTrue(isSafeSpawn(spawnKey), "spawn must be a safe biome, was " + spawnKey);
Set<ResourceKey<Biome>> northExpected = new HashSet<>();
northExpected.addAll(coldTag);
northExpected.addAll(coldOceans);
Set<ResourceKey<Biome>> undergroundCold = new HashSet<>(List.of(
Biomes.DRIPSTONE_CAVES, Biomes.DEEP_DARK));
northExpected.addAll(undergroundCold);
double north = bandRatio(perBand.get(BiomeBand.FROZEN), northExpected);
assertTrue(north > 60.0, "FROZEN band should be >60%% cold/frozen (by tag), was %.1f%%".formatted(north));
Set<ResourceKey<Biome>> southExpected = new HashSet<>();
southExpected.addAll(hotTag);
southExpected.addAll(hotOceans);
southExpected.add(Biomes.MANGROVE_SWAMP);
double south = bandRatio(perBand.get(BiomeBand.HOT), southExpected);
assertTrue(south > 60.0, "HOT band should be >60%% warm/hot (by tag), was %.1f%%".formatted(south));
Map<ResourceKey<Biome>, Integer> temperate = perBand.get(BiomeBand.TEMPERATE);
int temperateTotal = temperate.values().stream().mapToInt(Integer::intValue).sum();
int temperateSwamp = temperate.getOrDefault(Biomes.SWAMP, 0);
double swampRatio = 100.0 * temperateSwamp / temperateTotal;
assertTrue(swampRatio < 15.0,
"swamp must be <15%% of temperate band, was %.2f%%".formatted(swampRatio));
// Log a one-line summary so it shows up in the game test output.
CustomOreGenMod.LOGGER.info(String.format(
"Latitude game test OK: spawn=%s | north=%.0f%% | south=%.0f%% | swamp=%.2f%%",
spawnKey, north, south, swampRatio));
ImageIO.write(img, "png", OUT_DIR.resolve(pngName).toFile());
return new Slice(global, perBand);
}
private static double bandRatio(Map<ResourceKey<Biome>, Integer> m, Set<ResourceKey<Biome>> expected) {
private static void appendTop(StringBuilder r, Map<ResourceKey<Biome>, Integer> counts, int limit) {
int total = counts.values().stream().mapToInt(Integer::intValue).sum();
counts.entrySet().stream()
.sorted(Map.Entry.<ResourceKey<Biome>, Integer>comparingByValue().reversed())
.limit(limit)
.forEach(e -> r.append(String.format(" %-45s %6.2f%%%n", e.getKey(), 100.0 * e.getValue() / total)));
}
/** Ratio of a set of biomes within a single band of a slice. */
private static double sliceBandRatio(Slice slice, BiomeBand band, Set<ResourceKey<Biome>> expected) {
Map<ResourceKey<Biome>, Integer> m = slice.perBand.get(band);
int total = m.values().stream().mapToInt(Integer::intValue).sum();
if (total == 0) return 0;
int match = 0;
@@ -185,6 +214,23 @@ public class LatitudeGameTest {
return 100.0 * match / total;
}
/** Ratio of one or more biomes across a whole slice. */
private static double biomeRatio(Slice slice, ResourceKey<Biome>... keys) {
int total = slice.global.values().stream().mapToInt(Integer::intValue).sum();
if (total == 0) return 0;
int match = 0;
for (ResourceKey<Biome> k : keys) match += slice.global.getOrDefault(k, 0);
return 100.0 * match / total;
}
/** Ratio of a biome within a single band of a slice. */
private static double biomeRatioInBand(Slice slice, BiomeBand band, ResourceKey<Biome> key) {
Map<ResourceKey<Biome>, Integer> m = slice.perBand.get(band);
int total = m.values().stream().mapToInt(Integer::intValue).sum();
if (total == 0) return 0;
return 100.0 * m.getOrDefault(key, 0) / total;
}
/** Resolve all biome keys that belong to a tag, via the real registry. */
private static Set<ResourceKey<Biome>> resolveTagKeys(HolderGetter<Biome> getter, net.minecraft.tags.TagKey<Biome> tag) {
Set<ResourceKey<Biome>> keys = new HashSet<>();
@@ -197,6 +243,11 @@ public class LatitudeGameTest {
Biomes.FLOWER_FOREST, Biomes.MEADOW, Biomes.CHERRY_GROVE).contains(key);
}
/** A sampled depth slice: global counts + per-band counts. */
private record Slice(Map<ResourceKey<Biome>, Integer> global,
Map<BiomeBand, Map<ResourceKey<Biome>, Integer>> perBand) {
}
// Local assert helpers (no JUnit in the main classpath at runtime).
private static void assertTrue(boolean condition, String msg) {
if (!condition) throw new AssertionError(msg);