Generation and runtime fixes

- Surface detail blends raw noise with cached six-block interpolation
- Locked biome layers sample only the requested ranges
- Surface layers skip noise when slope requirements fail
- Stilt supports exclude configured materials and block entities
- Ice and glass remain eligible foundation materials
- Block-drop routing filters materials before loading biome history
- Vision displays biome previews before river refinement
- Interrupted previews retain reusable partial tiles
- Hydrology waiters stop without cancelling shared planning tasks
- Regression tests cover generation, supports, drop routing, and preview cancellation
This commit is contained in:
Brian Neumann-Fopiano committed 2026-09-13 17:00:33 -04:00
1 parent e3c6a32968
commit 9a1e4b91d1
25 files changed
+1290 -84

No files matched your search

@@ -24,6 +24,8 @@ 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.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@RunWith(Parameterized.class)
@@ -70,6 +72,7 @@ public final class IrisBiomeLayerSlopeSourceTest {
assertEquals(1, ledge.size());
assertSame(block, ledge.get(0));
assertTrue(cap.isEmpty());
verify(heightGenerator, times(1)).fit(1, 1, 12D, -8D);
}
@Test
@@ -109,5 +112,6 @@ public final class IrisBiomeLayerSlopeSourceTest {
assertTrue(hostLayers.isEmpty());
assertEquals(1, sourceLayers.size());
assertSame(surfaceBlock, sourceLayers.get(0));
verify(heightGenerator, times(1)).fit(1, 1, 12D, -8D);
}
}
@@ -1,14 +1,30 @@
package art.arcane.iris.generation.biome;
import art.arcane.iris.generation.noise.NoiseStyle;
import art.arcane.iris.generation.terrain.IrisSlopeClip;
import art.arcane.iris.spi.IrisLogging;
import art.arcane.iris.spi.PlatformBlockState;
import art.arcane.volmlib.util.collection.KList;
import art.arcane.volmlib.util.math.RNG;
import art.arcane.volmlib.util.noise.CNG;
import art.arcane.volmlib.util.stream.ProceduralStream;
import org.junit.Test;
import org.mockito.MockedStatic;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyDouble;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
public class IrisBiomeLockedLayerTest {
@Test
@@ -32,4 +48,133 @@ public class IrisBiomeLockedLayerTest {
}
}
}
@Test
public void sampledBandsMatchExpandedVariableMixedPalettes() {
PlatformBlockState first = mock(PlatformBlockState.class);
PlatformBlockState second = mock(PlatformBlockState.class);
PlatformBlockState third = mock(PlatformBlockState.class);
long[] seeds = {1L, 37L, -291L, Long.MAX_VALUE};
int[] heights = {Integer.MIN_VALUE, -1000, -1, 0, 509, 512, 513, 1000, Integer.MAX_VALUE};
int[] depths = {0, 1, 7, 39};
double[] coordinates = {-71.5D, -1D, 0D, 13.25D, 127D};
for (long seed : seeds) {
RNG rng = new RNG(seed);
IrisBiome biome = new IrisBiome().setLockLayers(true).setLockLayersMax(31).setLayers(new KList<>(
palette(0, 0, 1D, first),
palette(2, 5, 0.6D, first, second, second),
palette(0, 3, 3D, third, first),
palette(0, 0, 1D, second),
palette(1, 4, 1.5D, second, third, first, third),
palette(0, 0, 1D, third)));
for (double x : coordinates) {
for (int height : heights) {
for (int depth : depths) {
assertEquals(expandedBands(biome, x, -x, rng, depth, height, 0D),
biome.generateLockedLayers(x, -x, rng, depth, height, null, null));
}
}
}
}
}
@Test
public void nullEntriesAndRepeatedCyclesMatchExpandedBands() {
PlatformBlockState block = mock(PlatformBlockState.class);
IrisBiome biome = new IrisBiome().setLockLayers(true).setLockLayersMax(30).setLayers(new KList<>(
palette(1, 1, 1D, block), palette(2, 2, 1D, (PlatformBlockState) null)));
RNG rng = new RNG(37L);
assertEquals(expandedBands(biome, 3D, -11D, rng, 30, 1000, 0D),
biome.generateLockedLayers(3D, -11D, rng, 30, 1000, null, null));
}
@Test
public void zeroThicknessAndEmptyPalettesProduceNoBands() {
IrisBiome biome = new IrisBiome().setLockLayers(true).setLayers(new KList<>());
RNG rng = new RNG(37L);
assertTrue(biome.generateLockedLayers(0D, 0D, rng, 7, 64, null, null).isEmpty());
biome.setLayers(new KList<>(palette(0, 0, 1D, mock(PlatformBlockState.class))));
assertTrue(biome.generateLockedLayers(0D, 0D, rng, 7, 64, null, null).isEmpty());
assertTrue(biome.generateLockedLayers(0D, 0D, rng, -1, 64, null, null).isEmpty());
}
@Test
public void resolvesOnlyRequestedPositionsOfLongBands() {
IrisBiomePaletteLayer layer = spy(palette(1000, 1000, 0.6D,
mock(PlatformBlockState.class), mock(PlatformBlockState.class)));
IrisBiome biome = new IrisBiome().setLockLayers(true).setLayers(new KList<>(layer));
RNG rng = new RNG(37L);
assertEquals(7, biome.generateLockedLayers(13D, -27D, rng, 7, 64, null, null).size());
verify(layer, times(7)).get(same(rng), anyInt(), anyDouble(), anyDouble(), anyDouble(), isNull());
}
@Test
public void repeatedCyclesResolveEachPositionOnce() {
IrisBiomePaletteLayer layer = spy(palette(3, 3, 0.6D,
mock(PlatformBlockState.class), mock(PlatformBlockState.class)));
IrisBiome biome = new IrisBiome().setLockLayers(true).setLockLayersMax(100).setLayers(new KList<>(layer));
RNG rng = new RNG(37L);
assertEquals(100, biome.generateLockedLayers(13D, -27D, rng, 100, 64, null, null).size());
verify(layer, times(3)).get(same(rng), anyInt(), anyDouble(), anyDouble(), anyDouble(), isNull());
}
@Test
public void slopeFilteredBandsMatchExpandedCycle() {
IrisBiome biome = new IrisBiome().setLockLayers(true).setLockLayersMax(30).setLayers(new KList<>(
palette(2, 4, 0.6D, mock(PlatformBlockState.class)),
palette(3, 8, 1D, mock(PlatformBlockState.class)).setSlopeCondition(new IrisSlopeClip(3D, 255D)),
palette(1, 5, 2D, mock(PlatformBlockState.class)).setSlopeCondition(new IrisSlopeClip(0D, 3D))));
RNG rng = new RNG(37L);
double[] slopes = {0D, 3D, 8D};
for (double slope : slopes) {
ProceduralStream<Double> slopeStream = ProceduralStream.ofDouble((x, z) -> slope);
assertEquals(expandedBands(biome, 13D, -27D, rng, 30, 64, slope),
biome.generateLayersWithSlope(null, 13D, -27D, rng, 30, 64, null, slopeStream));
}
}
@Test
public void sampledPaletteFailuresAreReported() {
IrisBiomePaletteLayer layer = spy(palette(1, 1, 1D, mock(PlatformBlockState.class)));
IrisBiome biome = new IrisBiome().setLockLayers(true).setLayers(new KList<>(layer));
RNG rng = new RNG(37L);
IllegalStateException failure = new IllegalStateException("Palette unavailable");
doThrow(failure).when(layer).get(same(rng), anyInt(), anyDouble(), anyDouble(), anyDouble(), isNull());
try (MockedStatic<IrisLogging> logging = mockStatic(IrisLogging.class)) {
assertTrue(biome.generateLockedLayers(0D, 0D, rng, 7, 64, null, null).isEmpty());
logging.verify(() -> IrisLogging.reportError(failure));
}
}
private static IrisBiomePaletteLayer palette(int minimum, int maximum, double zoom, PlatformBlockState... blocks) {
IrisBiomePaletteLayer layer = new IrisBiomePaletteLayer().setMinHeight(minimum).setMaxHeight(maximum)
.setZoom(zoom).setStyle(NoiseStyle.NOWHERE.style());
layer.getBlockData().aquire(() -> new KList<>(blocks));
return layer;
}
private static KList<PlatformBlockState> expandedBands(IrisBiome biome, double x, double z, RNG rng,
int maxDepth, int height, double slope) {
KList<PlatformBlockState> expanded = new KList<>();
KList<PlatformBlockState> result = new KList<>();
KList<CNG> heights = biome.getLayerHeightGenerators(rng, null);
for (int index = 0; index < biome.getLayers().size(); index++) {
IrisBiomePaletteLayer layer = biome.getLayers().get(index);
double zoom = layer.getZoom();
int thickness = heights.get(index).fit(layer.getMinHeight(), layer.getMaxHeight(), x / zoom, z / zoom);
if (!layer.getSlopeCondition().isValid(slope)) {
continue;
}
for (int offset = 0; offset < thickness; offset++) {
expanded.add(layer.get(rng, index + offset, (x + offset) / zoom, offset, (z - offset) / zoom, null));
}
}
if (!expanded.isEmpty()) {
for (int depth = 0; depth < Math.min(maxDepth, biome.getLockLayersMax()); depth++) {
result.add(expanded.get(Math.floorMod(512L - height - depth, expanded.size())));
}
}
return result;
}
}
@@ -50,6 +50,57 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.when;
public class HydrologyTileCacheTest {
@Test
public void interruptedWaiterExitsWithoutCancellingTheSharedPlan() throws Exception {
HydrologyPlanner planner = mock(HydrologyPlanner.class);
HydrologyTile tile = mock(HydrologyTile.class);
HydrologyTileKey key = new HydrologyTileKey(0, 0);
CountDownLatch started = new CountDownLatch(1);
CountDownLatch release = new CountDownLatch(1);
CountDownLatch waiterExited = new CountDownLatch(1);
AtomicBoolean interrupted = new AtomicBoolean();
AtomicReference<Thread> waiterThread = new AtomicReference<>();
when(planner.plan(key)).thenAnswer(invocation -> {
started.countDown();
assertTrue(release.await(5L, TimeUnit.SECONDS));
return tile;
});
ExecutorService workers = Executors.newSingleThreadExecutor();
ExecutorService callers = Executors.newFixedThreadPool(2);
HydrologyTileCache cache = new HydrologyTileCache(planner, 4, workers);
try {
Future<?> waiter = callers.submit(() -> {
waiterThread.set(Thread.currentThread());
try {
assertThrows(CancellationException.class, () -> cache.get(key));
interrupted.set(Thread.currentThread().isInterrupted());
} finally {
waiterExited.countDown();
}
});
assertTrue(started.await(5L, TimeUnit.SECONDS));
Future<HydrologyTile> other = callers.submit(() -> cache.get(key));
waiterThread.get().interrupt();
assertTrue(waiterExited.await(2L, TimeUnit.SECONDS));
waiter.get(2L, TimeUnit.SECONDS);
assertTrue(interrupted.get());
assertFalse(other.isDone());
release.countDown();
assertSame(tile, other.get(5L, TimeUnit.SECONDS));
assertSame(tile, cache.get(key));
verify(planner, times(1)).plan(key);
} finally {
release.countDown();
workers.shutdownNow();
callers.shutdownNow();
assertTrue(workers.awaitTermination(5L, TimeUnit.SECONDS));
assertTrue(callers.awaitTermination(5L, TimeUnit.SECONDS));
cache.close();
}
}
@Test
public void closeDrainsBackgroundPlanningAndRejectsNewDemand() throws Exception {
HydrologyPlanner planner = mock(HydrologyPlanner.class);
@@ -0,0 +1,194 @@
package art.arcane.iris.generation.noise;
import art.arcane.iris.generation.runtime.Engine;
import art.arcane.iris.pack.loading.IrisData;
import art.arcane.volmlib.util.collection.KList;
import org.junit.Test;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.ToDoubleFunction;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class IrisGeneratorSurfaceDetailTest {
@Test
public void defaultUsesTheOriginalSampleOnce() {
AtomicInteger samples = new AtomicInteger();
IrisGenerator generator = generator((seed, x, z) -> {
samples.incrementAndGet();
return x * 0.01D + z * 0.02D;
});
assertEquals(0.07D, generator.getHeight(1, 3, 27L), 1e-12D);
assertEquals(1, samples.get());
}
@Test
public void halvesSubgridDetailAndPreservesLatticeHeights() {
IrisGenerator generator = generator((seed, x, z) ->
(int) x % 6 == 0 && (int) z % 6 == 0 ? 0.2D : 0.8D);
generator.setSurfaceDetail(0.5D);
for (int z = -12; z <= 12; z++) {
for (int x = -12; x <= 12; x++) {
double expected = x % 6 == 0 && z % 6 == 0 ? 0.2D : 0.5D;
assertEquals(expected, generator.getHeight(x, z, 27L), 1e-12D);
}
}
}
@Test
public void preservesPlanarSlopesAtNegativeAndFractionalCoordinates() {
IrisGenerator generator = generator((seed, x, z) -> 0.5D + x * 0.01D + z * 0.02D);
generator.setSurfaceDetail(0.5D);
for (double z = -4.25D; z < 4D; z += 0.5D) {
for (double x = -4.75D; x < 4D; x += 0.5D) {
assertEquals(0.5D + x * 0.01D + z * 0.02D,
generator.getHeight(x, z, 27L), 1e-12D);
}
}
}
@Test
public void reusesLatticeSamplesDuringDenseGeneration() {
AtomicInteger samples = new AtomicInteger();
IrisGenerator generator = generator((seed, x, z) -> {
samples.incrementAndGet();
return 0.5D;
});
generator.setSurfaceDetail(0.5D);
for (int z = 0; z < 16; z++) {
for (int x = 0; x < 16; x++) {
assertEquals(0.5D, generator.getHeight(x, z, 27L), 0D);
}
}
assertEquals(263, samples.get());
assertTrue(samples.get() < 256 * 1.1D);
}
@Test
public void fourBlockPreviewSamplingReusesCoarseCorners() {
AtomicInteger samples = new AtomicInteger();
IrisGenerator generator = generator((seed, x, z) -> {
samples.incrementAndGet();
return 0.5D;
});
generator.setSurfaceDetail(0.5D);
for (int z = -16; z < 16; z += 4) {
for (int x = -16; x < 16; x += 4) {
generator.getHeight(x, z, 27L);
}
}
assertEquals(91, samples.get());
}
@Test
public void cachedLatticeHeightsRemainIsolatedBySeed() {
IrisGenerator generator = generator((seed, x, z) -> seed * 0.000001D);
generator.setSurfaceDetail(0.5D);
double first = generator.getHeight(1, 1, 27L);
double second = generator.getHeight(1, 1, 28L);
assertEquals(0.000001D, second - first, 1e-12D);
assertEquals(first, generator.getHeight(1, 1, 27L), 0D);
}
@Test
public void cachedLatticeHeightsRemainIsolatedByEngine() {
Engine firstEngine = mock(Engine.class);
Engine secondEngine = mock(Engine.class);
AtomicReference<Engine> activeEngine = new AtomicReference<>(firstEngine);
IrisData data = mock(IrisData.class);
when(data.getEngine()).thenAnswer(ignored -> activeEngine.get());
IrisGenerator generator = new IrisGenerator().setSurfaceDetail(0.5D).setComposite(new KList<>(
new ContextNoise(loader -> loader.getEngine() == firstEngine ? 0.2D : 0.8D)));
generator.setLoader(data);
assertEquals(0.2D, generator.getHeight(0, 0, 27L), 1e-12D);
assertEquals(0.2D, generator.getHeight(1, 1, 27L), 1e-12D);
activeEngine.set(secondEngine);
assertEquals(0.8D, generator.getHeight(0, 0, 27L), 1e-12D);
assertEquals(0.8D, generator.getHeight(1, 1, 27L), 1e-12D);
activeEngine.set(firstEngine);
assertEquals(0.2D, generator.getHeight(1, 1, 27L), 1e-12D);
}
@Test
public void cachedLatticeHeightsRemainIsolatedByLoader() {
Engine engine = mock(Engine.class);
IrisData firstData = mock(IrisData.class);
IrisData secondData = mock(IrisData.class);
when(firstData.getEngine()).thenReturn(engine);
when(secondData.getEngine()).thenReturn(engine);
IrisGenerator generator = new IrisGenerator().setSurfaceDetail(0.5D).setComposite(new KList<>(
new ContextNoise(loader -> loader == firstData ? 0.2D : 0.8D)));
generator.setLoader(firstData);
assertEquals(0.2D, generator.getHeight(0, 0, 27L), 1e-12D);
assertEquals(0.2D, generator.getHeight(1, 1, 27L), 1e-12D);
generator.setLoader(secondData);
assertEquals(0.8D, generator.getHeight(0, 0, 27L), 1e-12D);
assertEquals(0.8D, generator.getHeight(1, 1, 27L), 1e-12D);
generator.setLoader(firstData);
assertEquals(0.2D, generator.getHeight(1, 1, 27L), 1e-12D);
}
@Test
public void rescalingClearsCachedLatticeHeights() {
IrisGenerator generator = generator((seed, x, z) -> x * 0.01D + z * 0.02D);
generator.setSurfaceDetail(0.5D);
assertEquals(0.06D, generator.getHeight(2, 2, 27L), 1e-12D);
generator.rescale(2D);
assertEquals(0.12D, generator.getHeight(2, 2, 27L), 1e-12D);
}
@Test(expected = IllegalArgumentException.class)
public void rejectsNonfiniteDetail() {
generator((seed, x, z) -> 0D).setSurfaceDetail(Double.NaN).getHeight(1, 1, 27L);
}
private static IrisGenerator generator(Sampler sampler) {
return new IrisGenerator().setComposite(new KList<>(new SampledNoise(sampler)));
}
private interface Sampler {
double sample(long seed, double x, double z);
}
private static final class SampledNoise extends IrisNoiseGenerator {
private final Sampler sampler;
private SampledNoise(Sampler sampler) {
this.sampler = sampler;
}
@Override
public double getNoise(long superSeed, double x, double z, IrisData data) {
return sampler.sample(superSeed, x, z);
}
}
private static final class ContextNoise extends IrisNoiseGenerator {
private final ToDoubleFunction<IrisData> sampler;
private ContextNoise(ToDoubleFunction<IrisData> sampler) {
this.sampler = sampler;
}
@Override
public double getNoise(long superSeed, double x, double z, IrisData data) {
return sampler.applyAsDouble(data);
}
}
}
@@ -12,6 +12,7 @@ import art.arcane.iris.platform.bukkit.BukkitWorldBinding;
import art.arcane.iris.world.task.J;
import art.arcane.volmlib.util.collection.KList;
import org.bukkit.Location;
import org.bukkit.GameMode;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.data.BlockData;
@@ -41,6 +42,30 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class WorldBlockDropRouterTest {
@Test
public void packWithoutDropRulesAllowsTheFirstCreativeAndSurvivalBreakWhileBiomesAreCold() {
for (GameMode mode : new GameMode[] {GameMode.CREATIVE, GameMode.SURVIVAL}) {
try (Fixture fixture = new Fixture()) {
when(fixture.data.hasBlockDropRules("minecraft:stone")).thenReturn(false);
when(fixture.data.hasBlockDropRules("minecraft:grass_block")).thenReturn(true);
when(fixture.player.getGameMode()).thenReturn(mode);
fixture.queries.when(() -> EngineBukkitOps.getBiome(fixture.engine, fixture.location))
.thenThrow(new SavedBiomeUnavailableException("Biome loading", true));
BlockBreakEvent event = fixture.event();
fixture.router.onBlockBreak(event);
assertFalse(mode.name(), event.isCancelled());
assertTrue(event.isDropItems());
fixture.queries.verifyNoInteractions();
verify(fixture.drops, never()).fillDrops(anyBoolean(), any());
assertEquals(1, fixture.delayed.size());
fixture.delayed.remove().run();
assertEquals(1, fixture.markers.size());
}
}
}
@Test
public void coldBiomeCancelsBeforeChangingDropsOrSchedulingWork() {
try (Fixture fixture = new Fixture()) {
@@ -206,6 +231,7 @@ public class WorldBlockDropRouterTest {
when(manager.getEngine()).thenReturn(engine);
when(manager.getTarget()).thenReturn(target);
when(manager.getData()).thenReturn(data);
when(data.hasBlockDropRules(anyString())).thenReturn(true);
when(target.getWorld()).thenReturn(irisWorld);
when(engine.getWorld()).thenReturn(irisWorld);
when(irisWorld.minHeight()).thenReturn(-64);
@@ -217,6 +243,7 @@ public class WorldBlockDropRouterTest {
when(block.getZ()).thenReturn(32);
when(block.getLocation()).thenReturn(location);
when(block.getBlockData()).thenReturn(blockData);
when(blockData.getAsString()).thenReturn("minecraft:stone");
when(biome.getBlockDrops()).thenReturn(new KList<>(drops));
when(region.getBlockDrops()).thenReturn(new KList<>());
when(dimension.getBlockDrops()).thenReturn(new KList<>());
@@ -3,6 +3,9 @@ package art.arcane.iris.pack.loading;
import art.arcane.iris.configuration.IrisSettings;
import art.arcane.iris.generation.runtime.PreservationRegistry;
import art.arcane.iris.generation.noise.IrisGenerator;
import art.arcane.iris.generation.block.B;
import art.arcane.iris.spi.PlatformBlockState;
import art.arcane.iris.pack.validation.ContentGate;
import art.arcane.iris.spi.IrisServices;
import art.arcane.iris.testsupport.PlatformLeakGuard;
import org.junit.After;
@@ -11,6 +14,7 @@ import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.mockito.MockedStatic;
import java.io.File;
import java.nio.file.Files;
@@ -25,6 +29,8 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;
public class IrisDataAuthoringCacheTest {
@ClassRule
@@ -105,4 +111,61 @@ public class IrisDataAuthoringCacheTest {
assertSame(keys, entry.getKey().getPossibleKeys());
}
}
@Test
public void dropRulePresenceIncludesEachScopeAndIsResetByPackReload() throws Exception {
for (String folder : List.of("dimensions", "regions", "biomes")) {
Path root = temporary.newFolder(folder + "-drops").toPath();
Files.createDirectories(root.resolve(folder));
Path resource = root.resolve(folder + "/sample.json");
Files.writeString(resource, "{}");
source = IrisData.openRuntime(root.toFile());
try (MockedStatic<B> blocks = mockStatic(B.class)) {
PlatformBlockState state = mock(PlatformBlockState.class);
when(state.placementBaseState()).thenReturn(state);
when(state.materialKey()).thenReturn("minecraft:stone");
blocks.when(() -> B.getStateOrNull("minecraft:stone", false)).thenReturn(state);
source.setContentGate(mock(ContentGate.class));
assertTrue(source.hasBlockDropRules("minecraft:stone"));
source.prepareBlockDropRules();
assertFalse(source.hasBlockDropRules("minecraft:stone"));
Files.writeString(resource, "{\"blockDrops\":[{\"blocks\":[{\"block\":\"minecraft:stone\"}]}]}");
source.hotloaded();
source.setContentGate(mock(ContentGate.class));
assertTrue(source.hasBlockDropRules("minecraft:stone"));
source.prepareBlockDropRules();
assertTrue(source.hasBlockDropRules("minecraft:stone"));
assertFalse(source.hasBlockDropRules("minecraft:grass_block"));
} finally {
source.close();
source = null;
}
}
}
@Test
public void customDropCandidatesUseTheResolvedCarrierAndKeepExactStateRulesEligible() throws Exception {
Path root = temporary.newFolder("custom-drops").toPath();
Files.createDirectories(root.resolve("biomes"));
Files.writeString(root.resolve("biomes/sample.json"), """
{"blockDrops":[{"exactBlocks":true,"blocks":[{"block":"rocks:ruby_ore"}]}]}
""");
source = IrisData.openRuntime(root.toFile());
source.setContentGate(mock(ContentGate.class));
PlatformBlockState custom = mock(PlatformBlockState.class);
PlatformBlockState carrier = mock(PlatformBlockState.class);
when(custom.materialKey()).thenReturn("rocks:ruby_ore");
when(custom.placementBaseState()).thenReturn(carrier);
when(carrier.materialKey()).thenReturn("minecraft:note_block");
try (MockedStatic<B> blocks = mockStatic(B.class)) {
blocks.when(() -> B.getStateOrNull("rocks:ruby_ore", false)).thenReturn(custom);
source.prepareBlockDropRules();
assertTrue(source.hasBlockDropRules("minecraft:note_block"));
assertFalse(source.hasBlockDropRules("minecraft:stone"));
assertFalse(source.hasBlockDropRules("rocks:ruby_ore"));
}
}
}
@@ -78,7 +78,7 @@ public class SchemaBuilderIdentityTest {
@ClassRule
public static final PlatformLeakGuard PLATFORM_GUARD = PlatformLeakGuard.clean();
private static final String EXPECTED_SCHEMA_DIGEST = "da993d49349b961b46f86a4966b8d2ab228a6b005c2a4ee279018e03e1139783";
private static final String EXPECTED_SCHEMA_DIGEST = "545cbfcdaa4b4c8dd03fca96f6b2617c37bbe9bf73d2011679d2fabcf3f2a6be";
private static final List<Class<?>> SCHEMA_ROOTS = List.of(
IrisDimension.class,
@@ -22,6 +22,7 @@ import art.arcane.iris.pack.loading.IrisData;
import art.arcane.iris.pack.loading.IrisRegistrant;
import art.arcane.iris.pack.loading.ResourceLoader;
import art.arcane.iris.generation.block.IrisBlockData;
import art.arcane.iris.generation.decoration.IrisStiltSettings;
import art.arcane.iris.generation.noise.IrisExpression;
import art.arcane.iris.generation.terrain.IrisDimension;
import art.arcane.iris.structure.jigsaw.IrisJigsawPiece;
@@ -124,6 +125,17 @@ public class SchemaBuilderParityTest {
}
}
@Test
public void stiltExclusionsExposeRegisteredMaterialKeys() {
IrisData data = structureSchemaData();
when(data.getBlockLoader().getPossibleKeys()).thenReturn(new String[] {"minecraft:barrel", "minecraft:calcite"});
JSONObject schema = new SchemaBuilder(IrisStiltSettings.class, data).construct();
JSONObject exclude = rootProperties(schema).getJSONObject("exclude");
assertEquals("array", exclude.getString("type"));
assertTrue(exclude.getJSONObject("items").getString("$ref").endsWith("enum-block-type"));
}
@Test
public void registryDependentEnumsMatchLegacyTransforms() {
JSONObject definitions = new SchemaBuilder(RegistryModel.class, (IrisData) null).construct().getJSONObject("definitions");
@@ -1,6 +1,11 @@
package art.arcane.iris.structure.object;
import art.arcane.iris.generation.block.TileData;
import art.arcane.iris.generation.decoration.IrisStiltSettings;
import art.arcane.iris.generation.block.IrisBlockData;
import art.arcane.iris.generation.decoration.formation.FormationGenerator;
import art.arcane.iris.generation.decoration.formation.IrisFormation;
import art.arcane.iris.generation.decoration.formation.IrisFormationForm;
import art.arcane.iris.generation.noise.IrisGeneratorStyle;
import art.arcane.iris.generation.noise.NoiseStyle;
import art.arcane.iris.generation.terrain.IrisMaterialPalette;
@@ -415,6 +420,194 @@ public class IrisObjectPlacementRunnerRegressionTest {
assertTrue(placer.writes().isEmpty());
}
@Test
public void everyStiltModePreservesTheBarrelAndItsTileWithoutRepeatingIt() {
PlatformBlockState barrel = state("minecraft:barrel[facing=up,open=false]", true);
when(barrel.materialKey()).thenReturn("minecraft:barrel");
when(barrel.isStorage()).thenReturn(true);
when(barrel.hasTileEntity()).thenReturn(true);
TileData tile = new TileData("minecraft:barrel", new KMap<>());
for (ObjectPlaceMode mode : List.of(ObjectPlaceMode.STILT, ObjectPlaceMode.FAST_STILT,
ObjectPlaceMode.MIN_STILT, ObjectPlaceMode.FAST_MIN_STILT, ObjectPlaceMode.CENTER_STILT,
ObjectPlaceMode.ERODE_STILT, ObjectPlaceMode.ORGANIC_STILT, ObjectPlaceMode.CEILING_HANG)) {
RecordingPlacer placer = new RecordingPlacer(null);
for (int x = -2; x <= 2; x++) {
for (int z = -2; z <= 2; z++) {
placer.set(x, ANCHOR_Y, z, solid);
placer.set(x, ANCHOR_Y + 20, z, solid);
}
}
placer.writes().clear();
IrisObject object = boxObject(5, 1, 5);
object.setUnsigned(3, 0, 2, barrel);
object.setUnsignedTile(3, 0, 2, tile);
IrisObjectPlacement placement = placement().setMode(mode)
.setRotation(IrisObjectRotation.of(0, 0, 0))
.setStiltSettings(new IrisStiltSettings().setOrganicJitter(0).setOrganicScratch(0));
object.place(0, ANCHOR_Y + 10, 0, placer, placement, new RNG(2L), data);
assertEquals(mode.name(), 1, placer.writesOf(barrel).size());
assertSame(mode.name(), barrel, placer.get(1, ANCHOR_Y + 10, 0));
assertSame(mode.name(), tile, placer.getData(1, ANCHOR_Y + 10, 0, TileData.class));
assertTrue(mode.name(), placer.writesOf(solid).stream()
.anyMatch(write -> write.y() != ANCHOR_Y + 10));
}
}
@Test
public void excludedSourceMaterialKeepsItsBlockAndDoesNotUseTheStiltPalette() {
PlatformBlockState calcite = state("minecraft:calcite", true);
PlatformBlockState cobblestone = state("minecraft:cobblestone", true);
IrisMaterialPalette palette = mock(IrisMaterialPalette.class);
when(palette.get(any(RNG.class), anyDouble(), anyDouble(), anyDouble(), any(IrisData.class)))
.thenReturn(cobblestone);
IrisObject object = lineObject(3);
object.setUnsigned(2, 0, 0, calcite);
RecordingPlacer placer = new RecordingPlacer(null);
IrisObjectPlacement placement = placement().setMode(ObjectPlaceMode.CENTER_STILT)
.setRotation(IrisObjectRotation.of(0, 0, 0))
.setStiltSettings(new IrisStiltSettings().setExclude(new KList<>("CALCITE")).setPalette(palette));
object.place(0, ANCHOR_Y + 10, 0, placer, placement, new RNG(2L), data);
assertEquals(1, placer.writesOf(calcite).size());
assertSame(calcite, placer.get(1, ANCHOR_Y + 10, 0));
assertNull(placer.get(1, ANCHOR_Y + 9, 0));
assertSame(cobblestone, placer.get(0, ANCHOR_Y + 9, 0));
}
@Test
public void translucentFullBlocksGrowFoundationsWhileCrystalModelsRemainUnextended() {
PlatformBlockState ice = state("minecraft:ice", true);
when(ice.isOccluding()).thenReturn(false);
PlatformBlockState cluster = state("minecraft:amethyst_cluster", true);
when(cluster.isOccluding()).thenReturn(false);
IrisObject object = new IrisObject(3, 1, 1);
object.setUnsigned(0, 0, 0, ice);
object.setUnsigned(2, 0, 0, cluster);
IrisMaterialPalette palette = mock(IrisMaterialPalette.class);
when(palette.get(any(RNG.class), anyDouble(), anyDouble(), anyDouble(), any(IrisData.class)))
.thenReturn(solid);
RecordingPlacer placer = new RecordingPlacer(null);
IrisObjectPlacement placement = placement().setMode(ObjectPlaceMode.CENTER_STILT)
.setRotation(IrisObjectRotation.of(0, 0, 0))
.setStiltSettings(new IrisStiltSettings().setPalette(palette));
object.place(0, ANCHOR_Y + 10, 0, placer, placement, new RNG(2L), data);
assertSame(solid, placer.get(-1, ANCHOR_Y + 9, 0));
assertEquals(1, placer.writesOf(cluster).size());
assertNull(placer.get(1, ANCHOR_Y + 9, 0));
}
@Test
public void generatedGlacialOverhangGrowsPackedIceRootsToTerrain() {
PlatformBlockState ice = state("minecraft:ice", true);
when(ice.isOccluding()).thenReturn(false);
PlatformBlockState packedIce = state("minecraft:packed_ice", true);
IrisMaterialPalette body = mock(IrisMaterialPalette.class);
when(body.getPalette()).thenReturn(new KList<>(new IrisBlockData("minecraft:ice")));
when(body.get(any(RNG.class), anyDouble(), anyDouble(), anyDouble(), any(IrisData.class)))
.thenReturn(ice);
IrisMaterialPalette roots = mock(IrisMaterialPalette.class);
when(roots.get(any(RNG.class), anyDouble(), anyDouble(), anyDouble(), any(IrisData.class)))
.thenReturn(packedIce);
IrisFormation formation = new IrisFormation().setForm(IrisFormationForm.OVERHANG)
.setHeightMin(16).setHeightMax(25).setBaseWidthMin(5).setBaseWidthMax(8)
.setOverhangReach(13).setOverhangDrop(6).setRoughness(0.3).setJitter(0.08)
.setBlockPalette(body);
IrisObject object = FormationGenerator.generate(formation, 16, new RNG(5055004L + 16L), data);
RecordingPlacer placer = new RecordingPlacer(null);
for (int x = -32; x <= 32; x++) {
for (int z = -32; z <= 32; z++) {
placer.set(x, ANCHOR_Y, z, solid);
}
}
placer.writes().clear();
IrisObjectPlacement placement = placement().setMode(ObjectPlaceMode.ORGANIC_STILT)
.setRotation(IrisObjectRotation.of(0, 0, 0))
.setStiltSettings(new IrisStiltSettings().setOrganicMaxScan(96).setOrganicJitter(0)
.setOrganicScratch(0).setPalette(roots));
object.place(0, ANCHOR_Y + object.getCenter().getBlockY() + 12, 0, placer, placement,
new RNG(2L), data);
List<BlockWrite> rootWrites = placer.writesOf(packedIce);
assertFalse(rootWrites.isEmpty());
assertTrue(rootWrites.stream().anyMatch(write -> write.y() == ANCHOR_Y + 1));
for (BlockWrite root : rootWrites) {
for (int y = ANCHOR_Y + 1; y < root.y(); y++) {
assertSame(packedIce, placer.get(root.x(), y, root.z()));
}
}
assertFalse(placer.writesOf(ice).isEmpty());
}
@Test
public void excludedLowestBlockDoesNotMoveTheFoundationUpIntoTheOriginalObject() {
PlatformBlockState machine = state("test:machine", true);
when(machine.hasTileEntity()).thenReturn(true);
IrisObject object = new IrisObject(1, 3, 1);
object.setUnsigned(0, 0, 0, machine);
object.setUnsigned(0, 1, 0, solid);
RecordingPlacer placer = new RecordingPlacer(null);
object.place(0, ANCHOR_Y + 10, 0, placer, placement().setMode(ObjectPlaceMode.CENTER_STILT),
new RNG(2L), data);
assertEquals(2, placer.writes().size());
assertSame(machine, placer.get(0, ANCHOR_Y + 9, 0));
}
@Test
public void stiltPaletteCannotIntroduceExcludedMaterialsOrContainers() {
PlatformBlockState calcite = state("minecraft:calcite", true);
PlatformBlockState barrel = state("minecraft:barrel", true);
when(barrel.isStorage()).thenReturn(true);
for (PlatformBlockState replacement : List.of(calcite, barrel)) {
IrisMaterialPalette palette = mock(IrisMaterialPalette.class);
when(palette.get(any(RNG.class), anyDouble(), anyDouble(), anyDouble(), any(IrisData.class)))
.thenReturn(replacement);
RecordingPlacer placer = new RecordingPlacer(null);
IrisObjectPlacement placement = placement().setMode(ObjectPlaceMode.CENTER_STILT)
.setStiltSettings(new IrisStiltSettings().setExclude(new KList<>("minecraft:calcite"))
.setPalette(palette));
lineObject(3).place(0, ANCHOR_Y + 10, 0, placer, placement, new RNG(2L), data);
assertEquals(replacement.key(), 3, placer.writes().size());
assertTrue(placer.writesOf(replacement).isEmpty());
}
}
@Test
public void objectEditsCannotIntroduceExcludedMaterialsOrTileEntitiesIntoStilts() {
PlatformBlockState calcite = state("minecraft:calcite", true);
PlatformBlockState machine = state("test:machine", true);
when(machine.hasTileEntity()).thenReturn(true);
for (PlatformBlockState replacement : List.of(calcite, machine)) {
IrisObjectReplace edit = mock(IrisObjectReplace.class);
IrisMaterialPalette palette = mock(IrisMaterialPalette.class);
when(edit.getChance()).thenReturn(1F);
when(edit.getFind(data)).thenReturn(new KList<>(solid));
when(edit.getReplace(any(RNG.class), anyDouble(), anyDouble(), anyDouble(), any(IrisData.class)))
.thenReturn(replacement);
when(edit.getReplace()).thenReturn(palette);
when(palette.getTile(any(RNG.class), anyDouble(), anyDouble(), anyDouble(), any(IrisData.class)))
.thenReturn(Optional.empty());
RecordingPlacer placer = new RecordingPlacer(null);
IrisObjectPlacement placement = placement().setMode(ObjectPlaceMode.CENTER_STILT)
.setStiltSettings(new IrisStiltSettings().setExclude(new KList<>("minecraft:calcite")));
placement.getEdit().add(edit);
lineObject(3).place(0, ANCHOR_Y + 10, 0, placer, placement, new RNG(2L), data);
assertEquals(replacement.key(), 3, placer.writesOf(replacement).size());
assertTrue(placer.writesOf(replacement).stream().allMatch(write -> write.y() == ANCHOR_Y + 10));
}
}
private IrisObjectPlacement placement() {
IrisObjectPlacement placement = new IrisObjectPlacement();
placement.setMode(ObjectPlaceMode.CENTER_HEIGHT);
@@ -139,6 +139,27 @@ public class IrisRendererPerformanceContractTest {
assertTrue(samples.get() < 128);
}
@Test
public void studioBiomeBaseDoesNotEnterHydrologyPlanning() {
Engine engine = mock(Engine.class);
IrisComplex complex = mock(IrisComplex.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(base.get(org.mockito.ArgumentMatchers.anyDouble(), org.mockito.ArgumentMatchers.anyDouble()))
.thenReturn(biome);
when(biome.getColor(engine, RenderType.BIOME)).thenReturn(Color.GREEN);
BufferedImage image = new IrisRenderer(engine).renderStudioBase(
0D, 0D, 256D, 64, RenderType.BIOME, () -> false);
assertEquals(Color.GREEN.getRGB(), image.getRGB(0, 0));
assertEquals(Color.GREEN.getRGB(), image.getRGB(63, 63));
verify(complex, never()).getHydrologyRuntime();
}
@Test
public void planarStudioHeightPagesInterpolateWithoutPerPixelEngineSampling() {
Engine engine = mock(Engine.class);
@@ -134,6 +134,32 @@ public class IrisRendererRiverTest {
verify(runtime).sampleRenderFootprint(12D, -7D, 20D, 1D);
}
@Test
public void biomeRefinementCopiesTheBaseAndRetainsTheFullRiverOverlay() {
Engine engine = mock(Engine.class);
IrisComplex complex = mock(IrisComplex.class);
IrisHydrologyRuntime runtime = mock(IrisHydrologyRuntime.class);
IrisBiome biome = mock(IrisBiome.class);
@SuppressWarnings("unchecked")
ProceduralStream<IrisBiome> base = mock(ProceduralStream.class);
when(engine.getComplex()).thenReturn(complex);
when(complex.getHydrologyRuntime()).thenReturn(runtime);
when(complex.getBaseBiomeStream()).thenReturn(base);
when(base.get(12D, -7D)).thenReturn(biome);
when(biome.getColor(engine, RenderType.BIOME)).thenReturn(Color.GREEN);
when(runtime.sampleRenderFootprint(12D, -7D, 20D, 1D))
.thenReturn(sample(HydrologyFeatureType.SURFACE_POOL, false));
IrisRenderer renderer = new IrisRenderer(engine);
BufferedImage preview = renderer.renderStudioBase(12D, -7D, 8D, 1, RenderType.BIOME, () -> false);
BufferedImage refined = renderer.refineStudioBiome(12D, -7D, 8D, preview, () -> false);
BufferedImage complete = renderer.renderStudio(12D, -7D, 8D, 1, RenderType.BIOME, () -> false);
assertEquals(Color.GREEN.getRGB(), preview.getRGB(0, 0));
assertNotEquals(preview.getRGB(0, 0), refined.getRGB(0, 0));
assertEquals(complete.getRGB(0, 0), refined.getRGB(0, 0));
}
@Test
public void riverVisionShowsAProjectedCandidateWithoutTreatingItAsAccepted() {
Engine engine = mock(Engine.class);
@@ -11,6 +11,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CancellationException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BooleanSupplier;
@@ -68,7 +69,7 @@ public class VisionRenderControllerTest {
public void everyPublishedPageIsFinalResolutionAndIdenticalRequestsAreCacheHits() throws Exception {
IrisRenderer renderer = mock(IrisRenderer.class);
AtomicInteger calls = new AtomicInteger();
when(renderer.renderStudio(
when(renderer.renderStudioBase(
anyDouble(),
anyDouble(),
anyDouble(),
@@ -84,6 +85,7 @@ public class VisionRenderControllerTest {
BufferedImage.TYPE_INT_RGB
);
});
passThroughRefinement(renderer);
VisionRenderController controller = controller(2);
VisionRenderController.RenderSpec spec = spec(renderer, RenderType.BIOME, 1L, 0D, 0D, 4D, 128, 128);
try {
@@ -153,6 +155,12 @@ public class VisionRenderControllerTest {
latestStarted.countDown();
return new BufferedImage(64, 64, BufferedImage.TYPE_INT_RGB);
});
when(renderer.renderStudioBase(anyDouble(), anyDouble(), anyDouble(), anyInt(),
any(RenderType.class), any(BooleanSupplier.class))).thenAnswer(invocation -> {
latestStarted.countDown();
return new BufferedImage(64, 64, BufferedImage.TYPE_INT_RGB);
});
passThroughRefinement(renderer);
VisionRenderController controller = controller(2);
try {
controller.request(spec(renderer, RenderType.REGION, 1L, 0D, 0D, 4D, 64, 64));
@@ -166,6 +174,103 @@ public class VisionRenderControllerTest {
}
}
@Test
public void completeBaseIsVisibleBeforeBlockedHydrologyRefinementStarts() throws Exception {
IrisRenderer renderer = exactRenderer();
CountDownLatch overlayStarted = new CountDownLatch(1);
CountDownLatch releaseOverlay = new CountDownLatch(1);
AtomicInteger baseCalls = new AtomicInteger();
AtomicInteger overlays = new AtomicInteger();
when(renderer.renderStudioBase(anyDouble(), anyDouble(), anyDouble(), anyInt(),
any(RenderType.class), any(BooleanSupplier.class))).thenAnswer(invocation -> {
baseCalls.incrementAndGet();
return new BufferedImage(64, 64, BufferedImage.TYPE_INT_RGB);
});
when(renderer.refineStudioBiome(anyDouble(), anyDouble(), anyDouble(), any(BufferedImage.class),
any(BooleanSupplier.class))).thenAnswer(invocation -> {
overlays.incrementAndGet();
overlayStarted.countDown();
assertTrue(releaseOverlay.await(2L, TimeUnit.SECONDS));
return invocation.getArgument(3);
});
VisionRenderController controller = controller(3);
try {
VisionRenderController.RenderSpec spec = spec(renderer, RenderType.BIOME, 1L, 0D, 0D, 4D, 192, 128);
VisionRenderController.Frame frame = controller.request(spec);
assertTrue(overlayStarted.await(2L, TimeUnit.SECONDS));
assertEquals(frame.tiles().size(), baseCalls.get());
assertEquals(1, overlays.get());
assertEquals(0, controller.progress(frame).ready());
for (VisionRenderController.VisibleTile tile : frame.tiles()) {
assertEquals(64, controller.image(frame, tile).getWidth());
}
releaseOverlay.countDown();
Await.until("every biome overlay to complete", Duration.ofSeconds(2L),
() -> controller.progress(frame).ready() == frame.tiles().size());
assertEquals(frame.tiles().size(), overlays.get());
VisionRenderController.Frame cached = controller.request(spec);
assertEquals(frame.tiles().size(), controller.progress(cached).ready());
assertEquals(frame.tiles().size(), baseCalls.get());
assertEquals(frame.tiles().size(), overlays.get());
} finally {
releaseOverlay.countDown();
controller.close();
}
}
@Test
public void cancelledOverlayKeepsCachedBaseWithoutMarkingItComplete() throws Exception {
IrisRenderer renderer = exactRenderer();
CountDownLatch staleStarted = new CountDownLatch(1);
CountDownLatch latestStarted = new CountDownLatch(1);
CountDownLatch releaseLatest = new CountDownLatch(1);
AtomicInteger baseCalls = new AtomicInteger();
AtomicInteger overlayCalls = new AtomicInteger();
when(renderer.renderStudioBase(anyDouble(), anyDouble(), anyDouble(), anyInt(),
any(RenderType.class), any(BooleanSupplier.class))).thenAnswer(invocation -> {
baseCalls.incrementAndGet();
return new BufferedImage(64, 64, BufferedImage.TYPE_INT_RGB);
});
when(renderer.refineStudioBiome(anyDouble(), anyDouble(), anyDouble(), any(BufferedImage.class),
any(BooleanSupplier.class))).thenAnswer(invocation -> {
if (overlayCalls.getAndIncrement() == 0) {
staleStarted.countDown();
BooleanSupplier cancelled = invocation.getArgument(4);
while (!cancelled.getAsBoolean()) {
Thread.onSpinWait();
}
throw new CancellationException();
}
latestStarted.countDown();
assertTrue(releaseLatest.await(2L, TimeUnit.SECONDS));
return invocation.getArgument(3);
});
VisionRenderController controller = controller(2);
try {
VisionRenderController.RenderSpec spec = spec(renderer, RenderType.BIOME, 1L, 0D, 0D, 4D, 128, 128);
VisionRenderController.Frame stale = controller.request(spec);
assertTrue(staleStarted.await(2L, TimeUnit.SECONDS));
VisionRenderController.Frame latest = controller.request(spec);
assertTrue(latestStarted.await(2L, TimeUnit.SECONDS));
assertEquals(stale.tiles().size(), baseCalls.get());
assertEquals(0, controller.progress(latest).ready());
for (VisionRenderController.VisibleTile tile : latest.tiles()) {
assertEquals(64, controller.image(latest, tile).getWidth());
}
releaseLatest.countDown();
Await.until("the replacement frame to finish its overlays", Duration.ofSeconds(2L),
() -> controller.progress(latest).ready() == latest.tiles().size());
assertEquals(0, controller.progress(stale).ready());
} finally {
releaseLatest.countDown();
controller.close();
}
}
private static VisionRenderController controller(int workers) {
return new VisionRenderController(
() -> {
@@ -188,9 +293,18 @@ public class VisionRenderControllerTest {
invocation.getArgument(3),
BufferedImage.TYPE_INT_RGB
));
when(renderer.renderStudioBase(anyDouble(), anyDouble(), anyDouble(), anyInt(),
any(RenderType.class), any(BooleanSupplier.class))).thenAnswer(invocation -> new BufferedImage(
invocation.getArgument(3), invocation.getArgument(3), BufferedImage.TYPE_INT_RGB));
passThroughRefinement(renderer);
return renderer;
}
private static void passThroughRefinement(IrisRenderer renderer) {
when(renderer.refineStudioBiome(anyDouble(), anyDouble(), anyDouble(), any(BufferedImage.class),
any(BooleanSupplier.class))).thenAnswer(invocation -> invocation.getArgument(3));
}
private static Set<VisionRenderController.TileKey> keys(VisionRenderController.Frame frame) {
Set<VisionRenderController.TileKey> keys = new HashSet<>();
for (VisionRenderController.VisibleTile tile : frame.tiles()) {
@@ -37,6 +37,7 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
@@ -45,6 +46,7 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
public class SavedBiomeRuntimeTest {
@ClassRule
@@ -102,6 +104,39 @@ public class SavedBiomeRuntimeTest {
}
}
@Test
public void packWithoutDropRulesDoesNotNeedAChunkQueryForTheOnlyEpoch() throws Exception {
IrisData current = mock(IrisData.class);
when(engine.getData()).thenReturn(current);
GenerationManifest manifest = history.manifest();
GenerationEpoch active = manifest.epoch("a".repeat(64)).orElseThrow();
when(manifest.activeEpoch()).thenReturn(active);
when(manifest.epochs()).thenReturn(List.of(active));
try (SavedBiomeRuntime runtime = new SavedBiomeRuntime(engine, history)) {
assertFalse(runtime.mayHaveBlockDropRules("minecraft:stone"));
verifyNoInteractions(store);
when(current.hasBlockDropRules("minecraft:stone")).thenReturn(true);
assertTrue(runtime.mayHaveBlockDropRules("minecraft:stone"));
}
}
@Test
public void unloadedHistoricalPackRetainsDropChecksWhenTheCurrentPackHasNoRules() throws Exception {
when(engine.getData()).thenReturn(mock(IrisData.class));
GenerationManifest manifest = history.manifest();
GenerationEpoch previous = manifest.epoch("a".repeat(64)).orElseThrow();
GenerationEpoch active = mock(GenerationEpoch.class);
when(active.epochId()).thenReturn("b".repeat(64));
when(manifest.activeEpoch()).thenReturn(active);
when(manifest.epochs()).thenReturn(List.of(previous, active));
try (SavedBiomeRuntime runtime = new SavedBiomeRuntime(engine, history)) {
assertTrue(runtime.mayHaveBlockDropRules("minecraft:stone"));
verifyNoInteractions(store);
}
}
@Test
public void chunkReadinessDoesNotRequireEveryCellToHaveAResolvedIdentity() throws Exception {
when(store.get(0, 0)).thenReturn(Optional.of(unresolvedChunk(1)));