Adapter and papi fixes

This commit is contained in:
Brian Neumann-Fopiano
2026-09-13 21:27:44 -04:00
parent c14a972ab8
commit dfb3748059
15 changed files with 796 additions and 30 deletions
+16 -1
View File
@@ -184,6 +184,19 @@ returns `---`. A real zero returns `0`.
| `%iris_world.available%` | `true` when the reading player is in an Iris world and a reading exists |
| `%iris_world.biome%` | Surface biome display name at the player, e.g. `Hot Desert Dunes` |
| `%iris_world.biome-key%` | Surface biome load key, e.g. `desert/hot-dunes` |
| `%iris_world.biome-custom%` | `true` when the surface biome defines custom derivatives, otherwise `false` |
| `%iris_world.biome-custom-id%` | Authored `customDerivitives[].id` when exactly one derivative is defined, for example `golden-dunes`; `---` for none or multiple |
| `%iris_world.biome-custom-ids%` | All authored custom derivative IDs, comma-and-space separated in definition order; `---` for none |
| `%iris_world.biome-custom-key%` | Namespaced Minecraft registry key for the sole custom derivative, such as `iris:biomes/<hash>`; `---` for none, multiple, or unavailable mapping |
| `%iris_world.biome-custom-keys%` | Registry keys for all custom derivatives in definition order, comma-and-space separated; `---` for none or any unavailable mapping |
| `%iris_world.biome-custom-count%` | Number of authored custom derivatives; `0` for a vanilla-only biome |
| `%iris_world.biome-derivative%` | Configured base derivative registry key, for example `minecraft:desert` |
| `%iris_world.biome-vanilla-derivative%` | Effective vanilla derivative registry key used when a vanilla biome is required |
| `%iris_world.biome-type%` | Inferred biome category: `land`, `sea`, `shore`, or `cave`; `---` when unavailable |
| `%iris_world.min-height%` | World minimum absolute Y, inclusive |
| `%iris_world.max-height%` | World maximum absolute Y, exclusive |
| `%iris_world.height%` | World vertical span, equal to maximum minus minimum height |
| `%iris_world.fluid-height%` | Dimension fluid level in absolute world Y; this is configuration, not a measurement of water at the player |
| `%iris_world.region%` | Region display name at the player |
| `%iris_world.region-key%` | Region load key |
| `%iris_world.dimension%` | Dimension (pack) load key of the player's world |
@@ -197,7 +210,9 @@ returns `---`. A real zero returns `0`.
| `%iris_pregen.chunks-per-second%` | Current rate |
| `%iris_pregen.paused%` | `true` while the job is paused |
The world values are the surface reading at the player's block column. Walking refreshes them at most
Custom derivative IDs are authored metadata. A singular ID or registry key is available only when the biome defines one derivative; plural keys list all definitions. Saved columns retain their historical biome definitions.
Biome and region values describe the surface at the player's block column. Height values describe world configuration. Walking refreshes them at most
once per second per player, so a whole board of `world.*` keys costs one refresh per player per
second no matter how many of them are on it, and a value may lag a sprinting player by up to a
second. A jump that is not walking - joining, respawning, changing worlds, stepping through a portal,
@@ -0,0 +1,24 @@
package art.arcane.iris.api.terrain;
import java.util.List;
public record IrisBiomeInfo(
String key,
String name,
String regionKey,
String regionName,
String derivativeKey,
String vanillaDerivativeKey,
String type,
List<IrisCustomBiomeInfo> customDerivatives) {
public IrisBiomeInfo {
key = key == null ? "" : key;
name = name == null ? "" : name;
regionKey = regionKey == null ? "" : regionKey;
regionName = regionName == null ? "" : regionName;
derivativeKey = derivativeKey == null ? "" : derivativeKey;
vanillaDerivativeKey = vanillaDerivativeKey == null ? "" : vanillaDerivativeKey;
type = type == null ? "" : type;
customDerivatives = List.copyOf(customDerivatives);
}
}
@@ -0,0 +1,8 @@
package art.arcane.iris.api.terrain;
public record IrisCustomBiomeInfo(String id, String registryKey) {
public IrisCustomBiomeInfo {
id = id == null ? "" : id;
registryKey = registryKey == null ? "" : registryKey;
}
}
@@ -18,6 +18,8 @@ public interface IrisTerrainService {
Optional<String> surfaceBiomeName(World world, int blockX, int blockZ);
Optional<IrisBiomeInfo> surfaceBiomeInfo(World world, int blockX, int blockZ);
Optional<String> biomeKey(World world, int blockX, int blockY, int blockZ);
Optional<String> regionKey(World world, int blockX, int blockZ);
@@ -42,6 +42,19 @@ public final class IrisPapiExpansion extends VolmitPlaceholderExpansion {
.key("world.available", state::worldAvailable)
.key("world.biome", state::biome)
.key("world.biome-key", state::biomeKey)
.key("world.biome-custom", state::biomeCustom)
.key("world.biome-custom-id", state::biomeCustomId)
.key("world.biome-custom-ids", state::biomeCustomIds)
.key("world.biome-custom-key", state::biomeCustomKey)
.key("world.biome-custom-keys", state::biomeCustomKeys)
.key("world.biome-custom-count", state::biomeCustomCount)
.key("world.biome-derivative", state::biomeDerivative)
.key("world.biome-vanilla-derivative", state::biomeVanillaDerivative)
.key("world.biome-type", state::biomeType)
.key("world.min-height", state::minHeight)
.key("world.max-height", state::maxHeight)
.key("world.height", state::height)
.key("world.fluid-height", state::fluidHeight)
.key("world.region", state::region)
.key("world.region-key", state::regionKey)
.key("world.dimension", state::dimension)
@@ -3,7 +3,6 @@ package art.arcane.iris.integration;
import art.arcane.iris.api.pregen.IrisPregenPhase;
import art.arcane.iris.api.pregen.IrisPregenProgress;
import art.arcane.iris.api.terrain.IrisTerrainService;
import art.arcane.iris.api.terrain.IrisWorldInfo;
import art.arcane.volmlib.util.bukkit.papi.PlaceholderSnapshot;
import art.arcane.volmlib.util.bukkit.papi.PlaceholderValues;
import art.arcane.volmlib.util.bukkit.papi.PlayerSnapshotStore;
@@ -103,6 +102,71 @@ public final class IrisPapiState {
return view == null ? PlaceholderValues.UNAVAILABLE : view.biomeKey();
}
public String biomeCustom(UUID playerId) {
IrisPapiWorldView view = viewOf(playerId);
return view == null ? PlaceholderValues.UNAVAILABLE : view.details().custom();
}
public String biomeCustomId(UUID playerId) {
IrisPapiWorldView view = viewOf(playerId);
return view == null ? PlaceholderValues.UNAVAILABLE : view.details().customId();
}
public String biomeCustomIds(UUID playerId) {
IrisPapiWorldView view = viewOf(playerId);
return view == null ? PlaceholderValues.UNAVAILABLE : view.details().customIds();
}
public String biomeCustomKey(UUID playerId) {
IrisPapiWorldView view = viewOf(playerId);
return view == null ? PlaceholderValues.UNAVAILABLE : view.details().customKey();
}
public String biomeCustomKeys(UUID playerId) {
IrisPapiWorldView view = viewOf(playerId);
return view == null ? PlaceholderValues.UNAVAILABLE : view.details().customKeys();
}
public String biomeCustomCount(UUID playerId) {
IrisPapiWorldView view = viewOf(playerId);
return view == null ? PlaceholderValues.UNAVAILABLE : view.details().customCount();
}
public String biomeDerivative(UUID playerId) {
IrisPapiWorldView view = viewOf(playerId);
return view == null ? PlaceholderValues.UNAVAILABLE : view.details().derivative();
}
public String biomeVanillaDerivative(UUID playerId) {
IrisPapiWorldView view = viewOf(playerId);
return view == null ? PlaceholderValues.UNAVAILABLE : view.details().vanillaDerivative();
}
public String biomeType(UUID playerId) {
IrisPapiWorldView view = viewOf(playerId);
return view == null ? PlaceholderValues.UNAVAILABLE : view.details().type();
}
public String minHeight(UUID playerId) {
IrisPapiWorldView view = viewOf(playerId);
return view == null ? PlaceholderValues.UNAVAILABLE : view.heights().minimum();
}
public String maxHeight(UUID playerId) {
IrisPapiWorldView view = viewOf(playerId);
return view == null ? PlaceholderValues.UNAVAILABLE : view.heights().maximum();
}
public String height(UUID playerId) {
IrisPapiWorldView view = viewOf(playerId);
return view == null ? PlaceholderValues.UNAVAILABLE : view.heights().height();
}
public String fluidHeight(UUID playerId) {
IrisPapiWorldView view = viewOf(playerId);
return view == null ? PlaceholderValues.UNAVAILABLE : view.heights().fluid();
}
public String region(UUID playerId) {
IrisPapiWorldView view = viewOf(playerId);
return view == null ? PlaceholderValues.UNAVAILABLE : view.region();
@@ -195,10 +259,7 @@ public final class IrisPapiState {
return IrisPapiWorldView.present(
position,
now,
service.surfaceBiomeName(world, blockX, blockZ),
service.surfaceBiomeKey(world, blockX, blockZ),
service.regionName(world, blockX, blockZ),
service.regionKey(world, blockX, blockZ),
service.worldInfo(world).map(IrisWorldInfo::dimensionKey));
service.surfaceBiomeInfo(world, blockX, blockZ),
service.worldInfo(world));
}
}
@@ -1,8 +1,13 @@
package art.arcane.iris.integration;
import art.arcane.iris.api.terrain.IrisBiomeInfo;
import art.arcane.iris.api.terrain.IrisCustomBiomeInfo;
import art.arcane.iris.api.terrain.IrisWorldInfo;
import art.arcane.volmlib.util.bukkit.papi.PlaceholderValues;
import java.util.List;
import java.util.Optional;
import java.util.StringJoiner;
public record IrisPapiWorldView(
IrisPapiPosition position,
@@ -12,7 +17,9 @@ public record IrisPapiWorldView(
String biomeKey,
String region,
String regionKey,
String dimension) {
String dimension,
BiomeDetails details,
Heights heights) {
public static IrisPapiWorldView absent(IrisPapiPosition position, long builtAtMs) {
return new IrisPapiWorldView(
position,
@@ -22,29 +29,82 @@ public record IrisPapiWorldView(
PlaceholderValues.UNAVAILABLE,
PlaceholderValues.UNAVAILABLE,
PlaceholderValues.UNAVAILABLE,
PlaceholderValues.UNAVAILABLE);
PlaceholderValues.UNAVAILABLE,
BiomeDetails.absent(),
Heights.absent());
}
public static IrisPapiWorldView present(
IrisPapiPosition position,
long builtAtMs,
Optional<String> biome,
Optional<String> biomeKey,
Optional<String> region,
Optional<String> regionKey,
Optional<String> dimension) {
Optional<IrisBiomeInfo> biome,
Optional<IrisWorldInfo> world) {
return new IrisPapiWorldView(
position,
builtAtMs,
PlaceholderValues.TRUE,
text(biome),
text(biomeKey),
text(region),
text(regionKey),
text(dimension));
text(biome.map(IrisBiomeInfo::name)),
text(biome.map(IrisBiomeInfo::key)),
text(biome.map(IrisBiomeInfo::regionName)),
text(biome.map(IrisBiomeInfo::regionKey)),
text(world.map(IrisWorldInfo::dimensionKey)),
biome.map(BiomeDetails::of).orElseGet(BiomeDetails::absent),
world.map(Heights::of).orElseGet(Heights::absent));
}
static String text(Optional<String> value) {
return value == null || value.isEmpty() ? PlaceholderValues.UNAVAILABLE : PlaceholderValues.text(value.get());
}
public record BiomeDetails(
String custom,
String customId,
String customIds,
String customKey,
String customKeys,
String customCount,
String derivative,
String vanillaDerivative,
String type) {
static BiomeDetails absent() {
String unavailable = PlaceholderValues.UNAVAILABLE;
return new BiomeDetails(unavailable, unavailable, unavailable, unavailable, unavailable,
unavailable, unavailable, unavailable, unavailable);
}
static BiomeDetails of(IrisBiomeInfo biome) {
List<IrisCustomBiomeInfo> derivatives = biome.customDerivatives();
StringJoiner ids = new StringJoiner(", ");
StringJoiner keys = new StringJoiner(", ");
boolean keysComplete = true;
for (IrisCustomBiomeInfo derivative : derivatives) {
ids.add(derivative.id());
keys.add(derivative.registryKey());
keysComplete &= !derivative.registryKey().isBlank();
}
IrisCustomBiomeInfo single = derivatives.size() == 1 ? derivatives.getFirst() : null;
return new BiomeDetails(
PlaceholderValues.bool(!derivatives.isEmpty()),
single == null ? PlaceholderValues.UNAVAILABLE : PlaceholderValues.text(single.id()),
PlaceholderValues.text(ids.toString()),
single == null ? PlaceholderValues.UNAVAILABLE : PlaceholderValues.text(single.registryKey()),
keysComplete ? PlaceholderValues.text(keys.toString()) : PlaceholderValues.UNAVAILABLE,
Integer.toString(derivatives.size()),
PlaceholderValues.text(biome.derivativeKey()),
PlaceholderValues.text(biome.vanillaDerivativeKey()),
PlaceholderValues.text(biome.type()));
}
}
public record Heights(String minimum, String maximum, String height, String fluid) {
static Heights absent() {
String unavailable = PlaceholderValues.UNAVAILABLE;
return new Heights(unavailable, unavailable, unavailable, unavailable);
}
static Heights of(IrisWorldInfo world) {
return new Heights(Integer.toString(world.minHeight()), Integer.toString(world.maxHeight()),
Integer.toString(world.height()), Integer.toString(world.fluidHeight()));
}
}
}
@@ -1,5 +1,6 @@
package art.arcane.iris.platform.bukkit.api;
import art.arcane.iris.api.terrain.IrisBiomeInfo;
import art.arcane.iris.api.terrain.IrisColumnField;
import art.arcane.iris.api.terrain.IrisColumnQuery;
import art.arcane.iris.api.terrain.IrisColumnSample;
@@ -10,6 +11,7 @@ import art.arcane.iris.api.terrain.IrisTerrainService;
import art.arcane.iris.api.terrain.IrisWorldInfo;
import art.arcane.iris.configuration.IrisSettings;
import art.arcane.iris.platform.bukkit.terrain.IrisApiFaultGuard;
import art.arcane.iris.platform.bukkit.terrain.IrisBiomeInfoFactory;
import art.arcane.iris.platform.bukkit.terrain.IrisColumnWalk;
import art.arcane.iris.platform.bukkit.terrain.IrisSampleLimits;
import art.arcane.iris.platform.bukkit.terrain.IrisSurfaceClassifier;
@@ -25,6 +27,7 @@ import art.arcane.iris.platform.bukkit.BukkitPlatform;
import art.arcane.iris.spi.IrisLogging;
import art.arcane.iris.spi.IrisServices;
import art.arcane.iris.platform.bukkit.plugin.IrisService;
import art.arcane.iris.world.history.SavedBiomeUnavailableException;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.generator.ChunkGenerator;
@@ -150,6 +153,22 @@ public class IrisTerrainSVC implements IrisService, IrisTerrainService {
}
}
@Override
public Optional<IrisBiomeInfo> surfaceBiomeInfo(World world, int blockX, int blockZ) {
Engine engine = liveEngineOf(world);
if (engine == null) {
return Optional.empty();
}
try {
return Optional.of(IrisBiomeInfoFactory.surface(engine, blockX, blockZ,
error -> reportQueryFault("surfaceBiomeInfo", world, error)));
} catch (Throwable error) {
reportQueryFault("surfaceBiomeInfo", world, error);
return Optional.empty();
}
}
@Override
public Optional<String> biomeKey(World world, int blockX, int blockY, int blockZ) {
Engine engine = liveEngineOf(world);
@@ -371,6 +390,10 @@ public class IrisTerrainSVC implements IrisService, IrisTerrainService {
}
private void reportQueryFault(String operation, World world, Throwable error) {
if (error instanceof SavedBiomeUnavailableException unavailable
&& unavailable.isLoading() && unavailable.getSuppressed().length == 0) {
return;
}
if (queryFaults.record(System.currentTimeMillis())) {
IrisLogging.reportError("Iris terrain API query \"" + operation + "\" failed for world \""
+ (world == null ? "null" : world.getName()) + "\" (" + queryFaults.faults()
@@ -379,6 +402,10 @@ public class IrisTerrainSVC implements IrisService, IrisTerrainService {
}
private void reportSinkFault(World world, Throwable error) {
if (error instanceof SavedBiomeUnavailableException unavailable
&& unavailable.isLoading() && unavailable.getSuppressed().length == 0) {
return;
}
if (sinkFaults.record(System.currentTimeMillis())) {
IrisLogging.reportError("Iris terrain API column sample failed for world \""
+ (world == null ? "null" : world.getName()) + "\" (" + sinkFaults.faults()
@@ -0,0 +1,59 @@
package art.arcane.iris.platform.bukkit.terrain;
import art.arcane.iris.api.terrain.IrisBiomeInfo;
import art.arcane.iris.api.terrain.IrisCustomBiomeInfo;
import art.arcane.iris.generation.biome.IrisBiome;
import art.arcane.iris.generation.biome.IrisBiomeCustom;
import art.arcane.iris.generation.runtime.BiomeEnvironment;
import art.arcane.iris.generation.runtime.Engine;
import art.arcane.iris.generation.terrain.InferredType;
import art.arcane.volmlib.util.collection.KList;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.function.Consumer;
public final class IrisBiomeInfoFactory {
private IrisBiomeInfoFactory() {
}
public static IrisBiomeInfo surface(Engine engine, int blockX, int blockZ, Consumer<Throwable> reportFailure) {
Objects.requireNonNull(reportFailure, "reportFailure");
BiomeEnvironment environment = engine.getSurfaceBiomeEnvironment(blockX, blockZ);
IrisBiome biome = environment.biome();
InferredType inferredType = biome.getInferredType();
return new IrisBiomeInfo(
biome.getLoadKey(),
biome.getName(),
environment.region().getLoadKey(),
environment.region().getName(),
biome.getDerivativeKey(),
biome.getVanillaDerivativeKey(),
inferredType == null ? "" : inferredType.name().toLowerCase(Locale.ROOT),
customDerivatives(environment, reportFailure));
}
private static List<IrisCustomBiomeInfo> customDerivatives(
BiomeEnvironment environment,
Consumer<Throwable> reportFailure) {
KList<IrisBiomeCustom> customBiomes = environment.biome().getCustomDerivitives();
if (customBiomes == null || customBiomes.isEmpty()) {
return List.of();
}
List<IrisCustomBiomeInfo> derivatives = new ArrayList<>(customBiomes.size());
for (IrisBiomeCustom customBiome : customBiomes) {
String registryKey = "";
try {
registryKey = environment.data().customBiomeResourceKey(environment.dimension(), customBiome);
} catch (RuntimeException failure) {
reportFailure.accept(new IllegalStateException(
"Unable to resolve the registry key for Iris custom biome '" + customBiome.getId() + "'.", failure));
}
derivatives.add(new IrisCustomBiomeInfo(customBiome.getId(), registryKey));
}
return derivatives;
}
}
@@ -0,0 +1,41 @@
package art.arcane.iris.api.terrain;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;
public class IrisBiomeInfoTest {
@Test
public void snapshotsDoNotExposeMutableDerivativeLists() {
List<IrisCustomBiomeInfo> derivatives = new ArrayList<>();
derivatives.add(new IrisCustomBiomeInfo("amber", "iris:biomes/amber"));
IrisBiomeInfo info = new IrisBiomeInfo(
"forests/amber", "Amber Forest", "temperate", "Temperate",
"minecraft:forest", "minecraft:forest", "land", derivatives);
derivatives.clear();
assertEquals(1, info.customDerivatives().size());
assertThrows(UnsupportedOperationException.class, () -> info.customDerivatives().clear());
}
@Test
public void unavailableStringsAreEmpty() {
IrisBiomeInfo info = new IrisBiomeInfo(null, null, null, null, null, null, null, List.of());
IrisCustomBiomeInfo custom = new IrisCustomBiomeInfo(null, null);
assertEquals("", info.key());
assertEquals("", info.name());
assertEquals("", info.regionKey());
assertEquals("", info.regionName());
assertEquals("", info.derivativeKey());
assertEquals("", info.vanillaDerivativeKey());
assertEquals("", info.type());
assertEquals("", custom.id());
assertEquals("", custom.registryKey());
}
}
@@ -1,5 +1,7 @@
package art.arcane.iris.integration;
import art.arcane.iris.api.terrain.IrisBiomeInfo;
import art.arcane.iris.api.terrain.IrisCustomBiomeInfo;
import art.arcane.iris.api.terrain.IrisColumnQuery;
import art.arcane.iris.api.terrain.IrisColumnSink;
import art.arcane.iris.api.terrain.IrisSurfaceKind;
@@ -8,6 +10,7 @@ import art.arcane.iris.api.terrain.IrisWorldInfo;
import org.bukkit.World;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.OptionalInt;
import java.util.Set;
@@ -20,6 +23,8 @@ final class FakeIrisTerrainService implements IrisTerrainService {
private String biomeKey = "desert/hot-dunes";
private String regionName = "Scorched Expanse";
private String regionKey = "scorched";
private List<IrisCustomBiomeInfo> derivatives = List.of(new IrisCustomBiomeInfo("golden-dunes", "iris:biomes/" + "a".repeat(64)));
private boolean biomeAvailable = true;
private int builds;
private int lastBlockX;
private int lastBlockZ;
@@ -36,6 +41,26 @@ final class FakeIrisTerrainService implements IrisTerrainService {
this.regionKey = regionKey;
}
void derivatives(List<IrisCustomBiomeInfo> derivatives) {
this.derivatives = derivatives;
}
void biomeAvailable(boolean available) {
biomeAvailable = available;
}
@Override
public Optional<IrisBiomeInfo> surfaceBiomeInfo(World world, int blockX, int blockZ) {
if (!isIrisWorld(world) || !biomeAvailable) {
return Optional.empty();
}
builds++;
lastBlockX = blockX;
lastBlockZ = blockZ;
return Optional.of(new IrisBiomeInfo(biomeKey, biomeName, regionKey, regionName,
"minecraft:desert", "minecraft:desert", "land", derivatives));
}
int builds() {
return builds;
}
@@ -70,19 +95,12 @@ final class FakeIrisTerrainService implements IrisTerrainService {
@Override
public Optional<String> surfaceBiomeKey(World world, int blockX, int blockZ) {
return isIrisWorld(world) ? Optional.ofNullable(biomeKey) : Optional.empty();
throw new AssertionError("placeholders must use a single biome metadata snapshot");
}
@Override
public Optional<String> surfaceBiomeName(World world, int blockX, int blockZ) {
if (!isIrisWorld(world)) {
return Optional.empty();
}
builds++;
lastBlockX = blockX;
lastBlockZ = blockZ;
return Optional.ofNullable(biomeName);
throw new AssertionError("placeholders must use a single biome metadata snapshot");
}
@Override
@@ -92,12 +110,12 @@ final class FakeIrisTerrainService implements IrisTerrainService {
@Override
public Optional<String> regionKey(World world, int blockX, int blockZ) {
return isIrisWorld(world) ? Optional.ofNullable(regionKey) : Optional.empty();
throw new AssertionError("placeholders must use a single biome metadata snapshot");
}
@Override
public Optional<String> regionName(World world, int blockX, int blockZ) {
return isIrisWorld(world) ? Optional.ofNullable(regionName) : Optional.empty();
throw new AssertionError("placeholders must use a single biome metadata snapshot");
}
@Override
@@ -36,8 +36,21 @@ public class IrisPapiExpansionTest {
"pregen.world",
"world.available",
"world.biome",
"world.biome-custom",
"world.biome-custom-count",
"world.biome-custom-id",
"world.biome-custom-ids",
"world.biome-custom-key",
"world.biome-custom-keys",
"world.biome-derivative",
"world.biome-key",
"world.biome-type",
"world.biome-vanilla-derivative",
"world.dimension",
"world.fluid-height",
"world.height",
"world.max-height",
"world.min-height",
"world.region",
"world.region-key");
@@ -102,6 +115,19 @@ public class IrisPapiExpansionTest {
assertEquals("true", expansion.onRequest(player, "world.available"));
assertEquals("Hot Desert Dunes", expansion.onRequest(player, "world.biome"));
assertEquals("desert/hot-dunes", expansion.onRequest(player, "world.biome-key"));
assertEquals("true", expansion.onRequest(player, "world.biome-custom"));
assertEquals("golden-dunes", expansion.onRequest(player, "world.biome-custom-id"));
assertEquals("golden-dunes", expansion.onRequest(player, "world.biome-custom-ids"));
assertEquals("iris:biomes/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", expansion.onRequest(player, "world.biome-custom-key"));
assertEquals("iris:biomes/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", expansion.onRequest(player, "world.biome-custom-keys"));
assertEquals("1", expansion.onRequest(player, "world.biome-custom-count"));
assertEquals("minecraft:desert", expansion.onRequest(player, "world.biome-derivative"));
assertEquals("minecraft:desert", expansion.onRequest(player, "world.biome-vanilla-derivative"));
assertEquals("land", expansion.onRequest(player, "world.biome-type"));
assertEquals("-64", expansion.onRequest(player, "world.min-height"));
assertEquals("320", expansion.onRequest(player, "world.max-height"));
assertEquals("384", expansion.onRequest(player, "world.height"));
assertEquals("63", expansion.onRequest(player, "world.fluid-height"));
assertEquals("Scorched Expanse", expansion.onRequest(player, "world.region"));
assertEquals("scorched", expansion.onRequest(player, "world.region-key"));
assertEquals("overworld", expansion.onRequest(player, "world.dimension"));
@@ -1,5 +1,6 @@
package art.arcane.iris.integration;
import art.arcane.iris.api.terrain.IrisCustomBiomeInfo;
import art.arcane.iris.api.pregen.IrisPregenPhase;
import art.arcane.iris.api.pregen.IrisPregenProgress;
import art.arcane.iris.api.terrain.IrisTerrainService;
@@ -110,6 +111,19 @@ public class IrisPapiStateTest {
state.region(PLAYER);
state.regionKey(PLAYER);
state.dimension(PLAYER);
state.biomeCustom(PLAYER);
state.biomeCustomId(PLAYER);
state.biomeCustomIds(PLAYER);
state.biomeCustomKey(PLAYER);
state.biomeCustomKeys(PLAYER);
state.biomeCustomCount(PLAYER);
state.biomeDerivative(PLAYER);
state.biomeVanillaDerivative(PLAYER);
state.biomeType(PLAYER);
state.minHeight(PLAYER);
state.maxHeight(PLAYER);
state.height(PLAYER);
state.fluidHeight(PLAYER);
}
assertEquals(1, terrain.builds());
@@ -391,4 +405,117 @@ public class IrisPapiStateTest {
assertEquals("false", state.worldAvailable(PLAYER));
assertEquals(DASH, state.biome(PLAYER));
}
@Test
public void multipleCustomDerivativesExposeAllDefinitionsWithoutGuessingTheSelectedOne() {
FakeIrisTerrainService terrain = new FakeIrisTerrainService();
World world = IrisPapiTestSupport.world("sandbox");
terrain.addIrisWorld(world);
terrain.derivatives(List.of(
new IrisCustomBiomeInfo("golden-dunes", "iris:biomes/first"),
new IrisCustomBiomeInfo("red-dunes", "iris:biomes/second")));
IrisPapiState state = new IrisPapiState(() -> terrain, new IrisPapiTestSupport.Clock());
state.trackPosition(PLAYER, world, 10, 10);
assertEquals("true", state.biomeCustom(PLAYER));
assertEquals("2", state.biomeCustomCount(PLAYER));
assertEquals("golden-dunes, red-dunes", state.biomeCustomIds(PLAYER));
assertEquals("iris:biomes/first, iris:biomes/second", state.biomeCustomKeys(PLAYER));
assertEquals(DASH, state.biomeCustomId(PLAYER));
assertEquals(DASH, state.biomeCustomKey(PLAYER));
}
@Test
public void vanillaBiomesExposeFalseAndARealZeroCustomCount() {
FakeIrisTerrainService terrain = new FakeIrisTerrainService();
World world = IrisPapiTestSupport.world("sandbox");
terrain.addIrisWorld(world);
terrain.derivatives(List.of());
IrisPapiState state = new IrisPapiState(() -> terrain, new IrisPapiTestSupport.Clock());
state.trackPosition(PLAYER, world, 10, 10);
assertEquals("false", state.biomeCustom(PLAYER));
assertEquals("0", state.biomeCustomCount(PLAYER));
assertEquals(DASH, state.biomeCustomId(PLAYER));
assertEquals(DASH, state.biomeCustomIds(PLAYER));
assertEquals(DASH, state.biomeCustomKeys(PLAYER));
assertEquals("minecraft:desert", state.biomeVanillaDerivative(PLAYER));
}
@Test
public void anUnavailableRegistryKeyPreservesAuthoredIdsWithoutPublishingPartialKeyLists() {
FakeIrisTerrainService terrain = new FakeIrisTerrainService();
World world = IrisPapiTestSupport.world("sandbox");
terrain.addIrisWorld(world);
terrain.derivatives(List.of(new IrisCustomBiomeInfo("golden-dunes", "")));
IrisPapiTestSupport.Clock clock = new IrisPapiTestSupport.Clock();
IrisPapiState state = new IrisPapiState(() -> terrain, clock);
state.trackPosition(PLAYER, world, 10, 10);
assertEquals("golden-dunes", state.biomeCustomId(PLAYER));
assertEquals(DASH, state.biomeCustomKey(PLAYER));
terrain.derivatives(List.of(new IrisCustomBiomeInfo("golden-dunes", "iris:biomes/first"),
new IrisCustomBiomeInfo("red-dunes", "")));
clock.advance(IrisPapiState.VIEW_TTL_MS);
assertEquals("golden-dunes, red-dunes", state.biomeCustomIds(PLAYER));
assertEquals(DASH, state.biomeCustomKeys(PLAYER));
}
@Test
public void missingBiomeMetadataDoesNotHideKnownDimensionHeights() {
FakeIrisTerrainService terrain = new FakeIrisTerrainService();
World world = IrisPapiTestSupport.world("sandbox");
terrain.addIrisWorld(world);
terrain.biomeAvailable(false);
IrisPapiState state = new IrisPapiState(() -> terrain, new IrisPapiTestSupport.Clock());
state.trackPosition(PLAYER, world, 10, 10);
assertEquals("true", state.worldAvailable(PLAYER));
assertEquals(DASH, state.biomeCustom(PLAYER));
assertEquals(DASH, state.biomeCustomCount(PLAYER));
assertEquals(DASH, state.biomeDerivative(PLAYER));
assertEquals("overworld", state.dimension(PLAYER));
assertEquals("-64", state.minHeight(PLAYER));
assertEquals("320", state.maxHeight(PLAYER));
assertEquals("384", state.height(PLAYER));
assertEquals("63", state.fluidHeight(PLAYER));
}
@Test
public void pendingBiomeMetadataIsRetriedAfterTheViewExpiresWithoutPlayerMovement() {
FakeIrisTerrainService terrain = new FakeIrisTerrainService();
World world = IrisPapiTestSupport.world("sandbox");
terrain.addIrisWorld(world);
terrain.biomeAvailable(false);
IrisPapiTestSupport.Clock clock = new IrisPapiTestSupport.Clock();
IrisPapiState state = new IrisPapiState(() -> terrain, clock);
state.trackPosition(PLAYER, world, 10, 10);
assertEquals(DASH, state.biomeCustomId(PLAYER));
assertEquals("true", state.worldAvailable(PLAYER));
terrain.biomeAvailable(true);
clock.advance(IrisPapiState.VIEW_TTL_MS - 1L);
assertEquals(DASH, state.biomeCustomId(PLAYER));
clock.advance(1L);
assertEquals("golden-dunes", state.biomeCustomId(PLAYER));
assertEquals("Hot Desert Dunes", state.biome(PLAYER));
}
@Test
public void customDerivativeIdsAreScrubbedAndRefreshWithTheCachedView() {
FakeIrisTerrainService terrain = new FakeIrisTerrainService();
World world = IrisPapiTestSupport.world("sandbox");
terrain.addIrisWorld(world);
IrisPapiTestSupport.Clock clock = new IrisPapiTestSupport.Clock();
IrisPapiState state = new IrisPapiState(() -> terrain, clock);
state.trackPosition(PLAYER, world, 10, 10);
assertEquals("golden-dunes", state.biomeCustomId(PLAYER));
terrain.derivatives(List.of(new IrisCustomBiomeInfo("§6red%dunes", "iris:biomes/second")));
assertEquals("golden-dunes", state.biomeCustomIds(PLAYER));
clock.advance(IrisPapiState.VIEW_TTL_MS);
assertEquals("reddunes", state.biomeCustomId(PLAYER));
assertEquals("reddunes", state.biomeCustomIds(PLAYER));
assertEquals(2, terrain.builds());
}
}
@@ -5,23 +5,43 @@ import art.arcane.iris.api.terrain.IrisColumnQuery;
import art.arcane.iris.api.terrain.IrisRiverState;
import art.arcane.iris.api.terrain.IrisSurfaceKind;
import art.arcane.iris.api.terrain.IrisTerrainService;
import art.arcane.iris.configuration.IrisSettings;
import art.arcane.iris.generation.biome.IrisBiome;
import art.arcane.iris.generation.runtime.BiomeEnvironment;
import art.arcane.iris.generation.runtime.Engine;
import art.arcane.iris.generation.terrain.IrisDimension;
import art.arcane.iris.generation.terrain.IrisRegion;
import art.arcane.iris.pack.loading.IrisData;
import art.arcane.iris.platform.generation.PlatformChunkGenerator;
import art.arcane.iris.spi.IrisLogging;
import art.arcane.iris.world.history.SavedBiomeUnavailableException;
import art.arcane.iris.generation.hydrology.HydrologyColumnLayer;
import art.arcane.iris.generation.hydrology.HydrologyColumnSample;
import art.arcane.iris.generation.hydrology.HydrologyFeatureRef;
import art.arcane.iris.generation.hydrology.HydrologyFeatureType;
import art.arcane.iris.platform.bukkit.plugin.IrisService;
import org.bukkit.World;
import org.bukkit.generator.ChunkGenerator;
import org.junit.Test;
import org.mockito.MockedStatic;
import java.lang.reflect.Field;
import java.util.EnumSet;
import java.util.List;
import java.util.Optional;
import java.util.OptionalInt;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.contains;
import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.withSettings;
public class IrisTerrainSVCTest {
private static final IrisColumnQuery SMALL = IrisColumnQuery.rect(
@@ -53,6 +73,7 @@ public class IrisTerrainSVCTest {
assertEquals(IrisSurfaceKind.UNKNOWN, service.surfaceKind(null, 0, 0));
assertTrue(service.surfaceBiomeKey(null, 0, 0).isEmpty());
assertTrue(service.surfaceBiomeName(null, 0, 0).isEmpty());
assertTrue(service.surfaceBiomeInfo(null, 0, 0).isEmpty());
assertTrue(service.biomeKey(null, 0, 64, 0).isEmpty());
assertTrue(service.regionKey(null, 0, 0).isEmpty());
assertTrue(service.regionName(null, 0, 0).isEmpty());
@@ -66,6 +87,9 @@ public class IrisTerrainSVCTest {
assertEquals(Optional.class,
IrisTerrainService.class.getMethod("regionName", World.class, int.class, int.class)
.getReturnType());
assertEquals(Optional.class,
IrisTerrainService.class.getMethod("surfaceBiomeInfo", World.class, int.class, int.class)
.getReturnType());
}
@Test
@@ -94,6 +118,124 @@ public class IrisTerrainSVCTest {
assertEquals(IrisRiverState.DRY, IrisTerrainSVC.riverState(river(false)));
}
@Test
public void pendingBiomeMetadataReturnsAbsenceAndResolvesOnRetryWithoutLogging() throws Exception {
Engine engine = mock(Engine.class);
World world = world(engine);
IrisTerrainSVC service = enabledService();
IrisBiome biome = mock(IrisBiome.class);
when(biome.getLoadKey()).thenReturn("desert/dunes");
BiomeEnvironment environment = new BiomeEnvironment(1L, biome, mock(IrisRegion.class),
mock(IrisDimension.class), mock(IrisData.class));
when(engine.getSurfaceBiomeEnvironment(-17, 32))
.thenThrow(new SavedBiomeUnavailableException("Saved biome information is loading", true))
.thenReturn(environment);
try (MockedStatic<IrisLogging> logging = mockStatic(IrisLogging.class)) {
assertTrue(service.surfaceBiomeInfo(world, -17, 32).isEmpty());
assertEquals("desert/dunes", service.surfaceBiomeInfo(world, -17, 32).orElseThrow().key());
logging.verifyNoInteractions();
}
}
@Test
public void pendingReadsDoNotConsumeTheFirstRealFailureReport() throws Exception {
Engine engine = mock(Engine.class);
World world = world(engine);
IrisTerrainSVC service = enabledService();
SavedBiomeUnavailableException failure = new SavedBiomeUnavailableException(
"Saved biome data could not be read", new IllegalStateException("Invalid saved data"));
when(engine.getSurfaceBiomeEnvironment(-17, 32))
.thenThrow(new SavedBiomeUnavailableException("Saved biome information is loading", true))
.thenThrow(failure);
try (MockedStatic<IrisLogging> logging = mockStatic(IrisLogging.class)) {
assertTrue(service.surfaceBiomeInfo(world, -17, 32).isEmpty());
logging.verifyNoInteractions();
assertTrue(service.surfaceBiomeInfo(world, -17, 32).isEmpty());
logging.verify(() -> IrisLogging.reportError(contains("(1 terrain API query faults"), same(failure)));
logging.verifyNoMoreInteractions();
}
}
@Test
public void biomeAndRegionReadsTreatLoadingAsTemporaryAbsence() throws Exception {
Engine engine = mock(Engine.class);
World world = world(engine);
IrisTerrainSVC service = enabledService();
SavedBiomeUnavailableException pending = new SavedBiomeUnavailableException("Loading saved biomes", true);
when(engine.getSurfaceBiome(0, 0)).thenThrow(pending);
when(engine.getBiome(0, 64, 0)).thenThrow(pending);
when(engine.getRegion(0, 0)).thenThrow(pending);
try (MockedStatic<IrisLogging> logging = mockStatic(IrisLogging.class)) {
assertTrue(service.surfaceBiomeKey(world, 0, 0).isEmpty());
assertTrue(service.surfaceBiomeName(world, 0, 0).isEmpty());
assertTrue(service.biomeKey(world, 0, 64, 0).isEmpty());
assertTrue(service.regionKey(world, 0, 0).isEmpty());
assertTrue(service.regionName(world, 0, 0).isEmpty());
logging.verifyNoInteractions();
}
}
@Test
public void aLoadingReadWithASuppressedFailureIsStillReported() throws Exception {
Engine engine = mock(Engine.class);
World world = world(engine);
IrisTerrainSVC service = enabledService();
SavedBiomeUnavailableException failure = new SavedBiomeUnavailableException("Loading saved biomes", true);
failure.addSuppressed(new IllegalStateException("Failed to release saved definitions"));
when(engine.getSurfaceBiomeEnvironment(0, 0)).thenThrow(failure);
try (MockedStatic<IrisLogging> logging = mockStatic(IrisLogging.class)) {
assertTrue(service.surfaceBiomeInfo(world, 0, 0).isEmpty());
logging.verify(() -> IrisLogging.reportError(contains("surfaceBiomeInfo"), same(failure)));
logging.verifyNoMoreInteractions();
}
}
@Test
public void pendingBatchReadDoesNotHideSubsequentSinkFailures() throws Exception {
Engine engine = mock(Engine.class);
World world = world(engine);
IrisTerrainSVC service = enabledService();
IrisColumnQuery query = IrisColumnQuery.rect(0, 0, 0, 0, 1, EnumSet.of(IrisColumnField.BIOME_KEY));
when(engine.getDimension()).thenReturn(mock(IrisDimension.class));
when(engine.getSurfaceBiome(0, 0))
.thenThrow(new SavedBiomeUnavailableException("Loading saved biomes", true))
.thenReturn(mock(IrisBiome.class));
AtomicInteger sinkCalls = new AtomicInteger();
IllegalStateException failure = new IllegalStateException("Consumer failed");
try (MockedStatic<IrisLogging> logging = mockStatic(IrisLogging.class);
MockedStatic<IrisSettings> settings = mockStatic(IrisSettings.class)) {
settings.when(IrisSettings::get).thenReturn(new IrisSettings());
assertFalse(service.sampleColumns(world, query, sample -> sinkCalls.incrementAndGet()));
assertEquals(0, sinkCalls.get());
logging.verifyNoInteractions();
assertFalse(service.sampleColumns(world, query, sample -> { throw failure; }));
logging.verify(() -> IrisLogging.reportError(contains("(1 terrain API sample faults"), same(failure)));
logging.verifyNoMoreInteractions();
}
}
private static World world(Engine engine) {
World world = mock(World.class);
ChunkGenerator generator = mock(ChunkGenerator.class, withSettings().extraInterfaces(PlatformChunkGenerator.class));
when(world.getGenerator()).thenReturn(generator);
when(world.getName()).thenReturn("sandbox");
when(((PlatformChunkGenerator) generator).getEngine()).thenReturn(engine);
return world;
}
private static IrisTerrainSVC enabledService() throws ReflectiveOperationException {
IrisTerrainSVC service = new IrisTerrainSVC();
Field enabled = IrisTerrainSVC.class.getDeclaredField("serviceEnabled");
enabled.setAccessible(true);
((AtomicBoolean) enabled.get(service)).set(true);
return service;
}
private static HydrologyColumnSample river(boolean connectedFluid) {
HydrologyFeatureRef feature = new HydrologyFeatureRef(
1L,
@@ -0,0 +1,143 @@
package art.arcane.iris.platform.bukkit.terrain;
import art.arcane.iris.api.terrain.IrisBiomeInfo;
import art.arcane.iris.api.terrain.IrisCustomBiomeInfo;
import art.arcane.iris.generation.biome.IrisBiome;
import art.arcane.iris.generation.biome.IrisBiomeCustom;
import art.arcane.iris.generation.runtime.BiomeEnvironment;
import art.arcane.iris.generation.runtime.Engine;
import art.arcane.iris.generation.terrain.InferredType;
import art.arcane.iris.generation.terrain.IrisDimension;
import art.arcane.iris.generation.terrain.IrisRegion;
import art.arcane.iris.pack.loading.IrisData;
import art.arcane.volmlib.util.collection.KList;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
public class IrisBiomeInfoFactoryTest {
@Test
public void historicalMetadataUsesTheRetainedEnvironmentInsteadOfTheActivePack() {
IrisBiomeCustom custom = new IrisBiomeCustom().setId("amber-forest");
Fixture fixture = fixture(List.of(custom));
IrisData activeData = mock(IrisData.class);
when(fixture.engine().getData()).thenReturn(activeData);
when(fixture.data().customBiomeResourceKey(fixture.dimension(), custom)).thenReturn("iris:biomes/retained");
List<Throwable> failures = new ArrayList<>();
IrisBiomeInfo info = IrisBiomeInfoFactory.surface(fixture.engine(), -17, 32, failures::add);
assertEquals("forests/amber", info.key());
assertEquals("Amber Forest", info.name());
assertEquals("temperate", info.regionKey());
assertEquals("Temperate Region", info.regionName());
assertEquals("minecraft:forest", info.derivativeKey());
assertEquals("minecraft:birch_forest", info.vanillaDerivativeKey());
assertEquals("land", info.type());
assertEquals(List.of(new IrisCustomBiomeInfo("amber-forest", "iris:biomes/retained")), info.customDerivatives());
assertTrue(failures.isEmpty());
verify(fixture.engine()).getSurfaceBiomeEnvironment(-17, 32);
verifyNoMoreInteractions(fixture.engine());
verify(fixture.data()).customBiomeResourceKey(fixture.dimension(), custom);
verifyNoInteractions(activeData);
}
@Test
public void allCustomDerivativesRemainInAuthoredOrderWithoutSelectingAVariant() {
IrisBiomeCustom first = new IrisBiomeCustom().setId("amber");
IrisBiomeCustom second = new IrisBiomeCustom().setId("copper");
Fixture fixture = fixture(List.of(first, second));
when(fixture.data().customBiomeResourceKey(fixture.dimension(), first)).thenReturn("iris:biomes/first");
when(fixture.data().customBiomeResourceKey(fixture.dimension(), second)).thenReturn("iris:biomes/second");
IrisBiomeInfo info = IrisBiomeInfoFactory.surface(fixture.engine(), -17, 32, failure -> {
throw new AssertionError(failure);
});
assertEquals(List.of(
new IrisCustomBiomeInfo("amber", "iris:biomes/first"),
new IrisCustomBiomeInfo("copper", "iris:biomes/second")), info.customDerivatives());
verify(fixture.engine()).getSurfaceBiomeEnvironment(-17, 32);
verifyNoMoreInteractions(fixture.engine());
}
@Test
public void aVanillaBiomeDoesNotResolveAnyCustomRegistryKeys() {
Fixture fixture = fixture(List.of());
IrisBiomeInfo info = IrisBiomeInfoFactory.surface(fixture.engine(), -17, 32, failure -> {
throw new AssertionError(failure);
});
assertEquals("Amber Forest", info.name());
assertTrue(info.customDerivatives().isEmpty());
verifyNoInteractions(fixture.data());
}
@Test
public void aMissingMappingPreservesTheAuthoredIdAndOtherMetadata() {
IrisBiomeCustom first = new IrisBiomeCustom().setId("amber");
IrisBiomeCustom second = new IrisBiomeCustom().setId("copper");
Fixture fixture = fixture(List.of(first, second));
IllegalStateException missing = new IllegalStateException("Missing historical registry mapping");
when(fixture.data().customBiomeResourceKey(fixture.dimension(), first)).thenThrow(missing);
when(fixture.data().customBiomeResourceKey(fixture.dimension(), second)).thenReturn("iris:biomes/second");
List<Throwable> failures = new ArrayList<>();
IrisBiomeInfo info = IrisBiomeInfoFactory.surface(fixture.engine(), -17, 32, failures::add);
assertEquals("Amber Forest", info.name());
assertEquals(List.of(
new IrisCustomBiomeInfo("amber", ""),
new IrisCustomBiomeInfo("copper", "iris:biomes/second")), info.customDerivatives());
assertEquals(1, failures.size());
assertSame(missing, failures.getFirst().getCause());
assertTrue(failures.getFirst().getMessage().contains("amber"));
}
@Test
public void anUnclassifiedBiomeReportsAnEmptyType() {
Fixture fixture = fixture(List.of());
when(fixture.biome().getInferredType()).thenReturn(null);
IrisBiomeInfo info = IrisBiomeInfoFactory.surface(fixture.engine(), -17, 32, failure -> {
throw new AssertionError(failure);
});
assertEquals("", info.type());
}
private static Fixture fixture(List<IrisBiomeCustom> customBiomes) {
Engine engine = mock(Engine.class);
IrisBiome biome = mock(IrisBiome.class);
IrisRegion region = mock(IrisRegion.class);
IrisDimension dimension = mock(IrisDimension.class);
IrisData data = mock(IrisData.class);
KList<IrisBiomeCustom> derivatives = new KList<>();
derivatives.addAll(customBiomes);
when(biome.getLoadKey()).thenReturn("forests/amber");
when(biome.getName()).thenReturn("Amber Forest");
when(biome.getDerivativeKey()).thenReturn("minecraft:forest");
when(biome.getVanillaDerivativeKey()).thenReturn("minecraft:birch_forest");
when(biome.getInferredType()).thenReturn(InferredType.LAND);
when(biome.getCustomDerivitives()).thenReturn(derivatives);
when(region.getLoadKey()).thenReturn("temperate");
when(region.getName()).thenReturn("Temperate Region");
when(engine.getSurfaceBiomeEnvironment(-17, 32)).thenReturn(
new BiomeEnvironment(4L, biome, region, dimension, data));
return new Fixture(engine, biome, dimension, data);
}
private record Fixture(Engine engine, IrisBiome biome, IrisDimension dimension, IrisData data) {
}
}