speed pass

This commit is contained in:
Brian Neumann-Fopiano
2026-02-23 19:04:19 -05:00
parent 18d4dce1db
commit bf207b7062
34 changed files with 2895 additions and 450 deletions
@@ -0,0 +1,93 @@
package art.arcane.iris.core;
import art.arcane.volmlib.util.nbt.io.NBTDeserializer;
import art.arcane.volmlib.util.nbt.io.NBTSerializer;
import art.arcane.volmlib.util.nbt.io.NamedTag;
import art.arcane.volmlib.util.nbt.tag.CompoundTag;
import art.arcane.volmlib.util.nbt.tag.IntTag;
import art.arcane.volmlib.util.nbt.tag.ListTag;
import art.arcane.volmlib.util.nbt.tag.Tag;
import org.junit.Test;
import java.io.ByteArrayInputStream;
import java.util.HashMap;
import java.util.Map;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
public class ExternalDataPackPipelineNbtRewriteTest {
@Test
public void rewritesOnlyJigsawPoolReferencesForCompressedAndUncompressedNbt() throws Exception {
for (boolean compressed : new boolean[]{false, true}) {
byte[] source = encodeStructureNbt(compressed, true);
Map<String, String> remapped = new HashMap<>();
remapped.put("minecraft:witch_hut/foundation", "iris_external_1:witch_hut/foundation");
byte[] rewritten = invokeRewrite(source, remapped);
CompoundTag root = decodeRoot(rewritten, compressed);
ListTag<?> blocks = root.getListTag("blocks");
CompoundTag jigsawBlock = (CompoundTag) blocks.get(0);
CompoundTag nonJigsawBlock = (CompoundTag) blocks.get(1);
assertEquals("iris_external_1:witch_hut/foundation", jigsawBlock.getCompoundTag("nbt").getString("pool"));
assertEquals("minecraft:witch_hut/foundation", nonJigsawBlock.getCompoundTag("nbt").getString("pool"));
}
}
@Test
public void nonJigsawPayloadIsLeftUnchanged() throws Exception {
byte[] source = encodeStructureNbt(false, false);
Map<String, String> remapped = new HashMap<>();
remapped.put("minecraft:witch_hut/foundation", "iris_external_1:witch_hut/foundation");
byte[] rewritten = invokeRewrite(source, remapped);
assertArrayEquals(source, rewritten);
}
private byte[] invokeRewrite(byte[] input, Map<String, String> remappedKeys) {
return StructureNbtJigsawPoolRewriter.rewrite(input, remappedKeys);
}
private byte[] encodeStructureNbt(boolean compressed, boolean includeJigsaw) throws Exception {
CompoundTag root = new CompoundTag();
ListTag<CompoundTag> palette = new ListTag<>(CompoundTag.class);
CompoundTag firstPalette = new CompoundTag();
firstPalette.putString("Name", includeJigsaw ? "minecraft:jigsaw" : "minecraft:stone");
palette.add(firstPalette);
CompoundTag secondPalette = new CompoundTag();
secondPalette.putString("Name", "minecraft:stone");
palette.add(secondPalette);
root.put("palette", palette);
ListTag<CompoundTag> blocks = new ListTag<>(CompoundTag.class);
blocks.add(blockTag(0, "minecraft:witch_hut/foundation"));
blocks.add(blockTag(1, "minecraft:witch_hut/foundation"));
root.put("blocks", blocks);
NamedTag named = new NamedTag("test", root);
return new NBTSerializer(compressed).toBytes(named);
}
private CompoundTag blockTag(int state, String pool) {
CompoundTag block = new CompoundTag();
block.putInt("state", state);
CompoundTag nbt = new CompoundTag();
nbt.putString("pool", pool);
block.put("nbt", nbt);
ListTag<IntTag> pos = new ListTag<>(IntTag.class);
pos.add(new IntTag(0));
pos.add(new IntTag(0));
pos.add(new IntTag(0));
block.put("pos", pos);
return block;
}
private CompoundTag decodeRoot(byte[] bytes, boolean compressed) throws Exception {
NamedTag namedTag = new NBTDeserializer(compressed).fromStream(new ByteArrayInputStream(bytes));
Tag<?> rootTag = namedTag.getTag();
return (CompoundTag) rootTag;
}
}
@@ -0,0 +1,54 @@
package art.arcane.iris.core.pregenerator;
import art.arcane.volmlib.util.collection.KList;
import art.arcane.volmlib.util.math.Position2;
import org.junit.Test;
import java.util.HashSet;
import java.util.Set;
import static org.junit.Assert.assertEquals;
public class PregenTaskInterleavedTraversalTest {
@Test
public void interleavedTraversalIsDeterministicAndComplete() {
PregenTask task = PregenTask.builder()
.center(new Position2(0, 0))
.radiusX(1024)
.radiusZ(1024)
.build();
KList<Long> baseline = new KList<>();
task.iterateAllChunks((x, z) -> baseline.add(asKey(x, z)));
KList<Long> firstInterleaved = new KList<>();
task.iterateAllChunksInterleaved((regionX, regionZ, chunkX, chunkZ, firstChunkInRegion, lastChunkInRegion) -> {
firstInterleaved.add(asKey(chunkX, chunkZ));
return true;
});
KList<Long> secondInterleaved = new KList<>();
task.iterateAllChunksInterleaved((regionX, regionZ, chunkX, chunkZ, firstChunkInRegion, lastChunkInRegion) -> {
secondInterleaved.add(asKey(chunkX, chunkZ));
return true;
});
assertEquals(baseline.size(), firstInterleaved.size());
assertEquals(firstInterleaved, secondInterleaved);
assertEquals(asSet(baseline), asSet(firstInterleaved));
}
private Set<Long> asSet(KList<Long> values) {
Set<Long> set = new HashSet<>();
for (Long value : values) {
set.add(value);
}
return set;
}
private long asKey(int x, int z) {
long high = (long) x << 32;
long low = z & 0xFFFFFFFFL;
return high | low;
}
}
@@ -0,0 +1,32 @@
package art.arcane.iris.core.pregenerator.methods;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class AsyncPregenMethodConcurrencyCapTest {
@Test
public void paperLikeRecommendedCapTracksWorkerThreads() {
assertEquals(8, AsyncPregenMethod.computePaperLikeRecommendedCap(1));
assertEquals(8, AsyncPregenMethod.computePaperLikeRecommendedCap(4));
assertEquals(24, AsyncPregenMethod.computePaperLikeRecommendedCap(12));
assertEquals(96, AsyncPregenMethod.computePaperLikeRecommendedCap(80));
}
@Test
public void foliaRecommendedCapTracksWorkerThreads() {
assertEquals(64, AsyncPregenMethod.computeFoliaRecommendedCap(1));
assertEquals(64, AsyncPregenMethod.computeFoliaRecommendedCap(12));
assertEquals(80, AsyncPregenMethod.computeFoliaRecommendedCap(20));
assertEquals(192, AsyncPregenMethod.computeFoliaRecommendedCap(80));
}
@Test
public void runtimeCapUsesGlobalCeilingAndWorkerRecommendation() {
assertEquals(80, AsyncPregenMethod.applyRuntimeConcurrencyCap(256, true, 20));
assertEquals(12, AsyncPregenMethod.applyRuntimeConcurrencyCap(12, true, 20));
assertEquals(64, AsyncPregenMethod.applyRuntimeConcurrencyCap(256, true, 8));
assertEquals(16, AsyncPregenMethod.applyRuntimeConcurrencyCap(256, false, 8));
assertEquals(20, AsyncPregenMethod.applyRuntimeConcurrencyCap(20, false, 40));
}
}
@@ -0,0 +1,268 @@
package art.arcane.iris.engine.mantle.components;
import art.arcane.iris.core.loader.IrisData;
import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.engine.framework.EngineMetrics;
import art.arcane.iris.engine.framework.SeedManager;
import art.arcane.iris.engine.mantle.MantleWriter;
import art.arcane.iris.engine.object.IrisCaveProfile;
import art.arcane.iris.engine.object.IrisDimension;
import art.arcane.iris.engine.object.IrisGeneratorStyle;
import art.arcane.iris.engine.object.IrisRange;
import art.arcane.iris.engine.object.IrisStyledRange;
import art.arcane.iris.engine.object.IrisWorld;
import art.arcane.iris.engine.object.NoiseStyle;
import art.arcane.volmlib.util.mantle.runtime.Mantle;
import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
import art.arcane.volmlib.util.matter.Matter;
import art.arcane.volmlib.util.matter.MatterCavern;
import art.arcane.volmlib.util.matter.MatterSlice;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.Server;
import org.bukkit.block.data.BlockData;
import org.junit.BeforeClass;
import org.junit.Test;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
public class IrisCaveCarver3DNearParityTest {
@BeforeClass
public static void setupBukkit() {
if (Bukkit.getServer() != null) {
return;
}
Server server = mock(Server.class);
BlockData emptyBlockData = mock(BlockData.class);
doReturn(Logger.getLogger("IrisTest")).when(server).getLogger();
doReturn("IrisTestServer").when(server).getName();
doReturn("1.0").when(server).getVersion();
doReturn("1.0").when(server).getBukkitVersion();
doReturn(emptyBlockData).when(server).createBlockData(any(Material.class));
doReturn(emptyBlockData).when(server).createBlockData(anyString());
Bukkit.setServer(server);
}
@Test
public void carvedCellDistributionStableAcrossEquivalentCarvers() {
Engine engine = createEngine(128, 92);
IrisCaveCarver3D firstCarver = new IrisCaveCarver3D(engine, createProfile());
WriterCapture firstCapture = createWriterCapture(128);
int firstCarved = firstCarver.carve(firstCapture.writer, 7, -3);
IrisCaveCarver3D secondCarver = new IrisCaveCarver3D(engine, createProfile());
WriterCapture secondCapture = createWriterCapture(128);
int secondCarved = secondCarver.carve(secondCapture.writer, 7, -3);
assertTrue(firstCarved > 0);
assertEquals(firstCarved, secondCarved);
assertEquals(firstCapture.carvedCells, secondCapture.carvedCells);
}
@Test
public void latticePathCarvesChunkEdgesAndRespectsWorldHeightClipping() {
Engine engine = createEngine(48, 46);
IrisCaveCarver3D carver = new IrisCaveCarver3D(engine, createProfile());
WriterCapture capture = createWriterCapture(48);
double[] columnWeights = new double[256];
Arrays.fill(columnWeights, 1D);
int[] precomputedSurfaceHeights = new int[256];
Arrays.fill(precomputedSurfaceHeights, 46);
int carved = carver.carve(capture.writer, 0, 0, columnWeights, 0D, 0D, new IrisRange(0D, 80D), precomputedSurfaceHeights);
assertTrue(carved > 0);
assertTrue(hasX(capture.carvedCells, 14));
assertTrue(hasX(capture.carvedCells, 15));
assertTrue(hasZ(capture.carvedCells, 14));
assertTrue(hasZ(capture.carvedCells, 15));
assertTrue(maxY(capture.carvedCells) <= 47);
assertTrue(minY(capture.carvedCells) >= 0);
}
private Engine createEngine(int worldHeight, int sampledHeight) {
Engine engine = mock(Engine.class);
IrisData data = mock(IrisData.class);
IrisDimension dimension = mock(IrisDimension.class);
SeedManager seedManager = new SeedManager(942_337_445L);
EngineMetrics metrics = new EngineMetrics(16);
IrisWorld world = IrisWorld.builder().minHeight(0).maxHeight(worldHeight).build();
doReturn(data).when(engine).getData();
doReturn(dimension).when(engine).getDimension();
doReturn(seedManager).when(engine).getSeedManager();
doReturn(metrics).when(engine).getMetrics();
doReturn(world).when(engine).getWorld();
doReturn(sampledHeight).when(engine).getHeight(anyInt(), anyInt());
doReturn(18).when(dimension).getCaveLavaHeight();
doReturn(64).when(dimension).getFluidHeight();
return engine;
}
private IrisCaveProfile createProfile() {
IrisCaveProfile profile = new IrisCaveProfile();
profile.setEnabled(true);
profile.setVerticalRange(new IrisRange(0D, 120D));
profile.setVerticalEdgeFade(14);
profile.setVerticalEdgeFadeStrength(0.21D);
profile.setBaseDensityStyle(new IrisGeneratorStyle(NoiseStyle.SIMPLEX).zoomed(0.07D));
profile.setDetailDensityStyle(new IrisGeneratorStyle(NoiseStyle.SIMPLEX).zoomed(0.17D));
profile.setWarpStyle(new IrisGeneratorStyle(NoiseStyle.SIMPLEX).zoomed(0.12D));
profile.setSurfaceBreakStyle(new IrisGeneratorStyle(NoiseStyle.SIMPLEX).zoomed(0.09D));
profile.setBaseWeight(1D);
profile.setDetailWeight(0.48D);
profile.setWarpStrength(0.37D);
profile.setDensityThreshold(new IrisStyledRange(1D, 1D, new IrisGeneratorStyle(NoiseStyle.FLAT)));
profile.setThresholdBias(0D);
profile.setSampleStep(2);
profile.setMinCarveCells(0);
profile.setRecoveryThresholdBoost(0D);
profile.setSurfaceClearance(5);
profile.setAllowSurfaceBreak(true);
profile.setSurfaceBreakNoiseThreshold(0.16D);
profile.setSurfaceBreakDepth(12);
profile.setSurfaceBreakThresholdBoost(0.17D);
profile.setAllowWater(true);
profile.setWaterMinDepthBelowSurface(8);
profile.setWaterRequiresFloor(false);
profile.setAllowLava(true);
return profile;
}
private WriterCapture createWriterCapture(int worldHeight) {
MantleWriter writer = mock(MantleWriter.class);
@SuppressWarnings("unchecked")
Mantle<Matter> mantle = mock(Mantle.class);
@SuppressWarnings("unchecked")
MantleChunk<Matter> chunk = mock(MantleChunk.class);
Map<Integer, Matter> sections = new HashMap<>();
Map<Integer, Map<Integer, MatterCavern>> sectionCells = new HashMap<>();
Set<String> carvedCells = new HashSet<>();
doReturn(mantle).when(writer).getMantle();
doReturn(worldHeight).when(mantle).getWorldHeight();
doReturn(chunk).when(writer).acquireChunk(anyInt(), anyInt());
doAnswer(invocation -> {
int sectionIndex = invocation.getArgument(0);
Matter section = sections.get(sectionIndex);
if (section != null) {
return section;
}
Matter created = createSection(sectionIndex, sectionCells, carvedCells);
sections.put(sectionIndex, created);
return created;
}).when(chunk).getOrCreate(anyInt());
return new WriterCapture(writer, carvedCells);
}
private Matter createSection(int sectionIndex, Map<Integer, Map<Integer, MatterCavern>> sectionCells, Set<String> carvedCells) {
Matter matter = mock(Matter.class);
@SuppressWarnings("unchecked")
MatterSlice<MatterCavern> slice = mock(MatterSlice.class);
Map<Integer, MatterCavern> localCells = sectionCells.computeIfAbsent(sectionIndex, key -> new HashMap<>());
doReturn(slice).when(matter).slice(MatterCavern.class);
doAnswer(invocation -> {
int localX = invocation.getArgument(0);
int localY = invocation.getArgument(1);
int localZ = invocation.getArgument(2);
return localCells.get(packLocal(localX, localY, localZ));
}).when(slice).get(anyInt(), anyInt(), anyInt());
doAnswer(invocation -> {
int localX = invocation.getArgument(0);
int localY = invocation.getArgument(1);
int localZ = invocation.getArgument(2);
MatterCavern value = invocation.getArgument(3);
localCells.put(packLocal(localX, localY, localZ), value);
int worldY = (sectionIndex << 4) + localY;
carvedCells.add(cellKey(localX, worldY, localZ));
return null;
}).when(slice).set(anyInt(), anyInt(), anyInt(), any(MatterCavern.class));
return matter;
}
private int packLocal(int x, int y, int z) {
return (x << 8) | (y << 4) | z;
}
private String cellKey(int x, int y, int z) {
return x + ":" + y + ":" + z;
}
private boolean hasX(Set<String> carvedCells, int x) {
for (String cell : carvedCells) {
String[] split = cell.split(":");
if (Integer.parseInt(split[0]) == x) {
return true;
}
}
return false;
}
private boolean hasZ(Set<String> carvedCells, int z) {
for (String cell : carvedCells) {
String[] split = cell.split(":");
if (Integer.parseInt(split[2]) == z) {
return true;
}
}
return false;
}
private int maxY(Set<String> carvedCells) {
int max = Integer.MIN_VALUE;
for (String cell : carvedCells) {
String[] split = cell.split(":");
int y = Integer.parseInt(split[1]);
if (y > max) {
max = y;
}
}
return max;
}
private int minY(Set<String> carvedCells) {
int min = Integer.MAX_VALUE;
for (String cell : carvedCells) {
String[] split = cell.split(":");
int y = Integer.parseInt(split[1]);
if (y < min) {
min = y;
}
}
return min;
}
private static final class WriterCapture {
private final MantleWriter writer;
private final Set<String> carvedCells;
private WriterCapture(MantleWriter writer, Set<String> carvedCells) {
this.writer = writer;
this.carvedCells = carvedCells;
}
}
}
@@ -0,0 +1,143 @@
package art.arcane.iris.engine.mantle.components;
import art.arcane.iris.engine.object.IrisCaveProfile;
import org.junit.BeforeClass;
import org.junit.Test;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import static org.junit.Assert.assertEquals;
public class MantleCarvingComponentTop2BlendTest {
private static Constructor<?> weightedProfileConstructor;
private static Method limitMethod;
private static Method expandTileMethod;
private static Field profileField;
private static Field columnWeightsField;
@BeforeClass
public static void setup() throws Exception {
Class<?> weightedProfileClass = Class.forName("art.arcane.iris.engine.mantle.components.MantleCarvingComponent$WeightedProfile");
weightedProfileConstructor = weightedProfileClass.getDeclaredConstructor(IrisCaveProfile.class, double[].class, double.class, Class.forName("art.arcane.iris.engine.object.IrisRange"));
weightedProfileConstructor.setAccessible(true);
limitMethod = MantleCarvingComponent.class.getDeclaredMethod("limitAndMergeBlendedProfiles", List.class, int.class, int.class);
limitMethod.setAccessible(true);
expandTileMethod = MantleCarvingComponent.class.getDeclaredMethod("expandTileWeightsToColumns", double[].class);
expandTileMethod.setAccessible(true);
profileField = weightedProfileClass.getDeclaredField("profile");
profileField.setAccessible(true);
columnWeightsField = weightedProfileClass.getDeclaredField("columnWeights");
columnWeightsField.setAccessible(true);
}
@Test
public void topTwoProfilesAreKeptAndDroppedWeightsAreMergedIntoDominantColumns() throws Exception {
WeightedInput input = createWeightedProfiles();
List<?> limited = invokeLimit(input.weightedProfiles(), 2);
assertEquals(2, limited.size());
Map<IrisCaveProfile, double[]> byProfile = extractWeightsByProfile(limited);
IrisCaveProfile first = input.profiles().first();
IrisCaveProfile second = input.profiles().second();
assertEquals(1.0D, byProfile.get(first)[1], 0D);
assertEquals(1.0D, byProfile.get(second)[0], 0D);
}
@Test
public void topTwoMergeIsDeterministicAcrossRuns() throws Exception {
WeightedInput firstInput = createWeightedProfiles();
WeightedInput secondInput = createWeightedProfiles();
List<?> first = invokeLimit(firstInput.weightedProfiles(), 2);
List<?> second = invokeLimit(secondInput.weightedProfiles(), 2);
Map<IrisCaveProfile, double[]> firstByProfile = extractWeightsByProfile(first);
Map<IrisCaveProfile, double[]> secondByProfile = extractWeightsByProfile(second);
assertEquals(firstByProfile.get(firstInput.profiles().first())[0], secondByProfile.get(secondInput.profiles().first())[0], 0D);
assertEquals(firstByProfile.get(firstInput.profiles().first())[1], secondByProfile.get(secondInput.profiles().first())[1], 0D);
assertEquals(firstByProfile.get(firstInput.profiles().second())[0], secondByProfile.get(secondInput.profiles().second())[0], 0D);
assertEquals(firstByProfile.get(firstInput.profiles().second())[1], secondByProfile.get(secondInput.profiles().second())[1], 0D);
}
@Test
public void tileWeightsExpandIntoFourColumnsPerTile() throws Exception {
double[] tileWeights = new double[64];
tileWeights[0] = 0.42D;
tileWeights[9] = 0.73D;
double[] expanded = invokeExpand(tileWeights);
assertEquals(0.42D, expanded[(0 << 4) | 0], 0D);
assertEquals(0.42D, expanded[(0 << 4) | 1], 0D);
assertEquals(0.42D, expanded[(1 << 4) | 0], 0D);
assertEquals(0.42D, expanded[(1 << 4) | 1], 0D);
assertEquals(0.73D, expanded[(2 << 4) | 2], 0D);
assertEquals(0.73D, expanded[(2 << 4) | 3], 0D);
assertEquals(0.73D, expanded[(3 << 4) | 2], 0D);
assertEquals(0.73D, expanded[(3 << 4) | 3], 0D);
}
private WeightedInput createWeightedProfiles() throws Exception {
IrisCaveProfile first = new IrisCaveProfile().setEnabled(true).setBaseWeight(1.31D);
IrisCaveProfile second = new IrisCaveProfile().setEnabled(true).setBaseWeight(1.17D);
IrisCaveProfile third = new IrisCaveProfile().setEnabled(true).setBaseWeight(0.93D);
Profiles profiles = new Profiles(first, second, third);
double[] firstWeights = new double[64];
firstWeights[0] = 0.2D;
firstWeights[1] = 0.8D;
double[] secondWeights = new double[64];
secondWeights[0] = 0.7D;
secondWeights[1] = 0.1D;
double[] thirdWeights = new double[64];
thirdWeights[0] = 0.3D;
thirdWeights[1] = 0.4D;
List<Object> weighted = new ArrayList<>();
weighted.add(weightedProfileConstructor.newInstance(first, firstWeights, average(firstWeights), null));
weighted.add(weightedProfileConstructor.newInstance(second, secondWeights, average(secondWeights), null));
weighted.add(weightedProfileConstructor.newInstance(third, thirdWeights, average(thirdWeights), null));
return new WeightedInput(weighted, profiles);
}
private List<?> invokeLimit(List<Object> weightedProfiles, int limit) throws Exception {
return (List<?>) limitMethod.invoke(null, weightedProfiles, limit, 64);
}
private double[] invokeExpand(double[] tileWeights) throws Exception {
return (double[]) expandTileMethod.invoke(null, (Object) tileWeights);
}
private Map<IrisCaveProfile, double[]> extractWeightsByProfile(List<?> weightedProfiles) throws Exception {
Map<IrisCaveProfile, double[]> byProfile = new IdentityHashMap<>();
for (Object weightedProfile : weightedProfiles) {
IrisCaveProfile profile = (IrisCaveProfile) profileField.get(weightedProfile);
double[] weights = (double[]) columnWeightsField.get(weightedProfile);
byProfile.put(profile, weights);
}
return byProfile;
}
private double average(double[] weights) {
double total = 0D;
for (double weight : weights) {
total += weight;
}
return total / weights.length;
}
private record Profiles(IrisCaveProfile first, IrisCaveProfile second, IrisCaveProfile third) {
}
private record WeightedInput(List<Object> weightedProfiles, Profiles profiles) {
}
}
@@ -0,0 +1,186 @@
package art.arcane.iris.engine.modifier;
import org.junit.BeforeClass;
import org.junit.Test;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;
import static org.junit.Assert.assertEquals;
public class IrisCarveModifierZoneParityTest {
private static Constructor<?> columnMaskConstructor;
private static Method addMethod;
private static Method nextSetBitMethod;
private static Method clearMethod;
@BeforeClass
public static void setup() throws Exception {
Class<?> columnMaskClass = Class.forName("art.arcane.iris.engine.modifier.IrisCarveModifier$ColumnMask");
columnMaskConstructor = columnMaskClass.getDeclaredConstructor();
addMethod = columnMaskClass.getDeclaredMethod("add", int.class);
nextSetBitMethod = columnMaskClass.getDeclaredMethod("nextSetBit", int.class);
clearMethod = columnMaskClass.getDeclaredMethod("clear");
columnMaskConstructor.setAccessible(true);
addMethod.setAccessible(true);
nextSetBitMethod.setAccessible(true);
clearMethod.setAccessible(true);
}
@Test
public void randomColumnZonesMatchLegacySortedResolver() throws Exception {
Object columnMask = columnMaskConstructor.newInstance();
Random random = new Random(913_447L);
int maxHeight = 320;
for (int scenario = 0; scenario < 400; scenario++) {
clearMethod.invoke(columnMask);
int sampleSize = 1 + random.nextInt(180);
Set<Integer> uniqueHeights = new HashSet<>();
while (uniqueHeights.size() < sampleSize) {
uniqueHeights.add(random.nextInt(480) - 80);
}
int[] heights = toIntArray(uniqueHeights);
for (int index = 0; index < heights.length; index++) {
addMethod.invoke(columnMask, heights[index]);
}
List<String> expectedZones = legacyZones(heights, maxHeight);
List<String> actualZones = bitsetZones(columnMask, maxHeight);
assertEquals("scenario=" + scenario, expectedZones, actualZones);
}
}
@Test
public void edgeColumnsMatchLegacySortedResolver() throws Exception {
Object columnMask = columnMaskConstructor.newInstance();
int maxHeight = 320;
int[][] scenarios = new int[][]{
{-10, -1, 0, 1, 2, 5, 6, 9, 10, 11, 12, 200, 201, 205},
{300, 301, 302, 304, 305, 307, 308, 309, 310, 400, 401},
{0, 2, 4, 6, 8, 10, 12},
{10, 11, 12, 13, 14, 15, 16, 17}
};
for (int scenario = 0; scenario < scenarios.length; scenario++) {
clearMethod.invoke(columnMask);
int[] heights = Arrays.copyOf(scenarios[scenario], scenarios[scenario].length);
for (int index = 0; index < heights.length; index++) {
addMethod.invoke(columnMask, heights[index]);
}
List<String> expectedZones = legacyZones(heights, maxHeight);
List<String> actualZones = bitsetZones(columnMask, maxHeight);
assertEquals("edge-scenario=" + scenario, expectedZones, actualZones);
}
}
private int[] toIntArray(Set<Integer> values) {
int[] array = new int[values.size()];
int index = 0;
for (Integer value : values) {
array[index++] = value;
}
return array;
}
private List<String> legacyZones(int[] heights, int maxHeight) {
List<String> zones = new ArrayList<>();
if (heights.length == 0) {
return zones;
}
int[] sorted = Arrays.copyOf(heights, heights.length);
Arrays.sort(sorted);
int floor = sorted[0];
int ceiling = -1;
int buf = sorted[0] - 1;
for (int index = 0; index < sorted.length; index++) {
int y = sorted[index];
if (y < 0 || y > maxHeight) {
continue;
}
if (y == buf + 1) {
buf = y;
ceiling = buf;
} else if (isValidZone(floor, ceiling, maxHeight)) {
zones.add(zoneKey(floor, ceiling));
floor = y;
ceiling = -1;
buf = y;
} else {
floor = y;
ceiling = -1;
buf = y;
}
}
if (isValidZone(floor, ceiling, maxHeight)) {
zones.add(zoneKey(floor, ceiling));
}
return zones;
}
private List<String> bitsetZones(Object columnMask, int maxHeight) throws Exception {
List<String> zones = new ArrayList<>();
int firstHeight = nextSetBit(columnMask, 0);
if (firstHeight < 0) {
return zones;
}
int floor = firstHeight;
int ceiling = -1;
int buf = firstHeight - 1;
int y = firstHeight;
while (y >= 0) {
if (y >= 0 && y <= maxHeight) {
if (y == buf + 1) {
buf = y;
ceiling = buf;
} else if (isValidZone(floor, ceiling, maxHeight)) {
zones.add(zoneKey(floor, ceiling));
floor = y;
ceiling = -1;
buf = y;
} else {
floor = y;
ceiling = -1;
buf = y;
}
}
y = nextSetBit(columnMask, y + 1);
}
if (isValidZone(floor, ceiling, maxHeight)) {
zones.add(zoneKey(floor, ceiling));
}
return zones;
}
private int nextSetBit(Object columnMask, int fromBit) throws Exception {
return (Integer) nextSetBitMethod.invoke(columnMask, fromBit);
}
private boolean isValidZone(int floor, int ceiling, int maxHeight) {
return floor < ceiling
&& floor >= 0
&& ceiling <= maxHeight
&& ((ceiling - floor) - 1) > 0;
}
private String zoneKey(int floor, int ceiling) {
return floor + ":" + ceiling;
}
}
@@ -103,6 +103,27 @@ public class IrisDimensionCarvingResolverParityTest {
}
}
@Test
public void tileAnchoredChunkPlanResolutionIsStableAcrossRepeatedBuilds() {
Fixture fixture = createMixedDepthFixture();
IrisDimensionCarvingResolver.State state = new IrisDimensionCarvingResolver.State();
IrisDimensionCarvingEntry root = legacyResolveRootEntry(fixture.engine, 80);
for (int chunkX = -24; chunkX <= 24; chunkX += 6) {
for (int chunkZ = -24; chunkZ <= 24; chunkZ += 6) {
IrisDimensionCarvingEntry[] firstPlan = buildTilePlan(fixture.engine, root, chunkX, chunkZ, state);
IrisDimensionCarvingEntry[] secondPlan = buildTilePlan(fixture.engine, root, chunkX, chunkZ, state);
for (int tileIndex = 0; tileIndex < firstPlan.length; tileIndex++) {
assertSame(
"tile plan mismatch at chunkX=" + chunkX + " chunkZ=" + chunkZ + " tileIndex=" + tileIndex,
firstPlan[tileIndex],
secondPlan[tileIndex]
);
}
}
}
}
private Fixture createFixture() {
IrisBiome rootLowBiome = mock(IrisBiome.class);
IrisBiome rootHighBiome = mock(IrisBiome.class);
@@ -251,6 +272,19 @@ public class IrisDimensionCarvingResolverParityTest {
return new Fixture(engine);
}
private IrisDimensionCarvingEntry[] buildTilePlan(Engine engine, IrisDimensionCarvingEntry rootEntry, int chunkX, int chunkZ, IrisDimensionCarvingResolver.State state) {
IrisDimensionCarvingEntry[] plan = new IrisDimensionCarvingEntry[64];
for (int tileX = 0; tileX < 8; tileX++) {
int worldX = (chunkX << 4) + (tileX << 1);
for (int tileZ = 0; tileZ < 8; tileZ++) {
int worldZ = (chunkZ << 4) + (tileZ << 1);
int tileIndex = (tileX * 8) + tileZ;
plan[tileIndex] = IrisDimensionCarvingResolver.resolveFromRoot(engine, rootEntry, worldX, worldZ, state);
}
}
return plan;
}
private IrisDimensionCarvingEntry buildEntry(String id, String biome, IrisRange worldRange, int depth, List<String> children) {
IrisDimensionCarvingEntry entry = new IrisDimensionCarvingEntry();
entry.setId(id);
@@ -0,0 +1,148 @@
package art.arcane.iris.util.project.context;
import art.arcane.iris.engine.IrisComplex;
import art.arcane.iris.engine.object.IrisBiome;
import art.arcane.iris.engine.object.IrisRegion;
import art.arcane.iris.util.project.stream.ProceduralStream;
import org.bukkit.block.data.BlockData;
import org.junit.Test;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.anyDouble;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
public class ChunkContextPrefillPlanTest {
@Test
public void noCavePrefillSkipsCaveCacheFill() {
AtomicInteger caveCalls = new AtomicInteger();
AtomicInteger heightCalls = new AtomicInteger();
AtomicInteger biomeCalls = new AtomicInteger();
AtomicInteger rockCalls = new AtomicInteger();
AtomicInteger fluidCalls = new AtomicInteger();
AtomicInteger regionCalls = new AtomicInteger();
ChunkContext context = createContext(
ChunkContext.PrefillPlan.NO_CAVE,
caveCalls,
heightCalls,
biomeCalls,
rockCalls,
fluidCalls,
regionCalls
);
assertEquals(256, heightCalls.get());
assertEquals(256, biomeCalls.get());
assertEquals(256, rockCalls.get());
assertEquals(256, fluidCalls.get());
assertEquals(256, regionCalls.get());
assertEquals(0, caveCalls.get());
assertEquals(34051D, context.getHeight().get(2, 3), 0D);
context.getCave().get(2, 3);
context.getCave().get(2, 3);
assertEquals(1, caveCalls.get());
}
@Test
public void allPrefillIncludesCaveCacheFill() {
AtomicInteger caveCalls = new AtomicInteger();
AtomicInteger heightCalls = new AtomicInteger();
AtomicInteger biomeCalls = new AtomicInteger();
AtomicInteger rockCalls = new AtomicInteger();
AtomicInteger fluidCalls = new AtomicInteger();
AtomicInteger regionCalls = new AtomicInteger();
ChunkContext context = createContext(
ChunkContext.PrefillPlan.ALL,
caveCalls,
heightCalls,
biomeCalls,
rockCalls,
fluidCalls,
regionCalls
);
assertEquals(256, heightCalls.get());
assertEquals(256, biomeCalls.get());
assertEquals(256, rockCalls.get());
assertEquals(256, fluidCalls.get());
assertEquals(256, regionCalls.get());
assertEquals(256, caveCalls.get());
context.getCave().get(1, 1);
assertEquals(256, caveCalls.get());
}
private ChunkContext createContext(
ChunkContext.PrefillPlan prefillPlan,
AtomicInteger caveCalls,
AtomicInteger heightCalls,
AtomicInteger biomeCalls,
AtomicInteger rockCalls,
AtomicInteger fluidCalls,
AtomicInteger regionCalls
) {
IrisComplex complex = mock(IrisComplex.class);
@SuppressWarnings("unchecked")
ProceduralStream<Double> heightStream = mock(ProceduralStream.class);
doAnswer(invocation -> {
heightCalls.incrementAndGet();
double worldX = invocation.getArgument(0);
double worldZ = invocation.getArgument(1);
return (worldX * 1000D) + worldZ;
}).when(heightStream).get(anyDouble(), anyDouble());
@SuppressWarnings("unchecked")
ProceduralStream<IrisBiome> biomeStream = mock(ProceduralStream.class);
IrisBiome biome = mock(IrisBiome.class);
doAnswer(invocation -> {
biomeCalls.incrementAndGet();
return biome;
}).when(biomeStream).get(anyDouble(), anyDouble());
@SuppressWarnings("unchecked")
ProceduralStream<IrisBiome> caveStream = mock(ProceduralStream.class);
IrisBiome caveBiome = mock(IrisBiome.class);
doAnswer(invocation -> {
caveCalls.incrementAndGet();
return caveBiome;
}).when(caveStream).get(anyDouble(), anyDouble());
@SuppressWarnings("unchecked")
ProceduralStream<BlockData> rockStream = mock(ProceduralStream.class);
BlockData rock = mock(BlockData.class);
doAnswer(invocation -> {
rockCalls.incrementAndGet();
return rock;
}).when(rockStream).get(anyDouble(), anyDouble());
@SuppressWarnings("unchecked")
ProceduralStream<BlockData> fluidStream = mock(ProceduralStream.class);
BlockData fluid = mock(BlockData.class);
doAnswer(invocation -> {
fluidCalls.incrementAndGet();
return fluid;
}).when(fluidStream).get(anyDouble(), anyDouble());
@SuppressWarnings("unchecked")
ProceduralStream<IrisRegion> regionStream = mock(ProceduralStream.class);
IrisRegion region = mock(IrisRegion.class);
doAnswer(invocation -> {
regionCalls.incrementAndGet();
return region;
}).when(regionStream).get(anyDouble(), anyDouble());
doReturn(heightStream).when(complex).getHeightStream();
doReturn(biomeStream).when(complex).getTrueBiomeStream();
doReturn(caveStream).when(complex).getCaveBiomeStream();
doReturn(rockStream).when(complex).getRockStream();
doReturn(fluidStream).when(complex).getFluidStream();
doReturn(regionStream).when(complex).getRegionStream();
return new ChunkContext(32, 48, complex, true, prefillPlan, null);
}
}
@@ -0,0 +1,100 @@
package art.arcane.iris.util.project.noise;
import art.arcane.volmlib.util.math.RNG;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import static org.junit.Assert.assertEquals;
public class CNGFastPathParityTest {
@Test
public void identityFastPathMatchesLegacyAcrossSeedAndCoordinateGrid() {
for (long seed = 3L; seed <= 11L; seed++) {
CNG generator = createIdentityGenerator(seed);
assertFastPathParity("identity-seed-" + seed, generator);
}
}
@Test
public void transformedGeneratorsMatchLegacyAcrossSeedAndCoordinateGrid() {
for (long seed = 21L; seed <= 27L; seed++) {
List<CNG> generators = createTransformedGenerators(seed);
for (int index = 0; index < generators.size(); index++) {
assertFastPathParity("transformed-seed-" + seed + "-case-" + index, generators.get(index));
}
}
}
private void assertFastPathParity(String label, CNG generator) {
for (int x = -320; x <= 320; x += 19) {
for (int z = -320; z <= 320; z += 23) {
double expected = generator.noise(x, z);
double actual = generator.noiseFast2D(x, z);
assertEquals(label + " 2D x=" + x + " z=" + z, expected, actual, 1.0E-12D);
}
}
for (int x = -128; x <= 128; x += 17) {
for (int y = -96; y <= 96; y += 13) {
for (int z = -128; z <= 128; z += 19) {
double expected = generator.noise(x, y, z);
double actual = generator.noiseFast3D(x, y, z);
assertEquals(label + " 3D x=" + x + " y=" + y + " z=" + z, expected, actual, 1.0E-12D);
}
}
}
}
private CNG createIdentityGenerator(long seed) {
DeterministicNoiseGenerator generator = new DeterministicNoiseGenerator(0.31D + (seed * 0.01D));
return new CNG(new RNG(seed), generator, 1D, 1).bake();
}
private List<CNG> createTransformedGenerators(long seed) {
List<CNG> generators = new ArrayList<>();
CNG powerTransformed = createIdentityGenerator(seed).pow(1.27D);
generators.add(powerTransformed);
CNG offsetTransformed = createIdentityGenerator(seed + 1L).up(0.08D).down(0.03D).patch(0.91D);
generators.add(offsetTransformed);
CNG fractured = createIdentityGenerator(seed + 2L).fractureWith(createIdentityGenerator(seed + 300L), 12.5D);
generators.add(fractured);
CNG withChildren = createIdentityGenerator(seed + 3L);
withChildren.child(createIdentityGenerator(seed + 400L));
withChildren.child(createIdentityGenerator(seed + 401L));
generators.add(withChildren);
return generators;
}
private static class DeterministicNoiseGenerator implements NoiseGenerator {
private final double offset;
private DeterministicNoiseGenerator(double offset) {
this.offset = offset;
}
@Override
public double noise(double x) {
double angle = (x * 0.011D) + offset;
return 0.2D + (((Math.sin(angle) + 1D) * 0.5D) * 0.6D);
}
@Override
public double noise(double x, double z) {
double angle = (x * 0.013D) + (z * 0.017D) + offset;
return 0.2D + (((Math.sin(angle) + 1D) * 0.5D) * 0.6D);
}
@Override
public double noise(double x, double y, double z) {
double angle = (x * 0.007D) + (y * 0.015D) + (z * 0.019D) + offset;
return 0.2D + (((Math.sin(angle) + 1D) * 0.5D) * 0.6D);
}
}
}