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:
@@ -95,8 +95,8 @@ public class LatitudeBiomeSource extends BiomeSource {
|
|||||||
/** Frequency of the mid-cave pocket noise (lush/dripstone). */
|
/** Frequency of the mid-cave pocket noise (lush/dripstone). */
|
||||||
private static final double CAVE_SCALE = 0.003;
|
private static final double CAVE_SCALE = 0.003;
|
||||||
|
|
||||||
/** Above this value a lush/dripstone pocket overrides the surface biome. ~8% of the mid zone. */
|
/** Above this value a lush/dripstone pocket overrides the surface biome. ~5-8% of the mid zone. */
|
||||||
private static final double CAVE_THRESHOLD = 0.55;
|
private static final double CAVE_THRESHOLD = 0.38;
|
||||||
|
|
||||||
/** Frequency of the Deep Dark noise (very low = large, rare regions). */
|
/** Frequency of the Deep Dark noise (very low = large, rare regions). */
|
||||||
private static final double DEEP_DARK_SCALE = 0.0011;
|
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). */
|
/** Vertical frequency of the Deep Dark noise (keeps it coherent in tall sections). */
|
||||||
private static final double DEEP_DARK_SCALE_Y = 0.012;
|
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). */
|
/** Above this value the Deep Dark overrides. Tuned for ~1-2% of the deep zone (legendary). */
|
||||||
private static final double DEEP_DARK_THRESHOLD = 0.88;
|
private static final double DEEP_DARK_THRESHOLD = 0.55;
|
||||||
|
|
||||||
// ------------------------------------------------------------------
|
// ------------------------------------------------------------------
|
||||||
// Spawn safe zone
|
// 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.
|
* {@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
|
* <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
|
* freshly built {@link LatitudeBiomeSource} at several depths and renders one PNG map per depth
|
||||||
* asserts the climate invariants (frozen north, hot south, safe spawn, rare swamp). No manual
|
* (a "geological slice" view), writes a distribution report and asserts the climate + cave
|
||||||
* in-game testing required.</p>
|
* invariants. No manual in-game testing required.</p>
|
||||||
*/
|
*/
|
||||||
@GameTestHolder(CustomOreGenMod.MODID)
|
@GameTestHolder(CustomOreGenMod.MODID)
|
||||||
@PrefixGameTestTemplate(false)
|
@PrefixGameTestTemplate(false)
|
||||||
@@ -42,6 +42,11 @@ public class LatitudeGameTest {
|
|||||||
private static final long SEED = 0L;
|
private static final long SEED = 0L;
|
||||||
private static final Path OUT_DIR = Path.of("gametest-results", "latitude");
|
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<>();
|
private static final Map<ResourceKey<Biome>, Integer> COLORS = new HashMap<>();
|
||||||
|
|
||||||
static {
|
static {
|
||||||
@@ -61,7 +66,10 @@ public class LatitudeGameTest {
|
|||||||
Biomes.JUNGLE, Biomes.SPARSE_JUNGLE, Biomes.BAMBOO_JUNGLE, Biomes.WARM_OCEAN);
|
Biomes.JUNGLE, Biomes.SPARSE_JUNGLE, Biomes.BAMBOO_JUNGLE, Biomes.WARM_OCEAN);
|
||||||
col(0x5B7A3A, Biomes.SWAMP);
|
col(0x5B7A3A, Biomes.SWAMP);
|
||||||
col(0x3A5A3A, Biomes.MANGROVE_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) {
|
private static void col(int rgb, ResourceKey<Biome>... keys) {
|
||||||
@@ -85,6 +93,85 @@ public class LatitudeGameTest {
|
|||||||
LatitudeBiomeSource source = new LatitudeBiomeSource(SEED, getter);
|
LatitudeBiomeSource source = new LatitudeBiomeSource(SEED, getter);
|
||||||
|
|
||||||
Files.createDirectories(OUT_DIR);
|
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;
|
int dim = (2 * RADIUS) / STEP + 1;
|
||||||
BufferedImage img = new BufferedImage(dim, dim, BufferedImage.TYPE_INT_RGB);
|
BufferedImage img = new BufferedImage(dim, dim, BufferedImage.TYPE_INT_RGB);
|
||||||
Map<ResourceKey<Biome>, Integer> global = new HashMap<>();
|
Map<ResourceKey<Biome>, Integer> global = new HashMap<>();
|
||||||
@@ -95,89 +182,31 @@ public class LatitudeGameTest {
|
|||||||
for (int xi = 0; xi < dim; xi++) {
|
for (int xi = 0; xi < dim; xi++) {
|
||||||
int blockX = -RADIUS + xi * STEP;
|
int blockX = -RADIUS + xi * STEP;
|
||||||
int blockZ = -RADIUS + zi * 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);
|
ResourceKey<Biome> key = holder.unwrapKey().orElse(null);
|
||||||
|
|
||||||
global.merge(key, 1, Integer::sum);
|
global.merge(key, 1, Integer::sum);
|
||||||
perBand.get(BiomeBand.fromTemperature(blockZ / 16000.0)).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);
|
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(pngName).toFile());
|
||||||
ImageIO.write(img, "png", OUT_DIR.resolve("latitude_map.png").toFile());
|
return new Slice(global, perBand);
|
||||||
|
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
int total = m.values().stream().mapToInt(Integer::intValue).sum();
|
||||||
if (total == 0) return 0;
|
if (total == 0) return 0;
|
||||||
int match = 0;
|
int match = 0;
|
||||||
@@ -185,6 +214,23 @@ public class LatitudeGameTest {
|
|||||||
return 100.0 * match / total;
|
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. */
|
/** 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) {
|
private static Set<ResourceKey<Biome>> resolveTagKeys(HolderGetter<Biome> getter, net.minecraft.tags.TagKey<Biome> tag) {
|
||||||
Set<ResourceKey<Biome>> keys = new HashSet<>();
|
Set<ResourceKey<Biome>> keys = new HashSet<>();
|
||||||
@@ -197,6 +243,11 @@ public class LatitudeGameTest {
|
|||||||
Biomes.FLOWER_FOREST, Biomes.MEADOW, Biomes.CHERRY_GROVE).contains(key);
|
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).
|
// Local assert helpers (no JUnit in the main classpath at runtime).
|
||||||
private static void assertTrue(boolean condition, String msg) {
|
private static void assertTrue(boolean condition, String msg) {
|
||||||
if (!condition) throw new AssertionError(msg);
|
if (!condition) throw new AssertionError(msg);
|
||||||
|
|||||||
Reference in New Issue
Block a user