Remove Hydrology

This commit is contained in:
Brian Neumann-Fopiano committed 2026-08-27 00:45:15 -04:00
1 parent bdaa84975a
commit f398c55091
188 files changed
+1291 -21010

No files matched your search

@@ -57,7 +57,7 @@ public class VisionRenderControllerTest {
VisionRenderController.TileKey baseline = new VisionRenderController.TileKey(4L, RenderType.BIOME, 4D, 8L, 9L);
assertNotEquals(baseline, new VisionRenderController.TileKey(5L, RenderType.BIOME, 4D, 8L, 9L));
assertNotEquals(baseline, new VisionRenderController.TileKey(4L, RenderType.RIVER, 4D, 8L, 9L));
assertNotEquals(baseline, new VisionRenderController.TileKey(4L, RenderType.HEIGHT, 4D, 8L, 9L));
assertNotEquals(baseline, new VisionRenderController.TileKey(4L, RenderType.BIOME, 4.5D, 8L, 9L));
assertNotEquals(baseline, new VisionRenderController.TileKey(4L, RenderType.BIOME, 4D, 7L, 9L));
}
@@ -7,7 +7,6 @@ import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
public class VisionRenderTypeTest {
@Test
@@ -19,12 +18,6 @@ public class VisionRenderTypeTest {
}
}
@Test
public void riverRenderTypeUsesTheRiverNetworkLabel() {
assertSame(DesktopUiMessages.VISION_MODE_RIVER, VisionGUI.modeKey(RenderType.RIVER));
assertEquals("iris.desktop.vision.mode.river", DesktopUiMessages.VISION_MODE_RIVER.id());
}
@Test
public void teleportCoordinatesFloorAcrossTheNegativeOrigin() {
assertEquals(-1, VisionGUI.floorWorldCoordinate(-0.01D));
@@ -651,35 +651,6 @@ public class PackDownloaderTest {
assertEquals(0, PackDownloader.downloadLockCount());
}
@Test
public void invalidBuiltInRiverSchemaPreservesExistingTarget() throws Exception {
File packsFolder = temp.newFolder("invalid-river-packs");
File target = writePack(packsFolder.toPath().resolve("overworld"), "overworld", "old");
File extracted = writePack(temp.newFolder("invalid-river-source").toPath(), "overworld", "new");
Files.writeString(
extracted.toPath().resolve("dimensions/overworld.json"),
"{\"name\":\"Overworld\",\"regions\":[\"local\"],\"logicalHeight\":256,"
+ "\"dimensionHeight\":{\"min\":-64,\"max\":320},\"rivers\":{\"enabled\":true,"
+ "\"terrain\":{},\"water\":{\"mode\":\"SEA_LEVEL\"}}}",
StandardCharsets.UTF_8
);
PackDownloader.PackInstallResult result = PackDownloader.installExtractedPack(
packsFolder,
extracted,
true,
"overworld",
ignored -> {
}
);
assertNull(result);
assertEquals("old", Files.readString(target.toPath().resolve("state.txt"), StandardCharsets.UTF_8));
assertTrue(Files.isRegularFile(target.toPath().resolve("regions/local.json")));
assertTransactionStateClean(packsFolder);
assertEquals(0, PackDownloader.downloadLockCount());
}
@Test
public void forceOverwriteReplacesEnginelessLoadedPackData() throws Exception {
// A registered loader with no engines is a stale catalog registration (startup
File diff suppressed because it is too large. Load diff
@@ -1,192 +0,0 @@
package art.arcane.iris.core.project;
import art.arcane.iris.core.loader.IrisData;
import art.arcane.iris.core.loader.IrisRegistrant;
import art.arcane.iris.core.loader.ResourceLoader;
import art.arcane.iris.engine.object.IrisBiome;
import art.arcane.iris.engine.object.IrisBlockData;
import art.arcane.iris.engine.object.IrisExpression;
import art.arcane.iris.engine.object.IrisRiverNetwork;
import art.arcane.iris.engine.object.IrisRiverOverride;
import art.arcane.volmlib.util.collection.KList;
import art.arcane.volmlib.util.collection.KMap;
import art.arcane.volmlib.util.json.JSONArray;
import art.arcane.volmlib.util.json.JSONObject;
import art.arcane.iris.spi.IrisPlatform;
import art.arcane.iris.spi.IrisPlatforms;
import art.arcane.iris.spi.PlatformRegistries;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class IrisRiverSchemaTest {
private IrisPlatform previousPlatform;
@Before
public void bindPlatform() {
previousPlatform = IrisPlatforms.isBound() ? IrisPlatforms.get() : null;
if (previousPlatform != null) {
IrisPlatforms.unbind();
}
IrisPlatform platform = mock(IrisPlatform.class);
PlatformRegistries registries = mock(PlatformRegistries.class);
when(platform.registries()).thenReturn(registries);
when(registries.blockTypeKeys()).thenReturn(List.of());
IrisPlatforms.bind(platform);
}
@After
public void restorePlatform() {
IrisPlatforms.unbind();
if (previousPlatform != null) {
IrisPlatforms.bind(previousPlatform);
}
}
@Test
public void riverNetworkSchemaExposesNestedNoiseLimitsModesAndBiomePools() {
JSONObject schema = new SchemaBuilder(IrisRiverNetwork.class, schemaData()).construct();
JSONObject definitions = schema.getJSONObject("definitions");
JSONObject properties = schema.getJSONObject("properties");
JSONObject topology = referencedProperties(definitions, properties.getJSONObject("topology"));
JSONObject source = referencedProperties(definitions, topology.getJSONObject("source"));
JSONObject terrainDefinition = referencedDefinition(definitions, properties.getJSONObject("terrain"));
JSONObject terrain = terrainDefinition.getJSONObject("properties");
JSONObject worms = terrain.getJSONObject("worms");
JSONObject worm = referencedProperties(definitions, worms.getJSONObject("items"));
JSONObject water = referencedProperties(definitions, properties.getJSONObject("water"));
JSONObject biomes = referencedProperties(definitions, properties.getJSONObject("biomes"));
JSONObject caves = referencedProperties(definitions, properties.getJSONObject("caves"));
JSONObject deepPools = referencedProperties(definitions, caves.getJSONObject("deepPools"));
assertEquals("boolean", properties.getJSONObject("enabled").getString("type"));
assertEquals(64, topology.getJSONObject("cellSize").getInt("minimum"));
assertEquals(4096, topology.getJSONObject("cellSize").getInt("maximum"));
assertEquals(7, topology.getJSONObject("sinkSearchReaches").getInt("maximum"));
assertEquals(8, topology.getJSONObject("routingBasinCells").getInt("minimum"));
assertEquals(256, topology.getJSONObject("routingBasinCells").getInt("maximum"));
assertEquals(0D, source.getJSONObject("chance").getDouble("minimum"), 0D);
assertEquals(1D, source.getJSONObject("chance").getDouble("maximum"), 0D);
assertEquals(1D, terrain.getJSONObject("maxChannelWidth").getDouble("minimum"), 0D);
assertEquals(2048D, terrain.getJSONObject("maxChannelWidth").getDouble("maximum"), 0D);
assertEquals(0D, terrain.getJSONObject("maxBankWidth").getDouble("minimum"), 0D);
assertEquals(512D, terrain.getJSONObject("maxDepth").getDouble("maximum"), 0D);
assertTrue(arrayContains(terrainDefinition.getJSONArray("required"), "worms"));
assertEquals("array", worms.getString("type"));
assertEquals(1, worms.getInt("minItems"));
assertEquals(0.000001D, worm.getJSONObject("weight").getDouble("minimum"), 0D);
assertEquals(16384D, worm.getJSONObject("wavelength").getDouble("maximum"), 0D);
assertEquals(1D, worm.getJSONObject("tortuosity").getDouble("maximum"), 0D);
assertEquals(1024D, worm.getJSONObject("maxOffset").getDouble("maximum"), 0D);
assertEquals(64, worm.getJSONObject("segments").getInt("maximum"));
assertEquals(0.125D, worm.getJSONObject("widthMultiplier").getDouble("minimum"), 0D);
assertEquals(8D, worm.getJSONObject("bankMultiplier").getDouble("maximum"), 0D);
assertEquals(8D, worm.getJSONObject("depthMultiplier").getDouble("maximum"), 0D);
assertEquals(8D, worm.getJSONObject("bodyWavelength").getDouble("minimum"), 0D);
assertEquals(16384D, worm.getJSONObject("bodyDetailWavelength").getDouble("maximum"), 0D);
assertEquals(1D, worm.getJSONObject("bodyDetailInfluence").getDouble("maximum"), 0D);
assertEquals(0.875D, worm.getJSONObject("widthVariation").getDouble("maximum"), 0D);
assertEquals(0.875D, worm.getJSONObject("bankVariation").getDouble("maximum"), 0D);
assertEquals(0.875D, worm.getJSONObject("depthVariation").getDouble("maximum"), 0D);
assertEquals(0.875D, worm.getJSONObject("roofVariation").getDouble("maximum"), 0D);
assertEquals(8, worm.getJSONObject("branchCap").getInt("maximum"));
assertEquals(1D, worm.getJSONObject("branchDecay").getDouble("maximum"), 0D);
assertEquals(8D, worm.getJSONObject("confluenceMultiplier").getDouble("maximum"), 0D);
assertEquals(1D, worm.getJSONObject("childChance").getDouble("maximum"), 0D);
assertEquals(1D, worm.getJSONObject("branchChildChance").getDouble("maximum"), 0D);
assertEquals("array", worm.getJSONObject("children").getString("type"));
assertEquals(List.of("FIXED", "TERRACED"), enumValues(definitions, water.getJSONObject("mode")));
assertEquals(-2048, water.getJSONObject("fluidHeight").getInt("minimum"));
assertEquals(2048, water.getJSONObject("fluidHeight").getInt("maximum"));
assertTrue(water.has("fluidPalette"));
assertEquals(64D, terrain.getJSONObject("channelRadiusBonus").getDouble("maximum"), 0D);
assertEquals("array", biomes.getJSONObject("channel").getString("type"));
assertEquals("#/definitions/erzbiomes",
biomes.getJSONObject("channel").getJSONObject("items").getString("$ref"));
assertTrue(properties.has("terrain"));
assertTrue(properties.has("caves"));
assertEquals("boolean", deepPools.getJSONObject("enabled").getString("type"));
assertEquals(-2048, deepPools.getJSONObject("minimumFluidY").getInt("minimum"));
assertEquals(2048, deepPools.getJSONObject("maximumFluidY").getInt("maximum"));
assertEquals(128, deepPools.getJSONObject("horizontalRadius").getInt("maximum"));
assertEquals(64, deepPools.getJSONObject("verticalRadius").getInt("maximum"));
assertEquals(0.75D, deepPools.getJSONObject("shapeVariation").getDouble("maximum"), 0D);
assertEquals(64D, deepPools.getJSONObject("warpStrength").getDouble("maximum"), 0D);
assertTrue(deepPools.has("reach"));
assertTrue(deepPools.has("shapeStyle"));
assertTrue(deepPools.has("warpStyle"));
assertTrue(deepPools.has("fluidPalette"));
}
@Test
public void overrideSchemaKeepsEveryFieldOptionalAndTyped() {
JSONObject schema = new SchemaBuilder(IrisRiverOverride.class, schemaData()).construct();
JSONObject properties = schema.getJSONObject("properties");
assertTrue(!schema.has("required") || schema.getJSONArray("required").length() == 0);
assertEquals("boolean", properties.getJSONObject("allowSources").getString("type"));
assertEquals("number", properties.getJSONObject("routingCostMultiplier").getString("type"));
assertEquals("array", properties.getJSONObject("channelBiomes").getString("type"));
assertEquals("#/definitions/erzbiomes",
properties.getJSONObject("floodedCaveBiomes").getJSONObject("items").getString("$ref"));
}
@SuppressWarnings("unchecked")
private static IrisData schemaData() {
IrisData data = mock(IrisData.class);
ResourceLoader<IrisBiome> biomeLoader = mock(ResourceLoader.class);
ResourceLoader<IrisExpression> expressionLoader = mock(ResourceLoader.class);
ResourceLoader<IrisBlockData> blockLoader = mock(ResourceLoader.class);
KMap<Class<? extends IrisRegistrant>, ResourceLoader<? extends IrisRegistrant>> loaders = new KMap<>();
loaders.put(IrisBiome.class, biomeLoader);
loaders.put(IrisExpression.class, expressionLoader);
when(data.getBlockLoader()).thenReturn(blockLoader);
when(data.getLoaders()).thenReturn(loaders);
when(data.getPossibleSnippets(anyString())).thenReturn(new KList<>());
when(biomeLoader.getPossibleKeys()).thenReturn(new String[]{"river/channel"});
when(biomeLoader.getFolderName()).thenReturn("biomes");
when(biomeLoader.getResourceTypeName()).thenReturn("Biome");
when(blockLoader.getPossibleKeys()).thenReturn(new String[0]);
when(expressionLoader.getPossibleKeys()).thenReturn(new String[0]);
when(expressionLoader.getFolderName()).thenReturn("expressions");
when(expressionLoader.getResourceTypeName()).thenReturn("Expression");
return data;
}
private static JSONObject referencedProperties(JSONObject definitions, JSONObject reference) {
return referencedDefinition(definitions, reference).getJSONObject("properties");
}
private static JSONObject referencedDefinition(JSONObject definitions, JSONObject reference) {
String key = reference.getString("$ref").substring("#/definitions/".length());
return definitions.getJSONObject(key);
}
private static boolean arrayContains(JSONArray values, String expected) {
for (int index = 0; index < values.length(); index++) {
if (expected.equals(values.getString(index))) {
return true;
}
}
return false;
}
private static List<String> enumValues(JSONObject definitions, JSONObject reference) {
String key = reference.getString("$ref").substring("#/definitions/".length());
JSONArray values = definitions.getJSONObject(key).getJSONArray("oneOf");
List<String> names = new ArrayList<>(values.length());
for (int index = 0; index < values.length(); index++) {
names.add(values.getJSONObject(index).getString("const"));
}
return names;
}
}
@@ -0,0 +1,148 @@
package art.arcane.iris.engine;
import art.arcane.iris.engine.object.IrisGenerator;
import art.arcane.iris.engine.object.IrisInterpolator;
import art.arcane.iris.util.project.interpolation.InterpolationMethod;
import org.junit.Test;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringJoiner;
import static org.junit.Assert.assertEquals;
public class IrisComplexGeneratorOrderingTest {
private static final int[][] PERMUTATIONS = {
{0, 1, 2, 3},
{3, 2, 1, 0},
{1, 3, 0, 2},
{2, 0, 3, 1}
};
@Test
public void frozenGroupsUseStableOrderAcrossInsertionPermutations() {
IrisInterpolator bilinearWide = interpolator(InterpolationMethod.BILINEAR, 16D);
IrisInterpolator starcast = interpolator(InterpolationMethod.STARCAST_3, 8D);
IrisInterpolator bilinearNarrow = interpolator(InterpolationMethod.BILINEAR, 4D);
List<IrisGenerator> generators = List.of(
generator("zeta", starcast, 1L, 0D),
generator("beta", bilinearWide, 2L, 0D),
generator("alpha", bilinearWide, 3L, 0D),
generator("delta", bilinearNarrow, 4L, 0D)
);
String expected = "BILINEAR@4.0[delta]|BILINEAR@16.0[alpha,beta]|STARCAST_3@8.0[zeta]";
for (int[] permutation : PERMUTATIONS) {
IrisComplex.GeneratorGroup[] groups = IrisComplex.freezeGeneratorGroups(
groupByInterpolator(generators, permutation)
);
assertEquals(expected, signature(groups));
}
}
@Test
public void frozenGeneratorOrderProducesBitIdenticalOutputAcrossInsertionPermutations() {
IrisInterpolator interpolator = interpolator(InterpolationMethod.BILINEAR_STARCAST_6, 7D);
List<IrisGenerator> generators = List.of(
generator("alpha", interpolator, 1L, 1.0E16D),
generator("beta", interpolator, 2L, -1.0E16D),
generator("gamma", interpolator, 3L, 1D),
generator("omega", interpolator, 4L, 0D)
);
long expectedBits = Double.doubleToRawLongBits(0.25D);
for (int[] permutation : PERMUTATIONS) {
IrisComplex.GeneratorGroup[] groups = IrisComplex.freezeGeneratorGroups(
groupByInterpolator(generators, permutation)
);
double output = 0D;
for (IrisComplex.GeneratorGroup group : groups) {
output += IrisComplex.averageGeneratorHeights(
group.generators(),
0D,
1D,
0D,
0D,
0L
);
}
assertEquals(expectedBits, Double.doubleToRawLongBits(output));
}
}
@Test
public void emptyGeneratorCollectionFreezesAndAccumulatesAsEmpty() {
Map<IrisInterpolator, Set<IrisGenerator>> generators = Map.of();
assertEquals(0, IrisComplex.freezeGeneratorGroups(generators).length);
assertEquals(0D, IrisComplex.averageGeneratorHeights(
new IrisGenerator[0],
-1D,
1D,
0D,
0D,
0L
), 0D);
}
private IrisInterpolator interpolator(InterpolationMethod function, double horizontalScale) {
return new IrisInterpolator()
.setFunction(function)
.setHorizontalScale(horizontalScale);
}
private FixedGenerator generator(
String loadKey,
IrisInterpolator interpolator,
long seed,
double height
) {
FixedGenerator generator = new FixedGenerator(height);
generator.setLoadKey(loadKey);
generator.setInterpolator(interpolator);
generator.setSeed(seed);
return generator;
}
private Map<IrisInterpolator, Set<IrisGenerator>> groupByInterpolator(
List<IrisGenerator> generators,
int[] permutation
) {
Map<IrisInterpolator, Set<IrisGenerator>> groups = new LinkedHashMap<>();
for (int index : permutation) {
IrisGenerator generator = generators.get(index);
groups.computeIfAbsent(generator.getInterpolator(), ignored -> new LinkedHashSet<>()).add(generator);
}
return groups;
}
private String signature(IrisComplex.GeneratorGroup[] groups) {
StringJoiner groupSignature = new StringJoiner("|");
for (IrisComplex.GeneratorGroup group : groups) {
StringJoiner generatorKeys = new StringJoiner(",");
for (IrisGenerator generator : group.generators()) {
generatorKeys.add(generator.getLoadKey());
}
groupSignature.add(group.interpolator().getFunction().name()
+ "@" + group.interpolator().getHorizontalScale()
+ "[" + generatorKeys + "]");
}
return groupSignature.toString();
}
private static final class FixedGenerator extends IrisGenerator {
private final double height;
private FixedGenerator(double height) {
this.height = height;
}
@Override
public double getHeight(double x, double z, long seed) {
return height;
}
}
}
@@ -61,6 +61,51 @@ public class IrisComplexGridBoundsCacheTest {
assertEquals(1, interpolator.getInvocations());
}
@Test
public void horizontalGridAxisMatchesLegacyBilerpBitForBit() throws Exception {
IrisComplex complex = createComplex();
Method gridSampleBounds = gridSampleBoundsMethod();
CoordinateInterpolator interpolator = new CoordinateInterpolator();
double x = 67D;
double z = -32D;
NoiseBounds actual = invokeGridSampleBounds(complex, gridSampleBounds, interpolator, x, z);
NoiseBounds expected = legacyGridSampleBounds(x, z);
assertBoundsBitsEqual(expected, actual);
assertEquals(2, interpolator.getInvocations());
}
@Test
public void verticalGridAxisMatchesLegacyBilerpBitForBitAtNegativeCoordinates() throws Exception {
IrisComplex complex = createComplex();
Method gridSampleBounds = gridSampleBoundsMethod();
CoordinateInterpolator interpolator = new CoordinateInterpolator();
double x = -32D;
double z = -29D;
NoiseBounds actual = invokeGridSampleBounds(complex, gridSampleBounds, interpolator, x, z);
NoiseBounds expected = legacyGridSampleBounds(x, z);
assertBoundsBitsEqual(expected, actual);
assertEquals(2, interpolator.getInvocations());
}
@Test
public void interiorGridSampleMatchesLegacyBilerpBitForBit() throws Exception {
IrisComplex complex = createComplex();
Method gridSampleBounds = gridSampleBoundsMethod();
CoordinateInterpolator interpolator = new CoordinateInterpolator();
double x = -29D;
double z = 67D;
NoiseBounds actual = invokeGridSampleBounds(complex, gridSampleBounds, interpolator, x, z);
NoiseBounds expected = legacyGridSampleBounds(x, z);
assertBoundsBitsEqual(expected, actual);
assertEquals(4, interpolator.getInvocations());
}
private IrisComplex createComplex() throws Exception {
IrisComplex complex = mock(IrisComplex.class, CALLS_REAL_METHODS);
@@ -133,6 +178,50 @@ public class IrisComplexGridBoundsCacheTest {
return (NoiseBounds) method.invoke(complex, null, interpolator, 0, new IrisGenerator[0], x, z);
}
private NoiseBounds legacyGridSampleBounds(double x, double z) {
int grid = 4;
int xi = (int) Math.floor(x);
int zi = (int) Math.floor(z);
int mask = grid - 1;
int gx = xi & ~mask;
int gz = zi & ~mask;
double fx = (x - gx) / grid;
double fz = (z - gz) / grid;
NoiseBounds b00 = packedCoordinateBounds(gx, gz);
NoiseBounds b10 = packedCoordinateBounds(gx + grid, gz);
NoiseBounds b01 = packedCoordinateBounds(gx, gz + grid);
NoiseBounds b11 = packedCoordinateBounds(gx + grid, gz + grid);
return new NoiseBounds(
legacyBiLerp(b00.min(), b10.min(), b01.min(), b11.min(), fx, fz),
legacyBiLerp(b00.max(), b10.max(), b01.max(), b11.max(), fx, fz)
);
}
private NoiseBounds packedCoordinateBounds(int x, int z) {
return new NoiseBounds(
(float) CoordinateInterpolator.low(x, z),
(float) CoordinateInterpolator.high(x, z)
);
}
private double legacyBiLerp(
double v00,
double v10,
double v01,
double v11,
double fx,
double fz
) {
double a = v00 + ((v10 - v00) * fx);
double b = v01 + ((v11 - v01) * fx);
return a + ((b - a) * fz);
}
private void assertBoundsBitsEqual(NoiseBounds expected, NoiseBounds actual) {
assertEquals(Double.doubleToRawLongBits(expected.min()), Double.doubleToRawLongBits(actual.min()));
assertEquals(Double.doubleToRawLongBits(expected.max()), Double.doubleToRawLongBits(actual.max()));
}
private float unpackLow(long packed) {
return Float.intBitsToFloat((int) (packed >>> 32));
}
@@ -159,4 +248,26 @@ public class IrisComplexGridBoundsCacheTest {
return invocations.get();
}
}
private static final class CoordinateInterpolator extends IrisInterpolator {
private final AtomicInteger invocations = new AtomicInteger();
@Override
public NoiseBounds interpolateBounds(double x, double z, NoiseBoundsProvider provider) {
invocations.incrementAndGet();
return new NoiseBounds(low(x, z), high(x, z));
}
private static double low(double x, double z) {
return x * 0.125D - z * 0.0625D - 7.75D;
}
private static double high(double x, double z) {
return x * -0.03125D + z * 0.1875D + 12.5D;
}
private int getInvocations() {
return invocations.get();
}
}
}
@@ -0,0 +1,139 @@
package art.arcane.iris.engine;
import art.arcane.iris.engine.object.InferredType;
import art.arcane.iris.engine.object.IrisBiome;
import art.arcane.iris.engine.object.IrisRegion;
import art.arcane.iris.util.project.stream.ProceduralStream;
import art.arcane.iris.util.project.stream.interpolation.Interpolated;
import org.junit.Test;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
public class IrisComplexInferredBiomeStreamCacheTest {
@Test
public void compilesOncePerRegionIdentityAndPreservesSampling() {
IrisRegion first = new IrisRegion();
IrisRegion second = new IrisRegion();
AtomicInteger compilations = new AtomicInteger();
IdentityHashMap<IrisRegion, EnumMap<InferredType, IrisBiome>> expected = new IdentityHashMap<>();
List<InferredType> compilationOrder = new ArrayList<>();
Map<IrisRegion, Map<InferredType, ProceduralStream<IrisBiome>>> streams =
IrisComplex.compileInferredBiomeStreams(
List.of(first, first, second),
(region, inferredType) -> {
compilations.incrementAndGet();
compilationOrder.add(inferredType);
IrisBiome biome = new IrisBiome().setInferredType(inferredType);
expected.computeIfAbsent(region, ignored -> new EnumMap<>(InferredType.class))
.put(inferredType, biome);
return constant(biome);
}
);
assertEquals(8, compilations.get());
assertEquals(List.of(
InferredType.LAND,
InferredType.CAVE,
InferredType.SEA,
InferredType.SHORE,
InferredType.LAND,
InferredType.CAVE,
InferredType.SEA,
InferredType.SHORE
), compilationOrder);
assertEquals(2, streams.size());
assertNotSame(streams.get(first), streams.get(second));
for (InferredType inferredType : InferredType.values()) {
ProceduralStream<IrisBiome> firstStream = IrisComplex.preparedInferredBiomeStream(
streams, first, inferredType);
assertSame(firstStream, IrisComplex.preparedInferredBiomeStream(streams, first, inferredType));
assertSame(expected.get(first).get(inferredType), firstStream.get(13D, 17D));
assertSame(
expected.get(second).get(inferredType),
IrisComplex.preparedInferredBiomeStream(streams, second, inferredType).get(13D, 17D)
);
}
assertThrows(UnsupportedOperationException.class, streams::clear);
assertThrows(
UnsupportedOperationException.class,
() -> streams.get(first).put(InferredType.LAND, constant(new IrisBiome()))
);
}
@Test
public void rejectsOnlyUnpreparedRegionIdentities() {
IrisRegion prepared = new IrisRegion();
IrisRegion unprepared = new IrisRegion();
unprepared.setLoadKey("missing");
Map<IrisRegion, Map<InferredType, ProceduralStream<IrisBiome>>> streams =
IrisComplex.compileInferredBiomeStreams(
List.of(prepared),
(region, inferredType) -> constant(new IrisBiome().setInferredType(inferredType))
);
IllegalStateException error = assertThrows(
IllegalStateException.class,
() -> IrisComplex.preparedInferredBiomeStream(streams, unprepared, InferredType.LAND)
);
assertTrue(error.getMessage().contains("missing"));
}
@Test
public void immutableCacheSupportsConcurrentReads() throws Exception {
IrisRegion region = new IrisRegion();
IrisBiome biome = new IrisBiome().setInferredType(InferredType.LAND);
Map<IrisRegion, Map<InferredType, ProceduralStream<IrisBiome>>> streams =
IrisComplex.compileInferredBiomeStreams(
List.of(region),
(preparedRegion, inferredType) -> constant(
inferredType == InferredType.LAND
? biome
: new IrisBiome().setInferredType(inferredType)
)
);
ExecutorService executor = Executors.newFixedThreadPool(8);
try {
List<Future<IrisBiome>> reads = new ArrayList<>();
for (int task = 0; task < 32; task++) {
reads.add(executor.submit(() -> {
IrisBiome sampled = null;
for (int read = 0; read < 1_000; read++) {
sampled = IrisComplex.preparedInferredBiomeStream(
streams, region, InferredType.LAND).get(read, -read);
}
return sampled;
}));
}
for (Future<IrisBiome> read : reads) {
assertSame(biome, read.get(5, TimeUnit.SECONDS));
}
} finally {
executor.shutdownNow();
assertTrue(executor.awaitTermination(5, TimeUnit.SECONDS));
}
}
private static ProceduralStream<IrisBiome> constant(IrisBiome biome) {
return ProceduralStream.of(
(x, z) -> biome,
Interpolated.of(value -> 0D, value -> biome)
);
}
}
@@ -1,162 +1,16 @@
package art.arcane.iris.engine;
import art.arcane.iris.engine.object.IrisBiome;
import art.arcane.iris.engine.object.InferredType;
import art.arcane.iris.engine.object.IrisRegion;
import art.arcane.iris.engine.object.IrisRiverOverride;
import art.arcane.iris.engine.river.RiverRouteState;
import art.arcane.iris.engine.river.RiverSample;
import art.arcane.iris.engine.river.RiverSection;
import art.arcane.iris.util.project.stream.ProceduralStream;
import art.arcane.iris.util.project.stream.interpolation.Interpolated;
import org.junit.Test;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyDouble;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
public class IrisComplexSurfaceBiomeTest {
@Test
public void focusBiomeWithoutOverrideIgnoresOverridesOutsideFocus() {
IrisBiome focusBiome = new IrisBiome();
IrisBiome unreachableBiome = new IrisBiome().setRiverOverride(new IrisRiverOverride());
assertFalse(IrisComplex.biomeRiverOverridesPossible(focusBiome, List.of(unreachableBiome)));
}
@Test
public void focusBiomeOverrideAlwaysEnablesBiomeSampling() {
IrisBiome focusBiome = new IrisBiome().setRiverOverride(new IrisRiverOverride());
assertTrue(IrisComplex.biomeRiverOverridesPossible(focusBiome, List.of()));
}
@Test
public void nonFocusBiomeSamplingTracksReachableOverrides() {
assertFalse(IrisComplex.biomeRiverOverridesPossible(null, List.of(new IrisBiome())));
assertTrue(IrisComplex.biomeRiverOverridesPossible(
null,
List.of(new IrisBiome().setRiverOverride(new IrisRiverOverride()))
));
}
@Test
public void maxIncisionCapabilityIgnoresIdentityOverrides() {
assertFalse(IrisComplex.changesMaxIncision(null));
assertFalse(IrisComplex.changesMaxIncision(new IrisRiverOverride()));
assertFalse(IrisComplex.changesMaxIncision(
new IrisRiverOverride().setMaxIncisionMultiplier(1D)));
assertTrue(IrisComplex.changesMaxIncision(
new IrisRiverOverride().setMaxIncisionMultiplier(0.75D)));
}
@Test
public void naturalOceanMaskTracksContinentalIntent() {
assertTrue(IrisComplex.createNaturalOceanStream(
constantType(InferredType.SEA),
null
).get(0D, 0D));
assertFalse(IrisComplex.createNaturalOceanStream(
constantType(InferredType.LAND),
null
).get(0D, 0D));
}
@Test
public void naturalOceanMaskDoesNotSampleNaturalHeight() {
AtomicInteger heightSamples = new AtomicInteger();
ProceduralStream<Double> height = ProceduralStream.ofDouble((x, z) -> {
heightSamples.incrementAndGet();
return 62D;
});
Boolean ocean = IrisComplex.createNaturalOceanStream(
constantType(InferredType.LAND),
null
).get(8D, -3D);
assertFalse(ocean);
assertEquals(0, heightSamples.get());
}
@Test
public void focusNaturalOceanMaskIgnoresHeightForEverySurfaceType() {
AtomicInteger heightSamples = new AtomicInteger();
ProceduralStream<Double> height = ProceduralStream.ofDouble((x, z) -> {
heightSamples.incrementAndGet();
return -1_000D;
});
assertTrue(IrisComplex.createNaturalOceanStream(
constantType(InferredType.LAND),
new IrisBiome().setInferredType(InferredType.SEA)
).get(0D, 0D));
assertFalse(IrisComplex.createNaturalOceanStream(
constantType(InferredType.SEA),
new IrisBiome().setInferredType(InferredType.LAND)
).get(0D, 0D));
assertFalse(IrisComplex.createNaturalOceanStream(
constantType(InferredType.SEA),
new IrisBiome().setInferredType(InferredType.SHORE)
).get(0D, 0D));
assertEquals(0, heightSamples.get());
}
@Test
public void fixedOceanMaskUsesContinentalIntentWithoutSamplingHeight() {
AtomicInteger heightSamples = new AtomicInteger();
ProceduralStream<Double> height = ProceduralStream.ofDouble((x, z) -> {
heightSamples.incrementAndGet();
return -1_000D;
});
Boolean ocean = IrisComplex.createNaturalOceanStream(
constantType(InferredType.SEA),
null
).get(8D, -3D);
assertTrue(ocean);
assertEquals(0, heightSamples.get());
}
@Test
public void emptyRiverPoolsKeepWetChannelsAquaticAndDryChannelsLand() {
assertEquals(InferredType.SEA, IrisComplex.directRiverFallback(sample(RiverRouteState.WET, RiverSection.CHANNEL)));
assertEquals(InferredType.SEA, IrisComplex.directRiverFallback(sample(RiverRouteState.WET, RiverSection.MOUTH)));
assertEquals(InferredType.LAND, IrisComplex.directRiverFallback(sample(RiverRouteState.DRY, RiverSection.DRY_BANK)));
assertNull(IrisComplex.directRiverFallback(sample(RiverRouteState.WET, RiverSection.BANK)));
}
@Test
public void elevatedRiverBankUsesTheLocalWaterHead() {
IrisBiome base = mock(IrisBiome.class);
IrisBiome sea = mock(IrisBiome.class);
IrisRegion region = mock(IrisRegion.class);
doReturn(false).when(base).isShore();
doReturn(false).when(base).isAquatic();
doReturn(3D).when(region).getShoreHeight(12D, 18D);
IrisBiome resolved = IrisComplex.resolveSurfaceBiome(
68D,
base,
region,
12D,
18D,
70D,
constant(base),
constant(sea),
constant(base));
assertSame(sea, resolved);
}
@Test
public void shorelineHeightSelectsShoreBiome() {
IrisBiome base = mock(IrisBiome.class);
@@ -208,15 +62,4 @@ public class IrisComplexSurfaceBiomeTest {
doReturn(biome).when(stream).get(anyDouble(), anyDouble());
return stream;
}
private static ProceduralStream<InferredType> constantType(InferredType type) {
return ProceduralStream.of(
(x, z) -> type,
Interpolated.of(value -> 0D, value -> type)
);
}
private static RiverSample sample(RiverRouteState state, RiverSection section) {
return new RiverSample(true, state, section, 0D, 0.5D, 1D, 1, 1, 10D, 5D, 3D, false, null);
}
}
@@ -1,40 +0,0 @@
package art.arcane.iris.engine.actuator;
import art.arcane.iris.engine.river.RiverRouteState;
import art.arcane.iris.engine.river.RiverSample;
import art.arcane.iris.engine.river.RiverSection;
import art.arcane.iris.engine.river.runtime.IrisRiverSurfaceSample;
import org.junit.Test;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class IrisDecorantActuatorRiverTest {
@Test
public void dryRiverSurfaceDoesNotRunShorelineDecoration() {
IrisRiverSurfaceSample dry = new IrisRiverSurfaceSample(
river(RiverRouteState.DRY, RiverSection.DRY_CHANNEL),
70D,
65D,
65D,
false,
false
);
IrisRiverSurfaceSample wet = new IrisRiverSurfaceSample(
river(RiverRouteState.WET, RiverSection.CHANNEL),
70D,
65D,
65D,
false,
true
);
assertFalse(IrisDecorantActuator.shouldDecorateShoreline(dry, 65));
assertTrue(IrisDecorantActuator.shouldDecorateShoreline(wet, 65));
assertTrue(IrisDecorantActuator.shouldDecorateShoreline(IrisRiverSurfaceSample.none(65D, 65D), 65));
}
private static RiverSample river(RiverRouteState state, RiverSection section) {
return new RiverSample(true, state, section, 0D, 0.5D, 1D, 1, 1, 10D, 5D, 3D, false, null);
}
}
@@ -1,7 +1,6 @@
package art.arcane.iris.engine.decorator;
import art.arcane.iris.core.loader.IrisData;
import art.arcane.iris.engine.IrisComplex;
import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.engine.framework.SeedManager;
import art.arcane.iris.engine.object.InferredType;
@@ -11,7 +10,6 @@ import art.arcane.iris.engine.object.IrisDecorator;
import art.arcane.iris.engine.object.IrisDimension;
import art.arcane.iris.spi.PlatformBlockState;
import art.arcane.iris.util.project.hunk.Hunk;
import art.arcane.iris.util.project.stream.ProceduralStream;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
@@ -33,11 +31,6 @@ public class IrisDecoratorCaveContextTest {
IrisDimension dimension = mock(IrisDimension.class);
doReturn(63).when(dimension).getFluidHeight();
doReturn(dimension).when(engine).getDimension();
IrisComplex complex = mock(IrisComplex.class);
ProceduralStream<Double> fluidStream = mock(ProceduralStream.class);
doReturn(complex).when(engine).getComplex();
doReturn(fluidStream).when(complex).getRiverWaterSurfaceStream();
doReturn(63D).when(fluidStream).get(anyDouble(), anyDouble());
IrisDecorator decorator = mock(IrisDecorator.class);
doReturn(true).when(decorator).passesChanceGate(any(), anyDouble(), anyDouble(), any());
@@ -98,7 +98,6 @@ public class IrisShoreLineDecoratorTest {
IrisSlopeClip slope = mock(IrisSlopeClip.class);
PlatformBlockState decorant = mock(PlatformBlockState.class);
ProceduralStream<Double> heightStream = mock(ProceduralStream.class);
ProceduralStream<Double> fluidStream = mock(ProceduralStream.class);
when(engine.getCacheID()).thenReturn(1);
when(engine.getSeedManager()).thenReturn(seedManager);
@@ -108,9 +107,7 @@ public class IrisShoreLineDecoratorTest {
when(engine.getComplex()).thenReturn(complex);
when(complex.getFluidHeight()).thenReturn((double) FLUID_HEIGHT);
when(complex.getHeightStream()).thenReturn(heightStream);
when(complex.getRiverWaterSurfaceStream()).thenReturn(fluidStream);
when(heightStream.get(anyDouble(), anyDouble())).thenReturn((double) FLUID_HEIGHT - 1);
when(fluidStream.get(anyDouble(), anyDouble())).thenReturn((double) FLUID_HEIGHT);
when(engine.getData()).thenReturn(data);
when(biome.getDecoratorBucket(IrisDecorationPart.SHORE_LINE))
.thenReturn(new IrisDecorator[]{decorator});
@@ -417,7 +417,7 @@ public class IrisStructureLocatorContractTest {
assertEquals(IrisStructureLocator.LocateStatus.SEARCH_LIMIT_REACHED, result.status());
assertFalse(result.found());
verify(engine, times(8_192)).getComplex();
verify(engine, times(4_096)).getComplex();
}
@Test
@@ -1,6 +1,5 @@
package art.arcane.iris.engine.framework;
import art.arcane.iris.engine.IrisComplex;
import art.arcane.iris.engine.object.IrisNativeStructure;
import art.arcane.iris.engine.object.IrisNativeStructureDecision;
import art.arcane.iris.engine.object.IrisDimension;
@@ -10,7 +9,6 @@ import art.arcane.iris.engine.object.IrisStructureTerrain;
import art.arcane.iris.engine.object.IrisStructureTerrainMode;
import art.arcane.iris.engine.object.NativeStructureGenerationStatus;
import art.arcane.iris.engine.object.StructureDistribution;
import art.arcane.iris.util.project.stream.ProceduralStream;
import art.arcane.volmlib.util.collection.KList;
import art.arcane.volmlib.util.math.RNG;
import org.junit.Test;
@@ -22,7 +20,6 @@ import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class NativeStructurePlacementPlannerTest {
@@ -115,20 +112,6 @@ public class NativeStructurePlacementPlannerTest {
assertNotNull(NativeStructurePlacementPlanner.planAt(engine, placement, 0, 0));
}
@Test
public void submergedCheckUsesTheColumnRiverHead() {
Engine engine = engine(77L, -64, 384, 66);
IrisComplex complex = mock(IrisComplex.class);
@SuppressWarnings("unchecked")
ProceduralStream<Double> riverHead = mock(ProceduralStream.class);
when(engine.getComplex()).thenReturn(complex);
when(complex.getRiverWaterSurfaceStream()).thenReturn(riverHead);
when(riverHead.get(8, 8)).thenReturn(68D);
assertTrue(NativeStructurePlacementPlanner.isSubmerged(engine, 8, 8));
verify(riverHead).get(8, 8);
}
@Test
public void duplicateSelectedStructureUsesOneDeterministicPlan() {
Engine engine = engine(77L, -64, 384, 150);
@@ -3,16 +3,8 @@ package art.arcane.iris.engine.framework;
import art.arcane.iris.engine.object.IrisDimension;
import art.arcane.iris.engine.object.IrisStructureAnchorMode;
import art.arcane.iris.engine.object.IrisStructurePlacement;
import art.arcane.iris.engine.river.cave.RiverCaveAction;
import art.arcane.iris.engine.river.cave.RiverCaveHydrology;
import art.arcane.iris.spi.IrisPlatform;
import art.arcane.iris.spi.IrisPlatforms;
import art.arcane.iris.spi.PlatformBlockState;
import art.arcane.iris.spi.PlatformRegistries;
import art.arcane.volmlib.util.math.RNG;
import art.arcane.volmlib.util.matter.MatterCavern;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.util.Arrays;
@@ -27,7 +19,6 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
import static org.mockito.Mockito.mock;
@@ -36,22 +27,6 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class StructureCaveAnchorResolverTest {
@Before
public void bindPlatform() {
IrisPlatforms.unbind();
PlatformBlockState block = mock(PlatformBlockState.class);
PlatformRegistries registries = mock(PlatformRegistries.class);
when(registries.block(anyString())).thenReturn(block);
IrisPlatform platform = mock(IrisPlatform.class);
when(platform.registries()).thenReturn(registries);
IrisPlatforms.bind(platform);
}
@After
public void unbindPlatform() {
IrisPlatforms.unbind();
}
@Test
public void floorRequiresSolidBoundaryAndUpwardClearance() {
IntPredicate carved = carvedAt(10, 11, 12, 13);
@@ -180,21 +155,6 @@ public class StructureCaveAnchorResolverTest {
true, new MatterCavern(false, "", (byte) 0), 20, 8));
}
@Test
public void wetAndSealHydrologyCannotBecomeStructureAnchors() {
MatterCavern fluid = new MatterCavern(true, "", (byte) 1);
MatterCavern forcedAir = new MatterCavern(true, "", (byte) 3);
assertFalse(StructureCaveAnchorResolver.acceptsAnchorFluid(
true, fluid, RiverCaveHydrology.of(RiverCaveAction.WET_SOURCE), 20, 8));
assertFalse(StructureCaveAnchorResolver.acceptsAnchorFluid(
true, fluid, RiverCaveHydrology.of(RiverCaveAction.FALLING_FLUID), 20, 8));
assertFalse(StructureCaveAnchorResolver.acceptsAnchorFluid(
true, null, RiverCaveHydrology.of(RiverCaveAction.SEAL_GUARD), 20, 8));
assertTrue(StructureCaveAnchorResolver.acceptsAnchorFluid(
false, forcedAir, RiverCaveHydrology.of(RiverCaveAction.DRY_AIR), 0, 8));
}
private static Engine engineWithFloorAnchor(MatterCavern anchorCavern, int surfaceHeight) {
Engine engine = mock(Engine.class, RETURNS_DEEP_STUBS);
IrisDimension dimension = mock(IrisDimension.class);
@@ -66,7 +66,7 @@ public class StructurePlacementScopeTest {
}
@Test
public void dimensionOnlyPlacementsDoNotResolveRiverInclusiveBiomeScopes() {
public void dimensionOnlyPlacementsDoNotResolveBiomeScopes() {
IrisStructurePlacement dimensionPlacement = new IrisStructurePlacement();
Engine engine = mock(Engine.class, RETURNS_DEEP_STUBS);
IrisComplex complex = engine.getComplex();
@@ -71,33 +71,25 @@ public class IrisRendererPerformanceContractTest {
}
@Test
public void studioHeightUsesNaturalTerrainWhileProtocolRenderRemainsExact() {
public void studioAndProtocolHeightUseOrdinaryTerrain() {
Engine engine = mock(Engine.class);
IrisComplex complex = mock(IrisComplex.class);
@SuppressWarnings("unchecked")
ProceduralStream<Double> natural = mock(ProceduralStream.class);
@SuppressWarnings("unchecked")
ProceduralStream<Double> exact = mock(ProceduralStream.class);
when(engine.getComplex()).thenReturn(complex);
when(engine.getHeight()).thenReturn(320);
when(complex.getNaturalHeightStream()).thenReturn(natural);
when(complex.getHeightStream()).thenReturn(exact);
when(natural.getDouble(0D, 0D)).thenReturn(80D);
when(exact.getDouble(0D, 0D)).thenReturn(96D);
IrisRenderer renderer = new IrisRenderer(engine);
renderer.renderStudio(0D, 0D, 1D, 1, RenderType.HEIGHT, () -> false);
verify(natural).getDouble(0D, 0D);
verifyNoInteractions(exact);
renderer.render(0D, 0D, 1D, 1, RenderType.HEIGHT);
verify(exact).getDouble(0D, 0D);
verify(exact, times(2)).getDouble(0D, 0D);
}
@Test
public void studioBiomeAndContinentAvoidRiverAdjustedEngineLookups() {
public void studioBiomeAndContinentAvoidEngineBiomeLookups() {
Engine engine = mock(Engine.class);
IrisComplex complex = mock(IrisComplex.class);
IrisBiome biome = mock(IrisBiome.class);
@@ -144,12 +136,12 @@ public class IrisRendererPerformanceContractTest {
Engine engine = mock(Engine.class);
IrisComplex complex = mock(IrisComplex.class);
@SuppressWarnings("unchecked")
ProceduralStream<Double> natural = mock(ProceduralStream.class);
ProceduralStream<Double> exact = mock(ProceduralStream.class);
AtomicInteger samples = new AtomicInteger();
when(engine.getComplex()).thenReturn(complex);
when(engine.getHeight()).thenReturn(320);
when(complex.getNaturalHeightStream()).thenReturn(natural);
when(natural.getDouble(org.mockito.ArgumentMatchers.anyDouble(), org.mockito.ArgumentMatchers.anyDouble()))
when(complex.getHeightStream()).thenReturn(exact);
when(exact.getDouble(org.mockito.ArgumentMatchers.anyDouble(), org.mockito.ArgumentMatchers.anyDouble()))
.thenAnswer(invocation -> {
samples.incrementAndGet();
double x = invocation.getArgument(0);
@@ -1,96 +0,0 @@
package art.arcane.iris.engine.framework.render;
import art.arcane.iris.engine.IrisComplex;
import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.engine.object.IrisBiome;
import art.arcane.iris.engine.river.RiverSample;
import art.arcane.iris.engine.river.RiverSection;
import art.arcane.iris.engine.river.runtime.IrisRiverRuntime;
import art.arcane.iris.util.project.stream.ProceduralStream;
import org.junit.Test;
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.util.EnumMap;
import java.util.Map;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class IrisRendererRiverTest {
@Test
public void everyRiverSectionHasItsDiagnosticColor() {
Map<RiverSection, Integer> expected = new EnumMap<>(RiverSection.class);
expected.put(RiverSection.CHANNEL, new Color(48, 112, 190).getRGB());
expected.put(RiverSection.MOUTH, new Color(54, 164, 205).getRGB());
expected.put(RiverSection.BANK, new Color(92, 146, 78).getRGB());
expected.put(RiverSection.DRY_CHANNEL, new Color(171, 128, 68).getRGB());
expected.put(RiverSection.DRY_BANK, new Color(132, 105, 62).getRGB());
expected.put(RiverSection.NONE, new Color(28, 31, 38).getRGB());
assertEquals(RiverSection.values().length, expected.size());
for (RiverSection section : RiverSection.values()) {
assertEquals(section.name(), expected.get(section).intValue(), IrisRenderer.riverColor(section));
}
}
@Test
public void riverRenderTypeSamplesOneWorldFootprintPerRenderedPixel() {
Engine engine = mock(Engine.class);
IrisComplex complex = mock(IrisComplex.class);
IrisRiverRuntime runtime = mock(IrisRiverRuntime.class);
RiverSample river = mock(RiverSample.class);
when(engine.getComplex()).thenReturn(complex);
when(complex.getRiverRuntime()).thenReturn(runtime);
when(runtime.sampleFootprint(12D, -7D, 20D, 1D)).thenReturn(river);
when(river.present()).thenReturn(true);
when(river.section()).thenReturn(RiverSection.CHANNEL);
BufferedImage image = new IrisRenderer(engine).renderStudio(
12D,
-7D,
8D,
1,
RenderType.RIVER,
() -> false
);
assertEquals(new Color(48, 112, 190).getRGB(), image.getRGB(0, 0));
verify(runtime).sampleFootprint(12D, -7D, 20D, 1D);
}
@Test
public void biomeAtlasCompositesRiverChannelsOverTheFastBaseBiome() {
Engine engine = mock(Engine.class);
IrisComplex complex = mock(IrisComplex.class);
IrisRiverRuntime runtime = mock(IrisRiverRuntime.class);
RiverSample river = mock(RiverSample.class);
IrisBiome biome = mock(IrisBiome.class);
@SuppressWarnings("unchecked")
ProceduralStream<IrisBiome> base = mock(ProceduralStream.class);
when(engine.getComplex()).thenReturn(complex);
when(complex.getBaseBiomeStream()).thenReturn(base);
when(complex.getRiverRuntime()).thenReturn(runtime);
when(base.get(0D, 0D)).thenReturn(biome);
when(biome.getColor(engine, RenderType.BIOME)).thenReturn(Color.GREEN);
when(runtime.sampleFootprint(0D, 0D, 4D, 4D)).thenReturn(river);
when(river.present()).thenReturn(true);
when(river.section()).thenReturn(RiverSection.CHANNEL);
BufferedImage image = new IrisRenderer(engine).renderStudio(
0D,
0D,
4D,
1,
RenderType.BIOME,
() -> false
);
assertEquals(new Color(48, 112, 190).getRGB(), image.getRGB(0, 0));
verify(base).get(0D, 0D);
verify(runtime).sampleFootprint(0D, 0D, 4D, 4D);
}
}
@@ -2,7 +2,6 @@ package art.arcane.iris.engine.mantle;
import art.arcane.iris.core.link.Identifier;
import art.arcane.iris.engine.framework.TreeBlockMaterial;
import art.arcane.iris.engine.river.cave.RiverCaveHydrology;
import art.arcane.iris.spi.IrisPlatform;
import art.arcane.iris.spi.IrisPlatforms;
import art.arcane.iris.spi.PlatformBlockState;
@@ -138,7 +137,6 @@ public class EngineMantleCleanupTest {
verify(chunk).deleteSlices(MatterCavern.class);
verify(chunk, never()).deleteSlices(TileWrapper.class);
verify(chunk, never()).deleteSlices(Identifier.class);
verify(chunk, never()).deleteSlices(RiverCaveHydrology.class);
InOrder order = inOrder(chunk);
order.verify(chunk).raiseFlagUnchecked(eq(MantleFlag.CLEANED), any());
order.verify(chunk).release();
@@ -1,8 +1,6 @@
package art.arcane.iris.engine.mantle;
import art.arcane.iris.core.link.Identifier;
import art.arcane.iris.engine.river.cave.RiverCaveAction;
import art.arcane.iris.engine.river.cave.RiverCaveHydrology;
import art.arcane.iris.spi.IrisPlatform;
import art.arcane.iris.spi.IrisPlatforms;
import art.arcane.iris.spi.PlatformBlockState;
@@ -17,7 +15,6 @@ import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.doReturn;
import static org.mockito.ArgumentMatchers.anyString;
@@ -33,11 +30,9 @@ public class MantleWriterOverlayTest {
private MantleWriter writer;
private Matter matter;
private MantleChunk<Matter> chunk;
private MatterSlice<PlatformBlockState> blockSlice;
private MatterSlice<Identifier> identifierSlice;
private MatterSlice<MatterCavern> cavernSlice;
private MatterSlice<RiverCaveHydrology> hydrologySlice;
@Before
@SuppressWarnings("unchecked")
@@ -52,28 +47,22 @@ public class MantleWriterOverlayTest {
EngineMantle engineMantle = mock(EngineMantle.class);
Mantle<Matter> mantle = mock(Mantle.class);
chunk = mock(MantleChunk.class);
MantleChunk<Matter> chunk = mock(MantleChunk.class);
matter = mock(Matter.class);
blockSlice = mock(MatterSlice.class);
identifierSlice = mock(MatterSlice.class);
cavernSlice = mock(MatterSlice.class);
hydrologySlice = mock(MatterSlice.class);
when(mantle.getWorldHeight()).thenReturn(64);
when(mantle.getChunk(0, 0)).thenReturn(chunk);
when(chunk.use()).thenReturn(chunk);
when(chunk.getOrCreate(0)).thenReturn(matter);
when(chunk.exists(0)).thenReturn(true);
when(chunk.get(0)).thenReturn(matter);
when(matter.hasSlice(PlatformBlockState.class)).thenReturn(true);
when(matter.hasSlice(Identifier.class)).thenReturn(true);
when(matter.hasSlice(MatterCavern.class)).thenReturn(true);
when(matter.getSlice(PlatformBlockState.class)).thenReturn(blockSlice);
when(matter.getSlice(Identifier.class)).thenReturn(identifierSlice);
when(matter.getSlice(MatterCavern.class)).thenReturn(cavernSlice);
doReturn(blockSlice).when(matter).slice(PlatformBlockState.class);
doReturn(cavernSlice).when(matter).slice(MatterCavern.class);
doReturn(hydrologySlice).when(matter).getSlice(RiverCaveHydrology.class);
writer = new MantleWriter(engineMantle, mantle, 0, 0, 0, false);
}
@@ -116,41 +105,4 @@ public class MantleWriterOverlayTest {
verify(identifierSlice).set(X, Y, Z, null);
verify(blockSlice).set(X, Y, Z, replacement);
}
@Test
public void protectedHydrologyRejectsLaterBlockAndCavernWrites() {
PlatformBlockState replacement = mock(PlatformBlockState.class);
MatterCavern cavern = new MatterCavern(true, "", (byte) 3);
when(matter.hasSlice(RiverCaveHydrology.class)).thenReturn(true);
when(hydrologySlice.get(X, Y, Z))
.thenReturn(RiverCaveHydrology.of(RiverCaveAction.SEAL_GUARD));
writer.setData(X, Y, Z, replacement);
assertFalse(writer.carveDataIfAbsent(X, Y, Z, cavern));
writer.setForcedCarve(X, Y, Z, cavern);
verify(blockSlice, never()).set(X, Y, Z, replacement);
verify(blockSlice, never()).set(X, Y, Z, null);
verify(identifierSlice, never()).set(X, Y, Z, null);
verify(cavernSlice, never()).set(X, Y, Z, cavern);
}
@Test
public void hydrologyOverridesBaselineCarvedQueriesWithoutChangingIt() {
MatterCavern baseline = new MatterCavern(true, "", (byte) 0);
when(matter.hasSlice(RiverCaveHydrology.class)).thenReturn(true);
when(cavernSlice.get(X, Y, Z)).thenReturn(baseline);
when(hydrologySlice.get(X, Y, Z))
.thenReturn(RiverCaveHydrology.of(RiverCaveAction.SEAL_GUARD));
when(hydrologySlice.get(X, Y + 1, Z))
.thenReturn(RiverCaveHydrology.of(RiverCaveAction.DRY_AIR));
assertFalse(writer.isCarved(X, Y, Z));
assertTrue(writer.isCarved(X, Y + 1, Z));
byte[] column = writer.getCarvedColumn(X, Z, Y + 2);
assertEquals(0, column[Y]);
assertEquals(1, column[Y + 1]);
assertEquals(0, baseline.getLiquid());
}
}
@@ -74,7 +74,7 @@ public class MatterGeneratorCarvePassRadiusTest {
RecordingComponent carving = new RecordingComponent(ReservedFlag.CARVED, 0, 1);
RecordingComponent conditional = new RecordingComponent(
ReservedFlag.RIVER_HYDROLOGY,
ReservedFlag.JIGSAW,
1,
0,
160,
@@ -111,7 +111,7 @@ public class MatterGeneratorCarvePassRadiusTest {
RecordingComponent carving = new RecordingComponent(ReservedFlag.CARVED, 0, 0);
RecordingComponent conditional = new RecordingComponent(
ReservedFlag.RIVER_HYDROLOGY,
ReservedFlag.JIGSAW,
1,
0,
0,
@@ -21,8 +21,6 @@ package art.arcane.iris.engine.mantle.components;
import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.engine.object.IObjectPlacer;
import art.arcane.iris.engine.object.TileData;
import art.arcane.iris.engine.river.cave.RiverCaveAction;
import art.arcane.iris.engine.river.cave.RiverCaveHydrology;
import art.arcane.iris.spi.PlatformBlockState;
import org.junit.Test;
@@ -111,21 +109,6 @@ public class CaveObjectPlacementTransactionTest {
assertEquals(61, MantleObjectComponent.caveAnchorScanUpperBound(128, 80, 20));
}
@Test
public void protectedHydrologyRejectsTheWholePlacement() {
IObjectPlacer delegate = createPlacer(128, 80, 20, 90);
when(delegate.getData(4, 30, 7, RiverCaveHydrology.class))
.thenReturn(RiverCaveHydrology.of(RiverCaveAction.WET_SOURCE));
CaveObjectPlacementTransaction transaction = new CaveObjectPlacementTransaction(delegate, 20, 10);
transaction.set(4, 30, 7, mock(PlatformBlockState.class));
transaction.setData(5, 30, 7, "object@1");
assertEquals(CaveObjectPlacementTransaction.CommitResult.REJECTED_HYDROLOGY, transaction.commit());
verify(delegate, never()).set(anyInt(), anyInt(), anyInt(), any());
verify(delegate, never()).setData(anyInt(), anyInt(), anyInt(), any());
}
private IObjectPlacer createPlacer(int worldHeight, int surfaceHeight, int caveFloor, int caveCeiling) {
Engine engine = mock(Engine.class);
when(engine.getHeight()).thenReturn(worldHeight);
@@ -868,11 +868,10 @@ public class IrisCaveCarver3DNearParityTest {
Engine engine = createEngine(80, 70);
int[] surfaceHeights = filledHeights(70);
surfaceHeights[0] = 60;
long[] boundaries = new long[256];
Arrays.fill(boundaries, SurfaceFluidBoundaryPlan.boundary(
SurfaceFluidBoundaryPlan.NO_BOUNDARY, Integer.MIN_VALUE));
boundaries[0] = SurfaceFluidBoundaryPlan.boundary(60, 64);
boundaries[16] = SurfaceFluidBoundaryPlan.boundary(61, 64);
int[] boundaryStartY = new int[256];
Arrays.fill(boundaryStartY, SurfaceFluidBoundaryPlan.NO_BOUNDARY);
boundaryStartY[0] = 60;
boundaryStartY[16] = 61;
WriterCapture capture = createWriterCapture(80);
CaveFluidSupportPlan supportPlan = new CaveFluidSupportPlan();
@@ -885,7 +884,7 @@ public class IrisCaveCarver3DNearParityTest {
0D,
null,
surfaceHeights,
boundaries,
boundaryStartY,
null,
supportPlan
);
@@ -1,8 +1,6 @@
package art.arcane.iris.engine.mantle.components;
import art.arcane.iris.engine.object.IrisBiome;
import art.arcane.iris.engine.river.cave.RiverCaveAction;
import art.arcane.iris.engine.river.cave.RiverCaveHydrology;
import art.arcane.volmlib.util.matter.MatterCavern;
import org.junit.Test;
@@ -48,18 +46,6 @@ public class MantleObjectComponentCaveAnchorTest {
));
}
@Test
public void protectedRiverHydrologyCannotBecomeAnObjectAnchor() {
MatterCavern water = new MatterCavern(true, "", (byte) 1);
assertFalse(MantleObjectComponent.acceptsCaveAnchorFluid(
true, water, RiverCaveHydrology.of(RiverCaveAction.WET_SOURCE), 20, 8));
assertFalse(MantleObjectComponent.acceptsCaveAnchorFluid(
true, water, RiverCaveHydrology.of(RiverCaveAction.FALLING_FLUID), 20, 8));
assertFalse(MantleObjectComponent.acceptsCaveAnchorFluid(
true, null, RiverCaveHydrology.of(RiverCaveAction.SEAL_GUARD), 20, 8));
}
private static IrisBiome biome(String loadKey) {
IrisBiome biome = new IrisBiome();
biome.setLoadKey(loadKey);
@@ -1,182 +0,0 @@
package art.arcane.iris.engine.mantle.components;
import art.arcane.iris.engine.river.cave.CavePosition;
import art.arcane.iris.engine.river.cave.CaveVoxel;
import art.arcane.iris.engine.river.cave.RiverCaveAction;
import art.arcane.iris.engine.river.cave.RiverCaveFluidKind;
import art.arcane.iris.engine.river.cave.RiverCaveHydrology;
import art.arcane.volmlib.util.collection.KMap;
import art.arcane.volmlib.util.mantle.runtime.Mantle;
import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
import art.arcane.volmlib.util.mantle.runtime.TectonicPlate;
import art.arcane.volmlib.util.matter.Matter;
import art.arcane.volmlib.util.matter.MatterSlice;
import art.arcane.volmlib.util.matter.MatterCavern;
import art.arcane.iris.spi.PlatformBlockState;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.when;
public class MantleRiverCaveVoxelViewTest {
@Test
@SuppressWarnings("unchecked")
public void absentCellsAboveLocalTerrainAreOpenAir() {
Mantle<Matter> mantle = mock(Mantle.class);
when(mantle.getLoadedRegions()).thenReturn(new KMap<Long, TectonicPlate<Matter>>());
MantleRiverCaveVoxelView view = new MantleRiverCaveVoxelView(
mantle,
128,
(x, z) -> x < 0 ? 20 : 60,
(x, z) -> null,
RiverCaveFluidKind.RIVER,
(chunkX, chunkZ) -> {
}
);
CavePosition cliffAir = new CavePosition(-1, 21, 0);
CavePosition terrain = new CavePosition(-1, 20, 0);
assertEquals(CaveVoxel.CAVE_AIR, view.voxelAt(cliffAir));
assertTrue(view.isOpenToSurface(cliffAir));
assertEquals(CaveVoxel.SOLID, view.voxelAt(terrain));
assertFalse(view.isOpenToSurface(terrain));
}
@Test
@SuppressWarnings("unchecked")
public void carvingInputLoadsOnlyOncePerReadChunk() {
Mantle<Matter> mantle = mock(Mantle.class);
when(mantle.getLoadedRegions()).thenReturn(new KMap<Long, TectonicPlate<Matter>>());
List<String> loaded = new ArrayList<>();
MantleRiverCaveVoxelView view = new MantleRiverCaveVoxelView(
mantle,
128,
(x, z) -> 60,
(x, z) -> null,
RiverCaveFluidKind.RIVER,
(chunkX, chunkZ) -> loaded.add(chunkX + "," + chunkZ)
);
view.voxelAt(new CavePosition(0, 20, 0));
view.voxelAt(new CavePosition(15, 21, 15));
view.riverHydrologyAt(new CavePosition(1, 22, 1));
view.voxelAt(new CavePosition(16, 20, 0));
assertEquals(List.of("0,0", "1,0"), loaded);
}
@Test
@SuppressWarnings("unchecked")
public void publishedRiverActionsDoNotReplaceTheUnderlyingPlanningBaseline() {
Mantle<Matter> mantle = mock(Mantle.class);
TectonicPlate<Matter> plate = mock(TectonicPlate.class);
MantleChunk<Matter> chunk = mock(MantleChunk.class);
Matter matter = mock(Matter.class);
MatterSlice<RiverCaveHydrology> hydrologySlice = mock(MatterSlice.class);
KMap<Long, TectonicPlate<Matter>> regions = new KMap<>();
regions.put(Mantle.key(0, 0), plate);
CavePosition position = new CavePosition(0, 20, 0);
when(mantle.getLoadedRegions()).thenReturn(regions);
when(plate.get(0, 0)).thenReturn(chunk);
when(chunk.exists(1)).thenReturn(true);
when(chunk.get(1)).thenReturn(matter);
when(matter.hasSlice(RiverCaveHydrology.class)).thenReturn(true);
doReturn(hydrologySlice).when(matter).getSlice(RiverCaveHydrology.class);
when(hydrologySlice.get(0, 4, 0)).thenReturn(RiverCaveHydrology.of(RiverCaveAction.WET_SOURCE));
MantleRiverCaveVoxelView view = new MantleRiverCaveVoxelView(
mantle,
128,
(x, z) -> 60,
(x, z) -> null,
RiverCaveFluidKind.RIVER,
(chunkX, chunkZ) -> {
}
);
assertEquals(CaveVoxel.SOLID, view.voxelAt(position));
assertEquals(RiverCaveAction.WET_SOURCE, view.riverHydrologyAt(position).action());
}
@Test
@SuppressWarnings("unchecked")
public void configuredLavaRiverTreatsPublishedLavaAsCompatibleFluid() {
Mantle<Matter> mantle = mock(Mantle.class);
TectonicPlate<Matter> plate = mock(TectonicPlate.class);
MantleChunk<Matter> chunk = mock(MantleChunk.class);
Matter matter = mock(Matter.class);
MatterSlice<MatterCavern> cavernSlice = mock(MatterSlice.class);
PlatformBlockState lava = mock(PlatformBlockState.class);
KMap<Long, TectonicPlate<Matter>> regions = new KMap<>();
regions.put(Mantle.key(0, 0), plate);
when(mantle.getLoadedRegions()).thenReturn(regions);
when(plate.get(0, 0)).thenReturn(chunk);
when(chunk.exists(1)).thenReturn(true);
when(chunk.get(1)).thenReturn(matter);
when(matter.hasSlice(MatterCavern.class)).thenReturn(true);
doReturn(cavernSlice).when(matter).getSlice(MatterCavern.class);
when(cavernSlice.get(0, 4, 0)).thenReturn(new MatterCavern(true, "", (byte) 2));
when(lava.materialKey()).thenReturn("minecraft:lava");
MantleRiverCaveVoxelView view = new MantleRiverCaveVoxelView(
mantle,
128,
(x, z) -> 60,
(x, z) -> lava,
RiverCaveFluidKind.RIVER,
(chunkX, chunkZ) -> {
}
);
assertEquals(CaveVoxel.COMPATIBLE_FLUID, view.voxelAt(new CavePosition(0, 20, 0)));
}
@Test
@SuppressWarnings("unchecked")
public void planningRejectsHydrologyOwnedByTheOtherFluidKind() {
Mantle<Matter> mantle = mock(Mantle.class);
TectonicPlate<Matter> plate = mock(TectonicPlate.class);
MantleChunk<Matter> chunk = mock(MantleChunk.class);
Matter matter = mock(Matter.class);
MatterSlice<RiverCaveHydrology> hydrologySlice = mock(MatterSlice.class);
KMap<Long, TectonicPlate<Matter>> regions = new KMap<>();
regions.put(Mantle.key(0, 0), plate);
CavePosition position = new CavePosition(0, 20, 0);
when(mantle.getLoadedRegions()).thenReturn(regions);
when(plate.get(0, 0)).thenReturn(chunk);
when(chunk.exists(1)).thenReturn(true);
when(chunk.get(1)).thenReturn(matter);
when(matter.hasSlice(RiverCaveHydrology.class)).thenReturn(true);
doReturn(hydrologySlice).when(matter).getSlice(RiverCaveHydrology.class);
when(hydrologySlice.get(0, 4, 0)).thenReturn(RiverCaveHydrology.of(
RiverCaveAction.WET_SOURCE,
RiverCaveFluidKind.DEEP_POOL
));
MantleRiverCaveVoxelView riverView = new MantleRiverCaveVoxelView(
mantle,
128,
(x, z) -> 60,
(x, z) -> null,
RiverCaveFluidKind.RIVER,
(chunkX, chunkZ) -> {
}
);
MantleRiverCaveVoxelView deepPoolView = new MantleRiverCaveVoxelView(
mantle,
128,
(x, z) -> 60,
(x, z) -> null,
RiverCaveFluidKind.DEEP_POOL,
(chunkX, chunkZ) -> {
}
);
assertEquals(CaveVoxel.INCOMPATIBLE_FLUID, riverView.voxelAt(position));
assertEquals(CaveVoxel.SOLID, deepPoolView.voxelAt(position));
}
}
@@ -25,8 +25,8 @@ public class SurfaceFluidBoundaryPlanTest {
int boundaryY = fixture.boundary(8, 8);
assertEquals(60, boundaryY);
assertFalse(SurfaceFluidBoundaryPlan.protects(fixture.boundaries, fixture.index(8, 8), 59));
assertTrue(SurfaceFluidBoundaryPlan.protects(fixture.boundaries, fixture.index(8, 8), 60));
assertFalse(SurfaceFluidBoundaryPlan.protects(fixture.boundaryStartY, fixture.index(8, 8), 59, FLUID_HEIGHT));
assertTrue(SurfaceFluidBoundaryPlan.protects(fixture.boundaryStartY, fixture.index(8, 8), 60, FLUID_HEIGHT));
}
@Test
@@ -38,10 +38,10 @@ public class SurfaceFluidBoundaryPlanTest {
int columnIndex = fixture.index(8, 8);
assertEquals(61, fixture.boundary(8, 8));
assertFalse(SurfaceFluidBoundaryPlan.protects(fixture.boundaries, columnIndex, 60));
assertTrue(SurfaceFluidBoundaryPlan.protects(fixture.boundaries, columnIndex, 61));
assertTrue(SurfaceFluidBoundaryPlan.protects(fixture.boundaries, columnIndex, 64));
assertFalse(SurfaceFluidBoundaryPlan.protects(fixture.boundaries, columnIndex, 65));
assertFalse(SurfaceFluidBoundaryPlan.protects(fixture.boundaryStartY, columnIndex, 60, FLUID_HEIGHT));
assertTrue(SurfaceFluidBoundaryPlan.protects(fixture.boundaryStartY, columnIndex, 61, FLUID_HEIGHT));
assertTrue(SurfaceFluidBoundaryPlan.protects(fixture.boundaryStartY, columnIndex, 64, FLUID_HEIGHT));
assertFalse(SurfaceFluidBoundaryPlan.protects(fixture.boundaryStartY, columnIndex, 65, FLUID_HEIGHT));
}
@Test
@@ -94,28 +94,13 @@ public class SurfaceFluidBoundaryPlanTest {
new int[CHUNK_SIZE * CHUNK_SIZE],
new double[FIELD_SIZE * FIELD_SIZE],
new boolean[FIELD_SIZE * FIELD_SIZE],
new double[FIELD_SIZE * FIELD_SIZE],
FIELD_SIZE,
0,
new long[CHUNK_SIZE * CHUNK_SIZE]
FLUID_HEIGHT,
new int[CHUNK_SIZE * CHUNK_SIZE]
));
}
@Test
public void terracedReservoirProtectsOnlyToItsLocalHead() {
Fixture fixture = new Fixture();
fixture.setFieldSurface(7, 8, 60D);
fixture.setFieldFluidHeight(7, 8, 67D);
fixture.resolve();
int columnIndex = fixture.index(8, 8);
assertEquals(61, fixture.boundary(8, 8));
assertEquals(67, fixture.boundaryEnd(8, 8));
assertTrue(SurfaceFluidBoundaryPlan.protects(fixture.boundaries, columnIndex, 67));
assertFalse(SurfaceFluidBoundaryPlan.protects(fixture.boundaries, columnIndex, 68));
}
private void assertEdgeBoundary(int localX, int localZ, int neighborLocalX, int neighborLocalZ) {
Fixture fixture = new Fixture();
fixture.setFieldSurface(neighborLocalX, neighborLocalZ, 60D);
@@ -126,14 +111,12 @@ public class SurfaceFluidBoundaryPlanTest {
private static final class Fixture {
private final int[] chunkSurfaceHeights = new int[CHUNK_SIZE * CHUNK_SIZE];
private final double[] fieldSurfaceHeights = new double[FIELD_SIZE * FIELD_SIZE];
private final double[] fieldFluidHeights = new double[FIELD_SIZE * FIELD_SIZE];
private final boolean[] fieldHasFluid = new boolean[FIELD_SIZE * FIELD_SIZE];
private final long[] boundaries = new long[CHUNK_SIZE * CHUNK_SIZE];
private final int[] boundaryStartY = new int[CHUNK_SIZE * CHUNK_SIZE];
private Fixture() {
Arrays.fill(chunkSurfaceHeights, 70);
Arrays.fill(fieldSurfaceHeights, 70D);
Arrays.fill(fieldFluidHeights, FLUID_HEIGHT);
Arrays.fill(fieldHasFluid, true);
}
@@ -154,30 +137,20 @@ public class SurfaceFluidBoundaryPlanTest {
fieldHasFluid[(fieldX * FIELD_SIZE) + fieldZ] = hasFluid;
}
private void setFieldFluidHeight(int localX, int localZ, double fluidHeight) {
int fieldX = localX + PADDING;
int fieldZ = localZ + PADDING;
fieldFluidHeights[(fieldX * FIELD_SIZE) + fieldZ] = fluidHeight;
}
private void resolve() {
SurfaceFluidBoundaryPlan.fill(
chunkSurfaceHeights,
fieldSurfaceHeights,
fieldHasFluid,
fieldFluidHeights,
FIELD_SIZE,
PADDING,
boundaries
FLUID_HEIGHT,
boundaryStartY
);
}
private int boundary(int localX, int localZ) {
return SurfaceFluidBoundaryPlan.startY(boundaries[index(localX, localZ)]);
}
private int boundaryEnd(int localX, int localZ) {
return SurfaceFluidBoundaryPlan.endY(boundaries[index(localX, localZ)]);
return boundaryStartY[index(localX, localZ)];
}
private int index(int localX, int localZ) {
@@ -5,8 +5,6 @@ import art.arcane.iris.core.loader.ResourceLoader;
import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.engine.object.IrisBiome;
import art.arcane.iris.engine.object.IrisDimensionCarvingResolver;
import art.arcane.iris.engine.river.cave.RiverCaveAction;
import art.arcane.iris.engine.river.cave.RiverCaveHydrology;
import art.arcane.iris.spi.PlatformBlockState;
import art.arcane.iris.util.project.hunk.Hunk;
import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
@@ -109,52 +107,10 @@ public class IrisCarveModifierBoundarySupportTest {
assertTrue(IrisCarveModifier.hasStableCaveFloorSupport(output, 0, 6, 0));
}
@Test
public void riverGuardOnlyAcceptsStableSolidBoundaryLayers() {
RiverCaveHydrology guard = RiverCaveHydrology.of(RiverCaveAction.SEAL_GUARD);
PlatformBlockState stone = state("minecraft:stone", true);
PlatformBlockState sand = state("minecraft:sand", true);
PlatformBlockState water = state("minecraft:water", false, true);
assertTrue(IrisCarveModifier.canReplaceRiverGuard(guard, stone, false));
assertTrue(IrisCarveModifier.canReplaceRiverGuard(guard, stone, true));
assertTrue(IrisCarveModifier.canReplaceRiverGuard(guard, sand, false));
assertFalse(IrisCarveModifier.canReplaceRiverGuard(guard, sand, true));
assertFalse(IrisCarveModifier.canReplaceRiverGuard(guard, water, false));
}
@Test
public void riverBiomeInheritanceIsColumnCoherentAndHonorsLimits() {
long seed = 7845123L;
boolean cell = IrisCarveModifier.selectsParentRiverBiome(seed, 8, 12, 0.5D);
for (int x = 8; x < 12; x++) {
for (int z = 12; z < 16; z++) {
assertTrue(cell == IrisCarveModifier.selectsParentRiverBiome(seed, x, z, 0.5D));
}
}
assertFalse(IrisCarveModifier.selectsParentRiverBiome(seed, 8, 12, 0D));
assertTrue(IrisCarveModifier.selectsParentRiverBiome(seed, 8, 12, 1D));
boolean inherited = false;
boolean overridden = false;
for (int x = -128; x <= 128; x += 4) {
boolean selected = IrisCarveModifier.selectsParentRiverBiome(seed, x, 0, 0.5D);
inherited |= selected;
overridden |= !selected;
}
assertTrue(inherited);
assertTrue(overridden);
}
private PlatformBlockState state(String key, boolean solid) {
return state(key, solid, false);
}
private PlatformBlockState state(String key, boolean solid, boolean fluid) {
PlatformBlockState state = mock(PlatformBlockState.class);
doReturn(key).when(state).key();
doReturn(solid).when(state).isSolid();
doReturn(fluid).when(state).isFluid();
return state;
}
}
@@ -15,7 +15,6 @@ import art.arcane.iris.spi.PlatformBlockState;
import art.arcane.iris.spi.PlatformRegistries;
import art.arcane.iris.util.project.hunk.Hunk;
import art.arcane.iris.util.project.stream.ProceduralStream;
import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
import art.arcane.volmlib.util.matter.MatterCavern;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import org.junit.AfterClass;
@@ -70,11 +69,10 @@ public class IrisCarveModifierInferenceIsolationTest {
Map<String, IrisBiome> customBiomes = new HashMap<>();
customBiomes.put("shared", biome);
CarveWallBuffer walls = new CarveWallBuffer(1);
walls.put(0, 1, 0, new MatterCavern(true, "shared", (byte) 0), false);
walls.put(0, 1, 0, new MatterCavern(true, "shared", (byte) 0));
Method paintBoundaryZone = IrisCarveModifier.class.getDeclaredMethod(
"paintBoundaryZone",
Hunk.class,
MantleChunk.class,
CarveWallBuffer.class,
int.class,
int.class,
@@ -90,7 +88,6 @@ public class IrisCarveModifierInferenceIsolationTest {
paintBoundaryZone.invoke(
modifier,
mock(Hunk.class),
mock(MantleChunk.class),
walls,
0,
0,
@@ -1,115 +0,0 @@
package art.arcane.iris.engine.modifier;
import art.arcane.iris.engine.river.cave.RiverCaveAction;
import art.arcane.iris.engine.river.cave.RiverCaveFluidKind;
import art.arcane.iris.engine.river.cave.RiverCaveHydrology;
import art.arcane.iris.spi.PlatformBlockState;
import art.arcane.volmlib.util.matter.MatterCavern;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class IrisCarveModifierRiverHydrologyTest {
@Test
public void overlayCompositionDoesNotMutateBaselineCavern() {
MatterCavern baseline = new MatterCavern(true, "baseline", (byte) 2);
assertSame(baseline, IrisCarveModifier.composeCavern(baseline, null));
assertNull(IrisCarveModifier.composeCavern(
baseline, RiverCaveHydrology.of(RiverCaveAction.SEAL_GUARD)));
MatterCavern wet = IrisCarveModifier.composeCavern(
baseline, new RiverCaveHydrology(
RiverCaveAction.WET_SOURCE,
"iris:flooded",
RiverCaveFluidKind.DEEP_POOL
));
MatterCavern dry = IrisCarveModifier.composeCavern(
baseline, RiverCaveHydrology.of(RiverCaveAction.DRY_AIR));
assertEquals(1, wet.getLiquid());
assertEquals("iris:flooded", wet.getCustomBiome());
assertEquals(3, dry.getLiquid());
assertEquals(2, baseline.getLiquid());
assertEquals("baseline", baseline.getCustomBiome());
}
@Test
public void hydrologyActionsResolveDistinctFluidStates() {
PlatformBlockState current = mock(PlatformBlockState.class);
PlatformBlockState source = mock(PlatformBlockState.class);
PlatformBlockState falling = mock(PlatformBlockState.class);
PlatformBlockState air = mock(PlatformBlockState.class);
when(source.key()).thenReturn("minecraft:water[level=0]");
when(source.withProperty("level", "8")).thenReturn(falling);
assertSame(source, IrisCarveModifier.resolveHydrologyState(
RiverCaveHydrology.of(RiverCaveAction.WET_SOURCE), current, source, air));
assertSame(falling, IrisCarveModifier.resolveHydrologyState(
RiverCaveHydrology.of(RiverCaveAction.FALLING_FLUID), current, source, air));
assertSame(air, IrisCarveModifier.resolveHydrologyState(
RiverCaveHydrology.of(RiverCaveAction.DRY_AIR), current, source, air));
}
@Test
public void waterloggingFollowsOnlyExplicitResultingWater() {
PlatformBlockState waterlogged = mock(PlatformBlockState.class);
PlatformBlockState dry = mock(PlatformBlockState.class);
PlatformBlockState water = mock(PlatformBlockState.class);
PlatformBlockState lava = mock(PlatformBlockState.class);
when(waterlogged.key()).thenReturn("minecraft:seagrass[waterlogged=true]");
when(waterlogged.withProperty("waterlogged", "false")).thenReturn(dry);
when(water.isWater()).thenReturn(true);
assertSame(waterlogged, IrisCarveModifier.normalizeWaterlogging(waterlogged, water));
assertSame(dry, IrisCarveModifier.normalizeWaterlogging(waterlogged, null));
assertSame(dry, IrisCarveModifier.normalizeWaterlogging(waterlogged, lava));
assertSame(dry, IrisCarveModifier.resolveHydrologyState(
RiverCaveHydrology.of(RiverCaveAction.SEAL_GUARD), waterlogged, water, null));
}
@Test
public void noOverlayLeavesBaselineWaterloggingUntouched() {
PlatformBlockState waterlogged = mock(PlatformBlockState.class);
PlatformBlockState water = mock(PlatformBlockState.class);
MatterCavern flooded = new MatterCavern(true, "", (byte) 1);
assertSame(waterlogged, IrisCarveModifier.normalizeHydrologyWaterlogging(
waterlogged,
flooded,
null,
water
));
verify(waterlogged, never()).withProperty("waterlogged", "false");
}
@Test
public void explicitOverlayUsesComposedFluidIntentForWaterlogging() {
PlatformBlockState waterlogged = mock(PlatformBlockState.class);
PlatformBlockState dry = mock(PlatformBlockState.class);
PlatformBlockState water = mock(PlatformBlockState.class);
MatterCavern flooded = new MatterCavern(true, "", (byte) 1);
when(waterlogged.key()).thenReturn("minecraft:seagrass[waterlogged=true]");
when(waterlogged.withProperty("waterlogged", "false")).thenReturn(dry);
when(water.isWater()).thenReturn(true);
assertSame(waterlogged, IrisCarveModifier.normalizeHydrologyWaterlogging(
waterlogged,
flooded,
RiverCaveHydrology.of(RiverCaveAction.WET_SOURCE),
water
));
assertSame(dry, IrisCarveModifier.normalizeHydrologyWaterlogging(
waterlogged,
flooded,
RiverCaveHydrology.of(RiverCaveAction.DRY_AIR),
water
));
}
}
@@ -22,21 +22,19 @@ public class IrisCarveScratchTest {
int y = 20 + index;
int z = (index * 3) & 15;
MatterCavern cavern = new MatterCavern(true, "cave-" + index, (byte) 0);
buffer.put(x, y, z, cavern, (index & 1) == 0);
buffer.put(x, y, z, cavern);
expected.put(key(x, y, z), cavern);
}
MatterCavern replacement = new MatterCavern(true, "replacement", (byte) 0);
buffer.put(5, 25, 15, replacement, true);
buffer.put(5, 25, 15, replacement);
expected.put(key(5, 25, 15), replacement);
assertSame(replacement, buffer.get(5, 25, 15));
assertTrue(buffer.isRiverBoundary(5, 25, 15));
assertNull(buffer.get(5, 26, 15));
assertFalse(buffer.isRiverBoundary(5, 26, 15));
Map<String, MatterCavern> actual = new HashMap<>();
buffer.forEach((x, y, z, cavern, riverBoundary) -> actual.put(key(x, y, z), cavern));
buffer.forEach((x, y, z, cavern) -> actual.put(key(x, y, z), cavern));
assertEquals(expected.keySet(), actual.keySet());
for (Map.Entry<String, MatterCavern> entry : expected.entrySet()) {
assertSame(entry.getValue(), actual.get(entry.getKey()));
@@ -49,7 +47,7 @@ public class IrisCarveScratchTest {
MatterCavern cavern = new MatterCavern(true, "cave", (byte) 0);
scratch.columnMasks[0].add(12);
scratch.boundaryMasks[0].add(13);
scratch.walls.put(1, 12, 2, cavern, true);
scratch.walls.put(1, 12, 2, cavern);
scratch.customBiomeCache.put("cave", null);
scratch.customCaveBiomePresent = true;
@@ -60,7 +58,7 @@ public class IrisCarveScratchTest {
assertTrue(scratch.customBiomeCache.isEmpty());
assertFalse(scratch.customCaveBiomePresent);
int[] wallCount = new int[1];
scratch.walls.forEach((x, y, z, value, riverBoundary) -> wallCount[0]++);
scratch.walls.forEach((x, y, z, value) -> wallCount[0]++);
assertEquals(0, wallCount[0]);
}
@@ -1,71 +0,0 @@
package art.arcane.iris.engine.object;
import art.arcane.iris.engine.IrisComplex;
import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.util.project.stream.ProceduralStream;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.CALLS_REAL_METHODS;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class IObjectPlacerFluidHeightTest {
@Test
public void missingEngineOrComplexFallsBackToThePlacersScalarHead() {
IObjectPlacer withoutEngine = mock(IObjectPlacer.class, CALLS_REAL_METHODS);
when(withoutEngine.getFluidHeight()).thenReturn(63);
when(withoutEngine.getEngine()).thenReturn(null);
assertEquals(63, withoutEngine.getFluidHeight(12, -7));
IObjectPlacer withoutComplex = mock(IObjectPlacer.class, CALLS_REAL_METHODS);
Engine engine = mock(Engine.class);
when(withoutComplex.getFluidHeight()).thenReturn(63);
when(withoutComplex.getEngine()).thenReturn(engine);
when(engine.getComplex()).thenReturn(null);
assertEquals(63, withoutComplex.getFluidHeight(12, -7));
}
@Test
public void engineLocalPlacersUseTheRoundedPerColumnRiverHead() {
IObjectPlacer placer = placer(127, 127, 130.6D);
assertEquals(131, placer.getFluidHeight(12, -7));
verify(riverHead(placer)).get(12, -7);
}
@Test
public void worldCoordinatePlacersShiftTheLocalRiverHeadExactlyOnce() {
IObjectPlacer placer = placer(63, 127, 130.6D);
assertEquals(67, placer.getFluidHeight(12, -7));
verify(riverHead(placer)).get(12, -7);
}
private static IObjectPlacer placer(int placerFluidHeight, int dimensionFluidHeight, double riverFluidHeight) {
IObjectPlacer placer = mock(IObjectPlacer.class, CALLS_REAL_METHODS);
Engine engine = mock(Engine.class);
IrisComplex complex = mock(IrisComplex.class);
IrisDimension dimension = mock(IrisDimension.class);
@SuppressWarnings("unchecked")
ProceduralStream<Double> riverHead = mock(ProceduralStream.class);
when(placer.getFluidHeight()).thenReturn(placerFluidHeight);
when(placer.getEngine()).thenReturn(engine);
when(engine.getComplex()).thenReturn(complex);
when(engine.getDimension()).thenReturn(dimension);
when(dimension.getFluidHeight()).thenReturn(dimensionFluidHeight);
when(complex.getRiverWaterSurfaceStream()).thenReturn(riverHead);
when(riverHead.get(12, -7)).thenReturn(riverFluidHeight);
return placer;
}
private static ProceduralStream<Double> riverHead(IObjectPlacer placer) {
return placer.getEngine().getComplex().getRiverWaterSurfaceStream();
}
}
@@ -69,6 +69,28 @@ public class IrisDepositTuningTest {
assertEquals(firstWorld, new IrisDepositGenerator.ClumpCacheKey(41L, 4, 8));
}
@Test
public void clumpSaltIsStableAcrossEquivalentConfigInstances() {
IrisData data = mock(IrisData.class);
IrisDepositGenerator first = generatorWithState(data, false, "minecraft:granite");
IrisDepositGenerator second = generatorWithState(data, false, "minecraft:granite");
assertEquals(first.stableClumpSalt(data), second.stableClumpSalt(data));
}
@Test
public void clumpSaltIncludesAuthoredConfigAndPalette() {
IrisData data = mock(IrisData.class);
IrisDepositGenerator granite = generatorWithState(data, false, "minecraft:granite");
IrisDepositGenerator andesite = generatorWithState(data, false, "minecraft:andesite");
IrisDepositGenerator alteredShape = generatorWithState(data, false, "minecraft:granite");
alteredShape.setShape(IrisDepositShape.VANILLA_SCATTERED);
assertEquals(3112546198474861350L, granite.stableClumpSalt(data));
assertNotEquals(granite.stableClumpSalt(data), andesite.stableClumpSalt(data));
assertNotEquals(granite.stableClumpSalt(data), alteredShape.stableClumpSalt(data));
}
@Test
public void onlyOreDepositPalettesReceiveBiomeTuning() {
IrisData data = mock(IrisData.class);
@@ -143,10 +165,15 @@ public class IrisDepositTuningTest {
}
private IrisDepositGenerator generatorWithState(IrisData data, boolean ore) {
return generatorWithState(data, ore, ore ? "minecraft:iron_ore" : "minecraft:stone");
}
private IrisDepositGenerator generatorWithState(IrisData data, boolean ore, String key) {
IrisBlockData block = mock(IrisBlockData.class);
PlatformBlockState state = mock(PlatformBlockState.class);
when(block.getBlockData(data)).thenReturn(state);
when(state.isOre()).thenReturn(ore);
when(state.key()).thenReturn(key);
IrisDepositGenerator generator = new IrisDepositGenerator();
generator.getPalette().add(block);
return generator;
@@ -0,0 +1,86 @@
package art.arcane.iris.engine.object;
import art.arcane.iris.core.loader.IrisData;
import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.util.project.noise.CNG;
import art.arcane.volmlib.util.math.RNG;
import org.junit.Test;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public final class IrisMaterialPaletteGeneratorCacheTest {
private static final long GENERATOR_SALT = -23_498_896L;
@Test
public void callerSeedsDoNotShareAFirstWinnerGenerator() {
GeneratorFixture fixture = new GeneratorFixture();
assertSame(fixture.firstGenerator, fixture.palette.getLayerGenerator(new RNG(11L), fixture.data));
assertSame(fixture.secondGenerator, fixture.palette.getLayerGenerator(new RNG(29L), fixture.data));
assertSame(fixture.firstGenerator, fixture.palette.getLayerGenerator(new RNG(11L), fixture.data));
}
@Test
public void reverseInitializationOrderKeepsSeedAssignments() {
GeneratorFixture fixture = new GeneratorFixture();
assertSame(fixture.secondGenerator, fixture.palette.getLayerGenerator(new RNG(29L), fixture.data));
assertSame(fixture.firstGenerator, fixture.palette.getLayerGenerator(new RNG(11L), fixture.data));
}
@Test
public void concurrentInitializationKeepsSeedAssignments() throws Exception {
GeneratorFixture fixture = new GeneratorFixture();
CountDownLatch start = new CountDownLatch(1);
ExecutorService executor = Executors.newFixedThreadPool(2);
try {
Future<CNG> first = executor.submit(() -> {
assertTrue(start.await(5L, TimeUnit.SECONDS));
return fixture.palette.getLayerGenerator(new RNG(11L), fixture.data);
});
Future<CNG> second = executor.submit(() -> {
assertTrue(start.await(5L, TimeUnit.SECONDS));
return fixture.palette.getLayerGenerator(new RNG(29L), fixture.data);
});
start.countDown();
assertSame(fixture.firstGenerator, first.get(5L, TimeUnit.SECONDS));
assertSame(fixture.secondGenerator, second.get(5L, TimeUnit.SECONDS));
} finally {
executor.shutdownNow();
}
}
private static final class GeneratorFixture {
private final IrisData data = mock(IrisData.class);
private final Engine engine = mock(Engine.class);
private final IrisGeneratorStyle style = mock(IrisGeneratorStyle.class);
private final CNG firstGenerator = mock(CNG.class);
private final CNG secondGenerator = mock(CNG.class);
private final IrisMaterialPalette palette = new IrisMaterialPalette().qclear().setStyle(style);
private GeneratorFixture() {
when(data.getEngine()).thenReturn(engine);
Map<Long, CNG> generators = Map.of(
11L + GENERATOR_SALT, firstGenerator,
29L + GENERATOR_SALT, secondGenerator
);
when(style.create(any(RNG.class), same(data), same(engine))).thenAnswer(invocation -> {
RNG rng = invocation.getArgument(0);
return generators.get(rng.getSeed());
});
}
}
}
@@ -43,7 +43,7 @@ public class IrisMathNoiseHotPathParityTest {
.setOpacity(0.91D)
.setComposite(new KList<IrisNoiseGenerator>().qadd(noiseGenerator));
assertEquals(0.451949817597527D, generator.getHeight(63.0D, -27.0D, 445566L), 0D);
assertEquals(0.5671980255964687D, generator.getHeight(63.0D, -27.0D, 445566L), 0D);
}
@Test
@@ -0,0 +1,93 @@
package art.arcane.iris.engine.object;
import art.arcane.iris.core.loader.IrisData;
import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.util.project.noise.CNG;
import art.arcane.volmlib.util.math.RNG;
import org.junit.Test;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public final class IrisNoiseGeneratorCacheTest {
private static final long GENERATOR_SALT = 33_955_677L;
private static final long CONFIGURED_SEED = 7L;
private static final int OCTAVES = 3;
@Test
public void callerSeedsDoNotShareAFirstWinnerGenerator() {
GeneratorFixture fixture = new GeneratorFixture();
assertSame(fixture.firstGenerator, fixture.generator.getGenerator(11L, fixture.data));
assertSame(fixture.secondGenerator, fixture.generator.getGenerator(29L, fixture.data));
assertSame(fixture.firstGenerator, fixture.generator.getGenerator(11L, fixture.data));
}
@Test
public void reverseInitializationOrderKeepsSeedAssignments() {
GeneratorFixture fixture = new GeneratorFixture();
assertSame(fixture.secondGenerator, fixture.generator.getGenerator(29L, fixture.data));
assertSame(fixture.firstGenerator, fixture.generator.getGenerator(11L, fixture.data));
}
@Test
public void concurrentInitializationKeepsSeedAssignments() throws Exception {
GeneratorFixture fixture = new GeneratorFixture();
CountDownLatch start = new CountDownLatch(1);
ExecutorService executor = Executors.newFixedThreadPool(2);
try {
Future<CNG> first = executor.submit(() -> {
assertTrue(start.await(5L, TimeUnit.SECONDS));
return fixture.generator.getGenerator(11L, fixture.data);
});
Future<CNG> second = executor.submit(() -> {
assertTrue(start.await(5L, TimeUnit.SECONDS));
return fixture.generator.getGenerator(29L, fixture.data);
});
start.countDown();
assertSame(fixture.firstGenerator, first.get(5L, TimeUnit.SECONDS));
assertSame(fixture.secondGenerator, second.get(5L, TimeUnit.SECONDS));
} finally {
executor.shutdownNow();
}
}
private static final class GeneratorFixture {
private final IrisData data = mock(IrisData.class);
private final Engine engine = mock(Engine.class);
private final IrisGeneratorStyle style = mock(IrisGeneratorStyle.class);
private final CNG firstGenerator = mock(CNG.class);
private final CNG secondGenerator = mock(CNG.class);
private final IrisNoiseGenerator generator = new IrisNoiseGenerator()
.setStyle(style)
.setSeed(CONFIGURED_SEED)
.setOctaves(OCTAVES);
private GeneratorFixture() {
when(data.getEngine()).thenReturn(engine);
when(firstGenerator.oct(OCTAVES)).thenReturn(firstGenerator);
when(secondGenerator.oct(OCTAVES)).thenReturn(secondGenerator);
Map<Long, CNG> generators = Map.of(
11L + GENERATOR_SALT - CONFIGURED_SEED, firstGenerator,
29L + GENERATOR_SALT - CONFIGURED_SEED, secondGenerator
);
when(style.createNoCache(any(RNG.class), same(data))).thenAnswer(invocation -> {
RNG rng = invocation.getArgument(0);
return generators.get(rng.getSeed());
});
}
}
}
@@ -1,317 +0,0 @@
package art.arcane.iris.engine.object;
import art.arcane.iris.core.loader.IrisData;
import art.arcane.iris.core.loader.ResourceLoader;
import art.arcane.volmlib.util.collection.KList;
import com.google.gson.Gson;
import org.junit.Test;
import java.util.Set;
import java.util.stream.Collectors;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class IrisRiverConfigurationTest {
@Test
public void defaultsKeepRiverGenerationDisabledAndContained() {
IrisDimension dimension = new IrisDimension();
assertNotNull(dimension.getRivers());
assertFalse(dimension.getRivers().isEnabled());
assertEquals(IrisRiverWaterMode.FIXED, dimension.getRivers().getWater().getMode());
assertEquals(63, dimension.getRivers().getWater().getFluidHeight());
assertEquals("water", dimension.getRivers().getWater().getFluidPalette().getPalette().getFirst().getBlock());
assertFalse(dimension.getRivers().getTopology().isRequireOcean());
assertEquals(512, dimension.getRivers().getTopology().getCellSize());
assertEquals(16, dimension.getRivers().getTopology().getMaxRouteReaches());
assertEquals(0, dimension.getRivers().getTopology().getMinimumSourcesPerTile());
assertEquals(64, dimension.getRivers().getTopology().getRoutingBasinCells());
assertEquals(8D, dimension.getRivers().getTopology().getRoutingPlateauHeight(), 0D);
assertEquals(0.05D, dimension.getRivers().getTopology().getSource().getChance(), 0D);
assertEquals(0.035D, dimension.getRivers().getTopology().getSource().getInfluence(), 0D);
assertEquals(10D, dimension.getRivers().getTerrain().getMaxChannelWidth(), 0D);
assertEquals(4D, dimension.getRivers().getTerrain().getMaxBankWidth(), 0D);
assertEquals(10D, dimension.getRivers().getTerrain().getMaxDepth(), 0D);
assertTrue(dimension.getRivers().getTerrain().getWorms().isEmpty());
assertEquals(IrisRiverCaveMode.SEALED, dimension.getRivers().getCaves().getMode());
assertEquals(IrisRiverCaveFallback.SEALED, dimension.getRivers().getCaves().getFallback());
assertEquals(IrisRiverExistingFluidPolicy.REJECT,
dimension.getRivers().getCaves().getExistingFluidPolicy());
assertNotNull(dimension.getRivers().getCaves().getDeepPools());
assertFalse(dimension.getRivers().getCaves().getDeepPools().isEnabled());
assertEquals(-224, dimension.getRivers().getCaves().getDeepPools().getMinimumFluidY());
assertEquals(-104, dimension.getRivers().getCaves().getDeepPools().getMaximumFluidY());
assertEquals("lava", dimension.getRivers().getCaves().getDeepPools()
.getFluidPalette().getPalette().getFirst().getBlock());
assertTrue(dimension.getRivers().getBiomes().getAllBiomeIds().isEmpty());
assertNull(new IrisRegion().getRiverOverride());
assertNull(new IrisBiome().getRiverOverride());
}
@Test
public void deserializesTypedDimensionRegionAndBiomeSettings() {
Gson gson = new Gson();
IrisDimension dimension = gson.fromJson("""
{
"rivers": {
"enabled": true,
"topology": {
"cellSize": 512,
"minimumSourcesPerTile": 2,
"routingBasinCells": 96,
"routingPlateauHeight": 12,
"requireOcean": false,
"source": {"chance": 0.27, "influence": 0.4}
},
"terrain": {
"channelRadiusBonus": 3,
"maxChannelWidth": 9,
"maxBankWidth": 2.5,
"maxDepth": 8,
"maxIncision": 36,
"worms": [
{
"seed": 73,
"weight": 2.5,
"wavelength": 1536,
"detailWavelength": 192,
"tortuosity": 0.65,
"detailTortuosity": 0.2,
"maxOffset": 420,
"segments": 56,
"widthMultiplier": 1.4,
"bankMultiplier": 1.2,
"depthMultiplier": 0.8,
"bodyWavelength": 704,
"bodyDetailWavelength": 18,
"bodyDetailInfluence": 0.82,
"widthVariation": 0.75,
"bankVariation": 0.65,
"depthVariation": 0.55,
"roofVariation": 0.45
}
],
"terminalMode": "SUPPRESS"
},
"water": {
"mode": "TERRACED",
"fluidHeight": -48,
"fluidPalette": {
"palette": [{"block": "minecraft:lava"}]
},
"poolLength": 80
},
"biomes": {
"channel": ["river/channel"],
"floodedCave": ["river/grotto"]
},
"caves": {
"mode": "FLOOD_CLOSED_COMPONENT",
"maxFloodVolume": 2048,
"existingFluidPolicy": "ALLOW_SAME",
"deepPools": {
"enabled": true,
"reach": {
"chance": 0.4,
"influence": 0.1
},
"minimumSpacing": 896,
"maximumPerReach": 2,
"minimumFluidY": -220,
"maximumFluidY": -112,
"searchRadius": 24,
"searchAttempts": 18,
"horizontalRadius": 28,
"verticalRadius": 11,
"dryHeadroom": 5,
"shapeVariation": 0.7,
"warpStrength": 9,
"maximumVolume": 65536,
"fluidPalette": {
"palette": [{"block": "minecraft:lava"}]
}
}
}
}
}
""", IrisDimension.class);
IrisRegion region = gson.fromJson("""
{
"riverOverride": {
"allowSources": false,
"routingPolicy": "AVOID",
"widthMultiplier": 0.75,
"bankBiomes": ["river/region-bank"]
}
}
""", IrisRegion.class);
IrisBiome biome = gson.fromJson("""
{
"riverOverride": {
"routingPolicy": "BLOCK",
"caveEntryMultiplier": 0.2,
"floodedCaveBiomes": []
}
}
""", IrisBiome.class);
assertTrue(dimension.getRivers().isEnabled());
assertEquals(512, dimension.getRivers().getTopology().getCellSize());
assertEquals(2, dimension.getRivers().getTopology().getMinimumSourcesPerTile());
assertEquals(96, dimension.getRivers().getTopology().getRoutingBasinCells());
assertEquals(12D, dimension.getRivers().getTopology().getRoutingPlateauHeight(), 0D);
assertFalse(dimension.getRivers().getTopology().isRequireOcean());
assertEquals(0.27D, dimension.getRivers().getTopology().getSource().getChance(), 0D);
assertEquals(9D, dimension.getRivers().getTerrain().getMaxChannelWidth(), 0D);
assertEquals(3D, dimension.getRivers().getTerrain().getChannelRadiusBonus(), 0D);
assertEquals(2.5D, dimension.getRivers().getTerrain().getMaxBankWidth(), 0D);
assertEquals(8D, dimension.getRivers().getTerrain().getMaxDepth(), 0D);
assertEquals(36, dimension.getRivers().getTerrain().getMaxIncision());
IrisRiverWorm worm = dimension.getRivers().getTerrain().getWorms().get(0);
assertEquals(73L, worm.getSeed());
assertEquals(2.5D, worm.getWeight(), 0D);
assertEquals(1536D, worm.getWavelength(), 0D);
assertEquals(192D, worm.getDetailWavelength(), 0D);
assertEquals(0.65D, worm.getTortuosity(), 0D);
assertEquals(0.2D, worm.getDetailTortuosity(), 0D);
assertEquals(420D, worm.getMaxOffset(), 0D);
assertEquals(56, worm.getSegments());
assertEquals(1.4D, worm.getWidthMultiplier(), 0D);
assertEquals(1.2D, worm.getBankMultiplier(), 0D);
assertEquals(0.8D, worm.getDepthMultiplier(), 0D);
assertEquals(704D, worm.getBodyWavelength(), 0D);
assertEquals(18D, worm.getBodyDetailWavelength(), 0D);
assertEquals(0.82D, worm.getBodyDetailInfluence(), 0D);
assertEquals(0.75D, worm.getWidthVariation(), 0D);
assertEquals(0.65D, worm.getBankVariation(), 0D);
assertEquals(0.55D, worm.getDepthVariation(), 0D);
assertEquals(0.45D, worm.getRoofVariation(), 0D);
assertEquals(IrisRiverTerminalMode.SUPPRESS,
dimension.getRivers().getTerrain().getTerminalMode());
assertEquals(IrisRiverWaterMode.TERRACED, dimension.getRivers().getWater().getMode());
assertEquals(-48, dimension.getRivers().getWater().getFluidHeight());
assertEquals("minecraft:lava",
dimension.getRivers().getWater().getFluidPalette().getPalette().getFirst().getBlock());
assertEquals(Set.of("river/channel", "river/grotto"),
Set.copyOf(dimension.getRivers().getBiomes().getAllBiomeIds()));
assertEquals(IrisRiverCaveMode.FLOOD_CLOSED_COMPONENT,
dimension.getRivers().getCaves().getMode());
assertEquals(IrisRiverExistingFluidPolicy.ALLOW_SAME,
dimension.getRivers().getCaves().getExistingFluidPolicy());
IrisRiverDeepPools deepPools = dimension.getRivers().getCaves().getDeepPools();
assertTrue(deepPools.isEnabled());
assertEquals(0.4D, deepPools.getReach().getChance(), 0D);
assertEquals(0.1D, deepPools.getReach().getInfluence(), 0D);
assertEquals(896, deepPools.getMinimumSpacing());
assertEquals(2, deepPools.getMaximumPerReach());
assertEquals(-220, deepPools.getMinimumFluidY());
assertEquals(-112, deepPools.getMaximumFluidY());
assertEquals(24, deepPools.getSearchRadius());
assertEquals(18, deepPools.getSearchAttempts());
assertEquals(28, deepPools.getHorizontalRadius());
assertEquals(11, deepPools.getVerticalRadius());
assertEquals(5, deepPools.getDryHeadroom());
assertEquals(0.7D, deepPools.getShapeVariation(), 0D);
assertEquals(9D, deepPools.getWarpStrength(), 0D);
assertEquals(65536, deepPools.getMaximumVolume());
assertEquals("minecraft:lava", deepPools.getFluidPalette().getPalette().getFirst().getBlock());
assertEquals(Boolean.FALSE, region.getRiverOverride().getAllowSources());
assertEquals(IrisRiverRoutingPolicy.AVOID, region.getRiverOverride().getRoutingPolicy());
assertEquals(Double.valueOf(0.75D), region.getRiverOverride().getWidthMultiplier());
assertNull(region.getRiverOverride().getChannelBiomes());
assertEquals(new KList<>("river/region-bank"), region.getRiverOverride().getBankBiomes());
assertEquals(IrisRiverRoutingPolicy.BLOCK, biome.getRiverOverride().getRoutingPolicy());
assertNull(biome.getRiverOverride().getChannelBiomes());
assertNotNull(biome.getRiverOverride().getFloodedCaveBiomes());
assertTrue(biome.getRiverOverride().getFloodedCaveBiomes().isEmpty());
}
@Test
@SuppressWarnings("unchecked")
public void resolvesRiverPoolsOnlyWhenRiversAreEnabled() {
IrisRiverBiomes dimensionBiomes = new IrisRiverBiomes()
.setChannel(new KList<>("dimension-channel"));
IrisRiverOverride regionOverride = new IrisRiverOverride()
.setBankBiomes(new KList<>("region-bank"));
IrisRiverOverride biomeOverride = new IrisRiverOverride()
.setMouthBiomes(new KList<>("biome-mouth"))
.setFloodedCaveBiomes(new KList<>("biome-grotto"));
IrisDimension dimension = new IrisDimension()
.setRegions(new KList<>("region"))
.setRivers(new IrisRiverNetwork()
.setEnabled(true)
.setTerrain(new IrisRiverTerrain()
.setWorms(new KList<>(new IrisRiverWorm())))
.setBiomes(dimensionBiomes));
IrisRegion region = new IrisRegion()
.setLandBiomes(new KList<>("natural"))
.setRiverOverride(regionOverride);
IrisBiome natural = biome("natural").setRiverOverride(biomeOverride);
IrisData data = mock(IrisData.class);
ResourceLoader<IrisRegion> regionLoader = mock(ResourceLoader.class);
ResourceLoader<IrisBiome> biomeLoader = mock(ResourceLoader.class);
when(data.getRegionLoader()).thenReturn(regionLoader);
when(data.getBiomeLoader()).thenReturn(biomeLoader);
when(regionLoader.load("region")).thenReturn(region);
when(biomeLoader.load("natural")).thenReturn(natural);
when(biomeLoader.load("dimension-channel")).thenReturn(biome("dimension-channel"));
when(biomeLoader.load("region-bank")).thenReturn(biome("region-bank"));
when(biomeLoader.load("biome-mouth")).thenReturn(biome("biome-mouth"));
when(biomeLoader.load("biome-grotto")).thenReturn(biome("biome-grotto"));
Set<String> enabledKeys = keys(dimension.getReachableBiomes(() -> data));
dimension.getRivers().setEnabled(false);
Set<String> disabledKeys = keys(dimension.getReachableBiomes(() -> data));
assertEquals(Set.of("natural", "dimension-channel", "region-bank", "biome-mouth", "biome-grotto"),
enabledKeys);
assertEquals(Set.of("natural"), disabledKeys);
}
@Test
@SuppressWarnings("unchecked")
public void separatesNaturalAndRiverOnlyRegionBiomes() {
IrisRegion region = new IrisRegion()
.setLandBiomes(new KList<>("natural-parent"))
.setRiverOverride(new IrisRiverOverride()
.setChannelBiomes(new KList<>("river-parent")));
IrisBiome naturalParent = biome("natural-parent").setChildren(new KList<>("natural-child"));
IrisBiome naturalChild = biome("natural-child");
IrisBiome riverParent = biome("river-parent").setChildren(new KList<>("river-child"));
IrisBiome riverChild = biome("river-child");
IrisData data = mock(IrisData.class);
ResourceLoader<IrisBiome> biomeLoader = mock(ResourceLoader.class);
when(data.getBiomeLoader()).thenReturn(biomeLoader);
when(biomeLoader.load("natural-parent")).thenReturn(naturalParent);
when(biomeLoader.load("natural-child")).thenReturn(naturalChild);
when(biomeLoader.load("river-parent")).thenReturn(riverParent);
when(biomeLoader.load("river-child")).thenReturn(riverChild);
assertEquals(Set.of("natural-parent"), Set.copyOf(region.getNaturalBiomeIds()));
assertEquals(Set.of("natural-parent", "river-parent"), Set.copyOf(region.getAllBiomeIds()));
assertEquals(Set.of("natural-parent", "natural-child"),
keys(region.getNaturalBiomes(() -> data)));
assertEquals(Set.of("natural-parent", "natural-child", "river-parent", "river-child"),
keys(region.getAllBiomes(() -> data)));
}
private static IrisBiome biome(String loadKey) {
IrisBiome biome = new IrisBiome();
biome.setLoadKey(loadKey);
return biome;
}
private static Set<String> keys(KList<IrisBiome> biomes) {
return biomes.stream().map(IrisBiome::getLoadKey).collect(Collectors.toSet());
}
}
File diff suppressed because it is too large. Load diff
@@ -1,306 +0,0 @@
package art.arcane.iris.engine.river;
import org.junit.Test;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
public class RiverTileCacheTest {
@Test
public void concurrentRequestsBuildOneTileOnce() throws Exception {
int requestCount = 16;
AtomicInteger builds = new AtomicInteger();
CountDownLatch ready = new CountDownLatch(requestCount);
CountDownLatch start = new CountDownLatch(1);
CountDownLatch builderEntered = new CountDownLatch(1);
CountDownLatch releaseBuilder = new CountDownLatch(1);
RiverTileCache cache = new RiverTileCache(8, (tileX, tileZ) -> {
builds.incrementAndGet();
builderEntered.countDown();
assertTrue(releaseBuilder.await(5, TimeUnit.SECONDS));
return emptyTile(tileX, tileZ);
});
ExecutorService executor = Executors.newFixedThreadPool(requestCount);
try {
@SuppressWarnings("unchecked")
Future<RiverTile>[] futures = new Future[requestCount];
for (int request = 0; request < requestCount; request++) {
futures[request] = executor.submit(() -> {
ready.countDown();
assertTrue(start.await(5, TimeUnit.SECONDS));
return cache.get(-7, -11);
});
}
assertTrue(ready.await(5, TimeUnit.SECONDS));
start.countDown();
assertTrue(builderEntered.await(5, TimeUnit.SECONDS));
releaseBuilder.countDown();
RiverTile first = futures[0].get(5, TimeUnit.SECONDS);
for (Future<RiverTile> future : futures) {
assertSame(first, future.get(5, TimeUnit.SECONDS));
}
assertEquals(-7, first.tileX());
assertEquals(-11, first.tileZ());
assertEquals(1, builds.get());
assertEquals(1, cache.completedSize());
} finally {
releaseBuilder.countDown();
executor.shutdownNow();
cache.close();
}
}
@Test
public void differentKeysBuildConcurrentlyOutsideCacheLock() throws Exception {
CountDownLatch buildersEntered = new CountDownLatch(2);
CountDownLatch releaseBuilders = new CountDownLatch(1);
RiverTileCache cache = new RiverTileCache(4, (tileX, tileZ) -> {
buildersEntered.countDown();
assertTrue(releaseBuilders.await(5, TimeUnit.SECONDS));
return emptyTile(tileX, tileZ);
});
ExecutorService executor = Executors.newFixedThreadPool(2);
try {
Future<RiverTile> first = executor.submit(() -> cache.get(1, 1));
Future<RiverTile> second = executor.submit(() -> cache.get(2, 2));
assertTrue(buildersEntered.await(5, TimeUnit.SECONDS));
releaseBuilders.countDown();
assertEquals(1, first.get(5, TimeUnit.SECONDS).tileX());
assertEquals(2, second.get(5, TimeUnit.SECONDS).tileX());
} finally {
releaseBuilders.countDown();
executor.shutdownNow();
cache.close();
}
}
@Test
public void completedEntriesUseBoundedAccessOrderEviction() {
AtomicInteger builds = new AtomicInteger();
RiverTileCache cache = new RiverTileCache(2, (tileX, tileZ) -> {
builds.incrementAndGet();
return emptyTile(tileX, tileZ);
});
try {
RiverTile zero = cache.get(0, 0);
RiverTile one = cache.get(1, 0);
assertSame(zero, cache.get(0, 0));
cache.get(2, 0);
assertEquals(2, cache.completedSize());
assertSame(zero, cache.get(0, 0));
RiverTile rebuiltOne = cache.get(1, 0);
assertFalse(one == rebuiltOne);
assertEquals(4, builds.get());
assertEquals(2, cache.completedSize());
} finally {
cache.close();
}
}
@Test
public void failedBuildsAreRemovedAndRetryable() {
AtomicInteger attempts = new AtomicInteger();
RiverTileCache cache = new RiverTileCache(2, (tileX, tileZ) -> {
if (attempts.incrementAndGet() == 1) {
throw new IllegalArgumentException("expected failure");
}
return emptyTile(tileX, tileZ);
});
try {
IllegalArgumentException failure = assertThrows(
IllegalArgumentException.class,
() -> cache.get(-3, 4)
);
assertEquals("expected failure", failure.getMessage());
assertEquals(0, cache.completedSize());
RiverTile retried = cache.get(-3, 4);
assertEquals(-3, retried.tileX());
assertEquals(2, attempts.get());
assertEquals(1, cache.completedSize());
} finally {
cache.close();
}
}
@Test
public void mismatchedBuilderResultIsRemovedAndRetryable() {
AtomicInteger attempts = new AtomicInteger();
RiverTileCache cache = new RiverTileCache(2, (tileX, tileZ) -> attempts.incrementAndGet() == 1
? emptyTile(tileX + 1, tileZ)
: emptyTile(tileX, tileZ));
try {
assertThrows(IllegalStateException.class, () -> cache.get(5, 6));
RiverTile retried = cache.get(5, 6);
assertEquals(5, retried.tileX());
assertEquals(2, attempts.get());
} finally {
cache.close();
}
}
@Test
public void clearAndCloseReleaseEntriesAndEnforceLifecycle() {
AtomicInteger builds = new AtomicInteger();
RiverTileCache cache = new RiverTileCache(4, (tileX, tileZ) -> {
builds.incrementAndGet();
return emptyTile(tileX, tileZ);
});
cache.get(0, 0);
cache.get(1, 0);
assertEquals(2, cache.completedSize());
cache.clear();
assertEquals(0, cache.completedSize());
cache.get(0, 0);
assertEquals(3, builds.get());
cache.close();
cache.close();
assertTrue(cache.isClosed());
assertEquals(0, cache.completedSize());
assertThrows(IllegalStateException.class, () -> cache.get(0, 0));
assertThrows(IllegalStateException.class, cache::clear);
}
@Test
public void closeInvalidatesInflightBuildWithoutRetainingItsResult() throws Exception {
CountDownLatch builderEntered = new CountDownLatch(1);
CountDownLatch releaseBuilder = new CountDownLatch(1);
RiverTileCache cache = new RiverTileCache(2, (tileX, tileZ) -> {
builderEntered.countDown();
assertTrue(releaseBuilder.await(5, TimeUnit.SECONDS));
return emptyTile(tileX, tileZ);
});
ExecutorService executor = Executors.newSingleThreadExecutor();
try {
Future<RiverTile> result = executor.submit(() -> cache.get(9, -9));
assertTrue(builderEntered.await(5, TimeUnit.SECONDS));
cache.close();
releaseBuilder.countDown();
ExecutionException failure = assertThrows(
ExecutionException.class,
() -> result.get(5, TimeUnit.SECONDS)
);
assertTrue(failure.getCause() instanceof IllegalStateException);
assertEquals(0, cache.completedSize());
} finally {
releaseBuilder.countDown();
executor.shutdownNow();
cache.close();
}
}
@Test
public void clearInvalidatesInflightBuildAndFreshRequestPublishesNewEntry() throws Exception {
AtomicInteger attempts = new AtomicInteger();
CountDownLatch firstBuilderEntered = new CountDownLatch(1);
CountDownLatch releaseFirstBuilder = new CountDownLatch(1);
RiverTileCache cache = new RiverTileCache(2, (tileX, tileZ) -> {
if (attempts.incrementAndGet() == 1) {
firstBuilderEntered.countDown();
assertTrue(releaseFirstBuilder.await(5, TimeUnit.SECONDS));
}
return emptyTile(tileX, tileZ);
});
ExecutorService executor = Executors.newSingleThreadExecutor();
try {
Future<RiverTile> invalidated = executor.submit(() -> cache.get(4, -2));
assertTrue(firstBuilderEntered.await(5, TimeUnit.SECONDS));
cache.clear();
RiverTile replacement = cache.get(4, -2);
releaseFirstBuilder.countDown();
ExecutionException failure = assertThrows(
ExecutionException.class,
() -> invalidated.get(5, TimeUnit.SECONDS)
);
assertTrue(failure.getCause() instanceof IllegalStateException);
assertEquals(4, replacement.tileX());
assertEquals(-2, replacement.tileZ());
assertEquals(2, attempts.get());
assertEquals(1, cache.completedSize());
assertSame(replacement, cache.get(4, -2));
} finally {
releaseFirstBuilder.countDown();
executor.shutdownNow();
cache.close();
}
}
@Test
public void tileProvidesConstantTimeReachIdentityLookup() {
RiverReach reach = reach();
RiverTile tile = new RiverTile(0, 0, 0, 0, 64, 64, List.of(reach));
assertSame(reach, tile.reach(reach.id()));
assertNull(tile.reach(RiverEdgeId.of(new RiverNodeId(2L, 0L), new RiverNodeId(3L, 0L))));
assertThrows(
IllegalArgumentException.class,
() -> new RiverTile(0, 0, 0, 0, 64, 64, List.of(reach, reach))
);
}
private static RiverTile emptyTile(int tileX, int tileZ) {
int minimumX = tileX * 64;
int minimumZ = tileZ * 64;
return new RiverTile(tileX, tileZ, minimumX, minimumZ, minimumX + 64, minimumZ + 64, List.of());
}
private static RiverReach reach() {
RiverNode from = new RiverNode(
new RiverNodeId(0L, 0L),
8.0,
8.0,
20.0,
20.0,
20.0,
20.0,
false,
true
);
RiverNode to = new RiverNode(
new RiverNodeId(1L, 0L),
56.0,
8.0,
10.0,
10.0,
10.0,
10.0,
false,
true
);
return new RiverReach(
RiverEdgeId.of(from.id(), to.id()),
from,
to,
RiverRouteState.WET,
1,
1,
8.0,
4.0,
3.0,
RiverBodyProfile.constant(8.0, 4.0, 3.0),
false,
false,
new RiverPolyline(new double[]{8.0, 56.0}, new double[]{8.0, 8.0})
);
}
}
@@ -1,757 +0,0 @@
package art.arcane.iris.engine.river.cave;
import org.junit.Test;
import java.util.ArrayDeque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
public class RiverCaveContainmentPlannerTest {
private static final List<CavePosition> DIRECTIONS = List.of(
new CavePosition(1, 0, 0),
new CavePosition(-1, 0, 0),
new CavePosition(0, 1, 0),
new CavePosition(0, -1, 0),
new CavePosition(0, 0, 1),
new CavePosition(0, 0, -1)
);
private final RiverCaveContainmentPlanner planner = new RiverCaveContainmentPlanner();
@Test
public void closedComponentProducesConnectedThroatPoolAndGuards() {
TestVoxelView view = new TestVoxelView();
view.set(CaveVoxel.CAVE_AIR, position(0, 7, 0), position(1, 7, 0), position(1, 6, 0));
RiverCaveSource source = source(14L, 10, RiverCaveMode.CLOSED_COMPONENT, position(0, 7, 0));
RiverCavePlan plan = planner.plan(view, source, settings());
assertTrue(plan.accepted());
assertEquals(RiverCaveAction.DRY_AIR, plan.actions().get(position(0, 12, 0)));
assertEquals(RiverCaveAction.DRY_AIR, plan.actions().get(position(0, 11, 0)));
assertEquals(RiverCaveAction.WET_SOURCE, plan.actions().get(position(0, 10, 0)));
assertEquals(RiverCaveAction.WET_SOURCE, plan.actions().get(position(1, 6, 0)));
assertTrue(plan.actions().containsValue(RiverCaveAction.SEAL_GUARD));
assertMutationConnected(plan, source.entry(), position(1, 6, 0));
assertEquals(plan.actions().keySet(), plan.baselinePreconditions().keySet());
assertEquals(CaveVoxel.CAVE_AIR, plan.baselinePreconditions().get(position(1, 6, 0)).voxel());
assertEquals(CaveVoxel.SOLID, plan.baselinePreconditions().get(position(0, 12, 0)).voxel());
}
@Test
public void compatibleFluidFollowsExplicitPolicy() {
TestVoxelView view = new TestVoxelView();
CavePosition target = position(0, 7, 0);
view.set(CaveVoxel.COMPATIBLE_FLUID, target);
RiverCaveSource source = source(1L, 10, RiverCaveMode.CLOSED_COMPONENT, target);
RiverCavePlan allowed = planner.plan(view, source, settings(RiverCaveFluidPolicy.ALLOW_COMPATIBLE));
RiverCavePlan rejected = planner.plan(view, source, settings(RiverCaveFluidPolicy.REJECT_EXISTING));
assertTrue(allowed.accepted());
assertEquals(RiverCaveAction.WET_SOURCE, allowed.actions().get(target));
assertEquals(RiverCaveRejection.EXISTING_FLUID, rejected.rejection());
assertTrue(rejected.actions().isEmpty());
}
@Test
public void replacementPolicyIsDistinctAndStillRejectsLava() {
CavePosition target = position(0, 7, 0);
TestVoxelView compatibleView = new TestVoxelView();
compatibleView.set(CaveVoxel.COMPATIBLE_FLUID, target);
TestVoxelView incompatibleView = new TestVoxelView();
incompatibleView.set(CaveVoxel.INCOMPATIBLE_FLUID, target);
TestVoxelView lavaView = new TestVoxelView();
lavaView.set(CaveVoxel.LAVA, target);
RiverCaveSource source = source(101L, 10, RiverCaveMode.CLOSED_COMPONENT, target);
RiverCavePlan rejectedCompatible = planner.plan(
compatibleView,
source,
settings(RiverCaveFluidPolicy.REJECT_EXISTING)
);
RiverCavePlan allowedCompatible = planner.plan(
compatibleView,
source,
settings(RiverCaveFluidPolicy.ALLOW_COMPATIBLE)
);
RiverCavePlan rejectedIncompatible = planner.plan(
incompatibleView,
source,
settings(RiverCaveFluidPolicy.ALLOW_COMPATIBLE)
);
RiverCavePlan replacedIncompatible = planner.plan(
incompatibleView,
source,
settings(RiverCaveFluidPolicy.REPLACE_CONTAINED)
);
RiverCavePlan rejectedLava = planner.plan(
lavaView,
source,
settings(RiverCaveFluidPolicy.REPLACE_CONTAINED)
);
assertEquals(RiverCaveRejection.EXISTING_FLUID, rejectedCompatible.rejection());
assertTrue(allowedCompatible.accepted());
assertEquals(RiverCaveRejection.INCOMPATIBLE_FLUID, rejectedIncompatible.rejection());
assertTrue(replacedIncompatible.accepted());
assertEquals(RiverCaveAction.WET_SOURCE, replacedIncompatible.actions().get(target));
assertEquals(RiverCaveRejection.LAVA_CONTACT, rejectedLava.rejection());
}
@Test
public void throatRadiusExpandsTheConnectedBoreFootprint() {
TestVoxelView thinView = new TestVoxelView();
TestVoxelView thickView = new TestVoxelView();
CavePosition target = position(0, 7, 0);
thinView.set(CaveVoxel.CAVE_AIR, target);
thickView.set(CaveVoxel.CAVE_AIR, target);
RiverCaveSource source = source(102L, 10, RiverCaveMode.CLOSED_COMPONENT, target);
RiverCavePlannerSettings thinSettings = detailedSettings(
1,
0,
RiverCaveGrottoShape.ELLIPSOID,
RiverCaveFluidPolicy.ALLOW_COMPATIBLE
);
RiverCavePlannerSettings thickSettings = detailedSettings(
3,
0,
RiverCaveGrottoShape.ELLIPSOID,
RiverCaveFluidPolicy.ALLOW_COMPATIBLE
);
RiverCavePlan thin = planner.plan(thinView, source, thinSettings);
RiverCavePlan thick = planner.plan(thickView, source, thickSettings);
assertTrue(thin.accepted());
assertTrue(thick.accepted());
assertFalse(thin.actions().containsKey(position(2, 10, 0)));
assertEquals(RiverCaveAction.WET_SOURCE, thick.actions().get(position(2, 10, 0)));
assertTrue(mutationCount(thick) > mutationCount(thin));
}
@Test
public void generatedGrottoRetainsConfiguredDryHeadroom() {
TestVoxelView view = new TestVoxelView();
RiverCaveSource source = source(103L, 10, RiverCaveMode.GENERATED_GROTTO, position(0, 8, 0));
RiverCavePlannerSettings acceptedSettings = detailedSettings(
1,
2,
RiverCaveGrottoShape.ELLIPSOID,
RiverCaveFluidPolicy.ALLOW_COMPATIBLE
);
RiverCavePlannerSettings rejectedSettings = detailedSettings(
1,
5,
RiverCaveGrottoShape.ELLIPSOID,
RiverCaveFluidPolicy.ALLOW_COMPATIBLE
);
RiverCavePlan accepted = planner.plan(view, source, acceptedSettings);
RiverCavePlan rejected = planner.plan(view, source, rejectedSettings);
assertTrue(accepted.accepted());
assertEquals(RiverCaveAction.FALLING_FLUID, accepted.actions().get(position(0, 11, 0)));
assertEquals(RiverCaveAction.FALLING_FLUID, accepted.actions().get(position(0, 12, 0)));
assertEquals(RiverCaveRejection.DRY_HEADROOM_LIMIT, rejected.rejection());
}
@Test
public void configuredGrottoPredicateChangesFootprintDeterministically() {
TestVoxelView view = new TestVoxelView();
RiverCaveSource source = source(104L, 10, RiverCaveMode.GENERATED_GROTTO, position(0, 8, 0));
RiverCavePlannerSettings ellipsoid = detailedSettings(
1,
0,
RiverCaveGrottoShape.ELLIPSOID,
RiverCaveFluidPolicy.ALLOW_COMPATIBLE
);
RiverCaveGrottoShape compactShape = (candidate, settings, dx, dy, dz) -> {
double horizontal = settings.grottoHorizontalRadius();
double vertical = settings.grottoVerticalRadius();
double normalized = (dx * dx / (horizontal * horizontal))
+ (dy * dy / (vertical * vertical))
+ (dz * dz / (horizontal * horizontal));
return normalized <= 0.5D;
};
RiverCavePlannerSettings compact = detailedSettings(
1,
0,
compactShape,
RiverCaveFluidPolicy.ALLOW_COMPATIBLE
);
RiverCavePlan full = planner.plan(view, source, ellipsoid);
RiverCavePlan firstCompact = planner.plan(view, source, compact);
RiverCavePlan secondCompact = planner.plan(view, source, compact);
assertTrue(full.accepted());
assertTrue(firstCompact.accepted());
assertEquals(firstCompact, secondCompact);
assertTrue(mutationCount(full) > mutationCount(firstCompact));
}
@Test
public void radiusLimitRejectsWholeComponentWithoutClipping() {
TestVoxelView view = new TestVoxelView();
for (int x = 0; x <= 3; x++) {
view.set(CaveVoxel.CAVE_AIR, position(x, 7, 0));
}
RiverCavePlannerSettings settings = new RiverCavePlannerSettings(
2,
20,
64,
32,
1,
1,
RiverCaveFluidPolicy.ALLOW_COMPATIBLE
);
RiverCavePlan plan = planner.plan(
view,
source(2L, 10, RiverCaveMode.CLOSED_COMPONENT, position(0, 7, 0)),
settings
);
assertRejectedWithoutPublication(plan, RiverCaveRejection.RADIUS_LIMIT);
}
@Test
public void depthLimitRejectsWholeComponentWithoutClipping() {
TestVoxelView view = new TestVoxelView();
view.set(CaveVoxel.CAVE_AIR, position(0, 8, 0), position(0, 7, 0));
RiverCavePlannerSettings settings = new RiverCavePlannerSettings(
8,
4,
64,
32,
1,
1,
RiverCaveFluidPolicy.ALLOW_COMPATIBLE
);
RiverCavePlan plan = planner.plan(
view,
source(3L, 10, RiverCaveMode.CLOSED_COMPONENT, position(0, 8, 0)),
settings
);
assertRejectedWithoutPublication(plan, RiverCaveRejection.DEPTH_LIMIT);
}
@Test
public void volumeLimitRejectsWholeComponentWithoutClipping() {
TestVoxelView view = new TestVoxelView();
view.set(CaveVoxel.CAVE_AIR, position(0, 7, 0), position(1, 7, 0), position(2, 7, 0));
RiverCavePlannerSettings settings = new RiverCavePlannerSettings(
8,
20,
2,
32,
1,
1,
RiverCaveFluidPolicy.ALLOW_COMPATIBLE
);
RiverCavePlan plan = planner.plan(
view,
source(4L, 10, RiverCaveMode.CLOSED_COMPONENT, position(0, 7, 0)),
settings
);
assertRejectedWithoutPublication(plan, RiverCaveRejection.VOLUME_LIMIT);
}
@Test
public void worldBoundaryRejectsWholeComponent() {
TestVoxelView view = new TestVoxelView(-16, 0, 0, 32, -16, 16);
view.set(CaveVoxel.CAVE_AIR, position(0, 7, 0));
RiverCavePlan plan = planner.plan(
view,
source(5L, 10, RiverCaveMode.CLOSED_COMPONENT, position(0, 7, 0)),
settings()
);
assertRejectedWithoutPublication(plan, RiverCaveRejection.WORLD_BOUNDARY);
}
@Test
public void openSurfaceRejectsWholeComponent() {
TestVoxelView view = new TestVoxelView();
CavePosition target = position(0, 7, 0);
view.set(CaveVoxel.CAVE_AIR, target);
view.openToSurface(target);
RiverCavePlan plan = planner.plan(
view,
source(6L, 10, RiverCaveMode.CLOSED_COMPONENT, target),
settings()
);
assertRejectedWithoutPublication(plan, RiverCaveRejection.OPEN_SURFACE);
}
@Test
public void lavaAndIncompatibleFluidContactsRejectWholeComponent() {
TestVoxelView lavaView = new TestVoxelView();
lavaView.set(CaveVoxel.CAVE_AIR, position(0, 7, 0));
lavaView.set(CaveVoxel.LAVA, position(1, 7, 0));
TestVoxelView fluidView = new TestVoxelView();
fluidView.set(CaveVoxel.CAVE_AIR, position(0, 7, 0));
fluidView.set(CaveVoxel.INCOMPATIBLE_FLUID, position(1, 7, 0));
RiverCaveSource source = source(7L, 10, RiverCaveMode.CLOSED_COMPONENT, position(0, 7, 0));
RiverCavePlan lavaPlan = planner.plan(lavaView, source, settings());
RiverCavePlan fluidPlan = planner.plan(fluidView, source, settings());
assertRejectedWithoutPublication(lavaPlan, RiverCaveRejection.LAVA_CONTACT);
assertRejectedWithoutPublication(fluidPlan, RiverCaveRejection.INCOMPATIBLE_FLUID);
}
@Test
public void fallingHazardAbovePoolRejectsWholeComponent() {
TestVoxelView view = new TestVoxelView();
view.set(CaveVoxel.CAVE_AIR, position(0, 7, 0));
view.set(CaveVoxel.LAVA, position(1, 11, 0));
RiverCavePlan plan = planner.plan(
view,
source(15L, 10, RiverCaveMode.CLOSED_COMPONENT, position(0, 7, 0)),
settings()
);
assertRejectedWithoutPublication(plan, RiverCaveRejection.LAVA_CONTACT);
}
@Test
public void generatedGrottoHasBoundedWetDryAndGuardActions() {
TestVoxelView view = new TestVoxelView();
RiverCaveSource source = source(8L, 10, RiverCaveMode.GENERATED_GROTTO, position(0, 8, 0));
RiverCavePlan plan = planner.plan(view, source, settings());
assertTrue(plan.accepted());
assertEquals(RiverCaveAction.FALLING_FLUID, plan.actions().get(position(0, 12, 0)));
assertEquals(RiverCaveAction.WET_SOURCE, plan.actions().get(position(0, 8, 0)));
assertTrue(plan.actions().containsValue(RiverCaveAction.SEAL_GUARD));
assertEquals(plan.actions().keySet(), plan.baselinePreconditions().keySet());
assertFalse(plan.actions().containsKey(position(0, 13, 0)));
assertMutationConnected(plan, source.entry(), source.target());
}
@Test
public void generatedGrottoAcceptsItsCompleteSurfaceMouth() {
TestVoxelView view = new TestVoxelView();
CavePosition[] wetMouth = {
position(-1, 12, 0),
position(1, 12, 0),
position(0, 12, -1),
position(0, 12, 1)
};
CavePosition[] dryMouth = {
position(-1, 13, 0),
position(1, 13, 0),
position(0, 13, -1),
position(0, 13, 1)
};
view.set(CaveVoxel.COMPATIBLE_FLUID, wetMouth);
view.set(CaveVoxel.CAVE_AIR, dryMouth);
for (CavePosition position : wetMouth) {
view.openToSurface(position);
}
for (CavePosition position : dryMouth) {
view.openToSurface(position);
}
RiverCaveSource source = source(108L, 10, RiverCaveMode.GENERATED_GROTTO, position(0, 8, 0));
RiverCavePlan plan = planner.plan(view, source, detailedSettings(
2,
2,
RiverCaveGrottoShape.ELLIPSOID,
RiverCaveFluidPolicy.ALLOW_COMPATIBLE
));
assertTrue(plan.rejection().toString(), plan.accepted());
for (CavePosition position : wetMouth) {
assertEquals(RiverCaveAction.FALLING_FLUID, plan.actions().get(position));
}
}
@Test
public void generatedGrottoRejectsAnOpenShell() {
TestVoxelView view = new TestVoxelView();
view.set(CaveVoxel.CAVE_AIR, position(3, 8, 0));
RiverCavePlan plan = planner.plan(
view,
source(9L, 10, RiverCaveMode.GENERATED_GROTTO, position(0, 8, 0)),
settings()
);
assertRejectedWithoutPublication(plan, RiverCaveRejection.GROTTO_SHELL_OPEN);
}
@Test
public void deepPoolOpensOnlyAboveItsContainedFluidHead() {
TestVoxelView view = new TestVoxelView();
view.set(CaveVoxel.CAVE_AIR, position(0, 11, 0), position(0, 13, 0));
RiverCaveSource source = new RiverCaveSource(
109L,
position(0, 10, 0),
position(0, 8, 0),
10,
RiverCaveMode.DEEP_POOL
);
RiverCavePlan plan = planner.plan(view, source, detailedSettings(
1,
2,
RiverCaveGrottoShape.ELLIPSOID,
RiverCaveFluidPolicy.REJECT_EXISTING
));
assertTrue(plan.rejection().toString(), plan.accepted());
assertEquals(RiverCaveAction.WET_SOURCE, plan.actions().get(position(0, 10, 0)));
assertEquals(RiverCaveAction.DRY_AIR, plan.actions().get(position(0, 11, 0)));
assertFalse(plan.actions().containsKey(position(0, 13, 0)));
}
@Test
public void deepPoolRejectsCaveLeakAtOrBelowItsFluidHead() {
TestVoxelView view = new TestVoxelView();
view.set(CaveVoxel.CAVE_AIR, position(5, 8, 0));
RiverCaveSource source = new RiverCaveSource(
110L,
position(0, 10, 0),
position(0, 8, 0),
10,
RiverCaveMode.DEEP_POOL
);
RiverCavePlan plan = planner.plan(view, source, detailedSettings(
1,
2,
RiverCaveGrottoShape.ELLIPSOID,
RiverCaveFluidPolicy.REJECT_EXISTING
));
assertRejectedWithoutPublication(plan, RiverCaveRejection.GROTTO_SHELL_OPEN);
}
@Test
public void combinedModeUsesClosedCaveOrGeneratedGrottoFromBaseline() {
TestVoxelView caveView = new TestVoxelView();
CavePosition target = position(0, 8, 0);
caveView.set(CaveVoxel.CAVE_AIR, target);
TestVoxelView solidView = new TestVoxelView();
RiverCaveSource source = source(10L, 10, RiverCaveMode.GROTTO_OR_CLOSED_COMPONENT, target);
RiverCavePlan cavePlan = planner.plan(caveView, source, settings());
RiverCavePlan grottoPlan = planner.plan(solidView, source, settings());
assertTrue(cavePlan.accepted());
assertTrue(grottoPlan.accepted());
assertEquals(CaveVoxel.CAVE_AIR, cavePlan.baselinePreconditions().get(target).voxel());
assertEquals(CaveVoxel.SOLID, grottoPlan.baselinePreconditions().get(target).voxel());
}
@Test
public void waterfallKeepsColumnDistinctFromSourcePoolAndDryAir() {
TestVoxelView view = new TestVoxelView();
RiverCaveSource source = source(11L, 10, RiverCaveMode.WATERFALL_POOL, position(0, 8, 0));
RiverCavePlan plan = planner.plan(view, source, settings());
assertTrue(plan.accepted());
assertEquals(RiverCaveAction.FALLING_FLUID, plan.actions().get(position(0, 12, 0)));
assertEquals(RiverCaveAction.FALLING_FLUID, plan.actions().get(position(0, 11, 0)));
assertEquals(RiverCaveAction.WET_SOURCE, plan.actions().get(position(0, 10, 0)));
assertFalse(plan.actions().containsValue(RiverCaveAction.DRY_AIR));
}
@Test
public void waterfallRejectsAnUnsealedFallingColumn() {
TestVoxelView view = new TestVoxelView();
view.set(CaveVoxel.CAVE_AIR, position(0, 8, 0), position(1, 11, 0));
RiverCaveSource source = source(13L, 10, RiverCaveMode.WATERFALL_POOL, position(0, 8, 0));
RiverCavePlan plan = planner.plan(view, source, settings());
assertRejectedWithoutPublication(plan, RiverCaveRejection.WATERFALL_SHAFT_OPEN);
}
@Test
public void overlapArbitrationIsOrderIndependentAndNamesWinner() {
TestVoxelView view = new TestVoxelView();
view.set(CaveVoxel.CAVE_AIR, position(0, 7, 0), position(1, 7, 0), position(2, 7, 0));
RiverCaveSource lowerHead = new RiverCaveSource(
1L,
position(2, 12, 0),
position(2, 7, 0),
10,
RiverCaveMode.CLOSED_COMPONENT
);
RiverCaveSource higherHead = new RiverCaveSource(
20L,
position(0, 12, 0),
position(0, 7, 0),
11,
RiverCaveMode.CLOSED_COMPONENT
);
RiverCavePlanningResult forward = planner.planAll(view, List.of(lowerHead, higherHead), settings());
RiverCavePlanningResult reverse = planner.planAll(view, List.of(higherHead, lowerHead), settings());
assertEquals(forward, reverse);
assertEquals(higherHead, forward.plans().get(0).source());
assertTrue(forward.plans().get(0).accepted());
assertEquals(RiverCaveRejection.OVERLAPPING_SOURCE, forward.plans().get(1).rejection());
assertEquals(20L, forward.plans().get(1).arbitrationWinnerSourceId().getAsLong());
assertEquals(forward.actions().keySet(), forward.baselinePreconditions().keySet());
}
@Test
public void equalHeadOverlapUsesLowestStableSourceId() {
TestVoxelView view = new TestVoxelView();
view.set(CaveVoxel.CAVE_AIR, position(0, 7, 0), position(1, 7, 0), position(2, 7, 0));
RiverCaveSource largerId = new RiverCaveSource(
20L,
position(0, 12, 0),
position(0, 7, 0),
10,
RiverCaveMode.CLOSED_COMPONENT
);
RiverCaveSource smallerId = new RiverCaveSource(
1L,
position(2, 12, 0),
position(2, 7, 0),
10,
RiverCaveMode.CLOSED_COMPONENT
);
RiverCavePlanningResult result = planner.planAll(view, List.of(largerId, smallerId), settings());
assertEquals(smallerId, result.plans().get(0).source());
assertTrue(result.plans().get(0).accepted());
assertEquals(1L, result.plans().get(1).arbitrationWinnerSourceId().getAsLong());
}
@Test
public void arbitrationUsesDirectHigherPriorityPlansEvenWhenTheyAreRejected() {
TestVoxelView view = new TestVoxelView();
view.set(
CaveVoxel.CAVE_AIR,
position(0, 7, 0),
position(2, 7, 0),
position(4, 7, 0)
);
RiverCaveSource lowest = new RiverCaveSource(
30L,
position(0, 12, 0),
position(0, 7, 0),
10,
RiverCaveMode.CLOSED_COMPONENT
);
RiverCaveSource middle = new RiverCaveSource(
20L,
position(2, 12, 0),
position(2, 7, 0),
11,
RiverCaveMode.CLOSED_COMPONENT
);
RiverCaveSource highest = new RiverCaveSource(
10L,
position(4, 12, 0),
position(4, 7, 0),
12,
RiverCaveMode.CLOSED_COMPONENT
);
RiverCavePlanningResult forward = planner.planAll(
view,
List.of(lowest, middle, highest),
settings()
);
RiverCavePlanningResult reverse = planner.planAll(
view,
List.of(highest, middle, lowest),
settings()
);
assertEquals(forward, reverse);
assertTrue(forward.plans().get(0).accepted());
assertEquals(RiverCaveRejection.OVERLAPPING_SOURCE, forward.plans().get(1).rejection());
assertEquals(10L, forward.plans().get(1).arbitrationWinnerSourceId().getAsLong());
assertEquals(RiverCaveRejection.OVERLAPPING_SOURCE, forward.plans().get(2).rejection());
assertEquals(20L, forward.plans().get(2).arbitrationWinnerSourceId().getAsLong());
assertFalse(forward.actions().containsKey(position(0, 7, 0)));
assertTrue(forward.actions().containsKey(position(4, 7, 0)));
}
@Test
public void planOutputsAreImmutable() {
TestVoxelView view = new TestVoxelView();
view.set(CaveVoxel.CAVE_AIR, position(0, 7, 0));
RiverCaveSource source = source(12L, 10, RiverCaveMode.CLOSED_COMPONENT, position(0, 7, 0));
RiverCavePlanningResult result = planner.planAll(view, List.of(source), settings());
RiverCavePlan plan = result.plans().get(0);
assertThrows(
UnsupportedOperationException.class,
() -> plan.actions().put(position(9, 9, 9), RiverCaveAction.WET_SOURCE)
);
assertThrows(
UnsupportedOperationException.class,
() -> plan.baselinePreconditions().put(
position(9, 9, 9),
new CaveVoxelPrecondition(CaveVoxel.SOLID, false)
)
);
assertThrows(UnsupportedOperationException.class, () -> result.plans().add(plan));
assertThrows(
UnsupportedOperationException.class,
() -> result.actions().put(position(9, 9, 9), RiverCaveAction.WET_SOURCE)
);
}
private void assertMutationConnected(RiverCavePlan plan, CavePosition start, CavePosition expected) {
Set<CavePosition> mutations = new HashSet<>();
for (Map.Entry<CavePosition, RiverCaveAction> entry : plan.actions().entrySet()) {
if (entry.getValue() != RiverCaveAction.SEAL_GUARD) {
mutations.add(entry.getKey());
}
}
Queue<CavePosition> queue = new ArrayDeque<>();
Set<CavePosition> visited = new HashSet<>();
queue.add(start);
visited.add(start);
while (!queue.isEmpty()) {
CavePosition position = queue.remove();
for (CavePosition direction : DIRECTIONS) {
CavePosition neighbor = position.offset(direction.x(), direction.y(), direction.z());
if (mutations.contains(neighbor) && visited.add(neighbor)) {
queue.add(neighbor);
}
}
}
assertTrue(visited.contains(expected));
assertEquals(mutations, visited);
}
private int mutationCount(RiverCavePlan plan) {
int count = 0;
for (RiverCaveAction action : plan.actions().values()) {
if (action != RiverCaveAction.SEAL_GUARD) {
count++;
}
}
return count;
}
private void assertRejectedWithoutPublication(RiverCavePlan plan, RiverCaveRejection rejection) {
assertFalse(plan.accepted());
assertEquals(rejection, plan.rejection());
assertTrue(plan.actions().isEmpty());
assertTrue(plan.baselinePreconditions().isEmpty());
}
private RiverCaveSource source(long sourceId, int waterHeadY, RiverCaveMode mode, CavePosition target) {
return new RiverCaveSource(sourceId, position(0, 12, 0), target, waterHeadY, mode);
}
private RiverCavePlannerSettings settings() {
return settings(RiverCaveFluidPolicy.ALLOW_COMPATIBLE);
}
private RiverCavePlannerSettings settings(RiverCaveFluidPolicy policy) {
return new RiverCavePlannerSettings(12, 20, 64, 32, 2, 2, policy);
}
private RiverCavePlannerSettings detailedSettings(
int throatRadius,
int dryHeadroom,
RiverCaveGrottoShape shape,
RiverCaveFluidPolicy policy
) {
return new RiverCavePlannerSettings(
12,
20,
2048,
32,
throatRadius,
4,
4,
dryHeadroom,
policy,
shape
);
}
private CavePosition position(int x, int y, int z) {
return new CavePosition(x, y, z);
}
private static final class TestVoxelView implements CaveVoxelView {
private final int minX;
private final int maxX;
private final int minY;
private final int maxY;
private final int minZ;
private final int maxZ;
private final Map<CavePosition, CaveVoxel> voxels = new HashMap<>();
private final Set<CavePosition> surfaceOpenings = new HashSet<>();
private TestVoxelView() {
this(-32, 32, 0, 32, -32, 32);
}
private TestVoxelView(int minX, int maxX, int minY, int maxY, int minZ, int maxZ) {
this.minX = minX;
this.maxX = maxX;
this.minY = minY;
this.maxY = maxY;
this.minZ = minZ;
this.maxZ = maxZ;
}
@Override
public boolean isInWorld(CavePosition position) {
return position.x() >= minX
&& position.x() <= maxX
&& position.y() >= minY
&& position.y() <= maxY
&& position.z() >= minZ
&& position.z() <= maxZ;
}
@Override
public CaveVoxel voxelAt(CavePosition position) {
return voxels.getOrDefault(position, CaveVoxel.SOLID);
}
@Override
public boolean isOpenToSurface(CavePosition position) {
return surfaceOpenings.contains(position);
}
private void set(CaveVoxel voxel, CavePosition... positions) {
for (CavePosition position : positions) {
voxels.put(position, voxel);
}
}
private void openToSurface(CavePosition position) {
surfaceOpenings.add(position);
}
}
}
@@ -1,46 +0,0 @@
package art.arcane.iris.engine.river.cave;
import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
import art.arcane.volmlib.util.matter.Matter;
import art.arcane.volmlib.util.matter.MatterSlice;
import org.junit.Test;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class RiverCaveHydrologyStorageTest {
@Test
@SuppressWarnings("unchecked")
public void presentReadDoesNotMaterializeASectionOrSlice() {
MantleChunk<Matter> chunk = mock(MantleChunk.class);
Matter matter = mock(Matter.class);
MatterSlice<RiverCaveHydrology> slice = mock(MatterSlice.class);
RiverCaveHydrology expected = RiverCaveHydrology.of(RiverCaveAction.WET_SOURCE);
when(chunk.exists(2)).thenReturn(true);
when(chunk.get(2)).thenReturn(matter);
when(matter.hasSlice(RiverCaveHydrology.class)).thenReturn(true);
when(matter.getSlice(RiverCaveHydrology.class)).thenReturn(slice);
when(slice.get(3, 1, 5)).thenReturn(expected);
assertSame(expected, RiverCaveHydrologyStorage.getIfPresent(chunk, 3, 33, 5));
verify(chunk, never()).getOrCreate(2);
verify(matter, never()).slice(RiverCaveHydrology.class);
}
@Test
@SuppressWarnings("unchecked")
public void absentSliceReturnsWithoutCreatingIt() {
MantleChunk<Matter> chunk = mock(MantleChunk.class);
Matter matter = mock(Matter.class);
when(chunk.exists(2)).thenReturn(true);
when(chunk.get(2)).thenReturn(matter);
assertNull(RiverCaveHydrologyStorage.getIfPresent(chunk, 3, 33, 5));
verify(matter, never()).slice(RiverCaveHydrology.class);
verify(matter, never()).getSlice(RiverCaveHydrology.class);
}
}
@@ -1,62 +0,0 @@
package art.arcane.iris.engine.river.runtime;
import art.arcane.iris.engine.object.IrisBiome;
import art.arcane.iris.engine.object.IrisRegion;
import art.arcane.iris.engine.object.IrisRiverBiomes;
import art.arcane.iris.engine.object.IrisRiverNetwork;
import art.arcane.iris.engine.object.IrisRiverOverride;
import art.arcane.iris.engine.object.IrisRiverRoutingPolicy;
import art.arcane.iris.engine.object.IrisRiverTerminalMode;
import art.arcane.volmlib.util.collection.KList;
import org.junit.Test;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class EffectiveRiverSettingsTest {
@Test
public void appliesRegionThenBiomeOverridesWithoutChangingGraphSettings() {
IrisRiverNetwork network = new IrisRiverNetwork()
.setBiomes(new IrisRiverBiomes()
.setChannel(new KList<>("dimension-channel"))
.setBank(new KList<>("dimension-bank")));
IrisRegion region = new IrisRegion().setRiverOverride(new IrisRiverOverride()
.setAllowSources(false)
.setRoutingPolicy(IrisRiverRoutingPolicy.AVOID)
.setWidthMultiplier(1.5D)
.setChannelBiomes(new KList<>("region-channel")));
IrisBiome biome = new IrisBiome().setRiverOverride(new IrisRiverOverride()
.setRoutingPolicy(IrisRiverRoutingPolicy.BLOCK)
.setWidthMultiplier(0.5D)
.setChannelBiomes(new KList<>())
.setTerminalMode(IrisRiverTerminalMode.SUPPRESS));
EffectiveRiverSettings settings = EffectiveRiverSettings.resolve(network, region, biome);
assertFalse(settings.allowSources());
assertEquals(IrisRiverRoutingPolicy.BLOCK, settings.routingPolicy());
assertEquals(0.5D, settings.widthMultiplier(), 0D);
assertTrue(settings.channelBiomes().isEmpty());
assertEquals(List.of("dimension-bank"), settings.bankBiomes());
assertEquals(IrisRiverTerminalMode.SUPPRESS, settings.terminalMode());
assertTrue(settings.terminalModeOverridden());
}
@Test
public void inheritsDimensionDefaultsWhenOverridesAreAbsent() {
IrisRiverNetwork network = new IrisRiverNetwork()
.setBiomes(new IrisRiverBiomes().setDry(new KList<>("dry")));
EffectiveRiverSettings settings = EffectiveRiverSettings.resolve(network, null, null);
assertTrue(settings.allowSources());
assertEquals(IrisRiverRoutingPolicy.ALLOW, settings.routingPolicy());
assertEquals(1D, settings.routingCostMultiplier(), 0D);
assertEquals(List.of("dry"), settings.dryBiomes());
assertEquals(IrisRiverTerminalMode.DRY_CHANNEL, settings.terminalMode());
assertFalse(settings.terminalModeOverridden());
}
}
File diff suppressed because it is too large. Load diff
@@ -1,53 +0,0 @@
package art.arcane.iris.util.project.matter.slices;
import art.arcane.iris.engine.river.cave.RiverCaveAction;
import art.arcane.iris.engine.river.cave.RiverCaveFluidKind;
import art.arcane.iris.engine.river.cave.RiverCaveHydrology;
import org.junit.Test;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;
public class RiverCaveHydrologyMatterTest {
@Test
public void everyActionAndBiomeRoundTrips() throws IOException {
RiverCaveHydrologyMatter matter = new RiverCaveHydrologyMatter();
for (RiverCaveAction action : RiverCaveAction.values()) {
for (RiverCaveFluidKind fluidKind : RiverCaveFluidKind.values()) {
RiverCaveHydrology expected = new RiverCaveHydrology(
action,
"iris:flooded_grotto",
fluidKind
);
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
matter.writeNode(expected, new DataOutputStream(bytes));
RiverCaveHydrology actual = matter.readNode(
new DataInputStream(new ByteArrayInputStream(bytes.toByteArray())));
assertEquals(expected, actual);
}
}
}
@Test
public void emptyBiomeAndInvalidActionRemainUnambiguous() throws IOException {
RiverCaveHydrologyMatter matter = new RiverCaveHydrologyMatter();
RiverCaveHydrology expected = RiverCaveHydrology.of(RiverCaveAction.DRY_AIR);
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
matter.writeNode(expected, new DataOutputStream(bytes));
assertEquals(expected, matter.readNode(
new DataInputStream(new ByteArrayInputStream(bytes.toByteArray()))));
assertThrows(IOException.class, () -> matter.readNode(
new DataInputStream(new ByteArrayInputStream(new byte[]{99}))));
assertThrows(IOException.class, () -> matter.readNode(
new DataInputStream(new ByteArrayInputStream(new byte[]{1, 99}))));
}
}