Whoops Core

This commit is contained in:
Brian Neumann-Fopiano committed 2026-09-08 16:15:19 -04:00
1 parent 98e51cba8d
commit 0782bbf0b4
31 files changed
+1178 -190

No files matched your search

@@ -0,0 +1,35 @@
package art.arcane.iris.core.pack;
import art.arcane.volmlib.util.json.JSONObject;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;
public class PackObjectScaleFactorTest {
@Test
public void acceptsOmissionAndFiniteFactorsWithinResamplerLimits() {
for (String json : List.of("{}", "{\"allObjectScaleFactor\":0.01}",
"{\"allObjectScaleFactor\":0.5}", "{\"allObjectScaleFactor\":1}",
"{\"allObjectScaleFactor\":2}", "{\"allObjectScaleFactor\":50}")) {
List<String> errors = new ArrayList<>();
PackDimensionValidator.validateObjectScaleFactor("main", new JSONObject(json), errors);
assertEquals(json, List.of(), errors);
}
}
@Test
public void blocksInvalidTypesAndFactorsInPackAndRuntimeValidation() {
for (String value : List.of("null", "true", "\"2\"", "[]", "{}", "0", "-1", "0.009", "50.001")) {
JSONObject json = new JSONObject("{\"allObjectScaleFactor\":" + value + "}");
List<String> errors = new ArrayList<>();
PackDimensionValidator.validateObjectScaleFactor("main", json, errors);
assertEquals(value, 1, errors.size());
assertThrows(IllegalArgumentException.class,
() -> PackValidator.requireValidObjectSettings(null, "main", json));
}
}
}
@@ -78,7 +78,7 @@ public class SchemaBuilderIdentityTest {
@ClassRule
public static final PlatformLeakGuard PLATFORM_GUARD = PlatformLeakGuard.clean();
private static final String EXPECTED_SCHEMA_DIGEST = "2f47b30c04bf90a5342df92d7335670679176665520527913f3b90ed0cda65d5";
private static final String EXPECTED_SCHEMA_DIGEST = "ddfc51ba19c02dd50737a0d1ba30ee24cdb6cbbe5fed40c875adac69960ad81d";
private static final List<Class<?>> SCHEMA_ROOTS = List.of(
IrisDimension.class,
@@ -7,6 +7,14 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class IrisStructureComponentOverboreTest {
@Test
public void enlargedTreeSearchCoversPaddedScaleWithoutEnlargingShrunkTrees() {
assertEquals(4, IrisStructureComponent.scaledMarkerSpan(4, 1D));
assertEquals(4, IrisStructureComponent.scaledMarkerSpan(4, 0.5D));
assertEquals(8, IrisStructureComponent.scaledMarkerSpan(4, 2D));
assertEquals(20, IrisStructureComponent.scaledMarkerSpan(10, 2D));
}
@Test
public void ordinaryMarkersAreSeparatedFromStructureMarkers() {
assertTrue(IrisStructureComponent.isOrdinaryObjectMarker("trees/oak@42"));
@@ -66,6 +66,7 @@ public class MantleFloatingObjectComponentBoundaryRadiusTest {
.setFloatingObjects(new KList<>(placement));
IrisBiome biome = new IrisBiome().setFloatingChildBiomes(new KList<>(floatingChild));
IrisDimension dimension = mock(IrisDimension.class);
when(dimension.getAllObjectScaleFactor()).thenReturn(1D);
when(dimension.getReachableBiomes(org.mockito.ArgumentMatchers.any())).thenReturn(new KList<>(biome));
ResourceLoader<IrisObject> objectLoader = mock(ResourceLoader.class);
@@ -80,5 +81,7 @@ public class MantleFloatingObjectComponentBoundaryRadiusTest {
when(engineMantle.getData()).thenReturn(data);
assertEquals(33, new MantleFloatingObjectComponent(engineMantle).getRadius());
when(dimension.getAllObjectScaleFactor()).thenReturn(2D);
assertEquals(34, new MantleFloatingObjectComponent(engineMantle).getRadius());
}
}
@@ -96,7 +96,7 @@ public class MantleObjectComponentBoundaryRadiusTest {
.setRotation(IrisObjectRotation.of(90, 0, 0))
.setTranslate(new IrisObjectTranslate().setX(3).setY(4));
assertEquals(53, MantleObjectComponent.calculatePlacementReach(new IrisBlockVector(5, 48, 7), placement));
assertEquals(53, MantleObjectComponent.calculatePlacementReach(new IrisBlockVector(5, 48, 7), placement, 1D));
}
@Test
@@ -107,7 +107,7 @@ public class MantleObjectComponentBoundaryRadiusTest {
.setMode(ObjectPlaceMode.VACUUM)
.setVacuumSettings(new IrisVacuumSettings().setRadius(12));
assertEquals(43, MantleObjectComponent.calculatePlacementReach(new IrisBlockVector(5, 4, 3), placement));
assertEquals(39, MantleObjectComponent.calculatePlacementReach(new IrisBlockVector(5, 4, 3), placement, placement.getMaximumScale(null)));
}
@Test
@@ -126,6 +126,30 @@ public class MantleObjectComponentBoundaryRadiusTest {
verify(complex).allowsNewGenerationFootprint(95, 195, 105, 205);
}
@Test
public void inheritedRadiusMatchesScaledFrameAndExplicitScaleOverridesIt() {
IrisDimension dimension = new IrisDimension().setAllObjectScaleFactor(2D);
IrisObjectPlacement inherited = new IrisObjectPlacement();
IrisBlockVector size = new IrisBlockVector(8, 8, 8);
assertEquals(16, MantleObjectComponent.calculatePlacementReach(size, inherited,
inherited.getMaximumScale(dimension)));
dimension.setAllObjectScaleFactor(0.5D);
assertEquals(4, MantleObjectComponent.calculatePlacementReach(size, inherited,
inherited.getMaximumScale(dimension)));
inherited.setScale(new IrisObjectScale().setSize(1D));
assertEquals(8, MantleObjectComponent.calculatePlacementReach(size, inherited,
inherited.getMaximumScale(dimension)));
}
@Test
public void alreadyScaledObjectFootprintDoesNotApplyScaleTwice() {
IrisComplex complex = mock(IrisComplex.class);
IrisObject object = new IrisObject(20, 20, 20);
IrisObjectPlacement placement = new IrisObjectPlacement().setScale(new IrisObjectScale().setSize(2D));
MantleObjectComponent.allowsObjectPlacement(complex, object, placement, 100, 200);
verify(complex).allowsNewGenerationFootprint(80, 180, 120, 220);
}
@Test
public void sourceReplayUsesAscendingSignedCoordinates() {
List<String> replayedSources = new ArrayList<>();
@@ -185,6 +209,7 @@ public class MantleObjectComponentBoundaryRadiusTest {
when(region.getObjects()).thenReturn(new KList<>());
when(region.getProceduralObjects()).thenReturn(proceduralObjects);
IrisDimension dimension = mock(IrisDimension.class);
when(dimension.getAllObjectScaleFactor()).thenReturn(1D);
when(dimension.getAllRegions(any())).thenReturn(new KList<>(region));
when(dimension.getReachableBiomes(any())).thenReturn(new KList<>());
Engine engine = mock(Engine.class);
@@ -215,10 +240,12 @@ public class MantleObjectComponentBoundaryRadiusTest {
when(upperRegion.getObjects()).thenReturn(new KList<>());
when(upperRegion.getProceduralObjects()).thenReturn(proceduralObjects);
IrisDimension baseDimension = mock(IrisDimension.class);
when(baseDimension.getAllObjectScaleFactor()).thenReturn(1D);
when(baseDimension.isUpperDimensionObjects()).thenReturn(true);
when(baseDimension.getAllRegions(any())).thenReturn(new KList<>());
when(baseDimension.getReachableBiomes(any())).thenReturn(new KList<>());
IrisDimension upperDimension = mock(IrisDimension.class);
when(upperDimension.getAllObjectScaleFactor()).thenReturn(1D);
when(upperDimension.getAllRegions(any())).thenReturn(new KList<>(upperRegion));
when(upperDimension.getReachableBiomes(any())).thenReturn(new KList<>());
UpperDimensionContext upperContext = mock(UpperDimensionContext.class);
@@ -252,6 +279,7 @@ public class MantleObjectComponentBoundaryRadiusTest {
.setTranslate(new IrisObjectTranslate().setX(32));
IrisBiome biome = new IrisBiome().setObjects(new KList<>(placement));
IrisDimension dimension = mock(IrisDimension.class);
when(dimension.getAllObjectScaleFactor()).thenReturn(1D);
when(dimension.isUseMantle()).thenReturn(true);
when(dimension.getAllRegions(any())).thenReturn(new KList<>());
when(dimension.getReachableBiomes(any())).thenReturn(new KList<>(biome));
@@ -0,0 +1,195 @@
package art.arcane.iris.engine.object;
import art.arcane.iris.core.loader.IrisData;
import art.arcane.iris.spi.PlatformBlockState;
import art.arcane.iris.util.common.math.IrisBlockVector;
import art.arcane.volmlib.util.math.RNG;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.junit.After;
import org.junit.Test;
import org.junit.Rule;
import org.junit.rules.TemporaryFolder;
import java.nio.file.Files;
import java.nio.file.Path;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThrows;
import static org.mockito.Mockito.CALLS_REAL_METHODS;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class IrisObjectGlobalScaleTest {
@Rule
public final TemporaryFolder temporaryFolder = new TemporaryFolder();
private final Gson gson = new Gson();
@After
public void clearCache() {
IrisObjectScale.invalidate(null);
}
@Test
public void defaultFactorPreservesObjectAndRandomSequence() {
IrisObject origin = new IrisObject(8, 8, 8);
IrisObjectPlacement placement = gson.fromJson("{}", IrisObjectPlacement.class);
RNG actual = new RNG(731L);
RNG expected = new RNG(731L);
assertNull(placement.getScale());
assertSame(origin, placement.scaleObject(actual, origin, new IrisDimension()));
assertSame(origin, placement.scaleObject(actual, origin, null));
assertEquals(expected.nextLong(), actual.nextLong());
assertFalse(gson.toJson(placement).contains("\"scale\""));
assertNull(placement.toPlacement("trees/oak").getScale());
}
@Test
public void dimensionFactorsUseSeparateCachedVariantsWithoutChangingSourceOrRandomness() {
IrisObject origin = new IrisObject(8, 8, 8);
IrisObjectPlacement placement = new IrisObjectPlacement();
IrisDimension large = new IrisDimension().setAllObjectScaleFactor(2D);
IrisDimension small = new IrisDimension().setAllObjectScaleFactor(0.5D);
RNG actual = new RNG(731L);
RNG expected = new RNG(731L);
IrisObject doubled = placement.scaleObject(actual, origin, large);
IrisObject halved = placement.scaleObject(actual, origin, small);
assertEquals(16, doubled.getW());
assertEquals(4, halved.getW());
assertEquals(8, origin.getW());
assertNotSame(origin, doubled);
assertNotSame(doubled, halved);
assertSame(doubled, placement.scaleObject(actual, origin, large));
assertSame(halved, placement.scaleObject(actual, origin, small));
assertEquals(expected.nextLong(), actual.nextLong());
}
@Test
public void globalAndExplicitFactorsScaleOccupiedCubeBoundsExactly() {
IrisObject origin = new IrisObject(4, 4, 4);
PlatformBlockState stone = mock(PlatformBlockState.class);
for (int x = 0; x < 4; x++) {
for (int y = 0; y < 4; y++) {
for (int z = 0; z < 4; z++) {
origin.setUnsigned(x, y, z, stone);
}
}
}
IrisObjectPlacement inherited = new IrisObjectPlacement();
IrisDimension doubled = new IrisDimension().setAllObjectScaleFactor(2D);
assertOccupiedCube(inherited.scaleObject(new RNG(1), origin, doubled), 8);
assertOccupiedCube(inherited.scaleObject(new RNG(1), origin,
new IrisDimension().setAllObjectScaleFactor(0.5D)), 2);
assertOccupiedCube(new IrisObjectPlacement().setScale(new IrisObjectScale().setSize(0.5D))
.scaleObject(new RNG(1), origin, doubled), 2);
assertOccupiedCube(new IrisObjectPlacement().setScale(new IrisObjectScale())
.scaleObject(new RNG(1), origin, doubled), 4);
assertOccupiedCube(origin, 4);
}
@Test
public void disabledVariationRangeReportsTheUnscaledFootprint() {
IrisObject origin = new IrisObject(4, 4, 4);
IrisObjectScale scale = new IrisObjectScale().setMinimumScale(0.25D)
.setMaximumScale(0.25D).setVariations(0);
assertSame(origin, scale.get(new RNG(1), origin));
assertEquals(1D, scale.getMaxScale(), 0D);
}
@Test
public void explicitUnitAndEmptyScaleOverrideDimensionFactorAcrossJsonCopies() {
IrisObject origin = new IrisObject(8, 8, 8);
IrisDimension dimension = new IrisDimension().setAllObjectScaleFactor(2D);
for (String json : new String[]{"{\"scale\":{}}", "{\"scale\":{\"size\":1.0}}"}) {
IrisObjectPlacement placement = gson.fromJson(json, IrisObjectPlacement.class);
IrisObjectPlacement copy = gson.fromJson(gson.toJson(placement.toPlacement("tree")), IrisObjectPlacement.class);
assertSame(origin, copy.scaleObject(new RNG(1), origin, dimension));
assertEquals(1D, copy.getMaximumScale(dimension), 0D);
}
}
@Test
public void referencedScaleSnippetRemainsAnExplicitOverride() throws Exception {
Path root = temporaryFolder.getRoot().toPath();
Path snippets = Files.createDirectories(root.resolve("snippet/object-scale"));
Files.writeString(snippets.resolve("unit.json"), "{}");
Files.writeString(snippets.resolve("half.json"), "{\"size\":0.5}");
IrisData data = mock(IrisData.class, CALLS_REAL_METHODS);
when(data.getDataFolder()).thenReturn(root.toFile());
Gson loaderGson = new GsonBuilder().registerTypeAdapterFactory(data).create();
IrisDimension dimension = new IrisDimension().setAllObjectScaleFactor(2D);
IrisObject origin = new IrisObject(4, 4, 4);
IrisObjectPlacement unit = loaderGson.fromJson(
"{\"scale\":\"snippet/object-scale/unit\"}", IrisObjectPlacement.class);
IrisObjectPlacement half = loaderGson.fromJson(
"{\"scale\":\"snippet/object-scale/half\"}", IrisObjectPlacement.class);
assertNotNull(unit.getScale());
assertSame(origin, unit.scaleObject(new RNG(1), origin, dimension));
assertEquals(2, half.scaleObject(new RNG(1), origin, dimension).getW());
IrisObjectPlacement copied = loaderGson.fromJson(loaderGson.toJson(half.toPlacement("tree")),
IrisObjectPlacement.class);
assertEquals(0.5D, copied.getMaximumScale(dimension), 0D);
}
@Test
public void explicitRangeKeepsItsExistingSelectionSequenceWithoutMultiplying() {
IrisObject origin = new IrisObject(8, 8, 8);
IrisObjectScale authored = new IrisObjectScale().setMinimumScale(0.5D)
.setMaximumScale(1.5D).setVariations(4);
IrisObjectPlacement placement = new IrisObjectPlacement().setScale(authored);
IrisDimension dimension = new IrisDimension().setAllObjectScaleFactor(3D);
RNG actual = new RNG(1337);
RNG expected = new RNG(1337);
for (int index = 0; index < 12; index++) {
assertSame(authored.get(expected, origin), placement.scaleObject(actual, origin, dimension));
}
assertEquals(1.5D, placement.getMaximumScale(dimension), 0D);
assertEquals(expected.nextLong(), actual.nextLong());
}
@Test
public void explicitOppositeFactorWinsAndDirectSettersRejectUnsafeValues() {
IrisObject origin = new IrisObject(8, 8, 8);
IrisObjectPlacement placement = new IrisObjectPlacement().setScale(new IrisObjectScale().setSize(0.5D));
assertSame(IrisObjectScale.getFixed(origin, 0.5D), placement.scaleObject(new RNG(1), origin,
new IrisDimension().setAllObjectScaleFactor(2D)));
for (double invalid : new double[]{Double.NaN, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY,
0D, -1D, 0.009D, 50.001D}) {
assertThrows(IllegalArgumentException.class, () -> new IrisDimension().setAllObjectScaleFactor(invalid));
assertThrows(IllegalArgumentException.class, () -> new IrisObjectScale().setSize(invalid));
assertThrows(IllegalArgumentException.class, () -> new IrisStaticObject().setScale(invalid));
}
assertThrows(IllegalArgumentException.class, () -> gson.fromJson(
"{\"allObjectScaleFactor\":0}", IrisDimension.class).getAllObjectScaleFactor());
}
private static void assertOccupiedCube(IrisObject object, int width) {
assertEquals(width, object.getW());
assertEquals(width, object.getH());
assertEquals(width, object.getD());
assertEquals(width * width * width, object.getBlocks().size());
int minimum = -(width / 2);
for (int x = minimum; x < minimum + width; x++) {
for (int y = minimum; y < minimum + width; y++) {
for (int z = minimum; z < minimum + width; z++) {
assertNotNull(object.getBlocks().get(new IrisBlockVector(x, y, z)));
}
}
}
assertEquals(minimum, object.getAABB().min().getX());
assertEquals(minimum + width - 1, object.getAABB().max().getX());
}
}
@@ -2,12 +2,14 @@ package art.arcane.iris.engine.object;
import art.arcane.iris.core.loader.IrisData;
import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.engine.framework.NativeStructureVolume;
import art.arcane.iris.engine.framework.PlacedObject;
import art.arcane.iris.spi.IrisPlatform;
import art.arcane.iris.spi.IrisPlatforms;
import art.arcane.iris.spi.PlatformBlockState;
import art.arcane.iris.spi.PlatformRegistries;
import art.arcane.iris.testsupport.PlatformLeakGuard;
import art.arcane.iris.util.project.noise.CNG;
import art.arcane.volmlib.util.collection.KList;
import art.arcane.volmlib.util.collection.KMap;
import art.arcane.volmlib.util.math.RNG;
@@ -37,7 +39,9 @@ import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyDouble;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -279,6 +283,133 @@ public class IrisObjectPlacementRunnerRegressionTest {
assertEquals(1, object.getStates().size());
}
@Test
public void paintFollowsTheLowerLedgeBeneathADistantRoof() {
RecordingPlacer placer = new RecordingPlacer(null);
placer.terrain(1, 0, 0, 80, 150, 160);
placer.terrain(2, 0, 0, 80, 150, 160);
IrisObjectPlacement placement = placement().setMode(ObjectPlaceMode.PAINT)
.setRequireSurfaceSupport(true).setSurfaceSupportBuffer(0);
assertEquals(80, lineObject(5).place(0, -1, 0, placer, placement, new RNG(2L), data));
assertEquals(5, placer.writes().size());
assertTrue(placer.writes().stream().allMatch(write -> write.y() == 80));
}
@Test
public void paintClipsAtACliffWithoutReachingThePatchBeyondIt() {
RecordingPlacer placer = new RecordingPlacer(null);
placer.terrain(1, 0, 0, 20);
IrisObjectPlacement placement = placement().setMode(ObjectPlaceMode.PAINT);
lineObject(5).place(0, -1, 0, placer, placement, new RNG(2L), data);
assertEquals(3, placer.writes().size());
assertTrue(placer.writes().stream().allMatch(write -> write.x() <= 0 && write.y() == 80));
}
@Test
public void paintAccumulatesNormalAscendingAndDescendingSlopeSteps() {
RecordingPlacer placer = new RecordingPlacer(null);
for (int x = -6; x <= 6; x++) {
placer.terrain(x, 0, 0, 80 + x * 3);
}
lineObject(13).place(0, -1, 0, placer, placement().setMode(ObjectPlaceMode.PAINT), new RNG(2L), data);
assertEquals(13, placer.writes().size());
assertTrue(placer.writes().stream().allMatch(write -> write.y() == 80 + write.x() * 3));
}
@Test
public void paintingAndBoringCannotFeedBackIntoTheSurfaceProjection() {
RecordingPlacer placer = new RecordingPlacer(null);
placer.highestFollowsWrites = true;
IrisObjectPlacement placement = placement().setMode(ObjectPlaceMode.PAINT).setBore(true);
boxObject(1, 3, 1).place(0, -1, 0, placer, placement, new RNG(2L), data);
assertEquals(3, placer.writesOf(solid).size());
assertTrue(placer.writes().stream().allMatch(write -> write.y() >= 80 && write.y() <= 82));
}
@Test
public void paintUsesTranslatedAnchorAndMatchingRandomizedBoreBounds() {
RecordingPlacer placer = new RecordingPlacer(null);
placer.terrain(0, -3, 0, 108);
placer.terrain(0, -4, 0, 110);
placer.terrain(0, -5, 0, 112);
IrisObjectPlacement placement = placement().setMode(ObjectPlaceMode.PAINT).setBore(true)
.setRotation(IrisObjectRotation.of(0, 90, 0))
.setTranslate(new IrisObjectTranslate().setX(4).setY(5).setYRandom(3));
RNG rng = mock(RNG.class);
when(rng.i(0, 3)).thenReturn(3);
assertEquals(113, lineObject(3).place(0, -1, 0, placer, placement, rng, data));
assertEquals(3, placer.writesOf(solid).size());
assertEquals(3, placer.writesOf(IrisObject.States.air()).size());
assertTrue(placer.writes().stream().allMatch(write -> write.x() == 0
&& write.z() >= -5 && write.z() <= -3 && write.y() == 110 - (write.z() + 4) * 2 + 8));
}
@Test
public void nativeVetoUsesTheSelectedLedgeAndRandomTranslationOffset() {
RecordingPlacer placer = new RecordingPlacer(engine);
placer.terrain(1, 0, 0, 80, 150, 160);
when(engine.getMinHeight()).thenReturn(-64);
when(engine.getNativeStructureVolumes(anyInt(), anyInt(), anyInt(), anyInt())).thenReturn(
new KList<>(NativeStructureVolume.of("test", 1, 21, 0, 1, 21, 0)));
IrisObjectPlacement placement = placement().setMode(ObjectPlaceMode.PAINT).setForcePlace(true)
.setTranslate(new IrisObjectTranslate().setY(2).setYRandom(3));
RNG rng = mock(RNG.class);
when(rng.i(0, 3)).thenReturn(3);
assertEquals(-1, lineObject(3).place(0, -1, 0, placer, placement, rng, data));
assertTrue(placer.writes().isEmpty());
when(engine.getNativeStructureVolumes(anyInt(), anyInt(), anyInt(), anyInt())).thenReturn(
new KList<>(NativeStructureVolume.of("test", 1, 101, 0, 1, 101, 0)));
assertEquals(83, lineObject(3).place(0, -1, 0, placer, placement, rng, data));
assertTrue(placer.writes().stream().allMatch(write -> write.y() == 85));
}
@Test
public void warpedPaintAndNativeVetoUseTheSameFinalColumns() {
RecordingPlacer placer = new RecordingPlacer(engine);
for (int x = 1; x <= 3; x++) {
placer.terrain(x, 2, 0, 80, 150, 160);
}
IrisObjectPlacement placement = spy(placement().setMode(ObjectPlaceMode.PAINT).setForcePlace(true)
.setWarp(new IrisGeneratorStyle(NoiseStyle.SIMPLEX).setMultiplier(4)));
CNG warp = mock(CNG.class);
when(warp.fitDouble(anyDouble(), anyDouble(), anyDouble(), anyDouble(), anyDouble())).thenReturn(2D);
doReturn(warp).when(placement).getSurfaceWarp(any(RNG.class), any());
when(engine.getNativeStructureVolumes(anyInt(), anyInt(), anyInt(), anyInt())).thenReturn(
new KList<>(NativeStructureVolume.of("test", 3, 80, 2, 3, 80, 2)));
assertEquals(-1, lineObject(3).place(0, -1, 0, placer, placement, new RNG(2L), data));
assertTrue(placer.writes().isEmpty());
when(engine.getNativeStructureVolumes(anyInt(), anyInt(), anyInt(), anyInt())).thenReturn(NativeStructureVolume.NONE);
lineObject(3).place(0, -1, 0, placer, placement, new RNG(2L), data);
assertEquals(3, placer.writes().size());
assertTrue(placer.writes().stream().allMatch(write -> write.x() >= 1 && write.x() <= 3
&& write.z() == 2 && write.y() == 80));
}
@Test
public void paintSupportChecksTheThicknessOfTheSelectedLowerLedge() {
RecordingPlacer placer = new RecordingPlacer(null);
placer.terrain(1, 0, 0, 20, 80, 80, 150, 160);
IrisObjectPlacement placement = placement().setMode(ObjectPlaceMode.PAINT)
.setRequireSurfaceSupport(true).setSurfaceSupportBuffer(0).setSurfaceSupportDepth(2);
assertEquals(-1, lineObject(3).place(0, -1, 0, placer, placement, new RNG(2L), data));
assertTrue(placer.writes().isEmpty());
}
private IrisObjectPlacement placement() {
IrisObjectPlacement placement = new IrisObjectPlacement();
placement.setMode(ObjectPlaceMode.CENTER_HEIGHT);
@@ -326,9 +457,11 @@ public class IrisObjectPlacementRunnerRegressionTest {
private final Map<String, PlatformBlockState> world = new HashMap<>();
private final Map<String, Object> data = new HashMap<>();
private final List<String> sampledColumns = new ArrayList<>();
private final Map<String, int[]> terrain = new HashMap<>();
private final Engine engine;
private int failAfterWrites = Integer.MAX_VALUE;
private boolean debugSmartBore;
private boolean highestFollowsWrites;
private RecordingPlacer(Engine engine) {
this.engine = engine;
@@ -342,6 +475,16 @@ public class IrisObjectPlacementRunnerRegressionTest {
this.debugSmartBore = debugSmartBore;
}
private void terrain(int x, int z, int... spans) {
terrain.put(x + ":" + z, spans);
}
private int highest(int x, int z) {
int[] spans = terrain.get(x + ":" + z);
return (spans == null ? ANCHOR_Y : spans[spans.length - 1])
+ (highestFollowsWrites && !writes.isEmpty() ? 1000 : 0);
}
private List<BlockWrite> writes() {
return writes;
}
@@ -357,13 +500,13 @@ public class IrisObjectPlacementRunnerRegressionTest {
@Override
public int getHighest(int x, int z, IrisData data) {
sampledColumns.add(x + ":" + z);
return ANCHOR_Y;
return highest(x, z);
}
@Override
public int getHighest(int x, int z, IrisData data, boolean ignoreFluid) {
sampledColumns.add(x + ":" + z);
return ANCHOR_Y;
return highest(x, z);
}
@Override
@@ -390,6 +533,20 @@ public class IrisObjectPlacementRunnerRegressionTest {
return false;
}
@Override
public boolean isSurfaceSolid(int x, int y, int z) {
int[] spans = terrain.get(x + ":" + z);
if (spans == null) {
return y >= 0 && y <= ANCHOR_Y;
}
for (int index = 0; index < spans.length; index += 2) {
if (y >= spans[index] && y <= spans[index + 1]) {
return true;
}
}
return false;
}
@Override
public boolean isSolid(int x, int y, int z) {
PlatformBlockState state = get(x, y, z);
@@ -215,6 +215,56 @@ public class IrisObjectTransformsTileTest {
assertEquals("minecraft:barrel", scaled.getBlocks().get(new IrisBlockVector(4, 0, 0)).materialKey());
}
@Test
public void fractionalReductionIncludesTheOuterSavedVoxelEdge() {
IrisObject source = new IrisObject(4, 1, 1);
source.setUnsigned(3, 0, 0, state("minecraft:chest"));
source.setUnsignedTile(3, 0, 0, tile("minecraft:chest", "edge"));
IrisObject scaled = source.scaledAroundOrigin(0.75D, IrisObjectPlacementScaleInterpolator.NONE);
assertEquals(2, scaled.getBlocks().size());
assertEquals("minecraft:chest", scaled.getBlocks().get(new IrisBlockVector(1, 0, 0)).materialKey());
assertEquals("edge", scaled.getStates().get(new IrisBlockVector(1, 0, 0)).getProperties().get("name"));
assertEquals(1, scaled.getAABB().max().getX());
}
@Test
public void fractionalScaleWinnersDoNotDependOnSourceInsertionOrder() {
for (boolean savedOrigin : new boolean[]{false, true}) {
IrisObject ascending = collisionSource(false);
IrisObject descending = collisionSource(true);
IrisObject first = savedOrigin
? ascending.scaledAroundOrigin(0.5D, IrisObjectPlacementScaleInterpolator.NONE)
: ascending.scaled(0.5D, IrisObjectPlacementScaleInterpolator.NONE);
IrisObject second = savedOrigin
? descending.scaledAroundOrigin(0.5D, IrisObjectPlacementScaleInterpolator.NONE)
: descending.scaled(0.5D, IrisObjectPlacementScaleInterpolator.NONE);
assertEquals(2, first.getBlocks().size());
assertEquals(1, first.getStates().size());
assertEquals("minecraft:stone", first.getBlocks().get(new IrisBlockVector(-1, 0, 0)).materialKey());
assertEquals("winner", first.getStates().get(new IrisBlockVector(0, 0, 0)).getProperties().get("name"));
for (Map.Entry<IrisBlockVector, PlatformBlockState> entry : first.getBlocks()) {
assertEquals(entry.getValue().materialKey(), second.getBlocks().get(entry.getKey()).materialKey());
assertEquals(first.getStates().get(entry.getKey()), second.getStates().get(entry.getKey()));
}
}
}
private static IrisObject collisionSource(boolean reverse) {
IrisObject source = new IrisObject(4, 1, 1);
for (int index = 0; index < 4; index++) {
int x = reverse ? 3 - index : index;
String material = x == 1 ? "minecraft:stone" : "minecraft:chest";
source.setUnsigned(x, 0, 0, state(material));
if (x != 1) {
source.setUnsignedTile(x, 0, 0, tile(material, x == 3 ? "winner" : "overwritten"));
}
}
return source;
}
@Test
public void untypedTilesRemainAttachedToTheirScaledSourceMaterial() {
IrisObject source = new IrisObject(1, 1, 1);
@@ -0,0 +1,109 @@
package art.arcane.iris.engine.object;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class IrisPaintSurfaceProjectionTest {
@Test
public void positiveAndNegativeChunkSeamsHaveIdenticalRelativeSurfaces() {
IrisPaintSurfaceProjection positive = projection(15);
IrisPaintSurfaceProjection negative = projection(-17);
assertNotNull(positive);
assertNotNull(negative);
for (int x = -5; x <= 5; x++) {
for (int z = -1; z <= 1; z++) {
assertEquals(80 + x * 2, positive.surfaceY(15 + x, z));
assertEquals(positive.surfaceY(15 + x, z), negative.surfaceY(-17 + x, z));
}
}
}
@Test
public void exposedFloorsMustExistAtTheRequestedAnchor() {
IObjectPlacer placer = mock(IObjectPlacer.class);
when(placer.getHighest(anyInt(), anyInt(), any(), anyBoolean())).thenReturn(140);
when(placer.isSurfaceSolid(anyInt(), anyInt(), anyInt())).thenAnswer(invocation -> {
int y = invocation.getArgument(1);
return y >= 0 && y <= 100;
});
assertNull(IrisPaintSurfaceProjection.create(placer, request(80, false)));
assertNotNull(IrisPaintSurfaceProjection.create(placer, request(100, false)));
assertNull(IrisPaintSurfaceProjection.create(placer, request(140, false)));
}
@Test
public void aVirtualHeightmapDoesNotFallBackToPhysicalLedges() {
IObjectPlacer placer = mock(IObjectPlacer.class);
when(placer.getHighest(anyInt(), anyInt(), any(), anyBoolean()))
.thenAnswer(invocation -> (int) invocation.getArgument(0) == 0 ? 80 : 140);
when(placer.isSurfaceSolid(anyInt(), anyInt(), anyInt()))
.thenAnswer(invocation -> (int) invocation.getArgument(1) <= 80);
IrisPaintSurfaceProjection projection = IrisPaintSurfaceProjection.create(placer, request(80, true));
assertNotNull(projection);
assertEquals(80, projection.surfaceY(0, 0));
assertEquals(IrisPaintSurfaceProjection.MISSING, projection.surfaceY(1, 0));
}
@Test
public void surfaceChoicesStayCompatibleWhenTwoTraversalBranchesMeet() {
IObjectPlacer placer = mock(IObjectPlacer.class);
when(placer.getHighest(anyInt(), anyInt(), any(), anyBoolean())).thenAnswer(invocation -> {
int x = invocation.getArgument(0);
int z = invocation.getArgument(1);
return 80 + x * z;
});
when(placer.isSurfaceSolid(anyInt(), anyInt(), anyInt())).thenAnswer(invocation -> {
int x = invocation.getArgument(0);
int y = invocation.getArgument(1);
int z = invocation.getArgument(2);
return y <= 80 + x * z;
});
IrisPaintSurfaceProjection projection = IrisPaintSurfaceProjection.create(placer,
new IrisPaintSurfaceProjection.Request(null, -6, 6, -6, 6, 0, 80, 0, true, false));
assertNotNull(projection);
for (int x = -6; x < 6; x++) {
for (int z = -6; z < 6; z++) {
int surface = projection.surfaceY(x, z);
if (surface == IrisPaintSurfaceProjection.MISSING) {
continue;
}
int east = projection.surfaceY(x + 1, z);
int south = projection.surfaceY(x, z + 1);
assertTrue(east == IrisPaintSurfaceProjection.MISSING || Math.abs(surface - east) <= 4);
assertTrue(south == IrisPaintSurfaceProjection.MISSING || Math.abs(surface - south) <= 4);
}
}
}
private IrisPaintSurfaceProjection projection(int anchorX) {
IObjectPlacer placer = mock(IObjectPlacer.class);
when(placer.getHighest(anyInt(), anyInt(), any(), anyBoolean())).thenAnswer(invocation -> {
int x = invocation.getArgument(0);
return x > anchorX ? 200 : 80 + (x - anchorX) * 2;
});
when(placer.isSurfaceSolid(anyInt(), anyInt(), anyInt())).thenAnswer(invocation -> {
int x = invocation.getArgument(0);
int y = invocation.getArgument(1);
return y <= 80 + (x - anchorX) * 2 || x > anchorX && y >= 190 && y <= 200;
});
return IrisPaintSurfaceProjection.create(placer, new IrisPaintSurfaceProjection.Request(null,
anchorX - 5, anchorX + 5, -1, 1, anchorX, 80, 0, true, false));
}
private IrisPaintSurfaceProjection.Request request(int anchorY, boolean virtual) {
return new IrisPaintSurfaceProjection.Request(null, 0, 1, 0, 0, 0, anchorY, 0, true, virtual);
}
}
@@ -81,6 +81,41 @@ public class IrisStaticObjectLayerTest {
verifyNoInteractions(engine, loader);
}
@Test
public void globalScalingKeepsSavedOriginsAndExplicitEntrySizes() {
IrisObject cube = object("cube", 4, 4, 4);
for (int x = -2; x < 2; x++) {
for (int y = -2; y < 2; y++) {
for (int z = -2; z < 2; z++) {
cube.getBlocks().put(new IrisBlockVector(x, y, z), state("minecraft:stone"));
}
}
}
for (double factor : new double[]{2D, 0.5D}) {
IrisDimension dimension = new IrisDimension().setAllObjectScaleFactor(factor)
.setStaticObjects(new KList<>(entry("cube", 16, 100, 16),
entry("cube", 48, 100, 16).setScale(1D),
entry("cube", 80, 100, 16).setScale(1D / factor)));
IrisStaticObjectLayer layer = dimension.getStaticObjectLayer(data);
int[] counts = new int[3];
for (int chunkX = 0; chunkX < 6; chunkX++) {
for (int chunkZ = 0; chunkZ < 2; chunkZ++) {
counts[chunkX / 2] += layer.blocks(chunkX, chunkZ).size();
}
}
assertEquals(factor == 2D ? 512 : 8, counts[0]);
assertEquals(64, counts[1]);
assertEquals(factor == 2D ? 8 : 512, counts[2]);
int halfWidth = factor == 2D ? 4 : 1;
assertTrue(layer.contains(16 - halfWidth, 164 - halfWidth, 16 - halfWidth));
assertFalse(layer.contains(15 - halfWidth, 164 - halfWidth, 16 - halfWidth));
dimension.setAllObjectScaleFactor(1D);
assertNotSame(layer, dimension.getStaticObjectLayer(data));
}
assertEquals(64, cube.getBlocks().size());
assertEquals(4, cube.getW());
}
@Test
public void rotatedNegativeCoordinatesAndMinusOneYUseExactWorldOrigin() {
IrisObject object = object("cross", 5, 3, 5);
@@ -9,6 +9,7 @@ import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
@@ -23,7 +24,7 @@ public class IrisStaticObjectTest {
assertEquals(ObjectPlaceMode.STRUCTURE_PIECE, placement.getMode());
assertTrue(placement.isForcePlace());
assertFalse(placement.getRotation().isEnabled());
assertFalse(placement.getScale().shouldScale());
assertNull(placement.getScale());
assertEquals(new IrisPosition(100, 100, -100), object.getPosition());
}
@@ -69,8 +70,8 @@ public class IrisStaticObjectTest {
assertThrows(IllegalArgumentException.class, () -> new IrisStaticObject().validate(-64, 320));
assertThrows(IllegalArgumentException.class, () -> validObject().setPosition(null).validate(-64, 320));
assertThrows(IllegalArgumentException.class, () -> validObject().setScale(Double.NaN).validate(-64, 320));
assertThrows(IllegalArgumentException.class, () -> validObject().setScale(0).validate(-64, 320));
assertThrows(IllegalArgumentException.class, () -> validObject().setScale(51).validate(-64, 320));
assertThrows(IllegalArgumentException.class, () -> validObject().setScale(0D).validate(-64, 320));
assertThrows(IllegalArgumentException.class, () -> validObject().setScale(51D).validate(-64, 320));
assertThrows(IllegalArgumentException.class, () -> validObject().setRotation(null).validate(-64, 320));
assertThrows(IllegalArgumentException.class,
() -> validObject().setRotation(new IrisStaticObjectRotation().setY(-361)).validate(-64, 320));
@@ -0,0 +1,29 @@
package art.arcane.iris.util.common.director.specialhandlers;
import art.arcane.volmlib.util.director.exceptions.DirectorParsingException;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
public class ObjectScaleHandlerTest {
@Test
public void distinguishesInheritedScaleFromExplicitOne() throws Exception {
ObjectScaleHandler handler = new ObjectScaleHandler();
assertNull(handler.parse("dimension", true));
assertNull(handler.parse(" DIMENSION ", false));
assertEquals(1D, handler.parse("1.0", false), 0D);
assertEquals(0.5D, handler.parse("0.5", false), 0D);
assertEquals(2D, handler.parse("2", false), 0D);
assertEquals("dimension", handler.toString(null));
}
@Test
public void rejectsInvalidManualFactors() {
ObjectScaleHandler handler = new ObjectScaleHandler();
for (String invalid : new String[]{"NaN", "Infinity", "-Infinity", "0", "-1", "0.009", "50.001"}) {
assertThrows(invalid, DirectorParsingException.class, () -> handler.parse(invalid, false));
}
}
}