Keep native structures out of static object cells

Vanilla structure and feature placement now runs through the bounded
worldgen access with a protected-position predicate, so a jigsaw piece
or an imported feature can read a static object's blocks but never
overwrite them, and never schedules a tick or block entity there.
Dimensions without static objects keep the plain level as before.
This commit is contained in:
Brian Neumann-Fopiano committed 2026-09-03 05:02:53 -04:00
1 parent 0a791bb8bf
commit e92c0efe09
10 files changed
+129 -36

No files matched your search

@@ -7,6 +7,7 @@ import art.arcane.iris.engine.object.IrisBiomeCustom;
import art.arcane.iris.engine.object.IrisDecorationStep;
import art.arcane.iris.engine.object.IrisDimension;
import art.arcane.iris.engine.object.IrisImportedFeatureControl;
import art.arcane.iris.engine.object.IrisStaticObjectLayer;
import art.arcane.iris.spi.IrisLogging;
import it.unimi.dsi.fastutil.ints.IntArraySet;
import it.unimi.dsi.fastutil.ints.IntSet;
@@ -26,6 +27,7 @@ import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.LevelChunkSection;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.levelgen.RandomSupport;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.levelgen.WorldgenRandom;
import net.minecraft.world.level.levelgen.XoroshiroRandomSource;
import net.minecraft.world.level.levelgen.placement.PlacedFeature;
@@ -287,13 +289,20 @@ final class ImportedFeatureStage {
WorldgenRandom random = new WorldgenRandom(new XoroshiroRandomSource(RandomSupport.generateUniqueSeed()));
long decorationSeed = random.setDecorationSeed(level.getSeed(), origin.getX(), origin.getZ());
Set<Holder<Biome>> chunkBiomes = chunkBiomes(level, sectionPos, table);
IrisStaticObjectLayer staticObjects = engine.getDimension().getStaticObjectLayer(engine.getData());
int staticMinY = engine.getMinHeight();
WorldGenLevel placementLevel = staticObjects.isEmpty() ? level : NativeStructureWorldgenAccess.create(
level, centerPos,
(x, z) -> level.getHeight(Heightmap.Types.WORLD_SURFACE_WG, x, z),
(x, z) -> level.getHeight(Heightmap.Types.OCEAN_FLOOR_WG, x, z),
position -> staticObjects.contains(position.getX(), position.getY() - staticMinY, position.getZ()));
try {
for (int stepIndex = 0; stepIndex < steps.size(); stepIndex++) {
if (!table.control().shouldGenerateStep(IrisDecorationStep.byOrdinal(stepIndex))) {
continue;
}
placeStep(level, table, steps.get(stepIndex), featureRegistry, chunkBiomes, owner,
placeStep(placementLevel, table, steps.get(stepIndex), featureRegistry, chunkBiomes, owner,
random, decorationSeed, origin, stepIndex);
}
} catch (Throwable error) {
@@ -13,6 +13,7 @@ import art.arcane.iris.engine.object.IrisDimension;
import art.arcane.iris.engine.object.IrisDimensionCarvingResolver;
import art.arcane.iris.engine.object.IrisMaterialPalette;
import art.arcane.iris.engine.object.IrisNativeStructureDecision;
import art.arcane.iris.engine.object.IrisStaticObjectLayer;
import art.arcane.iris.nativegen.NativeStructureGenerationException;
import art.arcane.iris.nativegen.NativeStructureStartInjector;
import art.arcane.iris.nativegen.NativeStructureReferenceEnvelope;
@@ -873,9 +874,14 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
"heightmap priming", nativeStructureBatchContext(placementGroups),
chunkPos.x(), chunkPos.z(), error);
}
IrisStaticObjectLayer staticObjects = engine.getDimension().getStaticObjectLayer(engine.getData());
int staticMinY = engine.getMinHeight();
WorldGenLevel boundedWorld = staticObjects.isEmpty() ? world : NativeStructureWorldgenAccess.create(
world, chunkPos, worldgenSurfaceHeight(), worldgenFloorHeight(),
position -> staticObjects.contains(position.getX(), position.getY() - staticMinY, position.getZ()));
try {
NativeStructureVegetationClearer.clearIntersectingVegetation(
world, chunk, area, vegetationTargets);
boundedWorld, chunk, area, vegetationTargets);
} catch (Throwable error) {
throw NativeStructureGenerationException.failure(
"vegetation cleanup", nativeStructureBatchContext(placementGroups),
@@ -884,7 +890,7 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
NativeStructureSurfaceFitter.VacuumFoundationPlan vacuumFoundationPlan;
try {
vacuumFoundationPlan = NativeStructureSurfaceFitter.prepareSurfaceStructures(
world, area, terrainTargets,
boundedWorld, area, terrainTargets,
(x, z) -> engine.getHeight(x, z, true) + engine.getMinHeight());
} catch (Throwable error) {
throw NativeStructureGenerationException.failure(
@@ -893,7 +899,7 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
}
try {
NativeStructurePostProcessor.prepareTerrain(
world, area, terrainTargets, this::resolvePaletteBlock);
boundedWorld, area, terrainTargets, this::resolvePaletteBlock);
} catch (Throwable error) {
throw NativeStructureGenerationException.failure(
"terrain preparation", nativeStructureBatchContext(placementGroups),
@@ -903,7 +909,7 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
random.setFeatureSeed(decoSeed, group.featureIndex(), group.step());
try {
for (NativePlacement placement : group.placements()) {
placeVanillaStructure(world, structureManager, random, area, chunkPos,
placeVanillaStructure(boundedWorld, structureManager, random, area, chunkPos,
group.structureId(), placement.start(), placement.decision());
}
} catch (Throwable error) {
@@ -913,7 +919,7 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
}
try {
NativeStructureSurfaceFitter.repairVacuumFoundations(
world, area, vacuumFoundationPlan);
boundedWorld, area, vacuumFoundationPlan);
} catch (Throwable error) {
throw NativeStructureGenerationException.failure(
"foundation repair", nativeStructureBatchContext(placementGroups),
@@ -938,8 +944,8 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
private void placeVanillaStructure(WorldGenLevel world, StructureManager structureManager, WorldgenRandom random,
BoundingBox area, ChunkPos chunkPos, String structureId, StructureStart start,
IrisNativeStructureDecision decision) {
WorldGenLevel boundedWorld = NativeStructureWorldgenAccess.create(
world, chunkPos, worldgenSurfaceHeight(), worldgenFloorHeight());
WorldGenLevel boundedWorld = world instanceof NativeStructureWorldgenAccess ? world : NativeStructureWorldgenAccess.create(
world, chunkPos, worldgenSurfaceHeight(), worldgenFloorHeight(), position -> false);
world.setCurrentlyGenerating(() -> "Iris native structure " + structureId);
try {
NativeStructurePostProcessor.place(boundedWorld, structureManager, this, random, area, chunkPos,
@@ -64,6 +64,7 @@ final class NativeStructureWorldgenAccess implements WorldGenLevel {
private final ChunkPos generationCenter;
private final IntBinaryOperator surfaceFirstFreeY;
private final IntBinaryOperator floorFirstFreeY;
private final Predicate<BlockPos> protectedPosition;
private final Holder<Biome> fallbackBiome;
private final BiomeManager biomeManager;
private final AABB generationBounds;
@@ -80,6 +81,8 @@ final class NativeStructureWorldgenAccess implements WorldGenLevel {
boundary.surfaceFirstFreeY(), "Native structure world access requires a surface resolver");
this.floorFirstFreeY = Objects.requireNonNull(
boundary.floorFirstFreeY(), "Native structure world access requires an ocean-floor resolver");
this.protectedPosition = Objects.requireNonNull(
boundary.protectedPosition(), "World access requires a protected-position predicate");
BlockPos biomeSample = generationCenter.getMiddleBlockPosition(delegate.getSeaLevel());
this.fallbackBiome = delegate.getBiome(biomeSample);
this.biomeManager = delegate.getBiomeManager().withDifferentSource(this);
@@ -99,9 +102,10 @@ final class NativeStructureWorldgenAccess implements WorldGenLevel {
static NativeStructureWorldgenAccess create(WorldGenLevel delegate, ChunkPos generationCenter,
IntBinaryOperator surfaceFirstFreeY,
IntBinaryOperator floorFirstFreeY) {
IntBinaryOperator floorFirstFreeY,
Predicate<BlockPos> protectedPosition) {
return new NativeStructureWorldgenAccess(delegate, new Boundary(
generationCenter, surfaceFirstFreeY, floorFirstFreeY));
generationCenter, surfaceFirstFreeY, floorFirstFreeY, protectedPosition));
}
@Override
@@ -359,13 +363,13 @@ final class NativeStructureWorldgenAccess implements WorldGenLevel {
@Override
public BlockEntity getBlockEntity(BlockPos position) {
return isReadable(position) ? delegate.getBlockEntity(position) : null;
return isWritable(position) ? delegate.getBlockEntity(position) : null;
}
@Override
public <T extends BlockEntity> Optional<T> getBlockEntity(
BlockPos position, BlockEntityType<T> type) {
return isReadable(position) ? delegate.getBlockEntity(position, type) : Optional.empty();
return isWritable(position) ? delegate.getBlockEntity(position, type) : Optional.empty();
}
@Override
@@ -453,7 +457,7 @@ final class NativeStructureWorldgenAccess implements WorldGenLevel {
}
private boolean isWritable(BlockPos position) {
return isReadable(position);
return isReadable(position) && !protectedPosition.test(position);
}
private boolean isWritableNeighbourhood(BlockPos position) {
@@ -544,7 +548,8 @@ final class NativeStructureWorldgenAccess implements WorldGenLevel {
private record Boundary(ChunkPos generationCenter,
IntBinaryOperator surfaceFirstFreeY,
IntBinaryOperator floorFirstFreeY) {
IntBinaryOperator floorFirstFreeY,
Predicate<BlockPos> protectedPosition) {
}
private static final class BoundedTickAccess<T> implements LevelTickAccess<T> {
@@ -135,6 +135,34 @@ public class NativeStructureWorldgenAccessTest {
assertEquals(1, recording.blockTicks.count());
}
@Test
public void protectedCellsRemainReadableButRejectWritesTilesAndTicks() {
RecordingDelegate recording = new RecordingDelegate();
BlockPos position = generationCenter().getMiddleBlockPosition(70);
NativeStructureWorldgenAccess access = NativeStructureWorldgenAccess.create(
recording.world(), generationCenter(),
(x, z) -> SURFACE_FIRST_FREE_Y,
(x, z) -> FLOOR_FIRST_FREE_Y, position::equals);
assertSame(Blocks.DIRT, access.getBlockState(position).getBlock());
assertFalse(access.ensureCanWrite(position));
assertFalse(access.setBlock(position, Blocks.STONE.defaultBlockState(), 2));
assertFalse(access.removeBlock(position, false));
assertFalse(access.destroyBlock(position, false));
assertNull(access.getBlockEntity(position));
assertTrue(access.getBlockEntity(position, null).isEmpty());
access.getBlockTicks().schedule(new ScheduledTick<>(Blocks.DIRT, position, 1L, 0L));
access.updateNeighborsAt(position.east(), Blocks.DIRT);
access.neighborShapeChanged(
Direction.EAST, position, position.east(), Blocks.DIRT.defaultBlockState(), 2, 512);
assertEquals(1, recording.terrainReads);
assertEquals(0, recording.mutations);
assertEquals(0, recording.blockTicks.count());
assertTrue(access.setBlock(position.above(), Blocks.STONE.defaultBlockState(), 2));
assertEquals(1, recording.mutations);
}
@Test
public void statuslessChunkReadsPreserveWorldgenDelegateSemantics() {
RecordingDelegate recording = new RecordingDelegate();
@@ -183,7 +211,7 @@ public class NativeStructureWorldgenAccessTest {
return NativeStructureWorldgenAccess.create(
recording.world(), generationCenter(),
(x, z) -> SURFACE_FIRST_FREE_Y,
(x, z) -> FLOOR_FIRST_FREE_Y);
(x, z) -> FLOOR_FIRST_FREE_Y, position -> false);
}
private static ChunkPos generationCenter() {
@@ -226,7 +226,7 @@ public class NativeStructurePostProcessorEncaseTest {
String source = Files.readString(Path.of(System.getProperty("iris.nmsChunkGeneratorSource"))).replace("\r\n", "\n");
int prepareTerrain = source.indexOf("NativeStructurePostProcessor.prepareTerrain(");
int placementLoop = source.indexOf("for (NativePlacementGroup group : placementGroups)", prepareTerrain);
int placement = source.indexOf("placeVanillaStructure(world, structureManager, random", placementLoop);
int placement = source.indexOf("placeVanillaStructure(boundedWorld, structureManager, random", placementLoop);
assertTrue(prepareTerrain >= 0);
assertTrue(placementLoop > prepareTerrain);
@@ -25,6 +25,7 @@ import art.arcane.iris.engine.object.IrisBiome;
import art.arcane.iris.engine.object.IrisBiomeCustom;
import art.arcane.iris.engine.object.IrisDecorationStep;
import art.arcane.iris.engine.object.IrisImportedFeatureControl;
import art.arcane.iris.engine.object.IrisStaticObjectLayer;
import art.arcane.iris.util.project.context.IrisContext;
import it.unimi.dsi.fastutil.ints.IntArraySet;
import it.unimi.dsi.fastutil.ints.IntSet;
@@ -43,6 +44,7 @@ import net.minecraft.world.level.biome.FeatureSorter;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.LevelChunkSection;
import net.minecraft.world.level.levelgen.RandomSupport;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.levelgen.WorldgenRandom;
import net.minecraft.world.level.levelgen.XoroshiroRandomSource;
import net.minecraft.world.level.levelgen.placement.PlacedFeature;
@@ -321,13 +323,20 @@ final class ModdedImportedFeatureStage {
WorldgenRandom random = new WorldgenRandom(new XoroshiroRandomSource(RandomSupport.generateUniqueSeed()));
long decorationSeed = random.setDecorationSeed(level.getSeed(), origin.getX(), origin.getZ());
Set<Holder<Biome>> chunkBiomes = chunkBiomes(level, sectionPos, table);
IrisStaticObjectLayer staticObjects = engine.getDimension().getStaticObjectLayer(engine.getData());
int staticMinY = engine.getMinHeight();
WorldGenLevel placementLevel = staticObjects.isEmpty() ? level : ModdedNativeStructureWorldgenAccess.create(
level, centerPos,
(x, z) -> level.getHeight(Heightmap.Types.WORLD_SURFACE_WG, x, z),
(x, z) -> level.getHeight(Heightmap.Types.OCEAN_FLOOR_WG, x, z),
position -> staticObjects.contains(position.getX(), position.getY() - staticMinY, position.getZ()));
try {
for (int stepIndex = 0; stepIndex < steps.size(); stepIndex++) {
if (!table.control().shouldGenerateStep(IrisDecorationStep.byOrdinal(stepIndex))) {
continue;
}
placeStep(level, table, steps.get(stepIndex), featureRegistry, chunkBiomes, owner,
placeStep(placementLevel, table, steps.get(stepIndex), featureRegistry, chunkBiomes, owner,
random, decorationSeed, origin, stepIndex);
}
} catch (Throwable error) {
@@ -25,6 +25,7 @@ import art.arcane.iris.engine.framework.NativeStructureOwnershipRecord;
import art.arcane.iris.engine.framework.NativeStructureStartPlan;
import art.arcane.iris.engine.object.IrisMaterialPalette;
import art.arcane.iris.engine.object.IrisNativeStructureDecision;
import art.arcane.iris.engine.object.IrisStaticObjectLayer;
import art.arcane.iris.nativegen.NativeStructureGenerationException;
import art.arcane.iris.nativegen.NativeStructureLocatePersistence;
import art.arcane.iris.nativegen.NativeStructureLocateResults;
@@ -365,9 +366,14 @@ final class ModdedNativeStructureStage {
"heightmap priming", nativeStructureBatchContext(placementGroups),
chunkPos.x(), chunkPos.z(), error);
}
IrisStaticObjectLayer staticObjects = current.getDimension().getStaticObjectLayer(current.getData());
int staticMinY = current.getMinHeight();
WorldGenLevel boundedWorld = staticObjects.isEmpty() ? world : ModdedNativeStructureWorldgenAccess.create(
world, chunkPos, worldgenSurfaceHeight(current, world.getMinY()), worldgenFloorHeight(current, world.getMinY()),
position -> staticObjects.contains(position.getX(), position.getY() - staticMinY, position.getZ()));
try {
NativeStructureVegetationClearer.clearIntersectingVegetation(
world, chunk, area, vegetationTargets);
boundedWorld, chunk, area, vegetationTargets);
} catch (Throwable error) {
throw NativeStructureGenerationException.failure(
"vegetation cleanup", nativeStructureBatchContext(placementGroups),
@@ -376,7 +382,7 @@ final class ModdedNativeStructureStage {
NativeStructureSurfaceFitter.VacuumFoundationPlan vacuumFoundationPlan;
try {
vacuumFoundationPlan = NativeStructureSurfaceFitter.prepareSurfaceStructures(
world, area, terrainTargets,
boundedWorld, area, terrainTargets,
(x, z) -> current.getHeight(x, z, true) + current.getMinHeight());
} catch (Throwable error) {
throw NativeStructureGenerationException.failure(
@@ -385,7 +391,7 @@ final class ModdedNativeStructureStage {
}
try {
NativeStructurePostProcessor.prepareTerrain(
world, area, terrainTargets, this::resolvePaletteBlock);
boundedWorld, area, terrainTargets, this::resolvePaletteBlock);
} catch (Throwable error) {
throw NativeStructureGenerationException.failure(
"terrain preparation", nativeStructureBatchContext(placementGroups),
@@ -395,7 +401,7 @@ final class ModdedNativeStructureStage {
random.setFeatureSeed(decorationSeed, group.featureIndex(), group.step());
try {
for (NativePlacement placement : group.placements()) {
placeVanillaStructure(world, structureManager, random, area, chunkPos,
placeVanillaStructure(boundedWorld, structureManager, random, area, chunkPos,
group.structureId(), placement.start(), placement.decision());
}
} catch (Throwable error) {
@@ -405,7 +411,7 @@ final class ModdedNativeStructureStage {
}
try {
NativeStructureSurfaceFitter.repairVacuumFoundations(
world, area, vacuumFoundationPlan);
boundedWorld, area, vacuumFoundationPlan);
} catch (Throwable error) {
throw NativeStructureGenerationException.failure(
"foundation repair", nativeStructureBatchContext(placementGroups),
@@ -436,11 +442,8 @@ final class ModdedNativeStructureStage {
String structureId, StructureStart start,
IrisNativeStructureDecision decision) {
Engine current = generator.engine();
int runtimeMinY = world.getMinY();
WorldGenLevel boundedWorld = ModdedNativeStructureWorldgenAccess.create(
world, chunkPos,
worldgenSurfaceHeight(current, runtimeMinY),
worldgenFloorHeight(current, runtimeMinY));
WorldGenLevel boundedWorld = world instanceof ModdedNativeStructureWorldgenAccess ? world : ModdedNativeStructureWorldgenAccess.create(
world, chunkPos, worldgenSurfaceHeight(current, world.getMinY()), worldgenFloorHeight(current, world.getMinY()), position -> false);
world.setCurrentlyGenerating(() -> "Iris native structure " + structureId);
try {
NativeStructurePostProcessor.place(
@@ -63,6 +63,7 @@ final class ModdedNativeStructureWorldgenAccess implements WorldGenLevel {
private final ChunkPos generationCenter;
private final IntBinaryOperator surfaceFirstFreeY;
private final IntBinaryOperator floorFirstFreeY;
private final Predicate<BlockPos> protectedPosition;
private final Holder<Biome> fallbackBiome;
private final BiomeManager biomeManager;
private final AABB generationBounds;
@@ -79,6 +80,8 @@ final class ModdedNativeStructureWorldgenAccess implements WorldGenLevel {
boundary.surfaceFirstFreeY(), "Native structure world access requires a surface resolver");
this.floorFirstFreeY = Objects.requireNonNull(
boundary.floorFirstFreeY(), "Native structure world access requires an ocean-floor resolver");
this.protectedPosition = Objects.requireNonNull(
boundary.protectedPosition(), "World access requires a protected-position predicate");
BlockPos biomeSample = generationCenter.getMiddleBlockPosition(delegate.getSeaLevel());
this.fallbackBiome = delegate.getBiome(biomeSample);
this.biomeManager = delegate.getBiomeManager().withDifferentSource(this);
@@ -98,9 +101,10 @@ final class ModdedNativeStructureWorldgenAccess implements WorldGenLevel {
static ModdedNativeStructureWorldgenAccess create(WorldGenLevel delegate, ChunkPos generationCenter,
IntBinaryOperator surfaceFirstFreeY,
IntBinaryOperator floorFirstFreeY) {
IntBinaryOperator floorFirstFreeY,
Predicate<BlockPos> protectedPosition) {
return new ModdedNativeStructureWorldgenAccess(delegate, new Boundary(
generationCenter, surfaceFirstFreeY, floorFirstFreeY));
generationCenter, surfaceFirstFreeY, floorFirstFreeY, protectedPosition));
}
@Override
@@ -350,13 +354,13 @@ final class ModdedNativeStructureWorldgenAccess implements WorldGenLevel {
@Override
public BlockEntity getBlockEntity(BlockPos position) {
return isReadable(position) ? delegate.getBlockEntity(position) : null;
return isWritable(position) ? delegate.getBlockEntity(position) : null;
}
@Override
public <T extends BlockEntity> Optional<T> getBlockEntity(
BlockPos position, BlockEntityType<T> type) {
return isReadable(position) ? delegate.getBlockEntity(position, type) : Optional.empty();
return isWritable(position) ? delegate.getBlockEntity(position, type) : Optional.empty();
}
@Override
@@ -430,7 +434,7 @@ final class ModdedNativeStructureWorldgenAccess implements WorldGenLevel {
}
private boolean isWritable(BlockPos position) {
return isReadable(position);
return isReadable(position) && !protectedPosition.test(position);
}
private boolean isWritableNeighbourhood(BlockPos position) {
@@ -521,7 +525,8 @@ final class ModdedNativeStructureWorldgenAccess implements WorldGenLevel {
private record Boundary(ChunkPos generationCenter,
IntBinaryOperator surfaceFirstFreeY,
IntBinaryOperator floorFirstFreeY) {
IntBinaryOperator floorFirstFreeY,
Predicate<BlockPos> protectedPosition) {
}
private static final class BoundedTickAccess<T> implements LevelTickAccess<T> {
@@ -135,6 +135,34 @@ public class ModdedNativeStructureWorldgenAccessTest {
assertEquals(1, recording.blockTicks.count());
}
@Test
public void protectedCellsRemainReadableButRejectWritesTilesAndTicks() {
RecordingDelegate recording = new RecordingDelegate();
BlockPos position = generationCenter().getMiddleBlockPosition(70);
ModdedNativeStructureWorldgenAccess access = ModdedNativeStructureWorldgenAccess.create(
recording.world(), generationCenter(),
(x, z) -> SURFACE_FIRST_FREE_Y,
(x, z) -> FLOOR_FIRST_FREE_Y, position::equals);
assertSame(Blocks.DIRT, access.getBlockState(position).getBlock());
assertFalse(access.ensureCanWrite(position));
assertFalse(access.setBlock(position, Blocks.STONE.defaultBlockState(), 2));
assertFalse(access.removeBlock(position, false));
assertFalse(access.destroyBlock(position, false));
assertNull(access.getBlockEntity(position));
assertTrue(access.getBlockEntity(position, null).isEmpty());
access.getBlockTicks().schedule(new ScheduledTick<>(Blocks.DIRT, position, 1L, 0L));
access.updateNeighborsAt(position.east(), Blocks.DIRT);
access.neighborShapeChanged(
Direction.EAST, position, position.east(), Blocks.DIRT.defaultBlockState(), 2, 512);
assertEquals(1, recording.terrainReads);
assertEquals(0, recording.mutations);
assertEquals(0, recording.blockTicks.count());
assertTrue(access.setBlock(position.above(), Blocks.STONE.defaultBlockState(), 2));
assertEquals(1, recording.mutations);
}
@Test
public void statuslessChunkReadsPreserveWorldgenDelegateSemantics() {
RecordingDelegate recording = new RecordingDelegate();
@@ -183,7 +211,7 @@ public class ModdedNativeStructureWorldgenAccessTest {
return ModdedNativeStructureWorldgenAccess.create(
recording.world(), generationCenter(),
(x, z) -> SURFACE_FIRST_FREE_Y,
(x, z) -> FLOOR_FIRST_FREE_Y);
(x, z) -> FLOOR_FIRST_FREE_Y, position -> false);
}
private static ChunkPos generationCenter() {
@@ -103,12 +103,12 @@ public class NativeStructureFailureContractTest {
@Test
public void nativeStructurePostProcessingUsesBoundedWorldgenAccess() throws IOException {
String source = moddedSource("ModdedNativeStructureStage.java");
int placementStart = source.indexOf("private void placeVanillaStructure");
int placementStart = source.indexOf("void placeVanillaStructures");
int placementEnd = source.indexOf("private List<List<Structure>> structuresByStep", placementStart);
String placement = source.substring(placementStart, placementEnd);
assertTrue(placement.contains("ModdedNativeStructureWorldgenAccess.create("));
assertTrue(placement.contains("NativeStructurePostProcessor.place(\n boundedWorld"));
assertTrue(placement.contains("placeVanillaStructure(boundedWorld, structureManager"));
assertFalse(placement.contains("NativeStructurePostProcessor.place(world,"));
assertTrue(placement.contains("world.setCurrentlyGenerating(null);"));
}