Merge W2B-carving

This commit is contained in:
Brian Neumann-Fopiano committed 2026-09-28 14:15:03 -04:00
commit f3b16b784b
22 files changed
+852 -203

No files matched your search

@@ -0,0 +1,175 @@
package art.arcane.iris.generation.mantle;
import art.arcane.iris.generation.hydrology.cave.HydrologyCaveAction;
import art.arcane.iris.generation.hydrology.cave.HydrologyCaveCell;
import art.arcane.iris.world.storage.matter.IrisMatterSupport;
import art.arcane.iris.world.storage.matter.PreObjectMatterCell;
import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
import art.arcane.volmlib.util.matter.IrisMatter;
import art.arcane.volmlib.util.matter.Matter;
import art.arcane.volmlib.util.matter.MatterCavern;
import org.junit.BeforeClass;
import org.junit.Test;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
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.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class CaveTerrainSnapshotTest {
private static final int SECTIONS = 5;
@BeforeClass
public static void registerMatter() {
IrisMatterSupport.ensureRegistered();
}
@Test
public void snapshotMatchesTerrainViewForEveryCellFaceAndResolverInput() {
for (long seed = 1; seed <= 2; seed++) {
MantleChunk<Matter> chunk = randomChunk(new Random(seed), seed % 2 == 0);
CaveTerrainSnapshot snapshot = CaveTerrainSnapshot.capture(chunk, 3, -4);
assertTrue(snapshot.covers(3, -4));
assertFalse(snapshot.covers(3, 4));
boolean anyHydrology = false;
for (int x = 0; x < 16; x++) {
for (int y = -2; y < SECTIONS * 16 + 3; y++) {
for (int z = 0; z < 16; z++) {
String cell = "seed=" + seed + " " + x + "," + y + "," + z;
assertSame(cell, TerrainMatterView.get(chunk, x, y, z, MatterCavern.class), snapshot.cavern(x, y, z));
HydrologyCaveCell hydrology = TerrainMatterView.get(chunk, x, y, z, HydrologyCaveCell.class);
anyHydrology |= hydrology != null;
assertSame(cell, hydrology, snapshot.hydrology(x, y, z));
assertSame(cell, TerrainMatterView.getComposedCavern(chunk, x, y, z), snapshot.composedCavern(x, y, z));
}
}
}
assertEquals(anyHydrology, snapshot.hasHydrology());
for (TerrainMatterView.Face face : TerrainMatterView.Face.values()) {
for (int height : new int[]{0, 1, 37, SECTIONS * 16, SECTIONS * 16 + 9}) {
MatterCavern[] expected = TerrainMatterView.getComposedFace(chunk, face, height);
MatterCavern[] actual = snapshot.composedFace(face, height);
assertEquals(expected.length, actual.length);
for (int index = 0; index < expected.length; index++) {
assertSame(face + " " + index, expected[index], actual[index]);
}
}
}
assertEquals(legacyResolverInputs(chunk), snapshotResolverInputs(snapshot));
}
}
@Test
public void chunksWithoutHydrologyAnswerNullWithoutCells() {
Matter[] sections = new Matter[SECTIONS];
sections[1] = new IrisMatter(16, 16, 16);
MatterCavern cavern = new MatterCavern(true, "", (byte) 0);
sections[1].slice(MatterCavern.class).set(4, 5, 6, cavern);
sections[1].slice(PreObjectMatterCell.class).set(4, 6, 6, PreObjectMatterCell.hydrology(null));
CaveTerrainSnapshot snapshot = CaveTerrainSnapshot.capture(chunk(sections), 0, 0);
assertFalse(snapshot.hasHydrology());
assertNull(snapshot.hydrology(4, 22, 6));
assertSame(cavern, snapshot.composedCavern(4, 21, 6));
assertNull(snapshot.cavern(4, 22, 6));
}
private static Map<String, String> legacyResolverInputs(MantleChunk<Matter> chunk) {
Map<String, String> inputs = new HashMap<>();
for (int x = 0; x < 16; x++) {
for (int y = 0; y < SECTIONS * 16; y++) {
for (int z = 0; z < 16; z++) {
MatterCavern cavern = TerrainMatterView.get(chunk, x, y, z, MatterCavern.class);
HydrologyCaveCell hydrology = TerrainMatterView.get(chunk, x, y, z, HydrologyCaveCell.class);
if (cavern != null || hydrology != null) {
inputs.put(x + ":" + y + ":" + z, identity(cavern) + "/" + identity(hydrology));
}
}
}
}
return inputs;
}
private static Map<String, String> snapshotResolverInputs(CaveTerrainSnapshot snapshot) {
Map<String, String> inputs = new HashMap<>();
snapshot.forEachCell((x, y, z, cavern, hydrology) -> assertNull(inputs.put(x + ":" + y + ":" + z,
identity(cavern) + "/" + identity(hydrology))));
return inputs;
}
private static String identity(Object value) {
return value == null ? "null" : Integer.toHexString(System.identityHashCode(value));
}
private static MantleChunk<Matter> randomChunk(Random random, boolean manyCaverns) {
MatterCavern[] caverns = new MatterCavern[manyCaverns ? 40 : 4];
for (int index = 0; index < caverns.length; index++) {
caverns[index] = new MatterCavern(index % 3 != 0, "biome-" + index, (byte) (index % 4));
}
HydrologyCaveCell[] hydrology = new HydrologyCaveCell[HydrologyCaveAction.values().length];
for (int index = 0; index < hydrology.length; index++) {
hydrology[index] = new HydrologyCaveCell(HydrologyCaveAction.values()[index], "river", "flooded-" + index);
}
Matter[] sections = new Matter[SECTIONS];
for (int section = 0; section < SECTIONS; section++) {
if (section == 2) {
continue;
}
Matter matter = new IrisMatter(16, 16, 16);
sections[section] = matter;
int mode = section % 4;
for (int x = 0; x < 16; x++) {
for (int y = 0; y < 16; y++) {
for (int z = 0; z < 16; z++) {
boolean aligned = ((z << 8 | y << 4 | x) & 0x333) == 0;
int roll = random.nextInt(100);
if (mode == 1 ? aligned && roll < 50 : roll < 40) {
matter.slice(MatterCavern.class).set(x, y, z, caverns[random.nextInt(caverns.length)]);
}
if (mode != 3 && random.nextInt(100) < 6) {
matter.slice(HydrologyCaveCell.class).set(x, y, z, hydrology[random.nextInt(hydrology.length)]);
}
int journal = random.nextInt(100);
if (journal < 3) {
matter.slice(PreObjectMatterCell.class).set(x, y, z, PreObjectMatterCell.cavern(null));
} else if (journal < 6) {
matter.slice(PreObjectMatterCell.class).set(x, y, z,
PreObjectMatterCell.cavern(caverns[random.nextInt(caverns.length)]));
} else if (journal < 8) {
matter.slice(PreObjectMatterCell.class).set(x, y, z, PreObjectMatterCell.hydrology(null));
} else if (journal < 10) {
matter.slice(PreObjectMatterCell.class).set(x, y, z,
PreObjectMatterCell.block(null).captureHydrology(hydrology[random.nextInt(hydrology.length)]));
} else if (journal < 13) {
matter.slice(PreObjectMatterCell.class).set(x, y, z, PreObjectMatterCell.string("marker"));
}
}
}
}
}
return chunk(sections);
}
@SuppressWarnings("unchecked")
private static MantleChunk<Matter> chunk(Matter[] sections) {
MantleChunk<Matter> chunk = mock(MantleChunk.class);
when(chunk.sectionCount()).thenReturn(sections.length);
when(chunk.exists(anyInt())).thenAnswer(call -> {
int section = call.getArgument(0);
return section >= 0 && section < sections.length && sections[section] != null;
});
when(chunk.get(anyInt())).thenAnswer(call -> {
assertTrue(Thread.holdsLock(chunk));
int section = call.getArgument(0);
return section >= 0 && section < sections.length ? sections[section] : null;
});
return chunk;
}
}
@@ -45,6 +45,7 @@ import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertFalse;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -188,6 +189,23 @@ public class MantleObjectComponentBoundaryRadiusTest {
MantleObjectComponent.calculateInputRadius(Integer.MAX_VALUE, true));
}
@Test
public void eagerInputRadiusCoversEverySourceChunkPlusOneChunkOfFootprint() {
assertEquals(129, MantleObjectComponent.calculateEagerInputRadius(103, false));
assertEquals(65, MantleObjectComponent.calculateEagerInputRadius(33, false));
assertEquals(113, MantleObjectComponent.calculateEagerInputRadius(33, true));
assertEquals(27, MantleObjectComponent.calculateEagerInputRadius(10, false));
assertEquals(1, MantleObjectComponent.calculateEagerInputRadius(0, false));
for (int radius = 0; radius < 300; radius++) {
for (boolean collisions : new boolean[]{false, true}) {
assertTrue(MantleObjectComponent.calculateEagerInputRadius(radius, collisions)
<= MantleObjectComponent.calculateInputRadius(radius, collisions));
}
}
assertEquals(Integer.MAX_VALUE,
MantleObjectComponent.calculateEagerInputRadius(Integer.MAX_VALUE, true));
}
@Test
public void proceduralTreeTransformsContributeToRadius() {
IrisObjectPlacement placement = new IrisObjectPlacement()
@@ -309,8 +309,8 @@ public class MatterGeneratorCarvePassRadiusTest {
}
@Override
public boolean isInputGenerationLazy() {
return lazyInputGeneration;
public int getEagerInputRadius(int inputRadius) {
return lazyInputGeneration ? 0 : inputRadius;
}
@Override
@@ -387,27 +387,94 @@ public class MatterGeneratorConcurrencyTest {
}
@Test
public void contentPhaseRequiresTerrainEvenWhenItsExplicitPrerequisiteIsMissing() {
public void contentPhaseGeneratesTerrainOfEveryChunkBeforeItIsReadOrWritten() {
GeneratorFixture fixture = new GeneratorFixture(true);
List<String> events = new ArrayList<>();
RecordingComponent terrain = new RecordingComponent(ReservedFlag.CARVED, 0, 0) {
@Override
public MatterGenerationPhase getGenerationPhase() {
return MatterGenerationPhase.TERRAIN;
}
@Override
public void generateLayer(MantleWriter writer, int x, int z, ChunkContext context) {
assertSame(fixture.mantle.getChunk(x, z), writer.acquireChunk(x, z));
events.add("terrain " + x + "," + z);
}
};
RecordingComponent content = new RecordingComponent(ReservedFlag.OBJECT, 1, 0) {
@Override
public MantleFlag[] getPrerequisiteFlags() {
return new MantleFlag[]{ReservedFlag.CARVED};
}
@Override
public int getInputRadius() {
return 48;
}
@Override
public int getEagerInputRadius(int inputRadius) {
return 0;
}
@Override
public void generateLayer(MantleWriter writer, int x, int z, ChunkContext context) {
events.add("content " + x + "," + z);
assertSame(fixture.mantle.getChunk(x + 3, z), writer.acquireChunk(x + 3, z));
writer.acquireChunk(x + 3, z);
events.add("read " + (x + 3) + "," + z);
}
};
TestMatterGenerator generator = fixture.generator(List.of(
new MantlePass(List.of(terrain), 0, 0),
new MantlePass(List.of(content), 0, 0)));
assertThrows(IllegalStateException.class,
() -> generator.generateContentMatter(0, 0, false, fixture.context));
assertFalse(fixture.mantle.getChunk(0, 0).isFlagged(MantleFlag.PLANNED));
generator.generateTerrainMatter(0, 0, false, fixture.context);
assertEquals(List.of("terrain 0,0"), events);
generator.generateContentMatter(0, 0, false, fixture.context);
assertEquals(List.of("terrain 0,0", "content 0,0", "terrain 3,0", "read 3,0"), events);
assertTrue(fixture.mantle.getChunk(0, 0).isFlagged(MantleFlag.PLANNED));
assertTrue(fixture.mantle.getChunk(3, 0).isFlagged(ReservedFlag.CARVED));
assertFalse(fixture.mantle.getChunk(3, 0).isFlagged(ReservedFlag.OBJECT));
}
@Test
public void contentPhaseGeneratesMissingTerrainOfItsOwnChunk() {
GeneratorFixture fixture = new GeneratorFixture(true);
AtomicInteger terrainRuns = new AtomicInteger();
RecordingComponent terrain = new RecordingComponent(ReservedFlag.CARVED, 0, 0) {
@Override
public MatterGenerationPhase getGenerationPhase() {
return MatterGenerationPhase.TERRAIN;
}
@Override
public void generateLayer(MantleWriter writer, int x, int z, ChunkContext context) {
terrainRuns.incrementAndGet();
}
};
RecordingComponent content = new RecordingComponent(ReservedFlag.OBJECT, 1, 0) {
@Override
public MantleFlag[] getPrerequisiteFlags() {
return new MantleFlag[]{ReservedFlag.CARVED};
}
@Override
public void generateLayer(MantleWriter writer, int x, int z, ChunkContext context) {
assertEquals(1, terrainRuns.get());
}
};
TestMatterGenerator generator = fixture.generator(List.of(
new MantlePass(List.of(terrain), 0, 0),
new MantlePass(List.of(content), 0, 0)));
generator.generateContentMatter(0, 0, false, fixture.context);
assertEquals(1, terrainRuns.get());
assertTrue(fixture.mantle.getChunk(0, 0).isFlagged(ReservedFlag.OBJECT));
assertTrue(fixture.mantle.getChunk(0, 0).isFlagged(MantleFlag.PLANNED));
}
@Test
@@ -2,7 +2,6 @@ package art.arcane.iris.generation.runtime;
import art.arcane.iris.pack.loading.IrisData;
import art.arcane.iris.generation.mantle.EngineMantle;
import art.arcane.iris.generation.mantle.TerrainMatterView;
import art.arcane.iris.generation.stage.IrisCarveModifier;
import art.arcane.iris.generation.biome.IrisBiome;
import art.arcane.iris.generation.biome.IrisBiomePaletteLayer;
@@ -15,12 +14,13 @@ import art.arcane.iris.generation.context.ChunkContext;
import art.arcane.iris.generation.context.IrisContext;
import art.arcane.volmlib.util.hunk.Hunk;
import art.arcane.volmlib.util.collection.KMap;
import art.arcane.volmlib.util.function.Consumer4;
import art.arcane.volmlib.util.mantle.runtime.Mantle;
import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
import art.arcane.volmlib.util.math.RNG;
import art.arcane.volmlib.util.stream.ProceduralStream;
import art.arcane.volmlib.util.stream.interpolation.Interpolated;
import art.arcane.iris.world.storage.matter.IrisMatterSupport;
import art.arcane.volmlib.util.matter.IrisMatter;
import art.arcane.volmlib.util.matter.Matter;
import art.arcane.volmlib.util.matter.MatterCavern;
import org.junit.Test;
@@ -42,7 +42,6 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyDouble;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.CALLS_REAL_METHODS;
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
import static org.mockito.Mockito.doAnswer;
@@ -100,19 +99,10 @@ public class IrisCarveWallPaletteScopeTest {
}
try (IrisEngine.GenerationRuntimeScope generation = fixture.engine.openGenerationRuntimeScope(
new IrisEngine.GenerationRuntimeBinding(fixture.engine, selected));
IrisContext.Scope context = IrisContext.open(fixture.engine, 71L, fixture.context);
MockedStatic<TerrainMatterView> terrain = mockStatic(TerrainMatterView.class)) {
IrisContext.Scope context = IrisContext.open(fixture.engine, 71L, fixture.context)) {
if (mode == Mode.ASSEMBLY) {
fixture.engine.threadState.setAssembly(fixture.assembly);
}
terrain.when(() -> TerrainMatterView.iterate(eq(fixture.chunk), eq(MatterCavern.class), any()))
.thenAnswer(call -> {
Consumer4<Integer, Integer, Integer, MatterCavern> consumer = call.getArgument(2);
for (int coordinate : new int[]{4, 8, 12}) {
consumer.accept(coordinate, 10, coordinate, new MatterCavern(true, "", (byte) 0));
}
return null;
});
try {
assertSame(expectedData, fixture.engine.getData());
assertEquals(mode == Mode.NATURAL, expectedComplex.isNaturalTerrainContext());
@@ -196,6 +186,14 @@ public class IrisCarveWallPaletteScopeTest {
doReturn(mantle).when(engineMantle).getMantle();
doReturn(chunk).when(mantle).getChunk(-2, 3);
doReturn(chunk).when(chunk).use();
IrisMatterSupport.ensureRegistered();
Matter section = new IrisMatter(16, 16, 16);
for (int coordinate : new int[]{4, 8, 12}) {
section.slice(MatterCavern.class).set(coordinate, 10, coordinate, new MatterCavern(true, "", (byte) 0));
}
doReturn(1).when(chunk).sectionCount();
doReturn(true).when(chunk).exists(0);
doReturn(section).when(chunk).get(0);
active = runtime(mock(IrisData.class), mock(IrisComplex.class), engineMantle);
detached = runtime(mock(IrisData.class), mock(IrisComplex.class), engineMantle);
field(engine, IrisEngine.class, "lifecycleLock", new Object());
@@ -1,6 +1,7 @@
package art.arcane.iris.generation.stage;
import art.arcane.iris.generation.runtime.Engine;
import art.arcane.iris.generation.mantle.CaveTerrainSnapshot;
import art.arcane.iris.generation.mantle.TerrainMatterView;
import art.arcane.iris.world.IrisWorld;
import art.arcane.iris.world.storage.matter.IrisMatterSupport;
@@ -56,6 +57,7 @@ public class IrisCarveBoundaryFacesTest {
return true;
});
when(chunks[side].get(anyInt())).thenAnswer(call -> matter[selected][(int) call.getArgument(0)]);
when(chunks[side].sectionCount()).thenReturn(2);
if (side == 0) {
continue;
}
@@ -87,10 +89,10 @@ public class IrisCarveBoundaryFacesTest {
int[] surfaces = new int[256];
Arrays.fill(surfaces, 30);
Method method = IrisCarveModifier.class.getDeclaredMethod("addCrossChunkBoundaryWalls",
Mantle.class, MantleChunk.class, CarveWallBuffer.class, CarveColumnMask[].class,
Mantle.class, CaveTerrainSnapshot.class, CarveWallBuffer.class, CarveColumnMask[].class,
int.class, int.class, int[].class);
method.setAccessible(true);
method.invoke(modifier, mantle, chunks[0], walls, masks, 0, 0, surfaces);
method.invoke(modifier, mantle, CaveTerrainSnapshot.capture(chunks[0], 0, 0), walls, masks, 0, 0, surfaces);
CarveWallBuffer expectedWalls = new CarveWallBuffer(64);
for (int y = 1; y < 32; y++) {
for (int offset = 0; offset < 16; offset++) {
@@ -2,6 +2,7 @@ package art.arcane.iris.generation.stage;
import art.arcane.iris.pack.loading.IrisData;
import art.arcane.iris.pack.loading.ResourceLoader;
import art.arcane.iris.generation.mantle.CaveTerrainSnapshot;
import art.arcane.iris.generation.runtime.Engine;
import art.arcane.iris.generation.runtime.IrisComplex;
import art.arcane.iris.generation.biome.IrisBiome;
@@ -12,9 +13,10 @@ import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
import art.arcane.volmlib.util.hunk.Hunk;
import art.arcane.volmlib.util.stream.ProceduralStream;
import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
import art.arcane.iris.world.storage.matter.IrisMatterSupport;
import art.arcane.volmlib.util.matter.IrisMatter;
import art.arcane.volmlib.util.matter.Matter;
import art.arcane.volmlib.util.matter.MatterCavern;
import art.arcane.volmlib.util.matter.MatterSlice;
import art.arcane.volmlib.util.math.RNG;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import org.junit.Test;
@@ -55,30 +57,27 @@ public class IrisCarveModifierBoundarySupportTest {
rng.setAccessible(true);
rng.set(modifier, new RNG(71L));
IrisMatterSupport.ensureRegistered();
Matter floorMatter = new IrisMatter(16, 16, 16);
Matter ceilingMatter = new IrisMatter(16, 16, 16);
floorMatter.slice(MatterCavern.class).set(1, 6, 2, new MatterCavern(true, "custom/floor", (byte) 0));
ceilingMatter.slice(MatterCavern.class).set(1, 11, 2, new MatterCavern(true, "custom/other", (byte) 0));
MantleChunk<Matter> mantleChunk = mock(MantleChunk.class);
Matter floorMatter = mock(Matter.class);
Matter ceilingMatter = mock(Matter.class);
MatterSlice<MatterCavern> floorSlice = mock(MatterSlice.class);
MatterSlice<MatterCavern> ceilingSlice = mock(MatterSlice.class);
doReturn(3).when(mantleChunk).sectionCount();
doReturn(true).when(mantleChunk).exists(0);
doReturn(true).when(mantleChunk).exists(2);
doReturn(floorMatter).when(mantleChunk).get(0);
doReturn(ceilingMatter).when(mantleChunk).get(2);
doReturn(true).when(floorMatter).hasSlice(MatterCavern.class);
doReturn(true).when(ceilingMatter).hasSlice(MatterCavern.class);
doReturn(floorSlice).when(floorMatter).getSlice(MatterCavern.class);
doReturn(ceilingSlice).when(ceilingMatter).getSlice(MatterCavern.class);
doReturn(new MatterCavern(true, "custom/floor", (byte) 0)).when(floorSlice).get(1, 6, 2);
doReturn(null).when(ceilingSlice).get(1, 10, 2);
CaveTerrainSnapshot terrain = CaveTerrainSnapshot.capture(mantleChunk, 2, 2);
Long2ObjectOpenHashMap<IrisBiome> caveBiomeCache = new Long2ObjectOpenHashMap<>();
Map<String, IrisBiome> customBiomeCache = new HashMap<>();
IrisCarveModifier.CaveInputs resolverState = new IrisCarveModifier.CaveInputs(engine);
IrisBiome floor = modifier.resolveCaveBoundaryBiome(
mantleChunk, 1, 6, 2, 40, 44, resolverState, caveBiomeCache, customBiomeCache);
terrain, 1, 6, 2, 40, 44, resolverState, caveBiomeCache, customBiomeCache);
IrisBiome ceiling = modifier.resolveCaveBoundaryBiome(
mantleChunk, 1, 42, 2, 40, 44, resolverState, caveBiomeCache, customBiomeCache);
terrain, 1, 42, 2, 40, 44, resolverState, caveBiomeCache, customBiomeCache);
assertSame(customFloor, floor);
assertSame(resolvedCeiling, ceiling);
@@ -26,6 +26,7 @@ import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.CALLS_REAL_METHODS;
@@ -122,6 +123,31 @@ public class IrisCarveModifierCaveInputsTest {
}
}
@Test
public void contentChunkCapturesOnlyItsOwnColumnsAndDispatchesTheRestThroughHistory() {
try (Fixture fixture = new Fixture()) {
when(fixture.context.isNaturalTerrain()).thenReturn(false);
IrisBiome historical = biome("historical", 0);
when(fixture.engine.getCaveBiome(anyInt(), anyInt(), anyInt(), any(IrisDimensionCarvingResolver.State.class))).thenReturn(historical);
IrisCarveModifier.CaveInputs inputs = IrisCarveModifier.CaveInputs.forContentChunk(fixture.engine, -2, 1);
IrisDimensionCarvingResolver.State state = new IrisDimensionCarvingResolver.State();
for (int x = -32; x <= -17; x++) {
for (int z = 16; z <= 31; z += 5) {
for (int y : new int[]{0, 30, 33, 34, 39, 40}) {
assertSame(fixture.scalar.getCaveBiome(x, y, z, state), inputs.resolve(x, y, z));
}
}
}
int captured = fixture.samples.get();
assertTrue(captured > 0);
assertSame(historical, inputs.resolve(-33, 20, 16));
assertSame(historical, inputs.resolve(-16, 20, 16));
assertSame(historical, inputs.resolve(-17, 20, 15));
assertSame(historical, inputs.resolve(-17, 20, 32));
assertEquals(captured, fixture.samples.get());
}
}
private static IrisBiome biome(String key, int depth) {
IrisBiome biome = new IrisBiome();
biome.setLoadKey(key);
@@ -1,6 +1,7 @@
package art.arcane.iris.generation.stage;
import art.arcane.iris.pack.loading.IrisData;
import art.arcane.iris.generation.mantle.CaveTerrainSnapshot;
import art.arcane.iris.generation.runtime.IrisComplex;
import art.arcane.iris.generation.runtime.Engine;
import art.arcane.iris.generation.terrain.InferredType;
@@ -56,7 +57,7 @@ public class IrisCarveModifierInferenceIsolationTest {
Method paintBoundaryZone = IrisCarveModifier.class.getDeclaredMethod(
"paintBoundaryZone",
Hunk.class,
MantleChunk.class,
CaveTerrainSnapshot.class,
CarveWallBuffer.class,
int.class,
int.class,
@@ -72,7 +73,7 @@ public class IrisCarveModifierInferenceIsolationTest {
paintBoundaryZone.invoke(
modifier,
mock(Hunk.class),
mock(MantleChunk.class),
CaveTerrainSnapshot.capture(mock(MantleChunk.class), 0, 0),
walls,
0,
0,
@@ -5,6 +5,8 @@ import art.arcane.iris.generation.decoration.IrisCeilingDecorator;
import art.arcane.iris.generation.decoration.IrisSurfaceDecorator;
import art.arcane.iris.generation.runtime.Engine;
import art.arcane.iris.generation.runtime.SeedManager;
import art.arcane.iris.generation.context.ChunkContext;
import art.arcane.iris.generation.mantle.CaveTerrainSnapshot;
import art.arcane.iris.generation.mantle.EngineMantle;
import art.arcane.iris.generation.biome.IrisBiome;
import art.arcane.iris.generation.decoration.IrisDecorationPart;
@@ -32,18 +34,22 @@ 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.assertSame;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.CALLS_REAL_METHODS;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
public class IrisNaturalCaveScanTest {
@@ -70,6 +76,26 @@ public class IrisNaturalCaveScanTest {
verifyScan(16, 16);
}
@Test
public void decorationReusesOnlyTheCarveSnapshotOfItsOwnChunk() throws Exception {
Fixture fixture = new Fixture();
ChunkContext context = mock(ChunkContext.class);
doReturn(CaveTerrainSnapshot.capture(fixture.chunk, -2, 3)).when(context).getCaveTerrain();
clearInvocations(fixture.chunk);
fixture.modifier.decorateNaturalCaves(-32, 48, fixture.output(3, 2), context);
verify(fixture.chunk, never()).use();
verify(context).setCaveTerrain(null);
assertFalse(fixture.calls.isEmpty());
Fixture other = new Fixture();
ChunkContext stale = mock(ChunkContext.class);
doReturn(CaveTerrainSnapshot.capture(other.chunk, 5, 5)).when(stale).getCaveTerrain();
clearInvocations(other.chunk);
other.modifier.decorateNaturalCaves(-32, 48, other.output(3, 2), stale);
verify(other.chunk).use();
assertEquals(fixture.calls, other.calls);
}
@Test
public void listenerFailureReleasesMantleChunk() throws Exception {
Fixture fixture = new Fixture();
@@ -78,7 +104,7 @@ public class IrisNaturalCaveScanTest {
throw failure;
});
assertSame(failure, assertThrows(IllegalStateException.class,
() -> fixture.modifier.decorateNaturalCaves(-32, 48, output)));
() -> fixture.modifier.decorateNaturalCaves(-32, 48, output, null)));
verify(fixture.chunk).release();
}
@@ -92,7 +118,7 @@ public class IrisNaturalCaveScanTest {
IllegalStateException failure = new IllegalStateException("height unavailable");
doAnswer(call -> { throw failure; }).when(output).getHeight();
assertSame(failure, assertThrows(IllegalStateException.class,
() -> fixture.modifier.decorateNaturalCaves(-32, 48, output)));
() -> fixture.modifier.decorateNaturalCaves(-32, 48, output, null)));
verify(fixture.chunk).release();
}
@@ -104,7 +130,7 @@ public class IrisNaturalCaveScanTest {
Hunk<NativeBlockState> fullOutput = full.sparseOutput(Hunk.newArrayHunk(16, 32, 16));
List<String> fullWrites = new ArrayList<>();
full.modifier.decorateNaturalCaves(-32, 48,
fullOutput.listen((x, y, z, state) -> fullWrites.add(x + ":" + y + ":" + z + ":" + state.key())));
fullOutput.listen((x, y, z, state) -> fullWrites.add(x + ":" + y + ":" + z + ":" + state.key())), null);
Fixture bounded = new Fixture(true);
ChunkGenerator.ChunkData chunkData = mock(ChunkGenerator.ChunkData.class);
@@ -114,7 +140,7 @@ public class IrisNaturalCaveScanTest {
bounded.sparseOutput(holder);
List<String> boundedWrites = new ArrayList<>();
bounded.modifier.decorateNaturalCaves(-32, 48, new ColumnExtentListeningHunk<>(holder,
(x, y, z, state) -> boundedWrites.add(x + ":" + y + ":" + z + ":" + state.key())));
(x, y, z, state) -> boundedWrites.add(x + ":" + y + ":" + z + ":" + state.key())), null);
assertTrue("fixture must decorate a zone above the initial column top",
full.calls.contains("floor:0:0:27:1"));
@@ -141,7 +167,7 @@ public class IrisNaturalCaveScanTest {
Hunk<NativeBlockState> output = fixture.output(width, depth);
List<String> writes = new ArrayList<>();
fixture.modifier.decorateNaturalCaves(-32, 48,
output.listen((x, y, z, state) -> writes.add(x + ":" + y + ":" + z + ":" + state.key())));
output.listen((x, y, z, state) -> writes.add(x + ":" + y + ":" + z + ":" + state.key())), null);
List<String> expectedCalls = new ArrayList<>();
List<String> expectedMarkers = new ArrayList<>();
List<String> expectedWrites = new ArrayList<>();
@@ -239,7 +265,7 @@ public class IrisNaturalCaveScanTest {
IrisBiome biome = mock(IrisBiome.class);
doReturn(new IrisDecorator[]{new IrisDecorator()}).when(biome).getDecoratorBucket(IrisDecorationPart.NONE);
doReturn(new IrisDecorator[]{new IrisDecorator()}).when(biome).getDecoratorBucket(IrisDecorationPart.CEILING);
doReturn(biome).when(modifier).resolveCaveBoundaryBiome(eq(chunk), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), any(), any(), any());
doReturn(biome).when(modifier).resolveCaveBoundaryBiome(argThat((CaveTerrainSnapshot terrain) -> terrain != null && terrain.covers(-2, 3)), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), any(), any(), any());
IrisDecorantActuator decorant = mock(IrisDecorantActuator.class);
IrisSurfaceDecorator surface = mock(IrisSurfaceDecorator.class);
IrisCeilingDecorator roof = mock(IrisCeilingDecorator.class);