From e8dd4e1127dae610a11b36833e221f9a3efff335 Mon Sep 17 00:00:00 2001 From: Brian Neumann-Fopiano Date: Thu, 16 Jul 2026 18:18:23 -0400 Subject: [PATCH] iggy --- .gitignore | 2 + .../core/nms/v26_2_R1/IrisChunkGenerator.java | 3 +- ...nkGeneratorMonumentLocateContractTest.java | 2 + .../NativeStructurePostProcessor.java | 29 ++- .../iris/modded/IrisModdedChunkGenerator.java | 3 +- .../arcane/iris/modded/ModdedWorldCheck.java | 12 +- .../components/IrisStructureComponent.java | 174 +++++++++++++++--- .../StructureFoundationPlanner.java | 5 + .../IrisStructureComponentOverboreTest.java | 22 +++ .../StructureFoundationPlannerTest.java | 13 ++ 10 files changed, 229 insertions(+), 36 deletions(-) diff --git a/.gitignore b/.gitignore index 0fd13b24a..98b4d70ee 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,5 @@ service-account*.json docs/ CROSSPLATFORM_PLAN.md + +.qa/ diff --git a/adapters/bukkit/nms/v26_2_R1/src/main/java/art/arcane/iris/core/nms/v26_2_R1/IrisChunkGenerator.java b/adapters/bukkit/nms/v26_2_R1/src/main/java/art/arcane/iris/core/nms/v26_2_R1/IrisChunkGenerator.java index 7daddfd30..b083b4be6 100644 --- a/adapters/bukkit/nms/v26_2_R1/src/main/java/art/arcane/iris/core/nms/v26_2_R1/IrisChunkGenerator.java +++ b/adapters/bukkit/nms/v26_2_R1/src/main/java/art/arcane/iris/core/nms/v26_2_R1/IrisChunkGenerator.java @@ -465,7 +465,8 @@ public class IrisChunkGenerator extends CustomChunkGenerator { BoundingBox area, ChunkPos chunkPos, String structureId, StructureStart start, IrisNativeStructureDecision decision) { NativeStructurePostProcessor.place(world, structureManager, this, random, area, chunkPos, - structureId, start, decision, this::resolveStiltBlock); + structureId, start, decision, this::resolveStiltBlock, + (x, z) -> engine.getHeight(x, z, true) + engine.getMinHeight()); } private List> structuresByStep(Registry registry) { diff --git a/adapters/bukkit/nms/v26_2_R1/src/test/java/art/arcane/iris/core/nms/v26_2_R1/IrisChunkGeneratorMonumentLocateContractTest.java b/adapters/bukkit/nms/v26_2_R1/src/test/java/art/arcane/iris/core/nms/v26_2_R1/IrisChunkGeneratorMonumentLocateContractTest.java index 6ba42803d..d5eeceebe 100644 --- a/adapters/bukkit/nms/v26_2_R1/src/test/java/art/arcane/iris/core/nms/v26_2_R1/IrisChunkGeneratorMonumentLocateContractTest.java +++ b/adapters/bukkit/nms/v26_2_R1/src/test/java/art/arcane/iris/core/nms/v26_2_R1/IrisChunkGeneratorMonumentLocateContractTest.java @@ -85,10 +85,12 @@ public class IrisChunkGeneratorMonumentLocateContractTest { int placement = source.indexOf("start.placeInChunk(world, structureManager, generator"); int stiltPlacement = source.indexOf("placeStilts(world, area, structureId, start", placement); int occupancyCheck = source.indexOf("if (state.isSolid())", stiltPlacement); + int terrainFloor = source.indexOf("y > terrainY", stiltPlacement); assertTrue(placement >= 0); assertTrue(stiltPlacement > placement); assertTrue(occupancyCheck > stiltPlacement); + assertTrue(terrainFloor > stiltPlacement); assertFalse(source.contains("state.equals(")); assertFalse(source.contains("snapshot.states")); } diff --git a/adapters/minecraft-common/src/main/java/art/arcane/iris/nativegen/NativeStructurePostProcessor.java b/adapters/minecraft-common/src/main/java/art/arcane/iris/nativegen/NativeStructurePostProcessor.java index 5a7b8f1f9..391db4f2c 100644 --- a/adapters/minecraft-common/src/main/java/art/arcane/iris/nativegen/NativeStructurePostProcessor.java +++ b/adapters/minecraft-common/src/main/java/art/arcane/iris/nativegen/NativeStructurePostProcessor.java @@ -60,13 +60,15 @@ public final class NativeStructurePostProcessor { public static void place(WorldGenLevel world, StructureManager structureManager, ChunkGenerator generator, WorldgenRandom random, BoundingBox area, ChunkPos chunkPos, String structureId, StructureStart start, IrisNativeStructureDecision decision, - StiltBlockResolver stiltBlockResolver) { + StiltBlockResolver stiltBlockResolver, + IntBinaryOperator surfaceHeight) { IrisStructureStiltSettings stilt = decision.stilt(); ensureMonumentSeaLevelAlignment(start, structureId, decision.yShift(), generator.getSeaLevel(), area.minY(), area.maxY() + 1); start.placeInChunk(world, structureManager, generator, random, area, chunkPos); if (stilt != null) { - placeStilts(world, area, structureId, start, stilt, stiltBlockResolver); + placeStilts(world, area, structureId, start, stilt, stiltBlockResolver, surfaceHeight, + !isUndergroundStep(start.getStructure().step())); } } @@ -798,7 +800,10 @@ public final class NativeStructurePostProcessor { private static void placeStilts(WorldGenLevel world, BoundingBox area, String structureId, StructureStart start, IrisStructureStiltSettings settings, - StiltBlockResolver stiltBlockResolver) { + StiltBlockResolver stiltBlockResolver, + IntBinaryOperator surfaceHeight, + boolean surfaceStructure) { + Objects.requireNonNull(surfaceHeight, "Structure stilts require an Iris terrain height resolver"); BlockPos.MutableBlockPos position = new BlockPos.MutableBlockPos(); int structureHash = structureId == null ? 0 : structureId.hashCode(); RNG rng = new RNG(world.getSeed() ^ structureHash); @@ -807,8 +812,12 @@ public final class NativeStructurePostProcessor { if (foundationY == Integer.MIN_VALUE) { continue; } + int terrainY = surfaceStructure + ? Math.max(area.minY(), Math.min( + area.maxY(), surfaceHeight.applyAsInt(column.x(), column.z()))) + : area.minY() - 1; for (int depth = 0, y = foundationY - 1; - depth < settings.getMaxDepth() && y >= area.minY(); depth++, y--) { + depth < settings.getMaxDepth() && y > terrainY; depth++, y--) { position.set(column.x(), y, column.z()); BlockState existingState = world.getBlockState(position); boolean vegetation = existingState.is(BlockTags.LOGS) || existingState.is(BlockTags.LEAVES); @@ -825,8 +834,10 @@ public final class NativeStructurePostProcessor { } public static StiltSupportAudit auditStiltSupport(WorldGenLevel world, BoundingBox area, - StructureStart start, BlockState expectedStilt) { + StructureStart start, BlockState expectedStilt, + IntBinaryOperator surfaceHeight) { Objects.requireNonNull(expectedStilt, "Expected stilt state must not be null"); + Objects.requireNonNull(surfaceHeight, "Structure stilt audit requires an Iris terrain height resolver"); BlockPos.MutableBlockPos position = new BlockPos.MutableBlockPos(); int baseColumns = 0; int stiltBlocks = 0; @@ -838,9 +849,15 @@ public final class NativeStructurePostProcessor { continue; } baseColumns++; - boolean grounded = foundationY == area.minY(); + int terrainY = Math.max(area.minY(), Math.min( + area.maxY(), surfaceHeight.applyAsInt(column.x(), column.z()))); + boolean grounded = foundationY <= terrainY + 1; boolean stiltColumn = false; for (int y = foundationY - 1; y >= area.minY(); y--) { + if (y <= terrainY) { + grounded = true; + break; + } BlockState state = world.getBlockState(position.set(column.x(), y, column.z())); if (state.is(expectedStilt.getBlock())) { stiltBlocks++; diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/IrisModdedChunkGenerator.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/IrisModdedChunkGenerator.java index 504a0fcaa..e6abd0bb1 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/IrisModdedChunkGenerator.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/IrisModdedChunkGenerator.java @@ -1041,7 +1041,8 @@ public final class IrisModdedChunkGenerator extends ChunkGenerator { String structureId, StructureStart start, IrisNativeStructureDecision decision) { NativeStructurePostProcessor.place(world, structureManager, this, random, area, chunkPos, - structureId, start, decision, this::resolveStiltBlock); + structureId, start, decision, this::resolveStiltBlock, + (x, z) -> engine().getHeight(x, z, true) + engine().getMinHeight()); } private List> structuresByStep(Registry registry) { diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedWorldCheck.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedWorldCheck.java index 07f5b6936..154130e41 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedWorldCheck.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedWorldCheck.java @@ -19,6 +19,7 @@ package art.arcane.iris.modded; import art.arcane.iris.core.nms.datapack.DataVersion; +import art.arcane.iris.engine.framework.Engine; import art.arcane.iris.engine.framework.NativeStructureGenerationPolicy; import art.arcane.iris.engine.framework.StructureVerticalBounds; import art.arcane.iris.engine.object.IrisDimension; @@ -44,6 +45,7 @@ import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.chunk.ChunkAccess; import net.minecraft.world.level.chunk.ChunkGeneratorStructureState; +import net.minecraft.world.level.chunk.ChunkGenerator; import net.minecraft.world.level.chunk.LevelChunkSection; import net.minecraft.world.level.dimension.DimensionType; import net.minecraft.world.level.levelgen.Heightmap; @@ -71,6 +73,7 @@ import java.util.Optional; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.BooleanSupplier; +import java.util.function.IntBinaryOperator; public final class ModdedWorldCheck { private static final int EXIT_PASS = 0; @@ -872,9 +875,16 @@ public final class ModdedWorldCheck { BoundingBox area = new BoundingBox( minimumChunkX, minimumWorldY, minimumChunkZ, maximumChunkX, maximumWorldY, maximumChunkZ); + ChunkGenerator chunkGenerator = level.getChunkSource().getGenerator(); + if (!(chunkGenerator instanceof IrisModdedChunkGenerator irisGenerator)) { + throw new IllegalStateException("Iris structure audit requires the Iris chunk generator"); + } + Engine engine = irisGenerator.commandEngine(); + IntBinaryOperator surfaceHeight = (x, z) -> + engine.getHeight(x, z, true) + engine.getMinHeight(); NativeStructurePostProcessor.StiltSupportAudit foundation = NativeStructurePostProcessor.auditStiltSupport( - level, area, start, Blocks.COBBLESTONE.defaultBlockState()); + level, area, start, Blocks.COBBLESTONE.defaultBlockState(), surfaceHeight); foundationBaseColumns = foundation.baseColumns(); foundationBlocks = foundation.stiltBlocks(); foundationColumns = foundation.stiltColumns(); diff --git a/core/src/main/java/art/arcane/iris/engine/mantle/components/IrisStructureComponent.java b/core/src/main/java/art/arcane/iris/engine/mantle/components/IrisStructureComponent.java index cb31734d5..d19113151 100644 --- a/core/src/main/java/art/arcane/iris/engine/mantle/components/IrisStructureComponent.java +++ b/core/src/main/java/art/arcane/iris/engine/mantle/components/IrisStructureComponent.java @@ -41,19 +41,23 @@ import art.arcane.iris.engine.object.IrisStructurePlacement; import art.arcane.iris.engine.object.IrisStructureStiltSettings; import art.arcane.iris.spi.IrisLogging; import art.arcane.iris.spi.PlatformBlockState; +import art.arcane.iris.util.project.matter.TileWrapper; import art.arcane.iris.util.project.noise.CNG; import art.arcane.volmlib.util.collection.KList; import art.arcane.iris.util.project.context.ChunkContext; import art.arcane.volmlib.util.documentation.ChunkCoordinates; +import art.arcane.volmlib.util.mantle.runtime.MantleChunk; +import art.arcane.volmlib.util.matter.Matter; import art.arcane.volmlib.util.matter.MatterCavern; +import art.arcane.volmlib.util.matter.MatterStructurePOI; import art.arcane.volmlib.util.mantle.flag.ReservedFlag; import art.arcane.volmlib.util.math.RNG; import it.unimi.dsi.fastutil.longs.Long2IntMap; import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; -import java.util.HashSet; +import java.util.HashMap; +import java.util.Map; import java.util.Objects; -import java.util.Set; @ComponentFlag(ReservedFlag.JIGSAW) public class IrisStructureComponent extends IrisMantleComponent { @@ -159,12 +163,13 @@ public class IrisStructureComponent extends IrisMantleComponent { } requireAppliedPieces(resolved, cx, cz, failedPieces); if (failedPieces == 0 && placement.getStilt() != null) { - placeFoundation(writer, foundationColumns, placement.getStilt(), rng); + placeFoundation(writer, foundationColumns, placement.getStilt(), rng, !placement.isUnderground()); } } private void placeFoundation(MantleWriter writer, Long2IntOpenHashMap columns, - IrisStructureStiltSettings settings, RNG rng) { + IrisStructureStiltSettings settings, RNG rng, + boolean surfaceStructure) { IrisMaterialPalette palette = Objects.requireNonNull( settings.getPalette(), "Structure stilt palette must not be null"); int mantleOffset = getEngineMantle().getEngine().getMinHeight(); @@ -181,10 +186,17 @@ public class IrisStructureComponent extends IrisMantleComponent { int terrainHeight = getEngineMantle().trueHeight(worldX, worldZ); int groundY = StructureFoundationPlanner.findGroundY( foundationY, maxDepth, 0, - y -> StructureFoundationPlanner.isGroundSolid( - writer.getDataIfPresent(worldX, y, worldZ, PlatformBlockState.class), - writer.getDataIfPresent(worldX, y, worldZ, MatterCavern.class) != null, - y, terrainHeight)); + y -> { + PlatformBlockState overlay = writer.getDataIfPresent( + worldX, y, worldZ, PlatformBlockState.class); + boolean carved = writer.getDataIfPresent( + worldX, y, worldZ, MatterCavern.class) != null; + return surfaceStructure + ? StructureFoundationPlanner.isSurfaceSupportBoundary( + overlay, carved, y, terrainHeight) + : StructureFoundationPlanner.isGroundSolid( + overlay, carved, y, terrainHeight); + }); if (groundY == StructureFoundationPlanner.NO_GROUND) { continue; } @@ -398,6 +410,7 @@ public class IrisStructureComponent extends IrisMantleComponent { int mantleOffset = getEngineMantle().getEngine().getMinHeight(); int worldHeight = getEngineMantle().getEngine().getHeight(); int verticalTolerance = resolved.exactY() ? 0 : DYNAMIC_STRUCTURE_Y_TOLERANCE; + Map intersectingObjects = new HashMap<>(); for (PlacedStructurePiece piece : resolved.pieces()) { int minY = Math.max(0, piece.getMinY() - mantleOffset - verticalTolerance); int maxY = Math.min(worldHeight - 1, piece.getMaxY() - mantleOffset + verticalTolerance); @@ -406,15 +419,19 @@ public class IrisStructureComponent extends IrisMantleComponent { } for (int x = piece.getMinX(); x <= piece.getMaxX(); x++) { for (int z = piece.getMinZ(); z <= piece.getMaxZ(); z++) { - clearIntersectingObjectTreeColumn(writer, x, z, minY, maxY, worldHeight); + collectIntersectingObjectTreeMarkers( + writer, x, z, minY, maxY, intersectingObjects); } } } + for (Map.Entry entry : intersectingObjects.entrySet()) { + clearMarkerOwnedObject(writer, entry.getKey(), entry.getValue(), worldHeight); + } } - private void clearIntersectingObjectTreeColumn(MantleWriter writer, int x, int z, - int minY, int maxY, int worldHeight) { - Set intersectingMarkers = null; + private void collectIntersectingObjectTreeMarkers(MantleWriter writer, int x, int z, + int minY, int maxY, + Map intersectingObjects) { for (int y = minY; y <= maxY; y++) { PlatformBlockState state = writer.getDataIfPresent(x, y, z, PlatformBlockState.class); if (state == null || !state.isTreeBlock()) { @@ -422,26 +439,52 @@ public class IrisStructureComponent extends IrisMantleComponent { } String marker = writer.getDataIfPresent(x, y, z, String.class); if (isOrdinaryObjectMarker(marker)) { - if (intersectingMarkers == null) { - intersectingMarkers = new HashSet<>(); - } - intersectingMarkers.add(marker); + intersectingObjects.computeIfAbsent(marker, ignored -> new ObjectMarkerBounds()) + .include(x, y, z); } } - if (intersectingMarkers == null) { + } + + private void clearMarkerOwnedObject(MantleWriter writer, String marker, + ObjectMarkerBounds intersections, int worldHeight) { + StructurePlacementMarker.Decoded decoded = StructurePlacementMarker.decode(marker); + if (decoded == null) { return; } - for (int y = 0; y < worldHeight; y++) { - String marker = writer.getDataIfPresent(x, y, z, String.class); - if (!intersectingMarkers.contains(marker)) { - continue; + IrisObject object = getData().load(IrisObject.class, decoded.objectKey(), false); + if (object == null) { + return; + } + int horizontalSpan = Math.max(1, Math.max(object.getW(), object.getD())); + int verticalSpan = Math.max(1, object.getH()); + ObjectMarkerBounds search = intersections.expand(horizontalSpan - 1, verticalSpan - 1, worldHeight); + KList positions = new KList<>(); + int minChunkX = Math.floorDiv(search.minX, 16); + int maxChunkX = Math.floorDiv(search.maxX, 16); + int minChunkZ = Math.floorDiv(search.minZ, 16); + int maxChunkZ = Math.floorDiv(search.maxZ, 16); + for (int chunkX = minChunkX; chunkX <= maxChunkX; chunkX++) { + for (int chunkZ = minChunkZ; chunkZ <= maxChunkZ; chunkZ++) { + MantleChunk chunk = writer.acquireChunk(chunkX, chunkZ); + if (chunk == null) { + continue; + } + int blockX = chunkX << 4; + int blockZ = chunkZ << 4; + chunk.iterate(String.class, (localX, y, localZ, value) -> { + int x = blockX + localX; + int z = blockZ + localZ; + if (marker.equals(value) && search.contains(x, y, z)) { + positions.add(new ObjectBlockPosition(x, y, z)); + } + }); } - PlatformBlockState state = writer.getDataIfPresent(x, y, z, PlatformBlockState.class); - if (state == null || !state.isTreeBlock()) { - continue; - } - writer.clearBlock(x, y, z); - writer.clearData(x, y, z, String.class); + } + for (ObjectBlockPosition position : positions) { + writer.clearBlock(position.x(), position.y(), position.z()); + writer.clearData(position.x(), position.y(), position.z(), String.class); + writer.clearData(position.x(), position.y(), position.z(), TileWrapper.class); + writer.clearData(position.x(), position.y(), position.z(), MatterStructurePOI.class); } } @@ -450,6 +493,21 @@ public class IrisStructureComponent extends IrisMantleComponent { return decoded != null && !decoded.structureAware(); } + static ObjectMarkerBounds markerSearchBounds(int minX, int minY, int minZ, + int maxX, int maxY, int maxZ, + int horizontalSpan, int verticalSpan, + int worldHeight) { + ObjectMarkerBounds bounds = new ObjectMarkerBounds(); + bounds.minX = minX; + bounds.minY = minY; + bounds.minZ = minZ; + bounds.maxX = maxX; + bounds.maxY = maxY; + bounds.maxZ = maxZ; + return bounds.expand( + Math.max(0, horizontalSpan - 1), Math.max(0, verticalSpan - 1), worldHeight); + } + private int[] computePieceBounds(KList pieces) { if (pieces == null || pieces.isEmpty()) { return null; @@ -548,4 +606,66 @@ public class IrisStructureComponent extends IrisMantleComponent { } return max; } + + static final class ObjectMarkerBounds { + private int minX = Integer.MAX_VALUE; + private int minY = Integer.MAX_VALUE; + private int minZ = Integer.MAX_VALUE; + private int maxX = Integer.MIN_VALUE; + private int maxY = Integer.MIN_VALUE; + private int maxZ = Integer.MIN_VALUE; + + void include(int x, int y, int z) { + minX = Math.min(minX, x); + minY = Math.min(minY, y); + minZ = Math.min(minZ, z); + maxX = Math.max(maxX, x); + maxY = Math.max(maxY, y); + maxZ = Math.max(maxZ, z); + } + + ObjectMarkerBounds expand(int horizontal, int vertical, int worldHeight) { + ObjectMarkerBounds expanded = new ObjectMarkerBounds(); + expanded.minX = minX - horizontal; + expanded.minY = Math.max(0, minY - vertical); + expanded.minZ = minZ - horizontal; + expanded.maxX = maxX + horizontal; + expanded.maxY = Math.min(worldHeight - 1, maxY + vertical); + expanded.maxZ = maxZ + horizontal; + return expanded; + } + + boolean contains(int x, int y, int z) { + return x >= minX && x <= maxX + && y >= minY && y <= maxY + && z >= minZ && z <= maxZ; + } + + int minX() { + return minX; + } + + int minY() { + return minY; + } + + int minZ() { + return minZ; + } + + int maxX() { + return maxX; + } + + int maxY() { + return maxY; + } + + int maxZ() { + return maxZ; + } + } + + private record ObjectBlockPosition(int x, int y, int z) { + } } diff --git a/core/src/main/java/art/arcane/iris/engine/mantle/components/StructureFoundationPlanner.java b/core/src/main/java/art/arcane/iris/engine/mantle/components/StructureFoundationPlanner.java index 22af5d9b5..c7e5b5e94 100644 --- a/core/src/main/java/art/arcane/iris/engine/mantle/components/StructureFoundationPlanner.java +++ b/core/src/main/java/art/arcane/iris/engine/mantle/components/StructureFoundationPlanner.java @@ -47,6 +47,11 @@ final class StructureFoundationPlanner { return !carved && mantleY <= terrainHeight; } + static boolean isSurfaceSupportBoundary(PlatformBlockState overlay, boolean carved, + int mantleY, int terrainHeight) { + return carved || isGroundSolid(overlay, false, mantleY, terrainHeight); + } + static int fillSupportColumn(int foundationY, int groundY, IntConsumer supportAtY) { if (groundY == NO_GROUND || groundY >= foundationY - 1) { return 0; diff --git a/core/src/test/java/art/arcane/iris/engine/mantle/components/IrisStructureComponentOverboreTest.java b/core/src/test/java/art/arcane/iris/engine/mantle/components/IrisStructureComponentOverboreTest.java index 35a1d7bbd..4f59a0776 100644 --- a/core/src/test/java/art/arcane/iris/engine/mantle/components/IrisStructureComponentOverboreTest.java +++ b/core/src/test/java/art/arcane/iris/engine/mantle/components/IrisStructureComponentOverboreTest.java @@ -58,4 +58,26 @@ public class IrisStructureComponentOverboreTest { "@iris-structure:v1:dHJlZXMvb2Fr:42:bWluZWNyYWZ0Om1hbnNpb24")); assertFalse(IrisStructureComponent.isOrdinaryObjectMarker("not-a-marker")); } + + @Test + public void collidingObjectSearchCoversTheWholeMarkerOwnedObject() { + IrisStructureComponent.ObjectMarkerBounds bounds = IrisStructureComponent.markerSearchBounds( + 20, 64, -8, 21, 66, -7, 7, 12, 384); + + assertEquals(14, bounds.minX()); + assertEquals(53, bounds.minY()); + assertEquals(-14, bounds.minZ()); + assertEquals(27, bounds.maxX()); + assertEquals(77, bounds.maxY()); + assertEquals(-1, bounds.maxZ()); + } + + @Test + public void collidingObjectSearchClampsToTheWorldHeight() { + IrisStructureComponent.ObjectMarkerBounds bounds = IrisStructureComponent.markerSearchBounds( + 0, 2, 0, 0, 379, 0, 1, 12, 384); + + assertEquals(0, bounds.minY()); + assertEquals(383, bounds.maxY()); + } } diff --git a/core/src/test/java/art/arcane/iris/engine/mantle/components/StructureFoundationPlannerTest.java b/core/src/test/java/art/arcane/iris/engine/mantle/components/StructureFoundationPlannerTest.java index 29551523b..f75521f9c 100644 --- a/core/src/test/java/art/arcane/iris/engine/mantle/components/StructureFoundationPlannerTest.java +++ b/core/src/test/java/art/arcane/iris/engine/mantle/components/StructureFoundationPlannerTest.java @@ -59,6 +59,19 @@ public class StructureFoundationPlannerTest { assertTrue(StructureFoundationPlanner.isGroundSolid(null, true, 0, -20)); } + @Test + public void surfaceSupportStopsAtCarvedCaveBoundary() { + PlatformBlockState solid = mock(PlatformBlockState.class); + when(solid.isSolid()).thenReturn(true); + PlatformBlockState air = mock(PlatformBlockState.class); + when(air.isSolid()).thenReturn(false); + + assertTrue(StructureFoundationPlanner.isSurfaceSupportBoundary(null, true, 18, 24)); + assertTrue(StructureFoundationPlanner.isSurfaceSupportBoundary(solid, false, 30, 24)); + assertFalse(StructureFoundationPlanner.isSurfaceSupportBoundary(air, false, 30, 24)); + assertTrue(StructureFoundationPlanner.isSurfaceSupportBoundary(null, false, 24, 24)); + } + @Test public void fillsOnlyTheGapBetweenTheFoundationAndGround() { List written = new ArrayList<>();