Merged terrain assembly improvements

- Ceiling layer thickness resolves independently from surface layers
- Locked strata repeat correctly above the internal height reference
- Cave scratch releases generation references after each pass
- Terrain actuators omit redundant setup and checks
This commit is contained in:
Brian Neumann-Fopiano committed 2026-09-06 03:16:51 -04:00
commit a68cad5893
16 files changed
+446 -223

No files matched your search

@@ -11,7 +11,6 @@ import java.nio.file.Path;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class PackBiomeLayerValidatorTest {
@@ -19,14 +18,12 @@ public class PackBiomeLayerValidatorTest {
public TemporaryFolder temporaryFolder = new TemporaryFolder();
@Test
public void rejectsMoreCeilingLayersThanLayers() throws Exception {
public void acceptsMoreCeilingLayersThanSurfaceLayers() throws Exception {
File pack = temporaryFolder.newFolder("pack");
write(pack, "biomes/nested/cave.json",
"{\"layers\":[{}],\"caveCeilingLayers\":[{},{},{}]}");
assertEquals(List.of(
"Biome 'nested/cave' declares 3 caveCeilingLayers but only 1 layers. caveCeilingLayers reuses the layers height generators and must not have more entries."
), PackBiomeLayerValidator.validateCeilingLayerCounts(new File(pack, "biomes")));
assertTrue(PackBiomeLayerValidator.validateLayers(new File(pack, "biomes")).isEmpty());
}
@Test
@@ -35,9 +32,7 @@ public class PackBiomeLayerValidatorTest {
write(pack, "biomes/defaulted.json", "{\"name\":\"Defaulted\"}");
write(pack, "biomes/implicit.json", "{\"caveCeilingLayers\":[{},{}]}");
assertEquals(List.of(
"Biome 'implicit' declares 2 caveCeilingLayers but only 1 layers. caveCeilingLayers reuses the layers height generators and must not have more entries."
), PackBiomeLayerValidator.validateCeilingLayerCounts(new File(pack, "biomes")));
assertTrue(PackBiomeLayerValidator.validateLayers(new File(pack, "biomes")).isEmpty());
}
@Test
@@ -45,7 +40,7 @@ public class PackBiomeLayerValidatorTest {
File pack = temporaryFolder.newFolder("pack");
write(pack, "biomes/ok.json", "{\"layers\":[{},{},{}],\"caveCeilingLayers\":[{},{}]}");
assertTrue(PackBiomeLayerValidator.validateCeilingLayerCounts(new File(pack, "biomes")).isEmpty());
assertTrue(PackBiomeLayerValidator.validateLayers(new File(pack, "biomes")).isEmpty());
}
@Test
@@ -56,11 +51,11 @@ public class PackBiomeLayerValidatorTest {
assertEquals(List.of(
"Biome 'bad' layers must be an array.",
"Biome 'bad' caveCeilingLayers must be an array."
), PackBiomeLayerValidator.validateCeilingLayerCounts(new File(pack, "biomes")));
), PackBiomeLayerValidator.validateLayers(new File(pack, "biomes")));
}
@Test
public void ceilingLayerOverflowBlocksFullPackValidation() throws Exception {
public void independentCeilingLayersPassFullPackValidation() throws Exception {
File pack = temporaryFolder.newFolder("pack");
write(pack, "dimensions/main.json", "{\"regions\":[\"region\"]}");
write(pack, "regions/region.json", "{\"landBiomes\":[\"biome\"]}");
@@ -68,9 +63,7 @@ public class PackBiomeLayerValidatorTest {
PackValidationResult result = PackValidator.validate(pack);
assertFalse(result.isLoadable());
assertTrue(result.getBlockingErrors().contains(
"Biome 'biome' declares 2 caveCeilingLayers but only 1 layers. caveCeilingLayers reuses the layers height generators and must not have more entries."));
assertTrue(result.getBlockingErrors().toString(), result.isLoadable());
}
@Test
@@ -35,12 +35,15 @@ import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -114,6 +117,45 @@ public class IrisCaveCarver3DNearParityTest {
carveForcedAirField.setAccessible(true);
}
@Test
public void carvingReleasesSectionReferencesAfterSuccessAndFailure() throws Exception {
for (boolean fail : new boolean[]{false, true}) {
Engine engine = createEngine(128, 110);
IrisCaveProfile profile = createProfile(false, false).setAdaptiveSampling(false)
.setAllowFluid(false).setDetailWeight(0D)
.setDensityThreshold(new IrisStyledRange(2D, 2D, new IrisGeneratorStyle(NoiseStyle.FLAT)));
IrisCaveCarver3D carver = new IrisCaveCarver3D(engine, profile);
WriterCapture capture = createWriterCapture(128);
if (fail) {
AtomicInteger samples = new AtomicInteger();
CNG density = mock(CNG.class);
doAnswer(invocation -> {
if (samples.incrementAndGet() > 300) {
throw new IllegalStateException("Density sample failed");
}
return -1D;
}).when(density).noiseFastSigned3D(anyDouble(), anyDouble(), anyDouble());
baseDensityField.set(carver, density);
assertThrows(IllegalStateException.class, () -> carver.carve(capture.writer, 0, 0,
fullWeights(), 0D, 0D, null, filledHeights(110)));
} else {
carver.carve(capture.writer, 0, 0, fullWeights(), 0D, 0D, null, filledHeights(110));
}
assertFalse(capture.carvedCells.isEmpty());
Field scratchField = IrisCaveCarver3D.class.getDeclaredField("scratchCache");
scratchField.setAccessible(true);
ThreadLocal<?> scratchCache = (ThreadLocal<?>) scratchField.get(carver);
CaveCarveScratch scratch = (CaveCarveScratch) scratchCache.get();
assertTrue(scratch.sectionMatter.length > 0);
for (Matter matter : scratch.sectionMatter) {
assertNull(matter);
}
for (MatterSlice<?> slice : scratch.sectionSlices) {
assertNull(slice);
}
}
}
@Test
public void parallelDensityPreservesExactAndAdaptiveCavernsAndLiquids() throws Exception {
try (ForkJoinPool pool = new ForkJoinPool(4)) {
@@ -0,0 +1,170 @@
package art.arcane.iris.engine.mantle.components;
import art.arcane.iris.engine.IrisComplex;
import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.engine.framework.EngineMetrics;
import art.arcane.iris.engine.mantle.EngineMantle;
import art.arcane.iris.engine.mantle.MantleWriter;
import art.arcane.iris.engine.object.IrisBiome;
import art.arcane.iris.engine.object.IrisCaveProfile;
import art.arcane.iris.engine.object.IrisDimension;
import art.arcane.iris.engine.object.IrisRegion;
import art.arcane.iris.engine.object.IrisWorld;
import art.arcane.iris.spi.PlatformBlockState;
import art.arcane.iris.util.project.context.ChunkContext;
import art.arcane.iris.util.project.stream.ProceduralStream;
import art.arcane.volmlib.util.mantle.runtime.Mantle;
import art.arcane.volmlib.util.matter.Matter;
import org.junit.Before;
import org.junit.Test;
import java.lang.reflect.Field;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyDouble;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class MantleCarvingComponentScratchTest {
private MantleCarvingComponent component;
private IrisDimension dimension;
private ChunkContext context;
private MantleWriter writer;
@Before
@SuppressWarnings("unchecked")
public void prepare() {
Engine engine = mock(Engine.class);
EngineMantle mantle = mock(EngineMantle.class);
when(mantle.getEngine()).thenReturn(engine);
component = new MantleCarvingComponent(mantle);
dimension = new IrisDimension();
when(engine.getDimension()).thenReturn(dimension);
when(engine.getWorld()).thenReturn(mock(IrisWorld.class));
when(engine.getMetrics()).thenReturn(new EngineMetrics(16));
IrisComplex complex = mock(IrisComplex.class);
when(complex.allowsMantleChunkWrite(anyInt(), anyInt())).thenReturn(true);
ProceduralStream<Double> heights = mock(ProceduralStream.class);
when(heights.getDouble(anyDouble(), anyDouble())).thenAnswer(invocation -> {
double x = invocation.getArgument(0);
return x < 0D ? 90D : 70D;
});
when(complex.getNaturalHeightStream()).thenReturn(heights);
ProceduralStream<IrisRegion> regions = mock(ProceduralStream.class);
ProceduralStream<IrisBiome> biomes = mock(ProceduralStream.class);
ProceduralStream<PlatformBlockState> fluids = mock(ProceduralStream.class);
when(regions.get(anyDouble(), anyDouble())).thenReturn(new IrisRegion());
when(biomes.get(anyDouble(), anyDouble())).thenReturn(new IrisBiome());
when(complex.getRegionStream()).thenReturn(regions);
when(complex.getNaturalTrueBiomeStream()).thenReturn(biomes);
when(complex.getCaveBiomeStream()).thenReturn(biomes);
when(complex.getFluidStream()).thenReturn(fluids);
context = mock(ChunkContext.class);
when(context.getComplex()).thenReturn(complex);
writer = mock(MantleWriter.class);
Mantle<Matter> storage = mock(Mantle.class);
when(storage.getWorldHeight()).thenReturn(128);
when(writer.getMantle()).thenReturn(storage);
}
@Test
public void nestedGenerationPreservesOuterColumnHeights() throws Exception {
IrisCaveCarver3D carver = installProfile();
AtomicBoolean entered = new AtomicBoolean();
AtomicInteger calls = new AtomicInteger();
doAnswer(invocation -> {
int chunkX = invocation.getArgument(1);
int[] heights = invocation.getArgument(7);
int expectedHeight = chunkX < 0 ? 90 : 70;
assertEquals(expectedHeight, heights[0]);
calls.incrementAndGet();
if (entered.compareAndSet(false, true)) {
component.generateLayer(writer, -3, -3, context);
}
for (int height : heights) {
assertEquals(expectedHeight, height);
}
return 0;
}).when(carver).carve(any(), anyInt(), anyInt(), any(), anyDouble(), anyDouble(),
any(), any(), any(), any(), any());
component.generateLayer(writer, 2, 2, context);
assertEquals(2, calls.get());
assertReleased();
}
@Test
public void generationReleasesConfigurationReferencesWhenCarvingFails() throws Exception {
IrisCaveCarver3D carver = installProfile();
doAnswer(invocation -> {
throw new IllegalStateException("Carving failed");
}).when(carver).carve(any(), anyInt(), anyInt(), any(), anyDouble(), anyDouble(),
any(), any(), any(), any(), any());
assertThrows(IllegalStateException.class, () -> component.generateLayer(writer, 2, 2, context));
assertReleased();
}
@Test
public void changedProfilesReuseNumericBuffersWithoutKeepingProfileKeys() throws Exception {
Object firstBuffer = null;
for (int index = 0; index < 4; index++) {
installProfile();
component.generateLayer(writer, 2, 2, context);
Object scratch = assertReleased();
List<?> buffers = (List<?>) field(scratch, "weightBuffers");
assertEquals(1, buffers.size());
if (firstBuffer == null) {
firstBuffer = buffers.get(0);
} else {
assertSame(firstBuffer, buffers.get(0));
}
}
}
private IrisCaveCarver3D installProfile() throws Exception {
IrisCaveProfile profile = new IrisCaveProfile().setEnabled(true);
dimension.setCaveProfile(profile);
IrisCaveCarver3D carver = mock(IrisCaveCarver3D.class);
Map<IrisCaveProfile, IrisCaveCarver3D> carvers = new IdentityHashMap<>();
carvers.put(profile, carver);
Field cache = MantleCarvingComponent.class.getDeclaredField("profileCarvers");
cache.setAccessible(true);
cache.set(component, carvers);
return carver;
}
private Object assertReleased() throws Exception {
Field scratchField = MantleCarvingComponent.class.getDeclaredField("BLEND_SCRATCH");
scratchField.setAccessible(true);
ThreadLocal<?> scratchCache = (ThreadLocal<?>) scratchField.get(null);
Object scratch = scratchCache.get();
for (String name : List.of("profileField", "kernelProfiles", "fieldRegions", "fieldSurfaceBiomes", "fieldCaveBiomes")) {
for (Object value : (Object[]) field(scratch, name)) {
assertNull(value);
}
}
assertEquals(0, ((Map<?, ?>) field(scratch, "columnProfileWeights")).size());
assertEquals(0, ((List<?>) field(scratch, "profileOrder")).size());
assertFalse((boolean) field(scratch, "inUse"));
return scratch;
}
private Object field(Object object, String name) throws Exception {
Field field = object.getClass().getDeclaredField(name);
field.setAccessible(true);
return field.get(object);
}
}
@@ -1,55 +0,0 @@
package art.arcane.iris.engine.object;
import art.arcane.iris.core.loader.IrisData;
import art.arcane.iris.spi.PlatformBlockState;
import art.arcane.volmlib.util.collection.KList;
import art.arcane.volmlib.util.math.RNG;
import org.junit.Test;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
public class IrisBiomeCeilingLayerGuardTest {
private static IrisBiome biome(int layers, int ceilingLayers) {
IrisBiome biome = new IrisBiome();
KList<IrisBiomePaletteLayer> surface = new KList<>();
for (int i = 0; i < layers; i++) {
surface.add(new IrisBiomePaletteLayer().zero());
}
KList<IrisBiomePaletteLayer> ceiling = new KList<>();
for (int i = 0; i < ceilingLayers; i++) {
IrisBiomePaletteLayer layer = new IrisBiomePaletteLayer().zero();
layer.setMinHeight(0);
layer.setMaxHeight(0);
ceiling.add(layer);
}
biome.setLayers(surface);
biome.setCaveCeilingLayers(ceiling);
return biome;
}
@Test
public void moreCeilingLayersThanLayersMustNotThrow() {
IrisBiome biome = biome(1, 3);
KList<PlatformBlockState> result = biome.generateCeilingLayers(
new IrisDimension(), 0, 0, new RNG(1), 8, 64, mock(IrisData.class), null);
assertTrue("extra ceiling layers are skipped, not crashed on", result.isEmpty());
}
@Test
public void inRangeCeilingLayersKeepWorking() {
IrisBiome biome = biome(3, 2);
KList<PlatformBlockState> result = biome.generateCeilingLayers(
new IrisDimension(), 0, 0, new RNG(1), 8, 64, mock(IrisData.class), null);
assertTrue("zeroed palettes with zero heights produce no blocks", result.isEmpty());
}
@Test
public void omittedCeilingLayersLeaveCavesUnchanged() {
assertTrue(new IrisBiome().getCaveCeilingLayers().isEmpty());
}
}
@@ -0,0 +1,78 @@
package art.arcane.iris.engine.object;
import art.arcane.iris.spi.PlatformBlockState;
import art.arcane.volmlib.util.collection.KList;
import art.arcane.volmlib.util.math.RNG;
import org.junit.Test;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
public class IrisBiomeCeilingLayerTest {
@Test
public void emitsEveryCeilingLayerWithoutSurfaceLayers() {
PlatformBlockState first = mock(PlatformBlockState.class);
PlatformBlockState second = mock(PlatformBlockState.class);
PlatformBlockState third = mock(PlatformBlockState.class);
IrisBiome biome = new IrisBiome().setLayers(new KList<>()).setCaveCeilingLayers(
new KList<>(layer(first, 2), layer(second, 1), layer(third, 2)));
KList<PlatformBlockState> result = biome.generateCeilingLayers(
new IrisDimension(), 13, -27, new RNG(1), 8, 64, null, null);
assertEquals(List.of(first, first, second, third, third), result);
assertNull(biome.getLayerHeightGenerators().getIfPresent());
}
@Test
public void ceilingThicknessDoesNotDependOnSurfaceDefinitionOrWarmOrder() {
IrisBiome first = variableBiome(1, 1);
IrisBiome second = variableBiome(7, 31);
RNG rng = new RNG(37);
second.getLayerHeightGenerators(rng, null);
for (int x = -200; x <= 200; x += 7) {
KList<PlatformBlockState> firstCeiling = first.generateCeilingLayers(
new IrisDimension(), x, -x, rng, 100, 120, null, null);
KList<PlatformBlockState> secondCeiling = second.generateCeilingLayers(
new IrisDimension(), x, -x, rng, 100, 120, null, null);
assertEquals(firstCeiling.size(), secondCeiling.size());
}
first.getLayerHeightGenerators(rng, null);
assertNotSame(first.getLayerHeightGenerators().getIfPresent().get(0),
first.getLayerCeilingHeightGenerators().getIfPresent().get(0));
}
@Test
public void capsCeilingLayersAtAvailableDepth() {
PlatformBlockState first = mock(PlatformBlockState.class);
PlatformBlockState second = mock(PlatformBlockState.class);
IrisBiome biome = new IrisBiome().setCaveCeilingLayers(new KList<>(layer(first, 2), layer(second, 3)));
assertEquals(List.of(first, first, second), biome.generateCeilingLayers(
new IrisDimension(), 0, 0, new RNG(1), 3, 64, null, null));
assertTrue(biome.generateCeilingLayers(
new IrisDimension(), 0, 0, new RNG(1), 0, 64, null, null).isEmpty());
}
@Test
public void omittedCeilingLayersLeaveCavesUnchanged() {
assertTrue(new IrisBiome().getCaveCeilingLayers().isEmpty());
}
static IrisBiomePaletteLayer layer(PlatformBlockState block, int thickness) {
IrisBiomePaletteLayer layer = new IrisBiomePaletteLayer().setMinHeight(thickness).setMaxHeight(thickness);
layer.getBlockData().aquire(() -> new KList<>(block));
return layer;
}
private static IrisBiome variableBiome(int surfaceMinimum, int surfaceMaximum) {
PlatformBlockState block = mock(PlatformBlockState.class);
return new IrisBiome()
.setLayers(new KList<>(layer(block, surfaceMinimum).setMaxHeight(surfaceMaximum)))
.setCaveCeilingLayers(new KList<>(layer(block, 1).setMaxHeight(9)));
}
}
@@ -0,0 +1,35 @@
package art.arcane.iris.engine.object;
import art.arcane.iris.spi.PlatformBlockState;
import art.arcane.volmlib.util.collection.KList;
import art.arcane.volmlib.util.math.RNG;
import org.junit.Test;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
public class IrisBiomeLockedLayerTest {
@Test
public void repeatsLockedLayersAcrossTheReferenceHeight() {
PlatformBlockState first = mock(PlatformBlockState.class);
PlatformBlockState second = mock(PlatformBlockState.class);
PlatformBlockState third = mock(PlatformBlockState.class);
IrisBiome biome = new IrisBiome().setLockLayers(true).setLockLayersMax(8).setLayers(new KList<>(
IrisBiomeCeilingLayerTest.layer(first, 1),
IrisBiomeCeilingLayerTest.layer(second, 1),
IrisBiomeCeilingLayerTest.layer(third, 1)));
List<PlatformBlockState> cycle = List.of(first, second, third);
int[] heights = {1, 509, 512, 513, 1000, Integer.MAX_VALUE};
int[][] expectedCycles = {{1, 0, 2}, {0, 2, 1}, {0, 2, 1}, {2, 1, 0}, {1, 0, 2}, {1, 0, 2}};
for (int index = 0; index < heights.length; index++) {
KList<PlatformBlockState> blocks = biome.generateLockedLayers(
13, -27, new RNG(37), 8, heights[index], null, null);
assertEquals(8, blocks.size());
for (int depth = 0; depth < blocks.size(); depth++) {
assertEquals(cycle.get(expectedCycles[index][depth % 3]), blocks.get(depth));
}
}
}
}