This commit is contained in:
Brian Neumann-Fopiano
2026-08-05 01:16:30 -06:00
parent aaccbacf32
commit cd217c05f9
174 changed files with 28830 additions and 2645 deletions
@@ -4,7 +4,7 @@ import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.engine.framework.GenerationSessionException;
import art.arcane.iris.engine.framework.GenerationSessionLease;
import art.arcane.iris.engine.framework.IrisStructureLocator;
import art.arcane.iris.engine.framework.NativeStructurePlacementPlanner;
import art.arcane.iris.engine.framework.NativeStructureOwnershipRecord;
import art.arcane.iris.engine.framework.NativeStructureStartPlan;
import art.arcane.iris.engine.framework.NativeStructureGenerationPolicy;
import art.arcane.iris.engine.object.IrisDimension;
@@ -14,11 +14,15 @@ import art.arcane.iris.nativegen.NativeStructureGenerationException;
import art.arcane.iris.nativegen.NativeStructureStartInjector;
import art.arcane.iris.nativegen.NativeStructureReferenceEnvelope;
import art.arcane.iris.nativegen.NativeStructureLocateResults;
import art.arcane.iris.nativegen.NativeStructureLocatePersistence;
import art.arcane.iris.nativegen.NativeStructureOwnershipRecovery;
import art.arcane.iris.nativegen.NativeStructurePostProcessor;
import art.arcane.iris.nativegen.NativeStructureReferenceRepair;
import art.arcane.iris.nativegen.NativeStructureSurfaceFitter;
import art.arcane.iris.nativegen.NativeStructureTerrainIntegrator;
import art.arcane.iris.nativegen.NativeStructureVegetationClearer;
import art.arcane.iris.nativegen.NativeStructureVerticalPlacer;
import art.arcane.iris.nativegen.NativeStructureVanillaLocator;
import art.arcane.iris.nativegen.WorldgenTerrainHeightmaps;
import art.arcane.iris.spi.PlatformBlockState;
import art.arcane.iris.util.common.data.IrisCustomData;
@@ -87,6 +91,7 @@ import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -131,55 +136,99 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
public @Nullable Pair<BlockPos, Holder<Structure>> findNearestMapStructure(ServerLevel level, HolderSet<Structure> holders, BlockPos pos, int radius, boolean findUnexplored) {
try (GenerationSessionLease lease = requireGenerationLease("bukkit_nms_structure_locate");
IrisContext.Scope ignored = IrisContext.open(engine, lease.sessionId(), null)) {
Pair<BlockPos, Holder<Structure>> irisPlaced = findNearestIrisStructure(
level, holders, pos, Math.max(1, radius), findUnexplored);
HolderSet<Structure> reachable = filterReachableStructures(level, holders);
Pair<BlockPos, Holder<Structure>> nativeLocated = reachable == null || reachable.size() == 0
? null
: delegate.findNearestMapStructure(level, reachable, pos, radius, findUnexplored);
return NativeStructureLocateResults.nearest(pos, irisPlaced, nativeLocated);
NativeStructureVanillaLocator.Candidate nativeCandidate =
reachable == null || reachable.size() == 0 ? null
: NativeStructureVanillaLocator.predict(
level, reachable, pos, radius, findUnexplored);
return findNearestIrisStructure(
level, holders, pos, Math.max(0, radius),
findUnexplored, nativeCandidate);
}
}
private Pair<BlockPos, Holder<Structure>> findNearestIrisStructure(ServerLevel level,
HolderSet<Structure> holders,
BlockPos pos, int radius,
boolean findUnexplored) {
if (findUnexplored) {
return null;
}
boolean findUnexplored,
NativeStructureVanillaLocator.Candidate nativeCandidate) {
Pair<BlockPos, Holder<Structure>> nativeLocated =
nativeCandidate == null ? null : nativeCandidate.result();
Runnable nativeReference = () -> {
if (nativeCandidate != null) {
nativeCandidate.reference(level.structureManager());
}
};
Registry<Structure> registry = level.registryAccess().lookupOrThrow(Registries.STRUCTURE);
BlockPos best = null;
Holder<Structure> bestHolder = null;
long bestDist = Long.MAX_VALUE;
List<IrisNativeLocateSearch> searches = new ArrayList<>(holders.size());
NativeStructureLocatePersistence.ProbeBudget budget = NativeStructureLocatePersistence.probeBudget();
for (Holder<Structure> holder : holders) {
Object id = registry.getKey(holder.value());
if (id == null) {
throw new IllegalStateException("Native structure locate received an unregistered structure holder");
}
String structureId = id.toString();
if (!IrisStructureLocator.isPlaced(engine, structureId)) {
if (!IrisStructureLocator.hasNativePlacement(engine, structureId)) {
continue;
}
IrisStructureLocator.LocateResult result = IrisStructureLocator.locate(
engine, structureId, pos.getX(), pos.getZ(), radius);
if (result.status() == IrisStructureLocator.LocateStatus.SEARCH_LIMIT_REACHED) {
throw new IllegalStateException("Iris structure locate reached its safety limit for "
+ structureId + " within " + radius + " chunks");
}
if (!result.found()) {
continue;
}
long dx = (long) result.originX() - pos.getX();
long dz = (long) result.originZ() - pos.getZ();
long d = dx * dx + dz * dz;
if (d < bestDist) {
bestDist = d;
best = new BlockPos(result.originX(), result.baseY(), result.originZ());
bestHolder = holder;
}
NativeStructureLocatePersistence.Probe probe = NativeStructureLocatePersistence.probe(
level, holder.value(), findUnexplored, budget);
searches.add(new IrisNativeLocateSearch(
holder, structureId, NativeStructureLocatePersistence.search(
engine, structureId, pos.getX(), pos.getZ(), radius, probe)));
}
return best == null ? null : Pair.of(best, bestHolder);
searches.sort(Comparator.comparing(IrisNativeLocateSearch::structureId));
for (int attempt = 0; attempt < NativeStructureLocatePersistence.MAX_SELECTED_CANDIDATE_RETRIES; attempt++) {
IrisNativeLocateSearch bestSearch = null;
IrisStructureLocator.LocateResult bestResult = null;
long bestDistance = Long.MAX_VALUE;
for (IrisNativeLocateSearch search : searches) {
IrisStructureLocator.LocateResult result = search.search().predict();
if (result.status() == IrisStructureLocator.LocateStatus.SEARCH_LIMIT_REACHED) {
throw new IllegalStateException("Iris structure locate reached its safety limit for "
+ search.structureId() + " within " + radius + " placement rings");
}
if (!result.found()) {
continue;
}
long dx = (long) result.originX() - pos.getX();
long dz = (long) result.originZ() - pos.getZ();
long distance = dx * dx + dz * dz;
if (distance < bestDistance) {
bestDistance = distance;
bestSearch = search;
bestResult = result;
}
}
if (bestSearch == null) {
return NativeStructureLocateResults.selectAndReference(
pos, null, () -> { }, nativeLocated, nativeReference);
}
Pair<BlockPos, Holder<Structure>> predicted = Pair.of(
new BlockPos(bestResult.originX(), bestResult.baseY(), bestResult.originZ()),
bestSearch.holder());
if (NativeStructureLocateResults.nearest(pos, predicted, nativeLocated) != predicted) {
return NativeStructureLocateResults.selectAndReference(
pos, predicted, () -> { }, nativeLocated, nativeReference);
}
NativeStructureLocatePersistence.VerifiedStart verified =
bestSearch.search().verify(bestResult);
if (verified == null) {
bestSearch.search().reject(bestResult);
continue;
}
BlockPos located = new BlockPos(
bestResult.originX(), verified.ownership().locatorY(),
bestResult.originZ());
Pair<BlockPos, Holder<Structure>> irisLocated = Pair.of(located, bestSearch.holder());
IrisNativeLocateSearch selectedSearch = bestSearch;
NativeStructureLocatePersistence.VerifiedStart selectedStart = verified;
return NativeStructureLocateResults.selectAndReference(
pos, irisLocated, () -> selectedSearch.search().reference(selectedStart),
nativeLocated, nativeReference);
}
throw new IllegalStateException("Iris structure locate rejected too many selected candidates within "
+ radius + " placement rings");
}
private HolderSet<Structure> filterReachableStructures(ServerLevel level, HolderSet<Structure> holders) {
@@ -323,9 +372,10 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
decision.preserveSourceY(),
decision.yBand(),
(x, z) -> engine.getHeight(x, z, true) + engine.getMinHeight());
StructureStart wrapped = NativeStructureReferenceEnvelope.wrap(
start, structure, start.getReferences(), templateManager,
NativeStructureTerrainIntegrator.resolveNativeTerrain(start, decision.terrain()));
StructureStart wrapped = NativeStructureReferenceEnvelope.wrapForPublication(
start, structure, start.getReferences(),
NativeStructureTerrainIntegrator.resolveNativeTerrain(start, decision.terrain()),
structureId);
access.setStartForStructure(structure, wrapped);
} catch (Throwable error) {
throw NativeStructureGenerationException.failure(
@@ -341,7 +391,11 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
@Override
public void createReferences(WorldGenLevel generatoraccessseed, StructureManager structuremanager, ChunkAccess ichunkaccess) {
delegate.createReferences(generatoraccessseed, structuremanager, ichunkaccess);
try (GenerationSessionLease lease = requireGenerationLease("bukkit_nms_create_references");
IrisContext.Scope ignored = IrisContext.open(engine, lease.sessionId(), null)) {
NativeStructureReferenceRepair.createReferences(
engine, generatoraccessseed, structuremanager, ichunkaccess);
}
}
@Override
@@ -474,7 +528,6 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
int steps = GenerationStep.Decoration.values().length;
List<NativePlacementGroup> placementGroups = new ArrayList<>();
List<StructureStart> heightmapStarts = new ArrayList<>();
List<StructureStart> nativeStarts = new ArrayList<>();
List<NativeStructureVegetationClearer.VegetationTarget> vegetationTargets = new ArrayList<>();
List<NativeStructureTerrainIntegrator.TerrainTarget> terrainTargets = new ArrayList<>();
for (int step = 0; step < steps; step++) {
@@ -492,10 +545,11 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
List<StructureStart> starts = structureManager.startsForStructure(sectionPos, structure);
List<NativePlacement> resolvedPlacements = new ArrayList<>(starts.size());
for (StructureStart start : starts) {
NativeStructureStartPlan plan = NativeStructurePlacementPlanner.matchingPlan(
engine, structureId, start.getChunkPos().x(), start.getChunkPos().z());
IrisNativeStructureDecision decision = plan == null
? sourceDecision : NativeStructurePlacementPlanner.decisionFor(plan);
NativeStructureOwnershipRecord ownership =
NativeStructureOwnershipRecovery.resolve(
engine, world.getLevel(), structureId, structure, start);
IrisNativeStructureDecision decision =
ownership == null ? sourceDecision : ownership.restoredDecision();
if (!decision.generate()) {
continue;
}
@@ -505,9 +559,6 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
structureId, start,
NativeStructureTerrainIntegrator.resolveNativeTerrain(
start, decision.terrain())));
if (plan == null || !plan.placement().isUnderground()) {
nativeStarts.add(start);
}
boolean clearEntireFootprint = NativeStructureVegetationClearer
.shouldClearEntireVegetationFootprint(
structure.step(), decision.clearVegetation());
@@ -533,15 +584,6 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
"heightmap priming", nativeStructureBatchContext(placementGroups),
chunkPos.x(), chunkPos.z(), error);
}
try {
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world, area, nativeStarts,
(x, z) -> engine.getHeight(x, z, true) + engine.getMinHeight());
} catch (Throwable error) {
throw NativeStructureGenerationException.failure(
"terrain integration", nativeStructureBatchContext(placementGroups),
chunkPos.x(), chunkPos.z(), error);
}
try {
NativeStructureVegetationClearer.clearIntersectingVegetation(
world, chunk, area, vegetationTargets);
@@ -550,6 +592,15 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
"vegetation cleanup", nativeStructureBatchContext(placementGroups),
chunkPos.x(), chunkPos.z(), error);
}
try {
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world, area, terrainTargets,
(x, z) -> engine.getHeight(x, z, true) + engine.getMinHeight());
} catch (Throwable error) {
throw NativeStructureGenerationException.failure(
"terrain integration", nativeStructureBatchContext(placementGroups),
chunkPos.x(), chunkPos.z(), error);
}
try {
NativeStructurePostProcessor.prepareTerrain(
world, area, terrainTargets, this::resolvePaletteBlock);
@@ -641,8 +692,8 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
ChunkPos cp = chunk.getPos();
int i = cp.getMinBlockX();
int j = cp.getMinBlockZ();
int minY = chunk.getMinY();
int maxY = minY + chunk.getHeight() - 1;
int minY = chunk.getMinY() + 1;
int maxY = chunk.getMinY() + chunk.getHeight() - 1;
return new BoundingBox(i, minY, j, i + 15, maxY, j + 15);
}
@@ -827,4 +878,8 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
private record NativeLocateCandidate(Holder<Structure> holder, String key) {
}
private record IrisNativeLocateSearch(Holder<Structure> holder, String structureId,
NativeStructureLocatePersistence.Search search) {
}
}
@@ -15,7 +15,9 @@ import art.arcane.iris.engine.data.chunk.TerrainChunk;
import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.engine.object.IrisDimensionRuntimeContract;
import art.arcane.iris.engine.platform.PlatformChunkGenerator;
import art.arcane.iris.nativegen.NativeStructureFactory;
import art.arcane.iris.nativegen.NativeStructureGenerationException;
import art.arcane.iris.spi.PlatformStructureHooks.JigsawSourceMetadata;
import art.arcane.iris.util.project.agent.Agent;
import art.arcane.volmlib.util.collection.KList;
import art.arcane.volmlib.util.collection.KMap;
@@ -102,6 +104,7 @@ 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.structure.StructureCheck;
import net.minecraft.world.level.levelgen.structure.BoundingBox;
import net.minecraft.world.level.levelgen.structure.Structure;
import net.minecraft.world.level.levelgen.structure.structures.JigsawStructure;
import net.minecraft.world.level.levelgen.feature.AbstractHugeMushroomFeature;
@@ -557,6 +560,62 @@ public class NMSBinding implements INMSBinding {
return keys;
}
@Override
public JigsawSourceMetadata getJigsawSourceMetadata(String structureKey) {
try {
Identifier identifier = Identifier.tryParse(structureKey);
if (identifier == null) {
throw new IllegalArgumentException("Invalid registered structure key: " + structureKey);
}
Registry<Structure> structures = registry().lookupOrThrow(Registries.STRUCTURE);
Structure structure = structures.getValue(identifier);
if (structure == null) {
throw new IllegalArgumentException("Registered structure does not exist: " + structureKey);
}
if (!(structure instanceof JigsawStructure jigsaw)) {
throw new IllegalArgumentException("Registered structure is not a jigsaw: " + structureKey);
}
MinecraftServer server = ((CraftServer) Bukkit.getServer()).getHandle().getServer();
return NativeStructureFactory.sourceMetadata(
registry(), server.getStructureManager(), jigsaw);
} catch (RuntimeException error) {
throw new IllegalStateException("Iris failed to resolve live jigsaw metadata for registered structure '"
+ structureKey + "'", error);
}
}
@Override
public int getTemplatePoolHorizontalSpan(String templatePoolKey) {
try {
MinecraftServer server = ((CraftServer) Bukkit.getServer()).getHandle().getServer();
return NativeStructureFactory.templatePoolHorizontalSpan(
registry(), server.getStructureManager(), templatePoolKey);
} catch (RuntimeException error) {
throw new IllegalStateException("Iris failed to resolve the live horizontal span for registered "
+ "template pool '" + templatePoolKey + "'", error);
}
}
@Override
public int getJigsawStartPoolHorizontalSpan(String structureKey, String templatePoolKey) {
try {
Identifier identifier = Identifier.tryParse(structureKey);
if (identifier == null) {
throw new IllegalArgumentException("Invalid registered structure key: " + structureKey);
}
Structure structure = registry().lookupOrThrow(Registries.STRUCTURE).getValue(identifier);
if (!(structure instanceof JigsawStructure jigsaw)) {
throw new IllegalArgumentException("Registered structure is not a jigsaw: " + structureKey);
}
MinecraftServer server = ((CraftServer) Bukkit.getServer()).getHandle().getServer();
return NativeStructureFactory.jigsawStartPoolHorizontalSpan(
registry(), server.getStructureManager(), jigsaw, templatePoolKey);
} catch (RuntimeException error) {
throw new IllegalStateException("Iris failed to resolve the effective start-pool span for registered "
+ "jigsaw structure '" + structureKey + "' and pool '" + templatePoolKey + "'", error);
}
}
@Override
public KList<String> getStructureSetKeys() {
KList<String> keys = new KList<>();
@@ -700,7 +759,7 @@ public class NMSBinding implements INMSBinding {
return null;
}
net.minecraft.world.level.levelgen.structure.BoundingBox box = start.getBoundingBox();
BoundingBox box = start.getBoundingBox();
int spanX = box.maxX() - box.minX() + 1;
int spanY = box.maxY() - box.minY() + 1;
int spanZ = box.maxZ() - box.minZ() + 1;
@@ -717,7 +776,7 @@ public class NMSBinding implements INMSBinding {
for (int cz = minCZ; cz <= maxCZ; cz++) {
level.getChunk(cx, cz);
net.minecraft.world.level.ChunkPos cp = new net.minecraft.world.level.ChunkPos(cx, cz);
net.minecraft.world.level.levelgen.structure.BoundingBox chunkBox = new net.minecraft.world.level.levelgen.structure.BoundingBox(
BoundingBox chunkBox = new BoundingBox(
cp.getMinBlockX(), box.minY(), cp.getMinBlockZ(),
cp.getMaxBlockX(), box.maxY(), cp.getMaxBlockZ());
start.placeInChunk(level, structureManager, generator, random, chunkBox, cp);
@@ -49,9 +49,9 @@ public class IrisChunkGeneratorFailureContractTest {
assertTrue(placement.contains("because structure generation is disabled outside the pack"));
assertTrue(placement.contains("prepareSurfaceStructures"));
assertTrue(placement.contains("clearIntersectingVegetation"));
assertTrue(placement.indexOf("prepareSurfaceStructures")
< placement.indexOf("clearIntersectingVegetation"));
assertTrue(placement.indexOf("clearIntersectingVegetation")
< placement.indexOf("prepareSurfaceStructures"));
assertTrue(placement.indexOf("prepareSurfaceStructures")
< placement.indexOf("for (NativePlacementGroup group"));
assertFalse(placement.contains("IrisLogging.reportError"));
}
@@ -66,6 +66,20 @@ public class IrisChunkGeneratorFailureContractTest {
assertTrue(source.contains("catch (GenerationSessionException e)"));
}
@Test
public void structureReferenceRepairIsGenerationLeased() throws IOException {
String source = Files.readString(Path.of(System.getProperty("iris.nmsChunkGeneratorSource")));
int referencesStart = source.indexOf("public void createReferences");
int referencesEnd = source.indexOf("public CompletableFuture<ChunkAccess> createBiomes", referencesStart);
String references = source.substring(referencesStart, referencesEnd);
assertTrue(references.contains("requireGenerationLease(\"bukkit_nms_create_references\")"));
assertTrue(references.contains("IrisContext.open(engine, lease.sessionId(), null)"));
assertTrue(references.contains("NativeStructureReferenceRepair.createReferences("));
assertFalse(references.contains("delegate.createReferences("));
assertFalse(references.contains("catch ("));
}
@Test
public void terrainWritesPrimeTheWorldgenHeightmapsForEveryChunk() throws IOException {
String source = Files.readString(Path.of(System.getProperty("iris.nmsChunkGeneratorSource")));
@@ -89,6 +103,7 @@ public class IrisChunkGeneratorFailureContractTest {
assertTrue(placement.indexOf("WorldgenTerrainHeightmaps.primeStructurePlacement(")
< placement.indexOf("prepareSurfaceStructures"));
assertTrue(source.contains("engine.acquireGenerationLease(\"bukkit_nms_worldgen_heightmaps\")"));
assertTrue(source.contains("int minY = chunk.getMinY() + 1;"));
}
@Test
@@ -112,6 +127,38 @@ public class IrisChunkGeneratorFailureContractTest {
assertTrue(source.contains("import art.arcane.iris.nativegen.WorldgenTerrainHeightmaps;"));
}
@Test
public void onlySidecarOwnedInjectedStartsUsePersistedPlacementPolicy() throws IOException {
String source = Files.readString(Path.of(System.getProperty("iris.nmsChunkGeneratorSource")));
int placementStart = source.indexOf("private void placeVanillaStructures");
int placementEnd = source.indexOf("private static String nativeStructureBatchContext", placementStart);
String placement = source.substring(placementStart, placementEnd);
int ownershipResolution = placement.indexOf(
"NativeStructureOwnershipRecovery.resolve(");
int persistedDecision = placement.indexOf("ownership.restoredDecision()", ownershipResolution);
int adjustmentStart = source.indexOf("private void adjustGeneratedStructures");
int adjustmentEnd = source.indexOf("public ChunkGeneratorStructureState createState", adjustmentStart);
String adjustment = source.substring(adjustmentStart, adjustmentEnd);
Path nativegen = Path.of(System.getProperty("iris.nativeStructurePostProcessorSource")).getParent();
String factory = Files.readString(nativegen.resolve("NativeStructureFactory.java"));
String injector = Files.readString(nativegen.resolve("NativeStructureStartInjector.java"));
String recovery = Files.readString(nativegen.resolve("NativeStructureOwnershipRecovery.java"));
int ownershipRecord = injector.indexOf("NativeStructureOwnershipStore.record(");
int startPublication = injector.indexOf(
"context.structureManager().setStartForStructure(", ownershipRecord);
assertTrue(ownershipResolution >= 0);
assertTrue(persistedDecision > ownershipResolution);
assertTrue(recovery.contains("NativeStructureOwnershipStore.findPersisted("));
assertTrue(recovery.contains("NativeStructureOwnershipStore.record("));
assertTrue(ownershipRecord >= 0);
assertTrue(startPublication > ownershipRecord);
assertTrue(factory.contains("NativeStructureReferenceEnvelope.wrapForPublication("));
assertTrue(adjustment.contains("NativeStructureReferenceEnvelope.wrapForPublication("));
assertFalse(source.contains("wrapManaged("));
assertFalse(source.contains("isIrisManagedStart("));
}
private static int occurrences(String source, String needle) {
int count = 0;
int index = source.indexOf(needle);
@@ -10,7 +10,9 @@ import org.junit.Test;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertSame;
@@ -27,17 +29,19 @@ public class IrisChunkGeneratorMonumentLocateContractTest {
assertTrue(filterStart > irisHelperStart);
String outerMethod = source.substring(findStart, irisHelperStart);
String irisHelper = source.substring(irisHelperStart, filterStart);
int unexploredGuard = irisHelper.indexOf("if (findUnexplored)");
int registryLookup = irisHelper.indexOf("level.registryAccess().lookupOrThrow(Registries.STRUCTURE)");
int placedCheck = irisHelper.indexOf(
"if (!IrisStructureLocator.isPlaced(engine, structureId))");
int irisLocate = irisHelper.indexOf("IrisStructureLocator.locate(", placedCheck);
"if (!IrisStructureLocator.hasNativePlacement(engine, structureId))");
int irisLocate = irisHelper.indexOf("NativeStructureLocatePersistence.search(", placedCheck);
int searchLimit = irisHelper.indexOf("LocateStatus.SEARCH_LIMIT_REACHED", irisLocate);
int limitSkip = irisHelper.indexOf("continue;", searchLimit);
int nativeFilter = outerMethod.indexOf("filterReachableStructures(level, holders)");
int delegateLocate = outerMethod.indexOf("delegate.findNearestMapStructure(level, reachable");
int nearestSelection = outerMethod.indexOf(
"NativeStructureLocateResults.nearest(pos, irisPlaced, nativeLocated)");
int nativePrediction = outerMethod.indexOf("NativeStructureVanillaLocator.predict(", nativeFilter);
int irisResolution = outerMethod.indexOf("return findNearestIrisStructure(", nativePrediction);
int nearestSelection = irisHelper.indexOf(
"NativeStructureLocateResults.nearest(pos, predicted, nativeLocated)");
int selectedVerification = irisHelper.indexOf("bestSearch.search().verify(bestResult)", nearestSelection);
int selectedReference = irisHelper.indexOf(
"selectedSearch.search().reference(selectedStart)", selectedVerification);
int reachabilityStart = source.indexOf("private Set<String> reachableStructureKeys", filterStart);
assertTrue(reachabilityStart > filterStart);
String filterMethod = source.substring(filterStart, reachabilityStart);
@@ -46,22 +50,28 @@ public class IrisChunkGeneratorMonumentLocateContractTest {
int emptyNativePartition = filterMethod.indexOf("if (candidates.isEmpty())", filterContinue);
int reachabilityLookup = filterMethod.indexOf("reachableStructureKeys(level)", emptyNativePartition);
assertTrue(unexploredGuard >= 0);
assertTrue(registryLookup > unexploredGuard);
assertTrue(registryLookup >= 0);
assertTrue(placedCheck > registryLookup);
assertTrue(irisLocate > placedCheck);
assertTrue(searchLimit > irisLocate);
assertTrue(limitSkip > searchLimit);
assertTrue(nativeFilter >= 0);
assertTrue(delegateLocate > nativeFilter);
assertTrue(nearestSelection > delegateLocate);
assertTrue(nativePrediction > nativeFilter);
assertTrue(irisResolution > nativePrediction);
assertTrue(nearestSelection > irisLocate);
assertTrue(selectedVerification > nearestSelection);
assertTrue(selectedReference > selectedVerification);
assertTrue(policyFilter >= 0);
assertTrue(filterContinue > policyFilter);
assertTrue(emptyNativePartition > filterContinue);
assertTrue(reachabilityLookup > emptyNativePartition);
assertFalse(filterMethod.contains("NativeStructureLocateCapability"));
assertFalse(irisHelper.contains("NativeStructureLocateCapability.isPaperUnavailable(structureId)"));
assertTrue(irisHelper.contains("new BlockPos(result.originX(), result.baseY(), result.originZ())"));
assertTrue(irisHelper.contains("NativeStructureLocatePersistence.probe("));
assertTrue(irisHelper.contains("NativeStructureLocateResults.selectAndReference("));
assertTrue(irisHelper.contains("selectedSearch.search().reference(selectedStart)"));
assertTrue(irisHelper.contains("findUnexplored"));
assertTrue(irisHelper.contains("verified.ownership().locatorY()"));
assertFalse(outerMethod.contains("delegate.findNearestMapStructure("));
}
@Test
@@ -77,6 +87,50 @@ public class IrisChunkGeneratorMonumentLocateContractTest {
assertSame(nativeTie, NativeStructureLocateResults.nearest(origin, irisNear, nativeTie));
}
@Test
public void mixedUnexploredLocateReferencesOnlyTheSelectedProvider() {
BlockPos origin = BlockPos.ZERO;
Pair<BlockPos, Holder<Structure>> irisNear = Pair.of(new BlockPos(4, 70, 0), null);
Pair<BlockPos, Holder<Structure>> nativeFar = Pair.of(new BlockPos(8, 70, 0), null);
AtomicInteger irisReferences = new AtomicInteger();
AtomicInteger nativeReferences = new AtomicInteger();
Pair<BlockPos, Holder<Structure>> irisSelected =
NativeStructureLocateResults.selectAndReference(
origin,
irisNear, () -> irisReferences.incrementAndGet(),
nativeFar, () -> nativeReferences.incrementAndGet());
assertSame(irisNear, irisSelected);
assertEquals(1, irisReferences.get());
assertEquals(0, nativeReferences.get());
Pair<BlockPos, Holder<Structure>> nativeNear = Pair.of(new BlockPos(2, 70, 0), null);
Pair<BlockPos, Holder<Structure>> nativeSelected =
NativeStructureLocateResults.selectAndReference(
origin,
irisNear, () -> irisReferences.incrementAndGet(),
nativeNear, () -> nativeReferences.incrementAndGet());
assertSame(nativeNear, nativeSelected);
assertEquals(1, irisReferences.get());
assertEquals(1, nativeReferences.get());
}
@Test
public void nativePredictionIsReadOnlyUntilTheWinnerIsCommitted() throws IOException {
Path nativegen = Path.of(System.getProperty("iris.nativeStructurePostProcessorSource")).getParent();
String source = Files.readString(nativegen.resolve("NativeStructureVanillaLocator.java"));
int predictionStart = source.indexOf("private static Candidate predictAt(");
int candidateStart = source.indexOf("public static final class Candidate", predictionStart);
String prediction = source.substring(predictionStart, candidateStart);
String candidate = source.substring(candidateStart);
assertFalse(prediction.contains("addReference("));
assertTrue(candidate.contains("structureManager.addReference(referenceStart)"));
assertTrue(candidate.contains("committed.compareAndSet(false, true)"));
}
@Test
public void stiltSupportUsesPlacedSolidOccupancyWithoutSnapshotDifferenceRequirement() throws IOException {
Path processor = Path.of(System.getProperty("iris.nativeStructurePostProcessorSource"));
@@ -1,11 +1,23 @@
package art.arcane.iris.nativegen;
import art.arcane.iris.engine.object.IrisJigsawConfiguration;
import art.arcane.iris.engine.object.IrisStructureTerrain;
import com.mojang.datafixers.util.Either;
import com.mojang.datafixers.util.Pair;
import com.mojang.serialization.MapCodec;
import net.minecraft.SharedConstants;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.core.HolderOwner;
import net.minecraft.core.HolderSet;
import net.minecraft.core.Vec3i;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.IntTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.resources.Identifier;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.Bootstrap;
import net.minecraft.server.level.WorldGenRegion;
import net.minecraft.util.RandomSource;
@@ -13,10 +25,12 @@ import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.LevelHeightAccessor;
import net.minecraft.world.level.NoiseColumn;
import net.minecraft.world.level.StructureManager;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.BiomeManager;
import net.minecraft.world.level.biome.FixedBiomeSource;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkGenerator;
@@ -25,19 +39,40 @@ import net.minecraft.world.level.levelgen.RandomState;
import net.minecraft.world.level.levelgen.blending.Blender;
import net.minecraft.world.level.levelgen.structure.Structure;
import net.minecraft.world.level.levelgen.structure.StructureStart;
import net.minecraft.world.level.levelgen.structure.TerrainAdjustment;
import net.minecraft.world.level.levelgen.structure.BoundingBox;
import net.minecraft.world.level.levelgen.structure.pieces.PiecesContainer;
import net.minecraft.world.level.levelgen.structure.pools.EmptyPoolElement;
import net.minecraft.world.level.levelgen.structure.pools.ListPoolElement;
import net.minecraft.world.level.levelgen.structure.pools.SinglePoolElement;
import net.minecraft.world.level.levelgen.structure.pools.StructurePoolElement;
import net.minecraft.world.level.levelgen.structure.pools.StructurePoolElementType;
import net.minecraft.world.level.levelgen.structure.pools.StructureTemplatePool;
import net.minecraft.world.level.levelgen.structure.pools.alias.PoolAliasBinding;
import net.minecraft.world.level.levelgen.structure.structures.SwampHutPiece;
import net.minecraft.world.level.levelgen.structure.structures.SwampHutStructure;
import net.minecraft.world.level.levelgen.structure.templatesystem.LiquidSettings;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureProcessorList;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructurePlaceSettings;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
import org.junit.BeforeClass;
import org.junit.Test;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
public class NativeStructureFactoryTest {
@BeforeClass
@@ -67,20 +102,142 @@ public class NativeStructureFactoryTest {
}
@Test
public void forcedGeneratorSuppliesDeterministicPermissiveEnvironment() {
public void sourceMetadataReadsScalarAndAxisSpecificJigsawRanges() {
CompoundTag scalar = new CompoundTag();
scalar.putInt("max_distance_from_center", 80);
CompoundTag axisSpecific = new CompoundTag();
CompoundTag distance = new CompoundTag();
distance.putInt("horizontal", 96);
distance.putInt("vertical", 64);
axisSpecific.put("max_distance_from_center", distance);
assertEquals(80, NativeStructureFactory.sourceDistance(scalar, "horizontal"));
assertEquals(80, NativeStructureFactory.sourceDistance(scalar, "vertical"));
assertEquals(96, NativeStructureFactory.sourceDistance(axisSpecific, "horizontal"));
assertEquals(64, NativeStructureFactory.sourceDistance(axisSpecific, "vertical"));
}
@Test
public void sourceMetadataIncludesAdjustBoundingBoxExpansion() {
Structure none = new SwampHutStructure(new Structure.StructureSettings(HolderSet.empty()));
Structure beardBox = new SwampHutStructure(new Structure.StructureSettings.Builder(HolderSet.empty())
.terrainAdapation(TerrainAdjustment.BEARD_BOX)
.build());
assertEquals(0, NativeStructureFactory.horizontalReferenceExpansion(none));
assertEquals(12, NativeStructureFactory.horizontalReferenceExpansion(beardBox));
}
@Test
public void templatePoolSpanIncludesWeightedListsFallbacksAndRotations() {
StructurePoolElement narrow = inlineElement(30, 8, 70);
StructurePoolElement wide = inlineElement(96, 8, 12);
ListPoolElement list = new ListPoolElement(
List.of(narrow, wide, EmptyPoolElement.INSTANCE),
StructureTemplatePool.Projection.RIGID);
BoundPoolHolder fallbackHolder = new BoundPoolHolder("test:fallback");
StructureTemplatePool fallback = new StructureTemplatePool(
fallbackHolder,
List.of(Pair.of(inlineElement(110, 8, 20), 1)));
fallbackHolder.bind(fallback);
BoundPoolHolder startHolder = new BoundPoolHolder("test:start");
StructureTemplatePool start = new StructureTemplatePool(
fallbackHolder,
List.of(Pair.of(list, 5), Pair.of(EmptyPoolElement.INSTANCE, 1)));
startHolder.bind(start);
assertEquals(110, NativeStructureTemplatePoolBounds.horizontalSpan(start, null));
}
@Test
public void oversizedInlineStartTemplateReportsItsFullSpan() {
BoundPoolHolder holder = new BoundPoolHolder("test:oversized");
StructureTemplatePool pool = new StructureTemplatePool(
holder,
List.of(Pair.of(inlineElement(160, 8, 9), 1)));
holder.bind(pool);
assertEquals(160, NativeStructureTemplatePoolBounds.horizontalSpan(pool, null));
}
@Test
public void sourceSpanIncludesOnlyAliasesThatCanReplaceTheStartPool() {
BoundPoolHolder emptyHolder = new BoundPoolHolder("test:empty");
StructureTemplatePool empty = new StructureTemplatePool(emptyHolder, List.of());
emptyHolder.bind(empty);
BoundPoolHolder startHolder = new BoundPoolHolder("test:start");
StructureTemplatePool start = new StructureTemplatePool(
emptyHolder, List.of(Pair.of(inlineElement(20, 8, 12), 1)));
startHolder.bind(start);
BoundPoolHolder replacementHolder = new BoundPoolHolder("test:replacement");
StructureTemplatePool replacement = new StructureTemplatePool(
emptyHolder, List.of(Pair.of(inlineElement(30, 8, 12), 1)));
replacementHolder.bind(replacement);
BoundPoolHolder unrelatedHolder = new BoundPoolHolder("test:unrelated");
StructureTemplatePool unrelated = new StructureTemplatePool(
emptyHolder, List.of(Pair.of(inlineElement(200, 8, 12), 1)));
unrelatedHolder.bind(unrelated);
ResourceKey<StructureTemplatePool> startKey = startHolder.unwrapKey().orElseThrow();
ResourceKey<StructureTemplatePool> replacementKey = replacementHolder.unwrapKey().orElseThrow();
ResourceKey<StructureTemplatePool> unrelatedKey = unrelatedHolder.unwrapKey().orElseThrow();
ResourceKey<StructureTemplatePool> childAlias = ResourceKey.create(
Registries.TEMPLATE_POOL, Identifier.parse("test:child_alias"));
List<PoolAliasBinding> aliases = List.of(
PoolAliasBinding.direct(startKey, replacementKey),
PoolAliasBinding.direct(childAlias, unrelatedKey));
Map<ResourceKey<StructureTemplatePool>, StructureTemplatePool> pools = Map.of(
replacementKey, replacement,
unrelatedKey, unrelated);
assertEquals(30, NativeStructureTemplatePoolBounds.sourceHorizontalSpan(
null, start, startKey, aliases, pools::get));
}
@Test
public void sourceStartSpanDoesNotIncludeItsFallbackPool() {
BoundPoolHolder fallbackHolder = new BoundPoolHolder("test:large_fallback");
StructureTemplatePool fallback = new StructureTemplatePool(
fallbackHolder, List.of(Pair.of(inlineElement(180, 8, 10), 1)));
fallbackHolder.bind(fallback);
BoundPoolHolder startHolder = new BoundPoolHolder("test:small_start");
StructureTemplatePool start = new StructureTemplatePool(
fallbackHolder, List.of(Pair.of(inlineElement(24, 8, 10), 1)));
startHolder.bind(start);
assertEquals(180, NativeStructureTemplatePoolBounds.horizontalSpan(start, null));
assertEquals(24, NativeStructureTemplatePoolBounds.sourceHorizontalSpan(
null, start, startHolder.unwrapKey().orElseThrow(), List.of(), ignored -> null));
}
@Test
public void customPoolElementUsesItsBoundingBoxCapability() {
BoundPoolHolder fallbackHolder = new BoundPoolHolder("test:custom_fallback");
StructureTemplatePool fallback = new StructureTemplatePool(fallbackHolder, List.of());
fallbackHolder.bind(fallback);
BoundPoolHolder poolHolder = new BoundPoolHolder("test:custom");
StructureTemplatePool pool = new StructureTemplatePool(
fallbackHolder, List.of(Pair.of(new BoundingPoolElement(37, 11), 1)));
poolHolder.bind(pool);
assertEquals(37, NativeStructureTemplatePoolBounds.horizontalSpan(pool, null));
}
@Test
public void forcedGeneratorBypassesBiomeEligibilityWithoutFabricatingTerrain() {
ChunkGenerator delegate = new TestChunkGenerator();
Holder<Biome> biome = Holder.direct((Biome) null);
LevelHeightAccessor heightAccessor = LevelHeightAccessor.create(-64, 384);
ForcedStructureChunkGenerator generator = new ForcedStructureChunkGenerator(
delegate, biome, -20);
delegate, biome);
assertEquals(-63, generator.getSeaLevel());
assertEquals(81, generator.getBaseHeight(
assertEquals(63, generator.getSeaLevel());
assertEquals(64, generator.getBaseHeight(
0, 0, Heightmap.Types.WORLD_SURFACE_WG, heightAccessor, null));
NoiseColumn column = generator.getBaseColumn(
0, 0, heightAccessor, null);
assertSame(Blocks.STONE, column.getBlock(80).getBlock());
assertSame(Blocks.AIR, column.getBlock(81).getBlock());
assertSame(Blocks.STONE, column.getBlock(81).getBlock());
assertNotSame(delegate.getBiomeSource(), generator.getBiomeSource());
}
@Test
@@ -103,6 +260,96 @@ public class NativeStructureFactoryTest {
assertEquals(-20, relocated.getBoundingBox().minY());
}
@Test
public void invalidGeneratedStartIsANormalSkippedCandidate() {
Structure source = new SwampHutStructure(
new Structure.StructureSettings(HolderSet.empty()));
StructureStart valid = new StructureStart(
source,
new ChunkPos(0, 0),
0,
new PiecesContainer(List.of(
new SwampHutPiece(RandomSource.create(23L), 0, 0)))
);
assertFalse(NativeStructureStartInjector.isUsableGeneratedStart(
StructureStart.INVALID_START));
assertFalse(NativeStructureStartInjector.isUsableGeneratedStart(null));
assertFalse(NativeStructureStartInjector.isUsableGeneratedStart(
new StructureStart(source, new ChunkPos(0, 0), 0,
new PiecesContainer(List.of()))));
assertTrue(NativeStructureStartInjector.isUsableGeneratedStart(valid));
}
@Test
public void unrepresentableGeneratedContentBecomesAnUnpublishedInvalidStart() {
Structure source = new SwampHutStructure(
new Structure.StructureSettings(HolderSet.empty()));
SwampHutPiece boundaryPiece = new SwampHutPiece(RandomSource.create(29L), 0, 0);
boundaryPiece.move(143 - boundaryPiece.getBoundingBox().maxX(), 0, 0);
StructureStart boundary = new StructureStart(
source,
new ChunkPos(0, 0),
0,
new PiecesContainer(List.of(boundaryPiece))
);
SwampHutPiece oversizedPiece = new SwampHutPiece(RandomSource.create(31L), 0, 0);
oversizedPiece.move(144 - oversizedPiece.getBoundingBox().maxX(), 0, 0);
StructureStart oversized = new StructureStart(
source,
new ChunkPos(0, 0),
0,
new PiecesContainer(List.of(oversizedPiece))
);
StructureStart boundaryResult = NativeStructureReferenceEnvelope.wrapForPublication(
boundary, source, 0, new IrisStructureTerrain(), "test:boundary_content");
StructureStart result = NativeStructureReferenceEnvelope.wrapForPublication(
oversized, source, 0,
new IrisStructureTerrain(),
"test:oversized_content");
assertTrue(boundaryResult.isValid());
assertSame(StructureStart.INVALID_START, result);
assertFalse(NativeStructureStartInjector.isUsableGeneratedStart(result));
assertEquals(1, oversized.getPieces().size());
}
@Test
public void failedManualGenerationClearsStaleOwnershipAfterReplacementHandling() throws Exception {
String source = Files.readString(
Path.of(System.getProperty("iris.nativeStructureStartInjectorSource")),
StandardCharsets.UTF_8);
int failureStart = source.indexOf("if (!isUsableGeneratedStart(generated))");
int failureEnd = source.indexOf("continue;", failureStart);
String failureBranch = source.substring(failureStart, failureEnd);
assertTrue(failureBranch.contains("if (replacement)"));
assertTrue(failureBranch.indexOf("NativeStructureOwnershipStore.discard(")
> failureBranch.lastIndexOf("}") );
assertTrue(failureStart < source.indexOf("NativeStructureOwnershipFingerprint.capture("));
assertTrue(failureStart < source.indexOf("NativeStructureOwnershipStore.record("));
}
@Test
public void failedManualStartPublicationClearsRecordedOwnershipAndPreservesTheFailure() throws Exception {
String source = Files.readString(
Path.of(System.getProperty("iris.nativeStructureStartInjectorSource")),
StandardCharsets.UTF_8);
int ownershipRecord = source.indexOf("NativeStructureOwnershipStore.record(");
int publication = source.indexOf("context.structureManager().setStartForStructure(", ownershipRecord);
int cleanup = source.indexOf("NativeStructureOwnershipStore.discard(", publication);
int suppressed = source.indexOf("publicationError.addSuppressed(cleanupError)", cleanup);
int rethrow = source.indexOf("throw publicationError;", suppressed);
assertTrue(ownershipRecord >= 0);
assertTrue(publication > ownershipRecord);
assertTrue(cleanup > publication);
assertTrue(suppressed > cleanup);
assertTrue(rethrow > suppressed);
}
private static final class TestChunkGenerator extends ChunkGenerator {
private TestChunkGenerator() {
super(new FixedBiomeSource(Holder.direct((Biome) null)));
@@ -171,4 +418,91 @@ public class NativeStructureFactoryTest {
BlockPos feetPos) {
}
}
private static StructurePoolElement inlineElement(int sizeX, int sizeY, int sizeZ) {
StructureTemplate template = new StructureTemplate();
CompoundTag tag = new CompoundTag();
ListTag size = new ListTag();
size.add(IntTag.valueOf(sizeX));
size.add(IntTag.valueOf(sizeY));
size.add(IntTag.valueOf(sizeZ));
tag.put("size", size);
template.load(BuiltInRegistries.BLOCK, tag);
return new InlinePoolElement(template);
}
private static final class InlinePoolElement extends SinglePoolElement {
private final StructureTemplate inlineTemplate;
private InlinePoolElement(StructureTemplate template) {
super(Either.right(template), Holder.direct(new StructureProcessorList(List.of())),
StructureTemplatePool.Projection.RIGID, Optional.<LiquidSettings>empty());
inlineTemplate = template;
}
@Override
public BoundingBox getBoundingBox(StructureTemplateManager templateManager,
BlockPos position, Rotation rotation) {
return inlineTemplate.getBoundingBox(
new StructurePlaceSettings().setRotation(rotation), position);
}
}
private static final class BoundingPoolElement extends StructurePoolElement {
private final int width;
private final int depth;
private BoundingPoolElement(int width, int depth) {
super(StructureTemplatePool.Projection.RIGID);
this.width = width;
this.depth = depth;
}
@Override
public Vec3i getSize(StructureTemplateManager templateManager, Rotation rotation) {
return new Vec3i(width, 1, depth);
}
@Override
public List<StructureTemplate.JigsawBlockInfo> getShuffledJigsawBlocks(
StructureTemplateManager templateManager, BlockPos position,
Rotation rotation, RandomSource random) {
return List.of();
}
@Override
public BoundingBox getBoundingBox(StructureTemplateManager templateManager,
BlockPos position, Rotation rotation) {
return new BoundingBox(
position.getX(), position.getY(), position.getZ(),
position.getX() + width - 1, position.getY(),
position.getZ() + depth - 1);
}
@Override
public boolean place(StructureTemplateManager templateManager,
WorldGenLevel world,
StructureManager structureManager, ChunkGenerator chunkGenerator,
BlockPos position, BlockPos pivot, Rotation rotation,
BoundingBox area, RandomSource random,
LiquidSettings liquidSettings, boolean keepJigsaws) {
return true;
}
@Override
public StructurePoolElementType<?> getType() {
return StructurePoolElementType.EMPTY;
}
}
private static final class BoundPoolHolder extends Holder.Reference<StructureTemplatePool> {
private BoundPoolHolder(String key) {
super(Type.STAND_ALONE, new HolderOwner<>() {
}, ResourceKey.create(Registries.TEMPLATE_POOL, Identifier.parse(key)), null);
}
private void bind(StructureTemplatePool pool) {
bindValue(pool);
}
}
}
@@ -0,0 +1,197 @@
package art.arcane.iris.nativegen;
import net.minecraft.SharedConstants;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.Bootstrap;
import net.minecraft.world.level.ChunkPos;
import org.junit.BeforeClass;
import org.junit.Test;
import java.util.concurrent.CompletableFuture;
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.assertTrue;
public class NativeStructureLocatePersistenceTest {
@BeforeClass
public static void bootstrapMinecraftRegistries() {
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();
}
@Test
public void storageProbeScansEachCandidateOnlyOnce() {
AtomicInteger reads = new AtomicInteger();
CompoundTag chunk = new CompoundTag();
chunk.putInt("DataVersion", SharedConstants.getCurrentVersion().dataVersion().version());
chunk.putString("Status", "full");
CompoundTag start = new CompoundTag();
start.putString("id", "minecraft:village_plains");
start.putInt("references", 0);
CompoundTag starts = new CompoundTag();
starts.put("minecraft:village_plains", start);
CompoundTag structures = new CompoundTag();
structures.put("starts", starts);
chunk.put("structures", structures);
NativeStructureLocatePersistence.ProbeBudget budget =
new NativeStructureLocatePersistence.ProbeBudget(
512,
(chunkPos, visitor) -> {
reads.incrementAndGet();
chunk.acceptAsRoot(visitor);
return CompletableFuture.completedFuture(null);
});
ChunkPos chunkPos = new ChunkPos(7, -11);
assertTrue(budget.acceptsStored(
null, chunkPos, "minecraft:village_plains", true));
assertTrue(budget.acceptsStored(
null, chunkPos, "minecraft:village_plains", true));
assertEquals(1, reads.get());
assertEquals(1, budget.used());
}
@Test
public void legacyStoredChunkIsDatafixedBeforeItsStartIsClassified() {
AtomicBoolean datafixed = new AtomicBoolean();
CompoundTag legacy = new CompoundTag();
legacy.putInt("DataVersion", 100);
legacy.putString("Status", "full");
NativeStructureLocatePersistence.ProbeBudget budget =
new NativeStructureLocatePersistence.ProbeBudget(
512,
(chunkPos, visitor) -> {
legacy.acceptAsRoot(visitor);
return CompletableFuture.completedFuture(null);
},
(level, storedChunk) -> {
datafixed.set(true);
CompoundTag fixed = storedChunk.copy();
fixed.putInt("DataVersion",
SharedConstants.getCurrentVersion().dataVersion().version());
CompoundTag start = new CompoundTag();
start.putString("id", "minecraft:village_plains");
start.putInt("references", 1);
CompoundTag starts = new CompoundTag();
starts.put("minecraft:village_plains", start);
CompoundTag structures = new CompoundTag();
structures.put("starts", starts);
fixed.put("structures", structures);
return fixed;
});
assertFalse(budget.acceptsStored(
null, new ChunkPos(3, 4), "minecraft:village_plains", true));
assertTrue(datafixed.get());
}
@Test
public void partialDatafixFailureFallsBackToSelectedChunkVerification() {
CompoundTag legacy = new CompoundTag();
legacy.putInt("DataVersion", 100);
NativeStructureLocatePersistence.ProbeBudget budget =
new NativeStructureLocatePersistence.ProbeBudget(
512,
(chunkPos, visitor) -> {
legacy.acceptAsRoot(visitor);
return CompletableFuture.completedFuture(null);
},
(level, storedChunk) -> {
throw new IllegalStateException("broken partial datafix");
});
assertTrue(budget.acceptsStored(
null, new ChunkPos(9, -2), "minecraft:village_plains", true));
}
@Test
public void missingStartsContainerFallsBackToSelectedChunkVerification() {
CompoundTag chunk = currentChunk();
NativeStructureLocatePersistence.ProbeBudget budget = budget(chunk);
assertTrue(budget.acceptsStored(
null, new ChunkPos(5, 8), "minecraft:village_plains", false));
assertTrue(budget.acceptsStored(
null, new ChunkPos(5, 8), "minecraft:village_plains", true));
}
@Test
public void presentEmptyStartsContainerRejectsTheCandidateWithoutLoading() {
CompoundTag chunk = currentChunk();
CompoundTag structures = new CompoundTag();
structures.put("starts", new CompoundTag());
chunk.put("structures", structures);
NativeStructureLocatePersistence.ProbeBudget budget = budget(chunk);
assertFalse(budget.acceptsStored(
null, new ChunkPos(-3, 2), "minecraft:village_plains", false));
}
@Test
public void collisionTombstoneIsRejectedForNormalAndUnexploredLocate() {
CompoundTag chunk = currentChunk();
CompoundTag tombstone = new CompoundTag();
tombstone.putString("id", "INVALID");
CompoundTag starts = new CompoundTag();
starts.put("minecraft:village_plains", tombstone);
CompoundTag structures = new CompoundTag();
structures.put("starts", starts);
chunk.put("structures", structures);
ChunkPos chunkPos = new ChunkPos(12, -9);
assertFalse(budget(chunk).acceptsStored(
null, chunkPos, "minecraft:village_plains", false));
assertFalse(budget(chunk).acceptsStored(
null, chunkPos, "minecraft:village_plains", true));
}
@Test
public void malformedNonCompoundStartIsRejectedWithoutLoading() {
CompoundTag chunk = currentChunk();
CompoundTag starts = new CompoundTag();
starts.putInt("minecraft:village_plains", 1);
CompoundTag structures = new CompoundTag();
structures.put("starts", starts);
chunk.put("structures", structures);
assertFalse(budget(chunk).acceptsStored(
null, new ChunkPos(1, 1), "minecraft:village_plains", false));
}
@Test
public void referencedStoredStartIsOnlyRejectedForUnexploredLocate() {
CompoundTag chunk = currentChunk();
CompoundTag start = new CompoundTag();
start.putString("id", "minecraft:village_plains");
start.putInt("references", 1);
CompoundTag starts = new CompoundTag();
starts.put("minecraft:village_plains", start);
CompoundTag structures = new CompoundTag();
structures.put("starts", starts);
chunk.put("structures", structures);
ChunkPos chunkPos = new ChunkPos(-4, -7);
assertTrue(budget(chunk).acceptsStored(
null, chunkPos, "minecraft:village_plains", false));
assertFalse(budget(chunk).acceptsStored(
null, chunkPos, "minecraft:village_plains", true));
}
private static CompoundTag currentChunk() {
CompoundTag chunk = new CompoundTag();
chunk.putInt("DataVersion", SharedConstants.getCurrentVersion().dataVersion().version());
return chunk;
}
private static NativeStructureLocatePersistence.ProbeBudget budget(CompoundTag chunk) {
return new NativeStructureLocatePersistence.ProbeBudget(
512,
(chunkPos, visitor) -> {
chunk.acceptAsRoot(visitor);
return CompletableFuture.completedFuture(null);
});
}
}
@@ -0,0 +1,346 @@
package art.arcane.iris.nativegen;
import art.arcane.iris.engine.framework.NativeStructureOwnershipRecord;
import art.arcane.iris.engine.framework.NativeStructureStartPlan;
import art.arcane.iris.engine.object.IrisNativeStructure;
import art.arcane.iris.engine.object.IrisStructurePlacement;
import art.arcane.iris.engine.object.IrisStructureTerrain;
import art.arcane.iris.engine.object.IrisStructureTerrainMode;
import art.arcane.volmlib.util.collection.KList;
import net.minecraft.SharedConstants;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.HolderSet;
import net.minecraft.core.Vec3i;
import net.minecraft.nbt.ListTag;
import net.minecraft.server.Bootstrap;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.StructureManager;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.levelgen.LegacyRandomSource;
import net.minecraft.world.level.levelgen.RandomSupport;
import net.minecraft.world.level.levelgen.WorldgenRandom;
import net.minecraft.world.level.levelgen.structure.BoundingBox;
import net.minecraft.world.level.levelgen.structure.PoolElementStructurePiece;
import net.minecraft.world.level.levelgen.structure.StructurePiece;
import net.minecraft.world.level.levelgen.structure.StructureStart;
import net.minecraft.world.level.levelgen.structure.pieces.PiecesContainer;
import net.minecraft.world.level.levelgen.structure.pools.StructurePoolElement;
import net.minecraft.world.level.levelgen.structure.pools.StructurePoolElementType;
import net.minecraft.world.level.levelgen.structure.pools.StructureTemplatePool;
import net.minecraft.world.level.levelgen.structure.structures.OceanMonumentPieces;
import net.minecraft.world.level.levelgen.structure.structures.OceanMonumentStructure;
import net.minecraft.world.level.levelgen.structure.templatesystem.LiquidSettings;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
import org.junit.BeforeClass;
import org.junit.Test;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
public class NativeStructureOwnershipFingerprintTest {
@BeforeClass
public static void bootstrapMinecraftRegistries() {
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();
}
@Test
public void monumentFingerprintSurvivesSourceYRegenerationWithoutSyntheticPieces() {
long seed = 8372619L;
ChunkPos origin = new ChunkPos(9, -4);
OceanMonumentStructure structure = new OceanMonumentStructure(
new OceanMonumentStructure.StructureSettings(HolderSet.empty()));
StructureStart generated = monumentStart(structure, origin, seed);
NativeStructureVerticalPlacer.alignOceanMonumentToSeaLevel(
generated, 0, 80, -64, 320);
int alignedLocatorY = NativeStructureOwnershipFingerprint.locatorY(generated);
IrisStructureTerrain terrain = new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.FORCE_CARVE)
.setHorizontalPadding(24);
StructureStart wrapped = NativeStructureReferenceEnvelope.wrap(
generated,
structure,
0,
terrain);
String beforeReload = NativeStructureOwnershipFingerprint.fingerprint(
"minecraft:monument", wrapped);
ListTag serializedPieces = (ListTag) new PiecesContainer(wrapped.getPieces()).save(null);
PiecesContainer loadedPieces = PiecesContainer.load(serializedPieces, null);
assertEquals(1, loadedPieces.pieces().size());
assertTrue(loadedPieces.pieces().getFirst()
instanceof OceanMonumentPieces.MonumentBuilding);
PiecesContainer regeneratedPieces = OceanMonumentStructure.regeneratePiecesAfterLoad(
origin, seed, loadedPieces);
StructureStart reloaded = new StructureStart(structure, origin, 0, regeneratedPieces);
String afterReload = NativeStructureOwnershipFingerprint.fingerprint(
"minecraft:monument", reloaded);
NativeStructureOwnershipRecord ownership = NativeStructureOwnershipFingerprint.capture(
"minecraft:monument",
wrapped,
plan(origin, NativeStructureReferenceEnvelope.contentBounds(wrapped).minY()),
NativeStructureReferenceEnvelope.referenceBounds(wrapped, structure, terrain));
assertEquals(beforeReload, afterReload);
assertTrue(NativeStructureOwnershipFingerprint.matches(ownership, reloaded));
assertNotEquals(alignedLocatorY, NativeStructureOwnershipFingerprint.locatorY(reloaded));
assertEquals(alignedLocatorY, ownership.locatorY());
assertEquals(1, reloaded.getPieces().size());
assertEquals(1, wrapped.getPieces().size());
}
@Test
public void sameKeyAndOriginStillRequiresMatchingContentIdentity() {
long seed = 18273L;
ChunkPos origin = new ChunkPos(2, 3);
OceanMonumentStructure structure = new OceanMonumentStructure(
new OceanMonumentStructure.StructureSettings(HolderSet.empty()));
StructureStart expected = monumentStart(structure, origin, seed);
StructureStart moved = monumentStart(structure, origin, seed);
for (StructurePiece piece : moved.getPieces()) {
piece.move(1, 0, 0);
}
NativeStructureOwnershipRecord ownership = NativeStructureOwnershipFingerprint.capture(
"minecraft:monument",
expected,
plan(origin, NativeStructureReferenceEnvelope.contentBounds(expected).minY()),
expected.getBoundingBox());
assertFalse(NativeStructureOwnershipFingerprint.matches(ownership, moved));
}
@Test
public void persistedEnvelopeRepairsReloadedMonumentReferenceOutsideLiveBounds() {
long seed = 772931L;
ChunkPos origin = new ChunkPos(4, -7);
OceanMonumentStructure structure = new OceanMonumentStructure(
new OceanMonumentStructure.StructureSettings(HolderSet.empty()));
StructureStart generated = monumentStart(structure, origin, seed);
NativeStructureVerticalPlacer.alignOceanMonumentToSeaLevel(
generated, 0, 80, -64, 320);
IrisStructureTerrain terrain = new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.FORCE_CARVE)
.setHorizontalPadding(24);
StructureStart wrapped = NativeStructureReferenceEnvelope.wrap(
generated,
structure,
0,
terrain);
NativeStructureOwnershipRecord ownership = NativeStructureOwnershipFingerprint.capture(
"minecraft:monument",
wrapped,
plan(origin, NativeStructureReferenceEnvelope.contentBounds(wrapped).minY()),
NativeStructureReferenceEnvelope.referenceBounds(wrapped, structure, terrain));
PiecesContainer regeneratedPieces = OceanMonumentStructure.regeneratePiecesAfterLoad(
origin, seed, new PiecesContainer(wrapped.getPieces()));
StructureStart reloaded = new StructureStart(structure, origin, 0, regeneratedPieces);
ChunkPos target = outsideLiveBounds(ownership, reloaded);
assertNotNull(target);
assertTrue(NativeStructureReferenceRepair.requiresReference(
target, "minecraft:monument", reloaded, ownership));
assertFalse(NativeStructureReferenceRepair.requiresReference(
target, "minecraft:monument", reloaded, null));
}
@Test
public void overlappingManualStartsSixteenChunksApartRemainIndependentlyReferenced() {
OceanMonumentStructure structure = new OceanMonumentStructure(
new OceanMonumentStructure.StructureSettings(HolderSet.empty()));
ChunkPos westOrigin = new ChunkPos(0, 0);
ChunkPos eastOrigin = new ChunkPos(16, 0);
StructureStart west = monumentStart(structure, westOrigin, 317L);
StructureStart east = monumentStart(structure, eastOrigin, 941L);
west.getPieces().getFirst().move(112, 0, 0);
east.getPieces().getFirst().move(-96, 0, 0);
NativeStructureOwnershipRecord westOwnership = ownership(westOrigin, west, structure);
NativeStructureOwnershipRecord eastOwnership = ownership(eastOrigin, east, structure);
ChunkPos shared = new ChunkPos(8, 0);
assertTrue(NativeStructureReferenceEnvelope.contentBounds(west).intersects(
NativeStructureReferenceEnvelope.contentBounds(east)));
assertTrue(NativeStructureReferenceRepair.requiresReference(
shared, "minecraft:monument", west, westOwnership));
assertTrue(NativeStructureReferenceRepair.requiresReference(
shared, "minecraft:monument", east, eastOwnership));
}
@Test
public void denseAdjacentManualStartsDoNotSuppressEachOther() {
OceanMonumentStructure structure = new OceanMonumentStructure(
new OceanMonumentStructure.StructureSettings(HolderSet.empty()));
ChunkPos firstOrigin = new ChunkPos(0, 0);
ChunkPos secondOrigin = new ChunkPos(1, 0);
StructureStart first = monumentStart(structure, firstOrigin, 17L);
StructureStart second = monumentStart(structure, secondOrigin, 23L);
NativeStructureOwnershipRecord firstOwnership = ownership(firstOrigin, first, structure);
NativeStructureOwnershipRecord secondOwnership = ownership(secondOrigin, second, structure);
ChunkPos shared = new ChunkPos(0, 0);
assertTrue(NativeStructureReferenceEnvelope.contentBounds(first).intersects(
NativeStructureReferenceEnvelope.contentBounds(second)));
assertTrue(NativeStructureReferenceRepair.requiresReference(
shared, "minecraft:monument", first, firstOwnership));
assertTrue(NativeStructureReferenceRepair.requiresReference(
shared, "minecraft:monument", second, secondOwnership));
}
@Test
public void customPoolElementIdentityDoesNotDependOnProcessLocalToString() {
OceanMonumentStructure structure = new OceanMonumentStructure(
new OceanMonumentStructure.StructureSettings(HolderSet.empty()));
ChunkPos origin = new ChunkPos(3, -2);
StructureStart first = poolStart(structure, origin, new IdentityStringPoolElement());
StructureStart reloaded = poolStart(structure, origin, new IdentityStringPoolElement());
assertEquals(
NativeStructureOwnershipFingerprint.fingerprint("test:custom_pool", first),
NativeStructureOwnershipFingerprint.fingerprint("test:custom_pool", reloaded));
}
@Test
public void namedPoolTemplateRemainsPartOfTheStableFingerprint() {
OceanMonumentStructure structure = new OceanMonumentStructure(
new OceanMonumentStructure.StructureSettings(HolderSet.empty()));
ChunkPos origin = new ChunkPos(3, -2);
StructurePoolElement firstElement = StructurePoolElement.single("test:first")
.apply(StructureTemplatePool.Projection.RIGID);
StructurePoolElement secondElement = StructurePoolElement.single("test:second")
.apply(StructureTemplatePool.Projection.RIGID);
assertNotEquals(
NativeStructureOwnershipFingerprint.fingerprint(
"test:named_pool", poolStart(structure, origin, firstElement)),
NativeStructureOwnershipFingerprint.fingerprint(
"test:named_pool", poolStart(structure, origin, secondElement)));
}
private static StructureStart monumentStart(OceanMonumentStructure structure,
ChunkPos origin, long seed) {
WorldgenRandom random = new WorldgenRandom(
new LegacyRandomSource(RandomSupport.generateUniqueSeed()));
random.setLargeFeatureSeed(seed, origin.x(), origin.z());
Direction orientation = Direction.Plane.HORIZONTAL.getRandomDirection(random);
OceanMonumentPieces.MonumentBuilding building = new OceanMonumentPieces.MonumentBuilding(
random,
origin.getMinBlockX() - 29,
origin.getMinBlockZ() - 29,
orientation
);
return new StructureStart(
structure,
origin,
0,
new PiecesContainer(List.of(building))
);
}
private static NativeStructureStartPlan plan(ChunkPos origin, int baseY) {
IrisNativeStructure source = new IrisNativeStructure()
.setStructure("minecraft:monument")
.setWeight(1);
IrisStructurePlacement placement = new IrisStructurePlacement()
.setPlacementId("monument-test")
.setNativeStructures(new KList<IrisNativeStructure>().qadd(source));
return new NativeStructureStartPlan(
placement,
source,
origin.x(),
origin.z(),
baseY
);
}
private static NativeStructureOwnershipRecord ownership(
ChunkPos origin, StructureStart start, OceanMonumentStructure structure) {
return NativeStructureOwnershipFingerprint.capture(
"minecraft:monument",
start,
plan(origin, NativeStructureReferenceEnvelope.contentBounds(start).minY()),
NativeStructureReferenceEnvelope.referenceBounds(
start, structure, new IrisStructureTerrain())
);
}
private static StructureStart poolStart(
OceanMonumentStructure structure, ChunkPos origin, StructurePoolElement element) {
BlockPos position = new BlockPos(
origin.getMiddleBlockX(), 64, origin.getMiddleBlockZ());
PoolElementStructurePiece piece = new PoolElementStructurePiece(
null, element, position, 0, Rotation.NONE,
new BoundingBox(position), LiquidSettings.APPLY_WATERLOGGING);
return new StructureStart(
structure, origin, 0, new PiecesContainer(List.of(piece)));
}
private static final class IdentityStringPoolElement extends StructurePoolElement {
private IdentityStringPoolElement() {
super(StructureTemplatePool.Projection.RIGID);
}
@Override
public Vec3i getSize(StructureTemplateManager templateManager, Rotation rotation) {
return new Vec3i(1, 1, 1);
}
@Override
public List<StructureTemplate.JigsawBlockInfo> getShuffledJigsawBlocks(
StructureTemplateManager templateManager, BlockPos position,
Rotation rotation, RandomSource random) {
return List.of();
}
@Override
public BoundingBox getBoundingBox(StructureTemplateManager templateManager,
BlockPos position, Rotation rotation) {
return new BoundingBox(position);
}
@Override
public boolean place(StructureTemplateManager templateManager,
WorldGenLevel world, StructureManager structureManager,
ChunkGenerator chunkGenerator, BlockPos position, BlockPos pivot,
Rotation rotation, BoundingBox area, RandomSource random,
LiquidSettings liquidSettings, boolean keepJigsaws) {
return true;
}
@Override
public StructurePoolElementType<?> getType() {
return StructurePoolElementType.EMPTY;
}
@Override
public String toString() {
return "IdentityStringPoolElement@"
+ Integer.toHexString(System.identityHashCode(this));
}
}
private static ChunkPos outsideLiveBounds(NativeStructureOwnershipRecord ownership,
StructureStart start) {
for (int chunkX = ownership.referenceMinChunkX();
chunkX <= ownership.referenceMaxChunkX(); chunkX++) {
for (int chunkZ = ownership.referenceMinChunkZ();
chunkZ <= ownership.referenceMaxChunkZ(); chunkZ++) {
ChunkPos candidate = new ChunkPos(chunkX, chunkZ);
if (!start.getBoundingBox().intersects(
candidate.getMinBlockX(), candidate.getMinBlockZ(),
candidate.getMaxBlockX(), candidate.getMaxBlockZ())) {
return candidate;
}
}
}
return null;
}
}
@@ -0,0 +1,185 @@
package art.arcane.iris.nativegen;
import art.arcane.iris.engine.framework.NativeStructureOwnershipRecord;
import art.arcane.iris.engine.framework.NativeStructureStartPlan;
import art.arcane.iris.engine.framework.StructurePlacementGrid;
import art.arcane.iris.engine.object.IrisNativeStructure;
import art.arcane.iris.engine.object.IrisStructurePlacement;
import art.arcane.iris.engine.object.IrisStructureTerrain;
import art.arcane.iris.engine.object.IrisStructureTerrainMode;
import art.arcane.volmlib.util.collection.KList;
import net.minecraft.SharedConstants;
import net.minecraft.core.Direction;
import net.minecraft.core.HolderSet;
import net.minecraft.server.Bootstrap;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.levelgen.LegacyRandomSource;
import net.minecraft.world.level.levelgen.RandomSupport;
import net.minecraft.world.level.levelgen.WorldgenRandom;
import net.minecraft.world.level.levelgen.structure.StructurePiece;
import net.minecraft.world.level.levelgen.structure.StructureStart;
import net.minecraft.world.level.levelgen.structure.pieces.PiecesContainer;
import net.minecraft.world.level.levelgen.structure.structures.OceanMonumentPieces;
import net.minecraft.world.level.levelgen.structure.structures.OceanMonumentStructure;
import org.junit.BeforeClass;
import org.junit.Test;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
public class NativeStructureOwnershipRecoveryTest {
@BeforeClass
public static void bootstrapMinecraftRegistries() {
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();
}
@Test
public void missingSidecarRecoversExactPolicyEnvelopeAndLocatorAfterReload() {
long seed = 5519231L;
ChunkPos origin = new ChunkPos(7, -5);
OceanMonumentStructure structure = structure();
StructureStart expected = monumentStart(structure, origin, seed);
NativeStructureVerticalPlacer.alignOceanMonumentToSeaLevel(
expected, 0, 80, -64, 320);
int expectedLocatorY = NativeStructureOwnershipFingerprint.locatorY(expected);
NativeStructureStartPlan plan = plan(origin, "recovery-exact", 24);
StructureStart reloaded = new StructureStart(
structure,
origin,
expected.getReferences(),
OceanMonumentStructure.regeneratePiecesAfterLoad(
origin, seed, new PiecesContainer(expected.getPieces()))
);
NativeStructureOwnershipRecord recovered =
NativeStructureOwnershipRecovery.proveCandidate(
"minecraft:monument", structure, reloaded,
plan, expected, false);
assertNotNull(recovered);
assertEquals(StructurePlacementGrid.placementIdentity(plan.placement()),
recovered.placementIdentity());
assertEquals(expectedLocatorY, recovered.locatorY());
assertNotEquals(NativeStructureOwnershipFingerprint.locatorY(reloaded),
recovered.locatorY());
assertEquals(IrisStructureTerrainMode.FORCE_CARVE,
recovered.restoredDecision().terrain().resolvedMode());
assertEquals(24, recovered.restoredDecision().terrain().getHorizontalPadding());
assertTrue(recovered.referenceMinChunkX()
< NativeStructureReferenceEnvelope.contentBounds(reloaded).minX() >> 4);
}
@Test
public void naturalSameKeyStartRemainsUnownedWhenOriginIsAmbiguous() {
long seed = 22817L;
ChunkPos origin = new ChunkPos(-3, 6);
OceanMonumentStructure structure = structure();
StructureStart natural = monumentStart(structure, origin, seed);
NativeStructureStartPlan plan = plan(origin, "natural-ambiguous", 0);
NativeStructureOwnershipRecord recovered =
NativeStructureOwnershipRecovery.proveCandidate(
"minecraft:monument", structure, natural,
plan, natural, true);
assertNull(recovered);
}
@Test
public void changedPlacementIdentityOrGeometryCannotRecoverOwnership() {
long seed = 991723L;
ChunkPos origin = new ChunkPos(2, 4);
OceanMonumentStructure structure = structure();
StructureStart expected = monumentStart(structure, origin, seed);
StructureStart changedGeometry = monumentStart(structure, origin, seed);
for (StructurePiece piece : changedGeometry.getPieces()) {
piece.move(1, 0, 0);
}
NativeStructureStartPlan originalIdentity = plan(
origin, "original-identity", 0);
NativeStructureStartPlan changedIdentity = plan(
new ChunkPos(origin.x() + 1, origin.z()), "changed-identity", 0);
assertNotEquals(
StructurePlacementGrid.placementIdentity(originalIdentity.placement()),
StructurePlacementGrid.placementIdentity(changedIdentity.placement()));
assertNull(NativeStructureOwnershipRecovery.proveCandidate(
"minecraft:monument", structure, expected,
changedIdentity, expected, false));
assertNull(NativeStructureOwnershipRecovery.proveCandidate(
"minecraft:monument", structure, changedGeometry,
plan(origin, "geometry-check", 0), expected, false));
}
@Test
public void recoveredEnvelopeStillRequiresExactFingerprint() {
long seed = 81337L;
ChunkPos origin = new ChunkPos(-8, -9);
OceanMonumentStructure structure = structure();
StructureStart expected = monumentStart(structure, origin, seed);
NativeStructureOwnershipRecord recovered =
NativeStructureOwnershipRecovery.proveCandidate(
"minecraft:monument", structure, expected,
plan(origin, "fingerprint-check", 16), expected, false);
assertNotNull(recovered);
StructureStart moved = monumentStart(structure, origin, seed);
for (StructurePiece piece : moved.getPieces()) {
piece.move(0, 0, 1);
}
assertFalse(NativeStructureOwnershipFingerprint.matches(recovered, moved));
}
private static OceanMonumentStructure structure() {
return new OceanMonumentStructure(
new OceanMonumentStructure.StructureSettings(HolderSet.empty()));
}
private static StructureStart monumentStart(OceanMonumentStructure structure,
ChunkPos origin, long seed) {
WorldgenRandom random = new WorldgenRandom(
new LegacyRandomSource(RandomSupport.generateUniqueSeed()));
random.setLargeFeatureSeed(seed, origin.x(), origin.z());
Direction orientation = Direction.Plane.HORIZONTAL.getRandomDirection(random);
OceanMonumentPieces.MonumentBuilding building =
new OceanMonumentPieces.MonumentBuilding(
random,
origin.getMinBlockX() - 29,
origin.getMinBlockZ() - 29,
orientation
);
return new StructureStart(
structure, origin, 0, new PiecesContainer(List.of(building)));
}
private static NativeStructureStartPlan plan(ChunkPos origin,
String placementId,
int horizontalPadding) {
IrisNativeStructure source = new IrisNativeStructure()
.setStructure("minecraft:monument")
.setWeight(1);
IrisStructureTerrain terrain = new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.FORCE_CARVE)
.setHorizontalPadding(horizontalPadding);
IrisStructurePlacement placement = new IrisStructurePlacement()
.setPlacementId(placementId)
.setTerrain(terrain)
.setNativeStructures(new KList<IrisNativeStructure>().qadd(source));
return new NativeStructureStartPlan(
placement,
source,
origin.x(),
origin.z(),
NativeStructureReferenceEnvelope.contentBounds(
monumentStart(structure(), origin, 1L)).minY()
);
}
}
@@ -7,22 +7,35 @@ import art.arcane.iris.engine.object.IrisStructureYBand;
import art.arcane.volmlib.util.math.RNG;
import net.minecraft.SharedConstants;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.core.HolderSet;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.Identifier;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.Bootstrap;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.dimension.BuiltinDimensionTypes;
import net.minecraft.world.level.levelgen.GenerationStep;
import net.minecraft.world.level.levelgen.structure.BoundingBox;
import net.minecraft.world.level.levelgen.structure.PoolElementStructurePiece;
import net.minecraft.world.level.levelgen.structure.Structure;
import net.minecraft.world.level.levelgen.structure.StructurePiece;
import net.minecraft.world.level.levelgen.structure.StructureStart;
import net.minecraft.world.level.levelgen.structure.TerrainAdjustment;
import net.minecraft.world.level.levelgen.structure.pieces.PiecesContainer;
import net.minecraft.world.level.levelgen.structure.pools.StructurePoolElement;
import net.minecraft.world.level.levelgen.structure.pools.StructureTemplatePool;
import net.minecraft.world.level.levelgen.structure.structures.DesertPyramidPiece;
import net.minecraft.world.level.levelgen.structure.structures.DesertPyramidStructure;
import net.minecraft.world.level.levelgen.structure.templatesystem.BlockIgnoreProcessor;
import net.minecraft.world.level.levelgen.structure.templatesystem.LiquidSettings;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureProcessorList;
import org.junit.BeforeClass;
import org.junit.Test;
@@ -31,14 +44,16 @@ import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.BitSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
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.junit.Assert.assertTrue;
public class NativeStructurePostProcessorEncaseTest {
@@ -98,6 +113,96 @@ public class NativeStructurePostProcessorEncaseTest {
state(blocks, bounds.minX(), 0, bounds.minZ()));
}
@Test
public void defaultEncaseMaterialFollowsVanillaDimensionTerrain() {
assertEquals(Blocks.NETHERRACK.defaultBlockState(),
NativeStructureTerrainIntegrator.defaultEncaseBlock(Level.NETHER, 64));
assertEquals(Blocks.END_STONE.defaultBlockState(),
NativeStructureTerrainIntegrator.defaultEncaseBlock(Level.END, 64));
assertEquals(Blocks.STONE.defaultBlockState(),
NativeStructureTerrainIntegrator.defaultEncaseBlock(Level.OVERWORLD, 64));
}
@Test
public void sourceEncaseMaterialFollowsNearbyIrisTerrain() {
Map<BlockPos, BlockState> blocks = new HashMap<>();
BlockPos fill = new BlockPos(4, 64, 8);
put(blocks, fill.getX() + 3, fill.getY(), fill.getZ(), Blocks.TUFF.defaultBlockState());
assertEquals(Blocks.TUFF.defaultBlockState(),
NativeStructureTerrainIntegrator.sourceEncaseBlock(world(blocks), fill));
}
@Test
public void sourceMaterialSnapshotIgnoresPreviouslyDecoratedAdjacentChunks() {
Map<BlockPos, BlockState> blocks = new HashMap<>();
BlockPos fill = new BlockPos(0, 64, 8);
put(blocks, -1, 64, 8, Blocks.OAK_PLANKS.defaultBlockState());
put(blocks, 3, 64, 8, Blocks.TUFF.defaultBlockState());
WorldGenLevel world = world(blocks);
NativeStructureTerrainIntegrator.SourceTerrainSnapshot sourceTerrain =
sourceSnapshot(world, new BoundingBox(0, 64, 0, 15, 64, 15));
assertEquals(Blocks.TUFF.defaultBlockState(),
NativeStructureTerrainIntegrator.sourceEncaseBlock(
world, fill, sourceTerrain));
}
@Test
public void sourceMaterialSnapshotIsStableAcrossEarlierFillsInTheSameChunk() {
Map<BlockPos, BlockState> blocks = new HashMap<>();
BlockPos fill = new BlockPos(0, 64, 8);
put(blocks, 3, 64, 8, Blocks.TUFF.defaultBlockState());
WorldGenLevel world = world(blocks);
NativeStructureTerrainIntegrator.SourceTerrainSnapshot sourceTerrain =
sourceSnapshot(world, new BoundingBox(0, 64, 0, 15, 64, 15));
put(blocks, 1, 64, 8, Blocks.OAK_PLANKS.defaultBlockState());
assertEquals(Blocks.TUFF.defaultBlockState(),
NativeStructureTerrainIntegrator.sourceEncaseBlock(
world, fill, sourceTerrain));
}
@Test
public void sourceMaterialRejectsStructureBlocksAndOresBeforeDimensionFallback() {
Map<BlockPos, BlockState> blocks = new HashMap<>();
BlockPos fill = new BlockPos(4, -20, 8);
put(blocks, 3, -20, 8, Blocks.OAK_PLANKS.defaultBlockState());
put(blocks, 5, -20, 8, Blocks.STONE_BRICKS.defaultBlockState());
put(blocks, 4, -20, 7, Blocks.IRON_ORE.defaultBlockState());
assertEquals(Blocks.DEEPSLATE.defaultBlockState(),
NativeStructureTerrainIntegrator.sourceEncaseBlock(world(blocks), fill));
}
@Test
public void sourceMaterialSnapshotOnlyAllocatesActualFillLayers() {
StructureStart start = start(TerrainAdjustment.BURY, 64);
BoundingBox area = new BoundingBox(0, -2032, 0, 15, 2030, 15);
NativeStructureTerrainIntegrator.SourceTerrainSnapshot sourceTerrain =
NativeStructureTerrainIntegrator.captureSourceTerrain(
world(new HashMap<>()), area,
List.of(new NativeStructureTerrainIntegrator.TerrainTarget(
"test:bounded-snapshot", start,
new IrisStructureTerrain().setMode(IrisStructureTerrainMode.SOURCE))));
assertNotNull(sourceTerrain);
assertEquals(23 * 16 * 16, sourceTerrain.sampledCells());
}
@Test
public void customDimensionKeyUsesRegisteredVanillaDimensionType() {
ResourceKey<Level> customDimension = ResourceKey.create(
Registries.DIMENSION, Identifier.parse("iris:custom_nether"));
assertEquals(Blocks.NETHERRACK.defaultBlockState(),
NativeStructureTerrainIntegrator.defaultEncaseBlock(
customDimension, BuiltinDimensionTypes.NETHER, null, 64));
assertEquals(Blocks.END_STONE.defaultBlockState(),
NativeStructureTerrainIntegrator.defaultEncaseBlock(
customDimension, BuiltinDimensionTypes.END, null, 64));
}
@Test
public void configuredEncasePaletteReplacesTheDefaultMaterial() {
StructureStart start = start(TerrainAdjustment.BURY, 64);
@@ -129,58 +234,170 @@ public class NativeStructurePostProcessorEncaseTest {
}
@Test
public void buryAndEncapsulateAdaptationsAutoDefaultToEncase() {
for (TerrainAdjustment adjustment : List.of(TerrainAdjustment.BURY, TerrainAdjustment.ENCAPSULATE)) {
public void omittedTerrainConfigurationRetainsSourceSemantics() {
for (TerrainAdjustment adjustment : TerrainAdjustment.values()) {
IrisStructureTerrain resolved = NativeStructureTerrainIntegrator.resolveNativeTerrain(
start(adjustment, 64), null);
assertEquals(IrisStructureTerrainMode.ENCASE, resolved.resolvedMode());
assertEquals(3, resolved.getHorizontalPadding());
assertEquals(3, resolved.getCeilingPadding());
assertEquals(3, resolved.getFloorPadding());
assertNull(resolved.getEncasePalette());
assertEquals(IrisStructureTerrainMode.SOURCE, resolved.resolvedMode());
}
}
@Test
public void otherAdaptationsNeverAutoDefaultToEncase() {
for (TerrainAdjustment adjustment : List.of(
TerrainAdjustment.NONE, TerrainAdjustment.BEARD_THIN, TerrainAdjustment.BEARD_BOX)) {
assertNull(NativeStructureTerrainIntegrator.resolveNativeTerrain(
start(adjustment, 64), null));
}
public void sourceBuryUsesTheVanillaSixByTwelveInfluenceEnvelope() {
StructureStart start = start(TerrainAdjustment.BURY, 64);
BoundingBox bounds = start.getPieces().getFirst().getBoundingBox();
int groundY = bounds.minY();
BoundingBox area = new BoundingBox(
bounds.minX() - 7, groundY - 13, bounds.minZ(),
bounds.maxX(), groundY + 13, bounds.maxZ());
Map<BlockPos, BlockState> blocks = new HashMap<>();
NativeStructureTerrainIntegrator.integrateTerrain(
world(blocks), area, "test:bury", start,
new IrisStructureTerrain().setMode(IrisStructureTerrainMode.SOURCE), null);
assertEquals(Blocks.STONE.defaultBlockState(),
state(blocks, bounds.minX() - 5, groundY, bounds.minZ()));
assertEquals(Blocks.STONE.defaultBlockState(),
state(blocks, bounds.minX(), groundY + 11, bounds.minZ()));
assertEquals(Blocks.AIR.defaultBlockState(),
state(blocks, bounds.minX() - 6, groundY, bounds.minZ()));
assertEquals(Blocks.AIR.defaultBlockState(),
state(blocks, bounds.minX(), groundY + 12, bounds.minZ()));
assertEquals(Blocks.AIR.defaultBlockState(),
state(blocks, bounds.minX() - 7, groundY, bounds.minZ()));
assertEquals(Blocks.AIR.defaultBlockState(),
state(blocks, bounds.minX(), groundY + 13, bounds.minZ()));
}
@Test
public void sourceEncapsulateWrapsTheFullPieceBounds() {
StructureStart start = start(TerrainAdjustment.ENCAPSULATE, 64);
BoundingBox bounds = start.getPieces().getFirst().getBoundingBox();
BoundingBox area = new BoundingBox(
bounds.minX() - 13, bounds.minY(), bounds.minZ(),
bounds.maxX(), bounds.maxY() + 13, bounds.maxZ());
Map<BlockPos, BlockState> blocks = new HashMap<>();
NativeStructureTerrainIntegrator.integrateTerrain(
world(blocks), area, "test:encapsulate", start,
new IrisStructureTerrain().setMode(IrisStructureTerrainMode.SOURCE), null);
assertEquals(Blocks.STONE.defaultBlockState(),
state(blocks, bounds.minX() - 11, bounds.minY(), bounds.minZ()));
assertEquals(Blocks.STONE.defaultBlockState(),
state(blocks, bounds.minX(), bounds.maxY() + 11, bounds.minZ()));
assertEquals(Blocks.AIR.defaultBlockState(),
state(blocks, bounds.minX() - 12, bounds.minY(), bounds.minZ()));
assertEquals(Blocks.AIR.defaultBlockState(),
state(blocks, bounds.minX(), bounds.maxY() + 12, bounds.minZ()));
assertEquals(Blocks.AIR.defaultBlockState(),
state(blocks, bounds.minX() - 13, bounds.minY(), bounds.minZ()));
assertEquals(Blocks.AIR.defaultBlockState(),
state(blocks, bounds.minX(), bounds.maxY() + 13, bounds.minZ()));
}
@Test
public void sourceTerrainDensityOnlyUsesRigidPoolPieces() {
PoolElementStructurePiece terrainMatching = poolPiece(StructureTemplatePool.Projection.TERRAIN_MATCHING);
PoolElementStructurePiece rigid = poolPiece(StructureTemplatePool.Projection.RIGID);
assertFalse(NativeStructureTerrainIntegrator.isSourceRigidPiece(terrainMatching));
assertTrue(NativeStructureTerrainIntegrator.isSourceRigidPiece(rigid));
}
@Test
public void sourceJunctionTerrainUsesTheStrictLowerTwelveBlockEnvelope() {
assertTrue(NativeStructureTerrainIntegrator.insideSourceJunctionEnvelope(0, -1, 0));
assertTrue(NativeStructureTerrainIntegrator.insideSourceJunctionEnvelope(0, -11, 0));
assertFalse(NativeStructureTerrainIntegrator.insideSourceJunctionEnvelope(0, 0, 0));
assertFalse(NativeStructureTerrainIntegrator.insideSourceJunctionEnvelope(0, -12, 0));
assertFalse(NativeStructureTerrainIntegrator.insideSourceJunctionEnvelope(12, -1, 0));
}
@Test
public void explicitTerrainConfigurationWinsOverAutoEncase() {
IrisStructureTerrain configured = new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.SOURCE);
.setMode(IrisStructureTerrainMode.PRESERVE);
assertSame(configured, NativeStructureTerrainIntegrator.resolveNativeTerrain(
start(TerrainAdjustment.BURY, 64), configured));
}
@Test
public void legacyTemplateAirIsClearedAfterEveryTerrainFillPath() {
IrisStructureTerrain source = new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.SOURCE);
IrisStructureTerrain preserve = new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.PRESERVE);
IrisStructureTerrain encase = new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.ENCASE);
assertTrue(NativeStructureTerrainIntegrator.clearsLegacyTemplateAir(
start(TerrainAdjustment.BURY, 64), source));
assertTrue(NativeStructureTerrainIntegrator.clearsLegacyTemplateAir(
start(TerrainAdjustment.BEARD_BOX, 64), source));
assertTrue(NativeStructureTerrainIntegrator.clearsLegacyTemplateAir(
start(TerrainAdjustment.NONE, 64), encase));
assertFalse(NativeStructureTerrainIntegrator.clearsLegacyTemplateAir(
start(TerrainAdjustment.NONE, 64), source));
assertFalse(NativeStructureTerrainIntegrator.clearsLegacyTemplateAir(
start(TerrainAdjustment.BURY, 64), preserve));
}
@Test
public void encaseReservesTheNeighborChunkEnvelope() {
StructureStart generated = start(TerrainAdjustment.BURY, 64);
BoundingBox content = generated.getBoundingBox();
BoundingBox sourceBounds = generated.getBoundingBox();
BoundingBox content = NativeStructureReferenceEnvelope.contentBounds(generated);
IrisStructureTerrain terrain = new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.ENCASE)
.setHorizontalPadding(4);
StructureStart wrapped = NativeStructureReferenceEnvelope.wrap(
generated,
generated.getStructure(),
0,
null,
new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.ENCASE)
.setHorizontalPadding(4));
terrain);
BoundingBox referenceBounds = NativeStructureReferenceEnvelope.referenceBounds(
wrapped, wrapped.getStructure(), terrain);
assertEquals(content.minX() - 4, wrapped.getBoundingBox().minX());
assertEquals(content.maxX() + 4, wrapped.getBoundingBox().maxX());
assertEquals(content.minZ() - 4, wrapped.getBoundingBox().minZ());
assertEquals(content.maxZ() + 4, wrapped.getBoundingBox().maxZ());
assertEquals(2, wrapped.getPieces().stream()
.filter(NativeStructureReferenceEnvelope::isMarker)
.count());
assertEquals(sourceBounds, wrapped.getBoundingBox());
assertEquals(content.minX() - 4, referenceBounds.minX());
assertEquals(content.maxX() + 4, referenceBounds.maxX());
assertEquals(content.minZ() - 4, referenceBounds.minZ());
assertEquals(content.maxZ() + 4, referenceBounds.maxZ());
assertEquals(generated.getPieces().size(), wrapped.getPieces().size());
}
@Test
public void referenceEnvelopeRejectsChunkCoordinateOverflow() {
StructureStart generated = start(TerrainAdjustment.NONE, 64);
StructureStart extreme = new StructureStart(
generated.getStructure(), new ChunkPos(Integer.MAX_VALUE, 0), 0,
new PiecesContainer(generated.getPieces()));
assertThrows(IllegalStateException.class, () ->
NativeStructureReferenceEnvelope.referenceBounds(
extreme, extreme.getStructure(), new IrisStructureTerrain()));
}
@Test
public void explicitTerrainEnvelopeDoesNotInheritSourceAdjustment() {
StructureStart generated = start(TerrainAdjustment.BURY, 64);
BoundingBox content = NativeStructureReferenceEnvelope.contentBounds(generated);
IrisStructureTerrain terrain = new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.ENCASE)
.setHorizontalPadding(112);
StructureStart wrapped = NativeStructureReferenceEnvelope.wrap(
generated, generated.getStructure(), 0, terrain);
BoundingBox referenceBounds = NativeStructureReferenceEnvelope.referenceBounds(
wrapped, wrapped.getStructure(), terrain);
assertEquals(content.minX() - 112, referenceBounds.minX());
assertEquals(content.maxX() + 112, referenceBounds.maxX());
}
@Test
@@ -259,6 +476,24 @@ public class NativeStructurePostProcessorEncaseTest {
structure, new ChunkPos(0, 0), 0, new PiecesContainer(List.of(piece)));
}
private static PoolElementStructurePiece poolPiece(StructureTemplatePool.Projection projection) {
BlockPos position = new BlockPos(0, 64, 0);
StructurePoolElement element = StructurePoolElement.single(
"minecraft:empty",
Holder.direct(new StructureProcessorList(List.of(
BlockIgnoreProcessor.STRUCTURE_AND_AIR))),
LiquidSettings.APPLY_WATERLOGGING
).apply(projection);
return new PoolElementStructurePiece(
null,
element,
position,
0,
Rotation.NONE,
new BoundingBox(position),
LiquidSettings.APPLY_WATERLOGGING);
}
private static WorldGenLevel world(Map<BlockPos, BlockState> blocks) {
InvocationHandler handler = (proxy, method, arguments) -> {
String methodName = method.getName();
@@ -275,6 +510,9 @@ public class NativeStructurePostProcessorEncaseTest {
if (methodName.equals("getSeed")) {
return TEST_SEED;
}
if (methodName.equals("getLevel")) {
return null;
}
if (methodName.equals("hashCode")) {
return System.identityHashCode(proxy);
}
@@ -290,6 +528,14 @@ public class NativeStructurePostProcessorEncaseTest {
WorldGenLevel.class.getClassLoader(), new Class<?>[]{WorldGenLevel.class}, handler);
}
private static NativeStructureTerrainIntegrator.SourceTerrainSnapshot sourceSnapshot(
WorldGenLevel world, BoundingBox area) {
BitSet requiredLayers = new BitSet(area.getYSpan());
requiredLayers.set(0, area.getYSpan());
return NativeStructureTerrainIntegrator.SourceTerrainSnapshot.capture(
world, area, requiredLayers);
}
private static void put(Map<BlockPos, BlockState> blocks,
int x, int y, int z, BlockState state) {
blocks.put(new BlockPos(x, y, z), state);
@@ -1,5 +1,7 @@
package art.arcane.iris.nativegen;
import art.arcane.iris.engine.object.IrisStructureTerrain;
import art.arcane.iris.engine.object.IrisStructureTerrainMode;
import net.minecraft.SharedConstants;
import net.minecraft.core.Direction;
import net.minecraft.core.HolderSet;
@@ -16,7 +18,12 @@ import net.minecraft.world.level.levelgen.structure.structures.OceanMonumentStru
import org.junit.BeforeClass;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
@@ -80,6 +87,25 @@ public class NativeStructurePostProcessorMonumentTest {
assertEquals(51, start.getBoundingBox().maxY());
}
@Test
public void referenceEnvelopeDoesNotAddMonumentPieces() {
StructureStart start = monumentStart(1337L);
StructureStart wrapped = NativeStructureReferenceEnvelope.wrap(
start,
start.getStructure(),
0,
new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.FORCE_CARVE)
.setHorizontalPadding(4));
int offset = NativeStructureVerticalPlacer.applyVerticalPlacement(
wrapped, "minecraft:monument", 0, 63, -64, 320,
false, false, null, (x, z) -> 0);
assertEquals(0, offset);
assertEquals(1, wrapped.getPieces().size());
}
@Test
public void vanillaReloadRegenerationIsRealignedBeforePlacement() {
long seed = 1337L;
@@ -101,6 +127,42 @@ public class NativeStructurePostProcessorMonumentTest {
assertEquals(48, reloaded.getBoundingBox().maxY());
}
@Test
public void neighboringChunkPlacementRealignsReloadedMonumentOnce() throws Exception {
long seed = 1337L;
ChunkPos chunkPos = new ChunkPos(0, 0);
StructureStart initial = monumentStart(seed);
NativeStructureVerticalPlacer.applyVerticalPlacement(
initial, "minecraft:monument", 0, 50, -256, 512,
false, false, null, (x, z) -> 0);
PiecesContainer regenerated = OceanMonumentStructure.regeneratePiecesAfterLoad(
chunkPos, seed, new PiecesContainer(initial.getPieces()));
StructureStart reloaded = new StructureStart(
monumentStructure(), chunkPos, 0, regenerated);
ExecutorService executor = Executors.newFixedThreadPool(8);
CountDownLatch release = new CountDownLatch(1);
List<Future<?>> futures = new ArrayList<>();
try {
for (int task = 0; task < 16; task++) {
futures.add(executor.submit(() -> {
release.await();
NativeStructureVerticalPlacer.ensureMonumentSeaLevelAlignment(
reloaded, "minecraft:monument", 0, 50, -256, 512);
return null;
}));
}
release.countDown();
for (Future<?> future : futures) {
future.get();
}
} finally {
executor.shutdownNow();
}
assertEquals(26, reloaded.getBoundingBox().minY());
assertEquals(48, reloaded.getBoundingBox().maxY());
}
@Test
public void impossibleSeaLevelAlignmentFailsInsteadOfClippingTheMonument() {
StructureStart start = monumentStart(1337L);
@@ -135,6 +135,27 @@ public class NativeStructurePostProcessorScatteredFeatureTest {
assertTrue(field.trySetAccessible());
}
@Test
public void datapackOverridesOfSpecialVanillaIdsUseGenericPlacement() {
StructureStart pyramidOverride = swampStart(
new SwampHutPiece(RandomSource.create(43L), 0, 0));
int pyramidMinY = pyramidOverride.getBoundingBox().minY();
int pyramidOffset = NativeStructureVerticalPlacer.applyVerticalPlacement(
pyramidOverride, "minecraft:desert_pyramid", 5,
63, -64, 320, false, false, null, (x, z) -> 200);
assertEquals(5, pyramidOffset);
assertEquals(pyramidMinY + 5, pyramidOverride.getBoundingBox().minY());
StructureStart monumentOverride = swampStart(
new SwampHutPiece(RandomSource.create(47L), 0, 0));
int monumentMinY = monumentOverride.getBoundingBox().minY();
NativeStructureVerticalPlacer.ensureMonumentSeaLevelAlignment(
monumentOverride, "minecraft:monument", 0, 40, -64, 320);
assertEquals(monumentMinY, monumentOverride.getBoundingBox().minY());
}
private static StructureStart desertStart(StructurePiece piece) {
Structure structure = new DesertPyramidStructure(new Structure.StructureSettings(HolderSet.empty()));
return start(structure, piece);
@@ -5,23 +5,41 @@ import art.arcane.iris.engine.object.IrisStructureCarveShape;
import art.arcane.iris.engine.object.IrisStructureTerrain;
import art.arcane.iris.engine.object.IrisStructureTerrainMode;
import com.mojang.datafixers.util.Either;
import com.mojang.serialization.Codec;
import net.minecraft.SharedConstants;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.core.HolderSet;
import net.minecraft.core.IdMapper;
import net.minecraft.server.Bootstrap;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.LevelHeightAccessor;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.LevelChunkSection;
import net.minecraft.world.level.chunk.PalettedContainer;
import net.minecraft.world.level.chunk.PalettedContainerFactory;
import net.minecraft.world.level.chunk.ProtoChunk;
import net.minecraft.world.level.chunk.Strategy;
import net.minecraft.world.level.chunk.UpgradeData;
import net.minecraft.world.level.levelgen.GenerationStep;
import net.minecraft.world.level.levelgen.structure.BoundingBox;
import net.minecraft.world.level.levelgen.structure.PoolElementStructurePiece;
import net.minecraft.world.level.levelgen.structure.Structure;
import net.minecraft.world.level.levelgen.structure.StructurePiece;
import net.minecraft.world.level.levelgen.structure.StructureStart;
import net.minecraft.world.level.levelgen.structure.TerrainAdjustment;
import net.minecraft.world.level.levelgen.structure.pieces.PiecesContainer;
import net.minecraft.world.level.levelgen.structure.pools.SinglePoolElement;
import net.minecraft.world.level.levelgen.structure.pools.StructurePoolElement;
import net.minecraft.world.level.levelgen.structure.pools.StructureTemplatePool;
import net.minecraft.world.level.levelgen.structure.templatesystem.LiquidSettings;
import net.minecraft.world.level.levelgen.structure.structures.DesertPyramidPiece;
import net.minecraft.world.level.levelgen.structure.structures.DesertPyramidStructure;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructurePlaceSettings;
@@ -35,15 +53,22 @@ import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
public class NativeStructurePostProcessorSurfaceTerrainTest {
@@ -60,34 +85,148 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
}
@Test
public void onlySurfaceBeardThinStructuresPrepareTerrain() {
public void sourceBeardAdjustmentsPrepareTerrainAcrossDeclaredSteps() {
assertTrue(NativeStructureSurfaceFitter.shouldPrepareSurfaceTerrain(
TerrainAdjustment.BEARD_THIN, GenerationStep.Decoration.SURFACE_STRUCTURES));
assertFalse(NativeStructureSurfaceFitter.shouldPrepareSurfaceTerrain(
TerrainAdjustment.BURY, GenerationStep.Decoration.SURFACE_STRUCTURES));
assertFalse(NativeStructureSurfaceFitter.shouldPrepareSurfaceTerrain(
TerrainAdjustment.BEARD_BOX, GenerationStep.Decoration.SURFACE_STRUCTURES));
assertFalse(NativeStructureSurfaceFitter.shouldPrepareSurfaceTerrain(
TerrainAdjustment.ENCAPSULATE, GenerationStep.Decoration.SURFACE_STRUCTURES));
assertTrue(NativeStructureSurfaceFitter.shouldPrepareSurfaceTerrain(
TerrainAdjustment.BEARD_BOX, GenerationStep.Decoration.FLUID_SPRINGS));
assertTrue(NativeStructureSurfaceFitter.shouldPrepareSurfaceTerrain(
TerrainAdjustment.BEARD_THIN, GenerationStep.Decoration.UNDERGROUND_STRUCTURES));
}
@Test
public void undergroundAdjustmentsNeverPrepareSurfaceTerrain() {
for (TerrainAdjustment adjustment : List.of(
TerrainAdjustment.BEARD_THIN,
TerrainAdjustment.BURY,
TerrainAdjustment.BEARD_BOX,
TerrainAdjustment.ENCAPSULATE)) {
assertFalse(NativeStructureSurfaceFitter.shouldPrepareSurfaceTerrain(
adjustment, GenerationStep.Decoration.UNDERGROUND_STRUCTURES));
public void nonBeardAdjustmentsDoNotPrepareSurfaceTerrain() {
assertFalse(NativeStructureSurfaceFitter.shouldPrepareSurfaceTerrain(
TerrainAdjustment.BURY, GenerationStep.Decoration.SURFACE_STRUCTURES));
assertFalse(NativeStructureSurfaceFitter.shouldPrepareSurfaceTerrain(
TerrainAdjustment.ENCAPSULATE, GenerationStep.Decoration.SURFACE_STRUCTURES));
assertFalse(NativeStructureSurfaceFitter.shouldPrepareSurfaceTerrain(
TerrainAdjustment.NONE, GenerationStep.Decoration.FLUID_SPRINGS));
}
@Test
public void explicitTerrainOverrideDisablesSourceBeardFitting() {
StructureStart start = desertStart(TerrainAdjustment.BEARD_BOX);
assertTrue(NativeStructureSurfaceFitter.requiresSurfaceTerrain(
new NativeStructureTerrainIntegrator.TerrainTarget(
"test:beard_box", start,
new IrisStructureTerrain().setMode(IrisStructureTerrainMode.SOURCE))));
assertFalse(NativeStructureSurfaceFitter.requiresSurfaceTerrain(
new NativeStructureTerrainIntegrator.TerrainTarget(
"test:beard_box", start,
new IrisStructureTerrain().setMode(IrisStructureTerrainMode.PRESERVE))));
}
@Test
public void beardBoxUsesTheFullRigidHeightWhileBeardThinUsesTheGroundPlane() {
NativeStructureSurfaceFitter.SurfaceAnchor thin =
new NativeStructureSurfaceFitter.SurfaceAnchor(0, 4, 0, 4, 64, 2);
NativeStructureSurfaceFitter.SurfaceAnchor box =
new NativeStructureSurfaceFitter.SurfaceAnchor(
0, 4, 0, 4, 64, 2, 65, 90);
assertEquals(80, NativeStructureSurfaceFitter.resolveSurfaceTarget(
List.of(thin), 2, 2, 80));
assertEquals(64, NativeStructureSurfaceFitter.resolveSurfaceTarget(
List.of(box), 2, 2, 80));
}
@Test
public void beardInfluenceBeginsAtGroundYRatherThanTheSurfaceBlock() {
BoundingBox bounds = new BoundingBox(0, 64, 0, 4, 90, 4);
NativeStructureSurfaceFitter.SurfaceAnchor thin =
NativeStructureSurfaceFitter.surfaceAnchor(
bounds, 68, 2, TerrainAdjustment.BEARD_THIN);
NativeStructureSurfaceFitter.SurfaceAnchor box =
NativeStructureSurfaceFitter.surfaceAnchor(
bounds, 68, 2, TerrainAdjustment.BEARD_BOX);
assertEquals(67, thin.meetY());
assertEquals(0, thin.verticalDistance(68));
assertEquals(1, thin.verticalDistance(67));
assertEquals(0, box.verticalDistance(68));
assertEquals(1, box.verticalDistance(67));
assertEquals(0, box.verticalDistance(90));
assertEquals(1, box.verticalDistance(91));
}
@Test
public void rigidFootprintsRaiseTerrainAcrossGapsLargerThanTheBeardKernel() {
NativeStructureSurfaceFitter.SurfaceAnchor rigid = anchor(72, 2);
NativeStructureSurfaceFitter.SurfaceAnchor junction = anchor(72, 1);
assertEquals(72, NativeStructureSurfaceFitter.resolveSurfaceTarget(
List.of(rigid), 2, 2, 40));
assertEquals(40, NativeStructureSurfaceFitter.resolveSurfaceTarget(
List.of(junction), 2, 2, 40));
}
@Test
public void elevatedRigidFootprintsBlendIntoAHorizontalRescueTaper() {
int originalY = 40;
int kernelEdge = NativeStructureSurfaceFitter.resolveSurfaceTarget(
List.of(anchor(51, 2)), 5, 2, originalY);
int gapThirteen = NativeStructureSurfaceFitter.resolveSurfaceTarget(
List.of(anchor(52, 2)), 5, 2, originalY);
int gapEighteen = NativeStructureSurfaceFitter.resolveSurfaceTarget(
List.of(anchor(57, 2)), 5, 2, originalY);
int gapTwentyFour = NativeStructureSurfaceFitter.resolveSurfaceTarget(
List.of(anchor(63, 2)), 5, 2, originalY);
assertEquals(originalY, kernelEdge);
assertTrue(gapThirteen >= kernelEdge);
assertTrue(gapEighteen > gapThirteen);
assertTrue(gapTwentyFour > gapEighteen);
assertTrue(gapTwentyFour < 63);
assertEquals(originalY, NativeStructureSurfaceFitter.resolveSurfaceTarget(
List.of(anchor(63, 2)), 16, 2, originalY));
assertEquals(originalY, NativeStructureSurfaceFitter.resolveSurfaceTarget(
List.of(anchor(63, 1)), 5, 2, originalY));
assertEquals(80, NativeStructureSurfaceFitter.resolveSurfaceTarget(
List.of(anchor(64, 2)), 5, 2, 80));
}
@Test
public void projectedCenterAndRigidChildrenShareTerrainSupportAcrossTheAssembly() {
Structure structure = new DesertPyramidStructure(
new Structure.StructureSettings(
HolderSet.empty(), Map.of(),
GenerationStep.Decoration.SURFACE_STRUCTURES,
TerrainAdjustment.BEARD_BOX));
PoolElementStructurePiece center = rigidPiece(
new BoundingBox(0, 71, 0, 3, 80, 3), 1);
PoolElementStructurePiece child = rigidPiece(
new BoundingBox(10, 65, 0, 13, 76, 3), 7);
StructureStart start = new StructureStart(
structure, new ChunkPos(0, 0), 0,
new PiecesContainer(List.of(center, child)));
BoundingBox area = new BoundingBox(0, 60, 0, 15, 90, 15);
Map<BlockPos, BlockState> blocks = new HashMap<>();
for (int x = area.minX(); x <= area.maxX(); x++) {
for (int z = area.minZ(); z <= area.maxZ(); z++) {
put(blocks, x, 63, z, Blocks.DIRT.defaultBlockState());
put(blocks, x, 64, z, Blocks.GRASS_BLOCK.defaultBlockState());
}
}
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world(blocks), area,
List.of(new NativeStructureTerrainIntegrator.TerrainTarget(
"nova_structures:tavern_oak", start,
new IrisStructureTerrain().setMode(IrisStructureTerrainMode.SOURCE))),
(x, z) -> 64);
assertEquals(Blocks.GRASS_BLOCK.defaultBlockState(), state(blocks, 1, 71, 1));
assertEquals(Blocks.GRASS_BLOCK.defaultBlockState(), state(blocks, 11, 71, 1));
assertEquals(Blocks.DIRT.defaultBlockState(), state(blocks, 11, 70, 1));
}
@Test
public void surfaceAnchorIsFlushInsideAndUnchangedAtRadius() {
NativeStructureSurfaceFitter.SurfaceAnchor anchor = anchor(80, 2);
NativeStructureSurfaceFitter.SurfaceAnchor anchor = anchor(72, 2);
assertEquals(80, NativeStructureSurfaceFitter.resolveSurfaceTarget(
assertEquals(72, NativeStructureSurfaceFitter.resolveSurfaceTarget(
List.of(anchor), 2, 2, 64));
assertEquals(64, NativeStructureSurfaceFitter.resolveSurfaceTarget(
List.of(anchor), 16, 2, 64));
@@ -97,54 +236,88 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
@Test
public void surfaceAnchorRaisesAndLowersThroughTheTaper() {
NativeStructureSurfaceFitter.SurfaceAnchor raised = anchor(80, 2);
NativeStructureSurfaceFitter.SurfaceAnchor raised = anchor(68, 2);
NativeStructureSurfaceFitter.SurfaceAnchor lowered = anchor(64, 2);
assertEquals(68, NativeStructureSurfaceFitter.resolveSurfaceTarget(
assertEquals(64, NativeStructureSurfaceFitter.resolveSurfaceTarget(
List.of(raised), 10, 2, 64));
assertEquals(76, NativeStructureSurfaceFitter.resolveSurfaceTarget(
List.of(lowered), 10, 2, 80));
assertEquals(67, NativeStructureSurfaceFitter.resolveSurfaceTarget(
List.of(lowered), 10, 2, 68));
}
@Test
public void containingRigidFloorsHaveDeterministicPriority() {
NativeStructureSurfaceFitter.SurfaceAnchor rigid = anchor(70, 2);
NativeStructureSurfaceFitter.SurfaceAnchor junction = anchor(90, 1);
NativeStructureSurfaceFitter.SurfaceAnchor junction = anchor(74, 1);
assertEquals(70, NativeStructureSurfaceFitter.resolveSurfaceTarget(
List.of(rigid, junction), 2, 2, 64));
assertEquals(70, NativeStructureSurfaceFitter.resolveSurfaceTarget(
List.of(junction, rigid), 2, 2, 64));
NativeStructureSurfaceFitter.SurfaceAnchor weakTie = anchor(48, 1);
NativeStructureSurfaceFitter.SurfaceAnchor strongTie = anchor(80, 2);
assertEquals(80, NativeStructureSurfaceFitter.resolveSurfaceTarget(
NativeStructureSurfaceFitter.SurfaceAnchor weakTie = anchor(58, 1);
NativeStructureSurfaceFitter.SurfaceAnchor strongTie = anchor(70, 2);
assertEquals(70, NativeStructureSurfaceFitter.resolveSurfaceTarget(
List.of(weakTie, strongTie), 2, 2, 64));
assertEquals(80, NativeStructureSurfaceFitter.resolveSurfaceTarget(
assertEquals(70, NativeStructureSurfaceFitter.resolveSurfaceTarget(
List.of(strongTie, weakTie), 2, 2, 64));
}
@Test
public void stackedRigidPiecesPreserveTheLowerAuthoredSurface() {
Structure structure = new DesertPyramidStructure(
new Structure.StructureSettings(
HolderSet.empty(), Map.of(),
GenerationStep.Decoration.SURFACE_STRUCTURES,
TerrainAdjustment.BEARD_BOX));
PoolElementStructurePiece lower = rigidPiece(
new BoundingBox(0, 62, 0, 4, 65, 4), 1);
PoolElementStructurePiece upper = rigidPiece(
new BoundingBox(0, 66, 0, 4, 78, 4), 1);
StructureStart start = new StructureStart(
structure, new ChunkPos(0, 0), 0,
new PiecesContainer(List.of(upper, lower)));
BoundingBox area = new BoundingBox(0, 58, 0, 4, 78, 4);
Map<BlockPos, BlockState> blocks = new HashMap<>();
for (int x = area.minX(); x <= area.maxX(); x++) {
for (int z = area.minZ(); z <= area.maxZ(); z++) {
put(blocks, x, 59, z, Blocks.DIRT.defaultBlockState());
put(blocks, x, 60, z, Blocks.GRASS_BLOCK.defaultBlockState());
}
}
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world(blocks), area,
List.of(new NativeStructureTerrainIntegrator.TerrainTarget(
"nova_structures:tavern_oak", start,
new IrisStructureTerrain().setMode(IrisStructureTerrainMode.SOURCE))),
(x, z) -> 60);
assertEquals(Blocks.GRASS_BLOCK.defaultBlockState(), state(blocks, 2, 62, 2));
assertEquals(Blocks.AIR.defaultBlockState(), state(blocks, 2, 65, 2));
}
@Test
public void containingFootprintOverridesAnAdjacentPiecesFalloff() {
NativeStructureSurfaceFitter.SurfaceAnchor local =
new NativeStructureSurfaceFitter.SurfaceAnchor(0, 4, 0, 4, 65, 2);
new NativeStructureSurfaceFitter.SurfaceAnchor(0, 4, 0, 4, 66, 2);
NativeStructureSurfaceFitter.SurfaceAnchor adjacent =
new NativeStructureSurfaceFitter.SurfaceAnchor(5, 9, 0, 4, 80, 2);
new NativeStructureSurfaceFitter.SurfaceAnchor(5, 9, 0, 4, 70, 2);
assertEquals(77, NativeStructureSurfaceFitter.resolveSurfaceTarget(
assertEquals(65, NativeStructureSurfaceFitter.resolveSurfaceTarget(
List.of(adjacent), 4, 2, 64));
assertEquals(65, NativeStructureSurfaceFitter.resolveSurfaceTarget(
assertEquals(66, NativeStructureSurfaceFitter.resolveSurfaceTarget(
List.of(local, adjacent), 4, 2, 64));
assertEquals(65, NativeStructureSurfaceFitter.resolveSurfaceTarget(
assertEquals(66, NativeStructureSurfaceFitter.resolveSurfaceTarget(
List.of(adjacent, local), 4, 2, 64));
}
@Test
public void opposingFalloffsBlendWithoutAnAbruptMidpointSeam() {
NativeStructureSurfaceFitter.SurfaceAnchor high =
new NativeStructureSurfaceFitter.SurfaceAnchor(0, 0, 0, 0, 80, 2);
new NativeStructureSurfaceFitter.SurfaceAnchor(0, 0, 0, 0, 70, 2);
NativeStructureSurfaceFitter.SurfaceAnchor low =
new NativeStructureSurfaceFitter.SurfaceAnchor(12, 12, 0, 0, 48, 2);
new NativeStructureSurfaceFitter.SurfaceAnchor(12, 12, 0, 0, 58, 2);
int previous = NativeStructureSurfaceFitter.resolveSurfaceTarget(
List.of(high, low), 0, 0, 64);
@@ -154,7 +327,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
int reversed = NativeStructureSurfaceFitter.resolveSurfaceTarget(
List.of(low, high), x, 0, 64);
assertEquals(forward, reversed);
assertTrue(Math.abs(forward - previous) <= 4);
assertTrue(Math.abs(forward - previous) <= 6);
previous = forward;
}
assertEquals(64, NativeStructureSurfaceFitter.resolveSurfaceTarget(
@@ -195,6 +368,44 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
assertEquals(Blocks.GRASS_BLOCK.defaultBlockState(), state(raised, 0, 68, 0));
}
@Test
public void rigidSurfaceBaseClosesAOneBlockSubsurfaceGap() {
Map<BlockPos, BlockState> blocks = new HashMap<>();
put(blocks, 0, 62, 0, Blocks.STONE.defaultBlockState());
put(blocks, 0, 64, 0, Blocks.GRASS_BLOCK.defaultBlockState());
NativeStructureSurfaceFitter.applySurfaceColumn(
world(blocks), new BlockPos.MutableBlockPos(),
0, 0, 64, 64, -64, 319, true);
assertEquals(Blocks.STONE.defaultBlockState(), state(blocks, 0, 63, 0));
assertEquals(Blocks.GRASS_BLOCK.defaultBlockState(), state(blocks, 0, 64, 0));
}
@Test
public void rigidSurfaceBaseReconstructsAMissingMeetLayer() {
Map<BlockPos, BlockState> blocks = new HashMap<>();
put(blocks, 0, 62, 0, Blocks.STONE.defaultBlockState());
NativeStructureSurfaceFitter.applySurfaceColumn(
world(blocks), new BlockPos.MutableBlockPos(),
0, 0, 64, 64, -64, 319, true);
assertEquals(Blocks.STONE.defaultBlockState(), state(blocks, 0, 63, 0));
assertEquals(Blocks.STONE.defaultBlockState(), state(blocks, 0, 64, 0));
}
@Test
public void rigidSurfaceBaseReconstructsAMissingMeetLayerAtTheWorldFloor() {
Map<BlockPos, BlockState> blocks = new HashMap<>();
NativeStructureSurfaceFitter.applySurfaceColumn(
world(blocks), new BlockPos.MutableBlockPos(),
0, 0, -64, -64, -64, 319, true);
assertEquals(Blocks.STONE.defaultBlockState(), state(blocks, 0, -64, 0));
}
@Test
public void raisedSurfaceTerrainDoesNotSliceTreeBlocks() {
Map<BlockPos, BlockState> blocks = new HashMap<>();
@@ -213,6 +424,31 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
assertEquals(leaves, state(blocks, 0, 68, 0));
}
@Test
public void clearedIntersectingTreeCellsBecomeRaisedTerrainSupport() {
ProtoChunk chunk = new ProtoChunk(
new ChunkPos(0, 0), UpgradeData.EMPTY,
LevelHeightAccessor.create(-64, 384), containerFactory(), null);
write(chunk, 0, 63, 0, Blocks.DIRT.defaultBlockState());
write(chunk, 0, 64, 0, Blocks.GRASS_BLOCK.defaultBlockState());
write(chunk, 0, 66, 0, Blocks.OAK_LOG.defaultBlockState());
write(chunk, 0, 68, 0, Blocks.OAK_LEAVES.defaultBlockState());
WorldGenLevel world = world(chunk);
BoundingBox area = new BoundingBox(0, 63, 0, 0, 68, 0);
NativeStructureVegetationClearer.clearIntersectingVegetation(
world, chunk, area, List.of(
new NativeStructureVegetationClearer.VegetationTarget(desertStart(), false)));
NativeStructureSurfaceFitter.applySurfaceColumn(
world, new BlockPos.MutableBlockPos(),
0, 0, 64, 68, -64, 319);
assertEquals(Blocks.DIRT.defaultBlockState(), chunk.getBlockState(new BlockPos(0, 65, 0)));
assertEquals(Blocks.DIRT.defaultBlockState(), chunk.getBlockState(new BlockPos(0, 66, 0)));
assertEquals(Blocks.DIRT.defaultBlockState(), chunk.getBlockState(new BlockPos(0, 67, 0)));
assertEquals(Blocks.GRASS_BLOCK.defaultBlockState(), chunk.getBlockState(new BlockPos(0, 68, 0)));
}
@Test
public void loweredFluidColumnsRemainFluidFilled() {
Map<BlockPos, BlockState> blocks = new HashMap<>();
@@ -437,6 +673,47 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
assertNotSame(first, other);
}
@Test
public void concurrentCarveRequestsShareOneFootprintBuild() throws Exception {
StructureStart start = desertStart();
int requestCount = 16;
ExecutorService executor = Executors.newFixedThreadPool(requestCount);
CountDownLatch ready = new CountDownLatch(requestCount);
CountDownLatch release = new CountDownLatch(1);
List<Future<StructureCarvingFootprint>> futures = new ArrayList<>();
try {
for (int request = 0; request < requestCount; request++) {
futures.add(executor.submit(() -> {
ready.countDown();
release.await();
return NativeStructureTerrainIntegrator.carveFootprint(
start, 11,
NativeStructurePostProcessorSurfaceTerrainTest::forbiddenTemplateManager);
}));
}
assertTrue(ready.await(5, TimeUnit.SECONDS));
release.countDown();
StructureCarvingFootprint expected = futures.getFirst().get();
for (Future<StructureCarvingFootprint> future : futures) {
assertSame(expected, future.get());
}
} finally {
executor.shutdownNow();
}
}
@Test
public void carveFootprintCacheStaysInsideItsCellBudget() {
for (int index = 0; index < 40; index++) {
NativeStructureTerrainIntegrator.carveFootprint(
desertStart(), 128,
NativeStructurePostProcessorSurfaceTerrainTest::forbiddenTemplateManager);
}
assertTrue(NativeStructureTerrainIntegrator.cachedCarveFootprintCells()
<= NativeStructureTerrainIntegrator.maximumCachedCarveFootprintCells());
}
@Test
public void organicCarveNeverCutsBelowTheColumnSupportingFloor() {
StructureStart start = desertStart();
@@ -594,43 +871,48 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
public void nativeTerrainEnvelopePersistsHorizontalReferenceCoverage() {
StructureStart generated = desertStart();
BoundingBox content = generated.getBoundingBox();
IrisStructureTerrain terrain = new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.FORCE_CARVE)
.setHorizontalPadding(24);
StructureStart wrapped = NativeStructureReferenceEnvelope.wrap(
generated,
generated.getStructure(),
0,
null,
new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.FORCE_CARVE)
.setHorizontalPadding(24));
terrain);
BoundingBox referenceBounds = NativeStructureReferenceEnvelope.referenceBounds(
wrapped, wrapped.getStructure(), terrain);
assertEquals(content, NativeStructureReferenceEnvelope.contentBounds(wrapped));
assertEquals(content.minX() - 24, wrapped.getBoundingBox().minX());
assertEquals(content.minZ() - 24, wrapped.getBoundingBox().minZ());
assertEquals(content.maxX() + 24, wrapped.getBoundingBox().maxX());
assertEquals(content.maxZ() + 24, wrapped.getBoundingBox().maxZ());
assertEquals(2, wrapped.getPieces().stream()
.filter(NativeStructureReferenceEnvelope::isMarker)
.count());
assertEquals(content, wrapped.getBoundingBox());
assertEquals(content.minX() - 24, referenceBounds.minX());
assertEquals(content.minZ() - 24, referenceBounds.minZ());
assertEquals(content.maxX() + 24, referenceBounds.maxX());
assertEquals(content.maxZ() + 24, referenceBounds.maxZ());
assertEquals(generated.getPieces().size(), wrapped.getPieces().size());
}
@Test
public void nativeTerrainEnvelopeClipsToMinecraftReferenceCoverage() {
public void nativeTerrainEnvelopeClipsOptionalCoverageWithoutDroppingContent() {
StructureStart generated = desertStart();
ChunkPos origin = generated.getChunkPos();
BoundingBox content = NativeStructureReferenceEnvelope.contentBounds(generated);
IrisStructureTerrain terrain = new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.FORCE_CARVE)
.setHorizontalPadding(124);
StructureStart wrapped = NativeStructureReferenceEnvelope.wrap(
generated,
generated.getStructure(),
0,
null,
new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.FORCE_CARVE)
.setHorizontalPadding(128));
BoundingBox references = NativeStructureReferenceEnvelope.referenceBounds(
generated, generated.getStructure(), terrain, "test:clipped_terrain");
StructureStart published = NativeStructureReferenceEnvelope.wrapForPublication(
generated, generated.getStructure(), 0, terrain, "test:clipped_terrain");
assertEquals(-128, wrapped.getBoundingBox().minX());
assertEquals(-128, wrapped.getBoundingBox().minZ());
assertEquals(143, wrapped.getBoundingBox().maxX());
assertEquals(143, wrapped.getBoundingBox().maxZ());
assertEquals(Math.max(content.minX() - 124, (origin.x() - 8) << 4), references.minX());
assertEquals(Math.min(content.maxX() + 124, ((origin.x() + 8) << 4) + 15), references.maxX());
assertEquals(Math.max(content.minZ() - 124, (origin.z() - 8) << 4), references.minZ());
assertEquals(Math.min(content.maxZ() + 124, ((origin.z() + 8) << 4) + 15), references.maxZ());
assertTrue(references.isInside(content.getCenter()));
assertTrue(published.isValid());
assertEquals(1, generated.getPieces().size());
}
@Test
@@ -726,6 +1008,16 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
return new NativeStructureSurfaceFitter.SurfaceAnchor(0, 4, 0, 4, meetY, strength);
}
private static PoolElementStructurePiece rigidPiece(BoundingBox bounds, int groundLevelDelta) {
StructurePoolElement element = StructurePoolElement.single(
"minecraft:empty").apply(StructureTemplatePool.Projection.RIGID);
return new PoolElementStructurePiece(
null, element,
new BlockPos(bounds.minX(), bounds.minY(), bounds.minZ()),
groundLevelDelta, Rotation.NONE, bounds,
LiquidSettings.APPLY_WATERLOGGING);
}
private static NativeStructureTerrainIntegrator.OrganicCarve organicCarve(
StructureStart start, int horizontalPadding) {
return organicCarve(start, horizontalPadding, 0.85D);
@@ -831,8 +1123,14 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
}
private static StructureStart desertStart() {
return desertStart(TerrainAdjustment.NONE);
}
private static StructureStart desertStart(TerrainAdjustment adjustment) {
Structure structure = new DesertPyramidStructure(
new Structure.StructureSettings(HolderSet.empty()));
new Structure.StructureSettings(
HolderSet.empty(), Map.of(),
GenerationStep.Decoration.SURFACE_STRUCTURES, adjustment));
DesertPyramidPiece piece = new DesertPyramidPiece(RandomSource.create(7L), 0, 0);
return new StructureStart(
structure, new ChunkPos(0, 0), 0, new PiecesContainer(List.of(piece)));
@@ -854,6 +1152,9 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
if (methodName.equals("getSeed")) {
return TEST_SEED;
}
if (methodName.equals("getLevel")) {
return null;
}
if (methodName.equals("hashCode")) {
return System.identityHashCode(proxy);
}
@@ -869,6 +1170,62 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
WorldGenLevel.class.getClassLoader(), new Class<?>[]{WorldGenLevel.class}, handler);
}
private static WorldGenLevel world(ChunkAccess chunk) {
InvocationHandler handler = (proxy, method, arguments) -> {
String methodName = method.getName();
if (methodName.equals("getBlockState")) {
return chunk.getBlockState((BlockPos) arguments[0]);
}
if (methodName.equals("setBlock")) {
return chunk.setBlockState(
((BlockPos) arguments[0]).immutable(),
(BlockState) arguments[1], (int) arguments[2]) != null;
}
if (methodName.equals("getSeed")) {
return TEST_SEED;
}
if (methodName.equals("getLevel")) {
return null;
}
if (methodName.equals("hashCode")) {
return System.identityHashCode(proxy);
}
if (methodName.equals("equals")) {
return proxy == arguments[0];
}
if (methodName.equals("toString")) {
return "surface-test-chunk-world";
}
throw new UnsupportedOperationException(method.toString());
};
return (WorldGenLevel) Proxy.newProxyInstance(
WorldGenLevel.class.getClassLoader(), new Class<?>[]{WorldGenLevel.class}, handler);
}
private static void write(ProtoChunk chunk, int x, int y, int z, BlockState state) {
LevelChunkSection section = chunk.getSection(chunk.getSectionIndex(y));
section.setBlockState(x, y & 15, z, state, false);
}
private static PalettedContainerFactory containerFactory() {
Strategy<BlockState> blockStates = Strategy.createForBlockStates(Block.BLOCK_STATE_REGISTRY);
BlockState air = Blocks.AIR.defaultBlockState();
IdMapper<Holder<Biome>> biomeIds = new IdMapper<>();
Holder<Biome> defaultBiome = Holder.direct((Biome) null);
biomeIds.add(defaultBiome);
Strategy<Holder<Biome>> biomes = Strategy.createForBiomes(biomeIds);
Codec<Holder<Biome>> biomeCodec = Codec.STRING.xmap(
name -> defaultBiome, holder -> "iris-test-biome");
return new PalettedContainerFactory(
blockStates,
air,
PalettedContainer.codecRW(BlockState.CODEC, blockStates, air),
biomes,
defaultBiome,
PalettedContainer.codecRO(biomeCodec, biomes, defaultBiome),
PalettedContainer.codecRW(biomeCodec, biomes, defaultBiome));
}
private static void put(Map<BlockPos, BlockState> blocks,
int x, int y, int z, BlockState state) {
blocks.put(new BlockPos(x, y, z), state);
@@ -1,14 +1,33 @@
package art.arcane.iris.nativegen;
import net.minecraft.SharedConstants;
import net.minecraft.core.HolderSet;
import net.minecraft.server.Bootstrap;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.levelgen.GenerationStep;
import net.minecraft.world.level.levelgen.structure.BoundingBox;
import net.minecraft.world.level.levelgen.structure.Structure;
import net.minecraft.world.level.levelgen.structure.StructureStart;
import net.minecraft.world.level.levelgen.structure.pieces.PiecesContainer;
import net.minecraft.world.level.levelgen.structure.structures.DesertPyramidPiece;
import net.minecraft.world.level.levelgen.structure.structures.DesertPyramidStructure;
import org.junit.BeforeClass;
import org.junit.Test;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class NativeStructurePostProcessorVegetationTest {
@BeforeClass
public static void bootstrapMinecraftRegistries() {
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();
}
@Test
public void surfaceStructuresClearTreeColumnsAutomatically() {
assertTrue(NativeStructureVegetationClearer.shouldClearVegetationColumn(100, 100, false));
@@ -26,6 +45,22 @@ public class NativeStructurePostProcessorVegetationTest {
assertTrue(NativeStructureVegetationClearer.shouldClearVegetationColumn(20, 100, true));
}
@Test
public void nonForcedValidTargetsReachIntersectionProcessing() {
Structure structure = new DesertPyramidStructure(
new Structure.StructureSettings(HolderSet.empty()));
StructureStart start = new StructureStart(
structure, new ChunkPos(0, 0), 0,
new PiecesContainer(List.of(
new DesertPyramidPiece(RandomSource.create(1L), 0, 0))));
assertTrue(NativeStructureVegetationClearer.shouldProcessTarget(
new NativeStructureVegetationClearer.VegetationTarget(start, false)));
assertFalse(NativeStructureVegetationClearer.shouldProcessTarget(
new NativeStructureVegetationClearer.VegetationTarget(
StructureStart.INVALID_START, true)));
}
@Test
public void surfaceStructuresPreserveVegetationUnlessConfigured() {
assertFalse(NativeStructureVegetationClearer.shouldClearEntireVegetationFootprint(