Merged pipeline hygiene

- Lazy shore-band noise, zero interpolator groups dropped, inline prefill, dead streams removed
This commit is contained in:
Brian Neumann-Fopiano committed 2026-10-21 21:03:22 -04:00
commit 2471066937
13 files changed
+980 -126

No files matched your search

@@ -95,9 +95,42 @@ public class ChunkContextPrefillPlanTest {
}
@Test
public void singleOrNoFillTaskPrefillsInline() {
assertFalse(ChunkContext.shouldPrefillAsync(ChunkContext.PrefillPlan.NO_CAVE, 0));
assertFalse(ChunkContext.shouldPrefillAsync(ChunkContext.PrefillPlan.NO_CAVE, 1));
public void generationWorkersPrefillInlineWithoutConsultingSharedExecutor() {
try (MockedStatic<IrisPlatforms> platforms = mockStatic(IrisPlatforms.class)) {
platforms.when(IrisPlatforms::isBound).thenReturn(true);
assertFalse(ChunkContext.shouldPrefillAsync(ChunkContext.PrefillPlan.NO_CAVE, false));
assertFalse(ChunkContext.shouldPrefillAsync(ChunkContext.PrefillPlan.ALL, false));
platforms.verifyNoInteractions();
}
}
@Test
public void platformMainThreadKeepsBlockingPrefillOffTheTickThread() {
try (MockedStatic<IrisPlatforms> platforms = mockStatic(IrisPlatforms.class)) {
platforms.when(IrisPlatforms::isBound).thenReturn(true);
assertTrue(ChunkContext.shouldPrefillAsync(ChunkContext.PrefillPlan.NO_CAVE, true));
assertFalse(ChunkContext.shouldPrefillAsync(ChunkContext.PrefillPlan.NONE, true));
assertFalse(ChunkContext.shouldPrefillAsync(ChunkContext.PrefillPlan.NATURAL_TERRAIN, true));
}
}
@Test
public void inlinePrefillFinishesHeightsBeforeTheBiomePassStarts() {
AtomicInteger caveCalls = new AtomicInteger();
AtomicInteger heightCalls = new AtomicInteger();
AtomicInteger biomeCalls = new AtomicInteger();
AtomicInteger rockCalls = new AtomicInteger();
AtomicInteger fluidCalls = new AtomicInteger();
AtomicInteger regionCalls = new AtomicInteger();
AtomicInteger heightsSeenByFirstBiome = new AtomicInteger(-1);
try (MockedStatic<IrisPlatforms> platforms = mockStatic(IrisPlatforms.class)) {
platforms.when(IrisPlatforms::isBound).thenReturn(true);
createContext(ChunkContext.PrefillPlan.NO_CAVE, caveCalls, heightCalls, biomeCalls, rockCalls,
fluidCalls, regionCalls, null, () -> heightsSeenByFirstBiome.compareAndSet(-1, heightCalls.get()));
}
assertEquals(256, heightsSeenByFirstBiome.get());
assertEquals(256, biomeCalls.get());
}
@Test
@@ -110,7 +143,7 @@ public class ChunkContextPrefillPlanTest {
AtomicInteger regionCalls = new AtomicInteger();
try (MockedStatic<IrisPlatforms> platforms = mockStatic(IrisPlatforms.class)) {
platforms.when(IrisPlatforms::isBound).thenReturn(true);
assertFalse(ChunkContext.shouldPrefillAsync(ChunkContext.PrefillPlan.NATURAL_TERRAIN, 5));
assertFalse(ChunkContext.shouldPrefillAsync(ChunkContext.PrefillPlan.NATURAL_TERRAIN, true));
ChunkContext context = createContext(ChunkContext.PrefillPlan.NATURAL_TERRAIN,
caveCalls, heightCalls, biomeCalls, rockCalls, fluidCalls, regionCalls);
@@ -228,6 +261,22 @@ public class ChunkContextPrefillPlanTest {
AtomicInteger fluidCalls,
AtomicInteger regionCalls,
IrisHydrologyRuntime runtime
) {
return createContext(prefillPlan, caveCalls, heightCalls, biomeCalls, rockCalls, fluidCalls, regionCalls,
runtime, () -> {
});
}
private ChunkContext createContext(
ChunkContext.PrefillPlan prefillPlan,
AtomicInteger caveCalls,
AtomicInteger heightCalls,
AtomicInteger biomeCalls,
AtomicInteger rockCalls,
AtomicInteger fluidCalls,
AtomicInteger regionCalls,
IrisHydrologyRuntime runtime,
Runnable biomeObserver
) {
IrisComplex complex = mock(IrisComplex.class);
@@ -244,6 +293,7 @@ public class ChunkContextPrefillPlanTest {
ProceduralStream<IrisBiome> biomeStream = mock(ProceduralStream.class);
IrisBiome biome = mock(IrisBiome.class);
doAnswer(invocation -> {
biomeObserver.run();
biomeCalls.incrementAndGet();
return biome;
}).when(biomeStream).get(anyDouble(), anyDouble());
@@ -351,6 +351,7 @@ public class DimensionTerrain3DCompositionTest {
when(context.getBiome()).thenReturn(biomes);
when(context.getRegion()).thenReturn(regions);
when(context.getRock()).thenReturn(rocks);
when(context.getFluid()).thenReturn(mock(ChunkedDataCache.class));
when(context.getRoundedHeight(0, 0)).thenReturn(rootHeight);
Hunk<NativeBlockState> blocks = Hunk.newArrayHunk(1, 64, 1);
new IrisTerrainNormalActuator(engine).paint(0, 0, blocks, context);
@@ -0,0 +1,289 @@
package art.arcane.iris.generation.runtime;
import art.arcane.iris.generation.noise.IrisGenerator;
import art.arcane.iris.generation.noise.IrisInterpolator;
import art.arcane.volmlib.util.interpolation.NoiseBounds;
import art.arcane.volmlib.util.interpolation.NoiseBoundsProvider;
import org.junit.Test;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.SplittableRandom;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Answers.CALLS_REAL_METHODS;
import static org.mockito.Mockito.mock;
public class IrisComplexChunkCornersTest {
@Test
public void chunkCornerHeightsMatchPerGroupGridSamplingBitForBit() throws Exception {
for (int step : new int[]{2, 4, 8, 16}) {
IrisInterpolator[] interpolators = {
new PatchInterpolator(0, 1D),
new PatchInterpolator(1, -3D),
new ZeroInterpolator(true),
new PatchInterpolator(2, 0.5D),
new ZeroInterpolator(false)
};
IrisGenerator[][] generators = {
{new WaveGenerator(1D), new WaveGenerator(0.5D)},
{new WaveGenerator(-0.25D)},
{new WaveGenerator(2D)},
{new WaveGenerator(0.75D), new WaveGenerator(-1D), new WaveGenerator(0.125D)},
{new WaveGenerator(4D)}
};
IrisComplex candidate = createComplex(step, interpolators, generators);
IrisComplex reference = createComplex(step, interpolators, generators);
SplittableRandom random = new SplittableRandom(step * 31L);
for (int sample = 0; sample < 60_000; sample++) {
double x = coordinate(random);
double z = coordinate(random);
assertEquals("step " + step + " at " + x + "," + z,
Double.doubleToRawLongBits(expectedHeight(reference, interpolators, generators, x, z)),
Double.doubleToRawLongBits(interpolatedHeight(candidate, x, z)));
}
}
}
@Test
public void silentCellsNeverEvaluateTheirGenerators() throws Exception {
WaveGenerator quiet = new WaveGenerator(1D);
WaveGenerator loud = new WaveGenerator(1D);
IrisInterpolator[] interpolators = {new ZeroInterpolator(true), new PatchInterpolator(0, 2D)};
IrisGenerator[][] generators = {{quiet}, {loud}};
IrisComplex complex = createComplex(4, interpolators, generators);
for (int x = 0; x < 64; x++) {
for (int z = 0; z < 64; z++) {
interpolatedHeight(complex, x, z);
}
}
assertEquals(0, quiet.invocations);
assertTrue(loud.invocations > 0);
}
@Test
public void sparseSamplesFetchOnlyTheCornersTheirColumnsUse() throws Exception {
CountingInterpolator counting = new CountingInterpolator();
IrisInterpolator[] interpolators = {counting};
IrisGenerator[][] generators = {{new WaveGenerator(1D)}};
IrisComplex complex = createComplex(4, interpolators, generators);
for (int sample = 0; sample < 32; sample++) {
interpolatedHeight(complex, sample * 64D, sample * -64D);
}
assertEquals(32, counting.calls);
interpolatedHeight(complex, 2048D + 1D, 0D);
assertEquals(34, counting.calls);
interpolatedHeight(complex, 4096D, 1D);
assertEquals(36, counting.calls);
interpolatedHeight(complex, 8192D + 1D, 1D);
assertEquals(40, counting.calls);
}
@Test
public void nestedHeightSamplesLeaveTheOuterChunkBlockIntact() throws Exception {
ReentrantGenerator reentrant = new ReentrantGenerator();
IrisInterpolator[] interpolators = {new ZeroFreeInterpolator(), new PatchInterpolator(4, -2D)};
IrisGenerator[][] generators = {{reentrant}, {new WaveGenerator(0.5D)}};
IrisComplex candidate = createComplex(4, interpolators, generators);
IrisComplex reference = createComplex(4, interpolators, generators);
SplittableRandom random = new SplittableRandom(99L);
for (int sample = 0; sample < 4_000; sample++) {
double x = coordinate(random);
double z = coordinate(random);
reentrant.target = reference;
double expected = expectedHeight(reference, interpolators, generators, x, z);
reentrant.target = candidate;
assertEquals(Double.doubleToRawLongBits(expected), Double.doubleToRawLongBits(interpolatedHeight(candidate, x, z)));
}
}
@Test
public void nonFiniteCornersFailTheSameWayAndAreRetried() throws Exception {
NaNCornerInterpolator interpolator = new NaNCornerInterpolator();
IrisInterpolator[] interpolators = {interpolator};
IrisGenerator[][] generators = {{new WaveGenerator(1D)}};
IrisComplex complex = createComplex(4, interpolators, generators);
for (int attempt = 0; attempt < 2; attempt++) {
try {
interpolatedHeight(complex, 2D, 2D);
fail("A non-finite corner must fail the column");
} catch (InvocationTargetException expected) {
assertTrue(expected.getCause() instanceof IllegalStateException);
}
}
assertEquals(2, interpolator.nanCalls);
IrisComplex reference = createComplex(4, interpolators, generators);
assertEquals(Double.doubleToRawLongBits(expectedHeight(reference, interpolators, generators, 0D, 0D)),
Double.doubleToRawLongBits(interpolatedHeight(complex, 0D, 0D)));
}
private static double coordinate(SplittableRandom random) {
int base = random.nextInt(-80, 80);
return switch (random.nextInt(4)) {
case 0 -> base;
case 1 -> base + random.nextInt(4) * 0.25D;
case 2 -> base + random.nextDouble();
default -> base * 1031 + random.nextInt(-3, 3);
};
}
private static double expectedHeight(
IrisComplex complex,
IrisInterpolator[] interpolators,
IrisGenerator[][] generators,
double x,
double z
) throws Exception {
Method interpolate = IrisComplex.class.getDeclaredMethod("interpolateGenerators", Engine.class,
IrisInterpolator.class, int.class, IrisGenerator[].class, double.class, double.class, long.class);
interpolate.setAccessible(true);
double height = 0;
for (int group = 0; group < interpolators.length; group++) {
height += (double) interpolate.invoke(complex, null, interpolators[group], group, generators[group], x, z, 31L);
}
return height;
}
private static double interpolatedHeight(IrisComplex complex, double x, double z) throws Exception {
Method method = IrisComplex.class.getDeclaredMethod("getInterpolatedHeight", Engine.class,
double.class, double.class, long.class);
method.setAccessible(true);
return (double) method.invoke(complex, null, x, z, 31L);
}
private static IrisComplex createComplex(int step, IrisInterpolator[] interpolators, IrisGenerator[][] generators) throws Exception {
IrisComplex complex = mock(IrisComplex.class, CALLS_REAL_METHODS);
setField(complex, "biomeBoundsSamplingStep", step);
setField(complex, "generatorBounds", new HashMap<>());
setField(complex, "biomeBuffetGenerators", new HashMap<>());
setField(complex, "frozenInterpolators", interpolators);
setField(complex, "frozenGenerators", generators);
Class<?> cacheClass = Class.forName("art.arcane.iris.generation.runtime.IrisComplex$GridBoundsCache");
Constructor<?> cacheConstructor = cacheClass.getDeclaredConstructor();
cacheConstructor.setAccessible(true);
setField(complex, "gridBoundsCache", ThreadLocal.withInitial(() -> newCache(cacheConstructor)));
return complex;
}
private static Object newCache(Constructor<?> constructor) {
try {
return constructor.newInstance();
} catch (ReflectiveOperationException e) {
throw new IllegalStateException(e);
}
}
private static void setField(IrisComplex complex, String name, Object value) throws Exception {
Field field = IrisComplex.class.getDeclaredField(name);
field.setAccessible(true);
field.set(complex, value);
}
private static final class PatchInterpolator extends IrisInterpolator {
private final int salt;
private final double scale;
private PatchInterpolator(int salt, double scale) {
this.salt = salt;
this.scale = scale;
}
@Override
public NoiseBounds interpolateBounds(double x, double z, NoiseBoundsProvider provider) {
long cell = Math.floorDiv((long) x, 24L) * 73_856_093L ^ Math.floorDiv((long) z, 24L) * 19_349_663L ^ salt * 83_492_791L;
if (Math.floorMod(cell, 3L) != 0L) {
return new NoiseBounds(0D, 0D);
}
double low = (x * 0.0625D - z * 0.03125D) * scale;
return new NoiseBounds(low, low + 3.75D * scale + Math.floorMod(cell, 5L));
}
}
private static final class ZeroFreeInterpolator extends IrisInterpolator {
@Override
public NoiseBounds interpolateBounds(double x, double z, NoiseBoundsProvider provider) {
return new NoiseBounds(x * 0.01D - 1D, z * 0.01D + 3D);
}
}
private static final class ZeroInterpolator extends IrisInterpolator {
private final boolean negative;
private ZeroInterpolator(boolean negative) {
this.negative = negative;
}
@Override
public NoiseBounds interpolateBounds(double x, double z, NoiseBoundsProvider provider) {
return negative ? new NoiseBounds(-0D, 0D) : new NoiseBounds(0D, -0D);
}
}
private static final class CountingInterpolator extends IrisInterpolator {
private int calls;
@Override
public NoiseBounds interpolateBounds(double x, double z, NoiseBoundsProvider provider) {
calls++;
return new NoiseBounds(x * 0.001D, x * 0.001D + 2D);
}
}
private static final class NaNCornerInterpolator extends IrisInterpolator {
private int nanCalls;
@Override
public NoiseBounds interpolateBounds(double x, double z, NoiseBoundsProvider provider) {
if (x == 4D && z == 4D) {
nanCalls++;
return new NoiseBounds(Double.NaN, 1D);
}
return new NoiseBounds(0.25D, 0.5D);
}
}
private static final class ReentrantGenerator extends IrisGenerator {
private IrisComplex target;
private boolean nested;
@Override
public double getHeight(double x, double z, long seed) {
if (nested) {
return Math.cos(x * 0.13D) * 0.5D;
}
nested = true;
try {
return (interpolatedHeight(target, x + 4099D, z - 2053D) + interpolatedHeight(target, x - 8191D, z + 1031D)) * 0.001D;
} catch (Exception e) {
throw new IllegalStateException(e);
} finally {
nested = false;
}
}
}
private static final class WaveGenerator extends IrisGenerator {
private final double amplitude;
private int invocations;
private WaveGenerator(double amplitude) {
this.amplitude = amplitude;
}
@Override
public double getHeight(double x, double z, long seed) {
invocations++;
return Math.sin(x * 0.37D + seed) * Math.cos(z * 0.21D) * amplitude;
}
}
}
@@ -0,0 +1,85 @@
package art.arcane.iris.generation.runtime;
import art.arcane.iris.generation.biome.IrisBiome;
import art.arcane.iris.generation.biome.IrisBiomeGeneratorLink;
import art.arcane.iris.generation.noise.IrisGenerator;
import art.arcane.iris.generation.noise.IrisInterpolator;
import art.arcane.volmlib.util.collection.KList;
import art.arcane.volmlib.util.interpolation.InterpolationMethod;
import org.junit.Test;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
public class IrisComplexContributingGroupsTest {
@Test
public void groupsWithZeroBoundsInEveryBaseBiomeAreDropped() {
IrisComplex.GeneratorGroup flat = group(InterpolationMethod.BILINEAR_STARCAST_6, 7D, "default");
IrisComplex.GeneratorGroup hills = group(InterpolationMethod.BILINEAR_STARCAST_9, 12D, "hills", "cliffs");
IrisComplex.GeneratorGroup shoreOnly = group(InterpolationMethod.BILINEAR_STARCAST_3, 30D, "ocean");
IrisBiome plains = biome(link("default", 0, 0), link("hills", -4, 12));
IrisBiome desert = biome(link("default", 0, 0), link("cliffs", 0, 3));
IrisComplex.GeneratorGroup[] contributing = IrisComplex.contributingGroups(
new IrisComplex.GeneratorGroup[]{flat, hills, shoreOnly}, List.of(plains, desert));
assertEquals(1, contributing.length);
assertSame(hills, contributing[0]);
}
@Test
public void aNegativeOnlyOrMaximumOnlyLinkKeepsItsGroup() {
IrisComplex.GeneratorGroup sinks = group(InterpolationMethod.BILINEAR, 16D, "sinks");
IrisComplex.GeneratorGroup peaks = group(InterpolationMethod.STARCAST_12, 42D, "peaks");
IrisComplex.GeneratorGroup[] contributing = IrisComplex.contributingGroups(
new IrisComplex.GeneratorGroup[]{sinks, peaks},
List.of(biome(link("sinks", -6, 0)), biome(link("peaks", 0, 9))));
assertEquals(2, contributing.length);
assertSame(sinks, contributing[0]);
assertSame(peaks, contributing[1]);
}
@Test
public void linksThatCancelAcrossGeneratorsSumToZeroBoundsAndAreDropped() {
IrisComplex.GeneratorGroup pair = group(InterpolationMethod.BILINEAR_STARCAST_9, 52D, "rise", "fall");
IrisComplex.GeneratorGroup[] contributing = IrisComplex.contributingGroups(
new IrisComplex.GeneratorGroup[]{pair},
List.of(biome(link("rise", -5, 5), link("fall", 5, -5))));
assertEquals(0, contributing.length);
}
@Test
public void noBaseBiomesDropsEveryGroup() {
IrisComplex.GeneratorGroup hills = group(InterpolationMethod.BILINEAR_STARCAST_9, 12D, "hills");
assertEquals(0, IrisComplex.contributingGroups(new IrisComplex.GeneratorGroup[]{hills}, List.of()).length);
}
private static IrisComplex.GeneratorGroup group(InterpolationMethod function, double scale, String... keys) {
IrisInterpolator interpolator = new IrisInterpolator().setFunction(function).setHorizontalScale(scale);
IrisGenerator[] generators = new IrisGenerator[keys.length];
for (int index = 0; index < keys.length; index++) {
IrisGenerator generator = new IrisGenerator();
generator.setLoadKey(keys[index]);
generator.setInterpolator(interpolator);
generators[index] = generator;
}
return new IrisComplex.GeneratorGroup(interpolator, generators);
}
private static IrisBiome biome(IrisBiomeGeneratorLink... links) {
IrisBiome biome = new IrisBiome();
biome.setGenerators(new KList<>(List.of(links)));
return biome;
}
private static IrisBiomeGeneratorLink link(String generator, int min, int max) {
return new IrisBiomeGeneratorLink().setGenerator(generator).setMin(min).setMax(max);
}
}
@@ -0,0 +1,207 @@
package art.arcane.iris.generation.runtime;
import art.arcane.iris.generation.biome.IrisBiome;
import art.arcane.iris.generation.terrain.IrisRegion;
import art.arcane.volmlib.util.collection.KList;
import art.arcane.volmlib.util.noise.CNG;
import art.arcane.volmlib.util.noise.OffsetNoiseGenerator;
import art.arcane.volmlib.util.noise.SimplexNoise;
import art.arcane.volmlib.util.stream.ProceduralStream;
import org.junit.Test;
import java.util.SplittableRandom;
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.assertTrue;
import static org.mockito.ArgumentMatchers.anyDouble;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.withSettings;
public class IrisComplexShoreBandTest {
private static final double[] SHORE_EXTREMES = {0D, 1.2D, 3.2D, 2.5D, 7.75D, -1.5D, 4D, 0.0001D, 63.9D};
@Test
public void shoreHeightGeneratorIsAnIdentityPostSimplexSignature() {
for (int sample = 0; sample < 8; sample++) {
CNG generator = region(sample).getShoreHeightGenerator();
assertTrue(generator.getGenerator() instanceof OffsetNoiseGenerator);
assertTrue(((OffsetNoiseGenerator) generator.getGenerator()).getBase() instanceof SimplexNoise);
assertEquals(1D, generator.getOpacity(), 0D);
assertTrue(generator.getChildren() == null || generator.getChildren().isEmpty());
assertEquals(1D, generator.getPower(), 0D);
assertEquals(0D, generator.getUp(), 0D);
assertEquals(0D, generator.getDown(), 0D);
assertEquals(1D, generator.getPatch(), 0D);
assertNull(generator.getCache());
assertNull(generator.getCustomGenerator());
}
}
@Test
public void simplexAmplitudeBoundFitsInsideTheShoreNoiseRange() {
double kernelPeak = 0D;
for (int step = 0; step <= 2_000_000; step++) {
double radius = Math.sqrt(0.5D) * step / 2_000_000D;
double falloff = 0.5D - radius * radius;
kernelPeak = Math.max(kernelPeak, falloff * falloff * falloff * falloff * Math.sqrt(2D) * radius);
}
assertEquals(256D / 19683D, kernelPeak, 1e-9D);
double amplitude = 50D * 3D * 256D / 19683D;
assertTrue(0.5D + amplitude / 2D < IrisComplex.SHORE_NOISE_HIGHEST);
assertTrue(0.5D - amplitude / 2D > IrisComplex.SHORE_NOISE_LOWEST);
}
@Test
public void shoreHeightNoiseStaysInsideTheProvenRange() {
SplittableRandom random = new SplittableRandom(1337L);
double lowest = Double.POSITIVE_INFINITY;
double highest = Double.NEGATIVE_INFINITY;
for (int sample = 0; sample < 4; sample++) {
CNG generator = region(sample).getShoreHeightGenerator();
for (int point = 0; point < 250_000; point++) {
double scale = point % 2 == 0 ? 1D : 64D;
double noise = generator.noise(random.nextDouble(-1e6D, 1e6D) / scale, random.nextDouble(-1e6D, 1e6D) / scale);
lowest = Math.min(lowest, noise);
highest = Math.max(highest, noise);
}
}
assertTrue("lowest " + lowest, lowest >= IrisComplex.SHORE_NOISE_LOWEST);
assertTrue("highest " + highest, highest <= IrisComplex.SHORE_NOISE_HIGHEST);
}
@Test
public void lazyShoreBandMatchesTheEagerBandTop() {
SplittableRandom random = new SplittableRandom(42L);
ProceduralStream<Double> slopes = ProceduralStream.ofDouble((x, z) -> 2D);
for (int sample = 0; sample < 400_000; sample++) {
IrisRegion region = region(random.nextInt(64))
.setShoreHeightMin(SHORE_EXTREMES[random.nextInt(SHORE_EXTREMES.length)])
.setShoreHeightMax(SHORE_EXTREMES[random.nextInt(SHORE_EXTREMES.length)])
.setShoreHeightZoom(random.nextBoolean() ? 3.14D : 0.15D)
.setShoreMinimumWidth(random.nextInt(8) == 0 ? 2D : 0D);
double fluid = random.nextInt(4) == 0 ? 63D : random.nextInt(-64, 200);
double height = random.nextInt(3) == 0
? fluid + random.nextDouble(-3D, 9D)
: random.nextInt(-64, 320) + (random.nextBoolean() ? 0D : random.nextDouble());
double x = random.nextInt(-30_000_000, 30_000_000);
double z = random.nextInt(-30_000_000, 30_000_000);
boolean eager = height > IrisComplex.shoreBandTop(height, region, x, z, fluid, slopes);
assertEquals(eager, IrisComplex.aboveShoreBand(height, region, x, z, fluid, slopes));
}
}
@Test
public void lazySurfaceBiomeMatchesTheEagerSelection() {
SplittableRandom random = new SplittableRandom(7L);
IrisBiome land = mock(IrisBiome.class, withSettings().stubOnly());
IrisBiome sea = mock(IrisBiome.class, withSettings().stubOnly());
IrisBiome shore = mock(IrisBiome.class, withSettings().stubOnly());
IrisBiome[] bases = new IrisBiome[8];
for (int flags = 0; flags < bases.length; flags++) {
IrisBiome base = mock(IrisBiome.class, withSettings().stubOnly());
doReturn((flags & 1) != 0).when(base).isShore();
doReturn((flags & 2) != 0).when(base).isLand();
doReturn((flags & 4) != 0).when(base).isAquatic();
bases[flags] = base;
}
ProceduralStream<IrisBiome> lands = constant(land);
ProceduralStream<IrisBiome> seas = constant(sea);
ProceduralStream<IrisBiome> shores = constant(shore);
ProceduralStream<Double> slopes = ProceduralStream.ofDouble((x, z) -> 4D);
for (int sample = 0; sample < 200_000; sample++) {
IrisRegion region = region(random.nextInt(16))
.setShoreHeightMin(SHORE_EXTREMES[random.nextInt(SHORE_EXTREMES.length)])
.setShoreHeightMax(SHORE_EXTREMES[random.nextInt(SHORE_EXTREMES.length)])
.setShoreMinimumWidth(random.nextInt(8) == 0 ? 3D : 0D);
double fluid = 63D;
double height = random.nextBoolean() ? fluid + random.nextInt(-3, 8) : random.nextInt(-64, 320);
double x = random.nextInt(-100_000, 100_000);
double z = random.nextInt(-100_000, 100_000);
IrisBiome base = bases[random.nextInt(bases.length)];
IrisBiome expected = eagerSurfaceBiome(height, base, region, x, z, fluid, lands, seas, shores, slopes);
assertSame(expected, IrisComplex.resolveSurfaceBiome(height, base, region, x, z, fluid, lands, seas, shores, slopes));
}
}
@Test
public void columnsOutsideTheShoreWindowNeverSampleTheShoreNoise() {
IrisRegion region = mock(IrisRegion.class);
doReturn(1.2D).when(region).getShoreHeightMin();
doReturn(3.2D).when(region).getShoreHeightMax();
doReturn(0D).when(region).getShoreMinimumWidth();
doThrow(new AssertionError("The shore noise was sampled for a column outside the shore window"))
.when(region).getShoreHeight(anyDouble(), anyDouble());
ProceduralStream<Double> slopes = ProceduralStream.ofDouble((x, z) -> 0D);
assertTrue(IrisComplex.aboveShoreBand(68D, region, 3D, 4D, 63D, slopes));
assertTrue(IrisComplex.aboveShoreBand(250D, region, 3D, 4D, 63D, slopes));
assertFalse(IrisComplex.aboveShoreBand(63.2D, region, 3D, 4D, 63D, slopes));
assertFalse(IrisComplex.aboveShoreBand(-20D, region, 3D, 4D, 63D, slopes));
}
@Test
public void equalShoreExtremesNeverSampleTheShoreNoise() {
IrisRegion region = mock(IrisRegion.class);
doReturn(2D).when(region).getShoreHeightMin();
doReturn(2D).when(region).getShoreHeightMax();
doReturn(0D).when(region).getShoreMinimumWidth();
doThrow(new AssertionError("Equal shore extremes need no shore noise"))
.when(region).getShoreHeight(anyDouble(), anyDouble());
ProceduralStream<Double> slopes = ProceduralStream.ofDouble((x, z) -> 0D);
assertFalse(IrisComplex.aboveShoreBand(65D, region, 3D, 4D, 63D, slopes));
assertTrue(IrisComplex.aboveShoreBand(65.0001D, region, 3D, 4D, 63D, slopes));
}
private static IrisRegion region(int variant) {
IrisRegion region = new IrisRegion();
region.setName("Shore Region " + variant);
region.setLandBiomeZoom(1D + variant * 0.25D);
KList<String> lands = new KList<>();
for (int index = 0; index < variant % 5; index++) {
lands.add("land-" + index);
}
region.setLandBiomes(lands);
return region;
}
private static IrisBiome eagerSurfaceBiome(
double height,
IrisBiome biome,
IrisRegion region,
double x,
double z,
double fluidHeight,
ProceduralStream<IrisBiome> landBiomes,
ProceduralStream<IrisBiome> seaBiomes,
ProceduralStream<IrisBiome> shoreBiomes,
ProceduralStream<Double> shoreSlopes
) {
double shoreTop = IrisComplex.shoreBandTop(height, region, x, z, fluidHeight, shoreSlopes);
if (height >= fluidHeight - 1 && height <= shoreTop && !biome.isShore()) {
return shoreBiomes.get(x, z);
}
if (height > shoreTop && !biome.isLand()) {
return landBiomes.get(x, z);
}
if (height < fluidHeight && !biome.isAquatic()) {
return seaBiomes.get(x, z);
}
if (height == fluidHeight && !biome.isShore()) {
return shoreBiomes.get(x, z);
}
return biome;
}
private static ProceduralStream<IrisBiome> constant(IrisBiome biome) {
@SuppressWarnings("unchecked")
ProceduralStream<IrisBiome> stream = mock(ProceduralStream.class, withSettings().stubOnly());
doReturn(biome).when(stream).get(anyDouble(), anyDouble());
return stream;
}
}
@@ -119,6 +119,8 @@ public class IrisComplexSurfaceBiomeTest {
doReturn(false).when(base).isShore();
doReturn(false).when(base).isAquatic();
doReturn(3D).when(region).getShoreHeight(12D, 18D);
doReturn(1D).when(region).getShoreHeightMin();
doReturn(5D).when(region).getShoreHeightMax();
IrisBiome resolved = IrisComplex.resolveSurfaceBiome(
68D,
@@ -143,6 +145,8 @@ public class IrisComplexSurfaceBiomeTest {
IrisRegion region = mock(IrisRegion.class);
doReturn(false).when(base).isShore();
doReturn(3D).when(region).getShoreHeight(12D, 18D);
doReturn(1D).when(region).getShoreHeightMin();
doReturn(5D).when(region).getShoreHeightMax();
IrisBiome resolved = IrisComplex.resolveSurfaceBiome(
63D,
@@ -168,6 +172,8 @@ public class IrisComplexSurfaceBiomeTest {
doReturn(false).when(aquatic).isShore();
doReturn(false).when(aquatic).isLand();
doReturn(1D).when(region).getShoreHeight(4D, 7D);
doReturn(0.5D).when(region).getShoreHeightMin();
doReturn(1.5D).when(region).getShoreHeightMax();
IrisBiome resolved = IrisComplex.resolveSurfaceBiome(
66D,
@@ -193,6 +199,8 @@ public class IrisComplexSurfaceBiomeTest {
doReturn(false).when(base).isShore();
doReturn(false).when(base).isLand();
doReturn(3D).when(region).getShoreHeight(12D, 18D);
doReturn(1D).when(region).getShoreHeightMin();
doReturn(5D).when(region).getShoreHeightMax();
doReturn(0D).when(region).getShoreMinimumWidth();
IrisBiome resolved = IrisComplex.resolveSurfaceBiome(
@@ -218,6 +226,8 @@ public class IrisComplexSurfaceBiomeTest {
IrisRegion region = mock(IrisRegion.class);
doReturn(false).when(base).isShore();
doReturn(3D).when(region).getShoreHeight(12D, 18D);
doReturn(1D).when(region).getShoreHeightMin();
doReturn(5D).when(region).getShoreHeightMax();
doReturn(3D).when(region).getShoreMinimumWidth();
IrisBiome resolved = IrisComplex.resolveSurfaceBiome(
@@ -244,6 +254,8 @@ public class IrisComplexSurfaceBiomeTest {
doReturn(false).when(base).isShore();
doReturn(false).when(base).isLand();
doReturn(3D).when(region).getShoreHeight(12D, 18D);
doReturn(1D).when(region).getShoreHeightMin();
doReturn(5D).when(region).getShoreHeightMax();
doReturn(3D).when(region).getShoreMinimumWidth();
IrisBiome resolved = IrisComplex.resolveSurfaceBiome(
@@ -272,6 +284,8 @@ public class IrisComplexSurfaceBiomeTest {
doReturn(false).when(base).isLand();
doReturn(false).when(base).isAquatic();
doReturn(3D).when(region).getShoreHeight(12D, 18D);
doReturn(1D).when(region).getShoreHeightMin();
doReturn(5D).when(region).getShoreHeightMax();
doReturn(3D).when(region).getShoreMinimumWidth();
assertSame(sea, IrisComplex.resolveSurfaceBiome(
@@ -304,6 +318,8 @@ public class IrisComplexSurfaceBiomeTest {
public void shoreBandTopTradesHorizontalWidthForHeightOnSlopes() {
IrisRegion region = mock(IrisRegion.class);
doReturn(3D).when(region).getShoreHeight(12D, 18D);
doReturn(1D).when(region).getShoreHeightMin();
doReturn(5D).when(region).getShoreHeightMax();
doReturn(3D).when(region).getShoreMinimumWidth();
assertEquals(66D, IrisComplex.shoreBandTop(67D, region, 12D, 18D, 63D, constantSlope(0D)), 0D);
@@ -175,7 +175,6 @@ public class IrisTerrainNormalActuatorColumnParityTest {
when(complex.terrainColumn(eq(X), eq(Z), hydrology == null ? isNull() : any(HydrologyColumnSample.class)))
.thenReturn(terrainColumn);
when(complex.naturalTerrainColumn(X, Z)).thenReturn(terrainColumn);
when(complex.resolveSurfaceFluid(anyDouble(), anyDouble())).thenReturn(fluid);
when(context.getRoundedHeight(0, 0)).thenReturn(he);
when(context.getBiome()).thenReturn(new ChunkedDataCache<>(
ProceduralStream.of((x, z) -> biome, Interpolated.of(value -> 0D, value -> biome)), X, Z, false));
@@ -183,6 +182,8 @@ public class IrisTerrainNormalActuatorColumnParityTest {
ProceduralStream.of((x, z) -> region, Interpolated.of(value -> 0D, value -> region)), X, Z, false));
when(context.getRock()).thenReturn(new ChunkedDataCache<>(
ProceduralStream.of((x, z) -> rock, Interpolated.of(value -> 0D, value -> rock)), X, Z, false));
when(context.getFluid()).thenReturn(new ChunkedDataCache<>(
ProceduralStream.of((x, z) -> fluid, Interpolated.of(value -> 0D, value -> fluid)), X, Z, false));
when(biome.generateLayers(eq(dimension), anyDouble(), anyDouble(), any(RNG.class),
anyInt(), anyInt(), eq(data), eq(complex))).thenAnswer(invocation -> layers("layer", layerCount));
when(biome.generateSeaLayers(anyDouble(), anyDouble(), any(RNG.class), anyInt(), eq(data)))
@@ -259,6 +259,8 @@ public class IrisTerrainNormalActuatorHydrologyTest {
ProceduralStream.of((x, z) -> region, Interpolated.of(value -> 0D, value -> region)), 11, -4, false));
when(context.getRock()).thenReturn(new ChunkedDataCache<>(
ProceduralStream.of((x, z) -> rock, Interpolated.of(value -> 0D, value -> rock)), 11, -4, false));
when(context.getFluid()).thenReturn(new ChunkedDataCache<>(
ProceduralStream.of((x, z) -> null, Interpolated.of(value -> 0D, value -> null)), 11, -4, false));
when(biome.generateLayers(eq(dimension), anyDouble(), anyDouble(), any(RNG.class),
anyInt(), anyInt(), eq(data), eq(complex))).thenReturn(new KList<>(List.of(grass)));
Hunk<NativeBlockState> output = Hunk.newArrayHunk(1, 64, 1);
@@ -155,6 +155,8 @@ public class IrisTerrainNormalActuatorStrataCutTest {
ProceduralStream.of((x, z) -> region, Interpolated.of(value -> 0D, value -> region)), 11, -4, false));
when(context.getRock()).thenReturn(new ChunkedDataCache<>(
ProceduralStream.of((x, z) -> rock, Interpolated.of(value -> 0D, value -> rock)), 11, -4, false));
when(context.getFluid()).thenReturn(new ChunkedDataCache<>(
ProceduralStream.of((x, z) -> null, Interpolated.of(value -> 0D, value -> null)), 11, -4, false));
when(biome.generateLayers(eq(dimension), anyDouble(), anyDouble(), any(RNG.class),
anyInt(), anyInt(), eq(data), eq(complex))).thenReturn(new KList<>(List.of(grass, dirt)));
when(biome.generateCeilingLayers(eq(dimension), anyDouble(), anyDouble(), any(RNG.class),