This commit is contained in:
Brian Neumann-Fopiano
2026-02-22 23:25:01 -05:00
parent 651dfa247e
commit 18d4dce1db
36 changed files with 2109 additions and 253 deletions
@@ -0,0 +1,75 @@
package art.arcane.iris.core;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.Server;
import org.bukkit.block.data.BlockData;
import org.junit.Test;
import java.util.logging.Logger;
import static org.junit.Assert.assertEquals;
import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockingDetails;
public class IrisRuntimeSchedulerModeRoutingTest {
@Test
public void autoResolvesToPaperLikeOnPurpurBranding() {
installServer("Purpur", "git-Purpur-2562 (MC: 1.21.11)");
IrisSettings.IrisSettingsPregen pregen = new IrisSettings.IrisSettingsPregen();
pregen.runtimeSchedulerMode = IrisRuntimeSchedulerMode.AUTO;
IrisRuntimeSchedulerMode resolved = IrisRuntimeSchedulerMode.resolve(pregen);
assertEquals(IrisRuntimeSchedulerMode.PAPER_LIKE, resolved);
}
@Test
public void autoResolvesToFoliaWhenBrandingContainsFolia() {
installServer("Folia", "git-Folia-123 (MC: 1.21.11)");
IrisSettings.IrisSettingsPregen pregen = new IrisSettings.IrisSettingsPregen();
pregen.runtimeSchedulerMode = IrisRuntimeSchedulerMode.AUTO;
IrisRuntimeSchedulerMode resolved = IrisRuntimeSchedulerMode.resolve(pregen);
assertEquals(IrisRuntimeSchedulerMode.FOLIA, resolved);
}
@Test
public void explicitModeBypassesAutoDetection() {
installServer("Purpur", "git-Purpur-2562 (MC: 1.21.11)");
IrisSettings.IrisSettingsPregen pregen = new IrisSettings.IrisSettingsPregen();
pregen.runtimeSchedulerMode = IrisRuntimeSchedulerMode.FOLIA;
IrisRuntimeSchedulerMode foliaResolved = IrisRuntimeSchedulerMode.resolve(pregen);
assertEquals(IrisRuntimeSchedulerMode.PAPER_LIKE, foliaResolved);
pregen.runtimeSchedulerMode = IrisRuntimeSchedulerMode.PAPER_LIKE;
IrisRuntimeSchedulerMode paperResolved = IrisRuntimeSchedulerMode.resolve(pregen);
assertEquals(IrisRuntimeSchedulerMode.PAPER_LIKE, paperResolved);
}
private void installServer(String name, String version) {
Server server = Bukkit.getServer();
if (server == null) {
server = mock(Server.class);
try {
Bukkit.setServer(server);
} catch (Throwable ignored) {
server = Bukkit.getServer();
}
}
assumeTrue(server != null && mockingDetails(server).isMock());
BlockData emptyBlockData = mock(BlockData.class);
doReturn(Logger.getLogger("IrisTest")).when(server).getLogger();
doReturn(name).when(server).getName();
doReturn(version).when(server).getVersion();
doReturn(version).when(server).getBukkitVersion();
doReturn(emptyBlockData).when(server).createBlockData(any(Material.class));
doReturn(emptyBlockData).when(server).createBlockData(anyString());
}
}
@@ -0,0 +1,111 @@
package art.arcane.iris.engine;
import art.arcane.iris.engine.object.IrisBiome;
import art.arcane.iris.util.project.noise.CNG;
import art.arcane.volmlib.util.collection.KList;
import art.arcane.volmlib.util.math.RNG;
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.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
public class IrisComplexImplodeParityTest {
private static Method childSelectionCreateMethod;
private static Method childSelectionSelectMethod;
@BeforeClass
public static void setup() throws Exception {
if (Bukkit.getServer() == null) {
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);
}
Class<?> childSelectionClass = Class.forName("art.arcane.iris.engine.IrisComplex$ChildSelectionPlan");
childSelectionCreateMethod = childSelectionClass.getDeclaredMethod("create", KList.class);
childSelectionCreateMethod.setAccessible(true);
childSelectionSelectMethod = childSelectionClass.getDeclaredMethod("select", CNG.class, double.class, double.class);
childSelectionSelectMethod.setAccessible(true);
}
@Test
public void selectionPlanMatchesLegacyFitRarityAcrossSeedAndCoordinateGrid() throws Exception {
List<KList<IrisBiome>> scenarios = buildScenarios();
for (int scenarioIndex = 0; scenarioIndex < scenarios.size(); scenarioIndex++) {
KList<IrisBiome> options = scenarios.get(scenarioIndex);
Object selectionPlan = childSelectionCreateMethod.invoke(null, options);
for (long seed = 1L; seed <= 7L; seed++) {
CNG generator = new CNG(new RNG(seed), 4);
for (int x = -512; x <= 512; x += 37) {
for (int z = -512; z <= 512; z += 41) {
IrisBiome expected = generator.fitRarity(options, x, z);
IrisBiome actual = (IrisBiome) childSelectionSelectMethod.invoke(selectionPlan, generator, (double) x, (double) z);
assertSame("scenario=" + scenarioIndex + " seed=" + seed + " x=" + x + " z=" + z, expected, actual);
}
}
}
}
}
@Test
public void emptySelectionPlanMatchesLegacyEmptyBehavior() throws Exception {
KList<IrisBiome> options = new KList<>();
CNG generator = new CNG(new RNG(9L), 2);
Object selectionPlan = childSelectionCreateMethod.invoke(null, options);
IrisBiome expected = generator.fitRarity(options, 12D, -32D);
IrisBiome actual = (IrisBiome) childSelectionSelectMethod.invoke(selectionPlan, generator, 12D, -32D);
assertNull(expected);
assertNull(actual);
}
private List<KList<IrisBiome>> buildScenarios() {
List<KList<IrisBiome>> scenarios = new ArrayList<>();
KList<IrisBiome> scenarioA = new KList<>();
scenarioA.add(createBiome(1));
scenarioA.add(createBiome(3));
scenarioA.add(createBiome(5));
scenarioA.add(createBiome(2));
scenarios.add(scenarioA);
KList<IrisBiome> scenarioB = new KList<>();
scenarioB.add(createBiome(7));
scenarioB.add(createBiome(2));
scenarioB.add(createBiome(2));
scenarioB.add(createBiome(6));
scenarioB.add(createBiome(1));
scenarios.add(scenarioB);
KList<IrisBiome> scenarioC = new KList<>();
scenarioC.add(createBiome(4));
scenarios.add(scenarioC);
return scenarios;
}
private IrisBiome createBiome(int rarity) {
IrisBiome biome = mock(IrisBiome.class);
doReturn(rarity).when(biome).getRarity();
return biome;
}
}
@@ -57,8 +57,8 @@ public class IrisDimensionCarvingResolverParityTest {
IrisDimensionCarvingEntry statefulRoot = IrisDimensionCarvingResolver.resolveRootEntry(fixture.engine, worldY, state);
assertSame("root mismatch at worldY=" + worldY, legacyRoot, statefulRoot);
for (int worldX = -192; worldX <= 192; worldX += 31) {
for (int worldZ = -192; worldZ <= 192; worldZ += 37) {
for (int worldX = -384; worldX <= 384; worldX += 29) {
for (int worldZ = -384; worldZ <= 384; worldZ += 31) {
IrisDimensionCarvingEntry legacyResolved = legacyResolveFromRoot(fixture.engine, legacyRoot, worldX, worldZ);
IrisDimensionCarvingEntry statefulResolved = IrisDimensionCarvingResolver.resolveFromRoot(fixture.engine, statefulRoot, worldX, worldZ, state);
assertSame("entry mismatch at worldY=" + worldY + " worldX=" + worldX + " worldZ=" + worldZ, legacyResolved, statefulResolved);
@@ -67,6 +67,26 @@ public class IrisDimensionCarvingResolverParityTest {
}
}
@Test
public void resolverStatefulOverloadsMatchLegacyResolverAcrossMixedDepthGraph() {
Fixture fixture = createMixedDepthFixture();
IrisDimensionCarvingResolver.State state = new IrisDimensionCarvingResolver.State();
for (int worldY = -64; worldY <= 320; worldY += 17) {
IrisDimensionCarvingEntry legacyRoot = legacyResolveRootEntry(fixture.engine, worldY);
IrisDimensionCarvingEntry statefulRoot = IrisDimensionCarvingResolver.resolveRootEntry(fixture.engine, worldY, state);
assertSame("mixed root mismatch at worldY=" + worldY, legacyRoot, statefulRoot);
for (int worldX = -640; worldX <= 640; worldX += 79) {
for (int worldZ = -640; worldZ <= 640; worldZ += 83) {
IrisDimensionCarvingEntry legacyResolved = legacyResolveFromRoot(fixture.engine, legacyRoot, worldX, worldZ);
IrisDimensionCarvingEntry statefulResolved = IrisDimensionCarvingResolver.resolveFromRoot(fixture.engine, statefulRoot, worldX, worldZ, state);
assertSame("mixed entry mismatch at worldY=" + worldY + " worldX=" + worldX + " worldZ=" + worldZ, legacyResolved, statefulResolved);
}
}
}
}
@Test
public void caveBiomeStateOverloadMatchesDefaultOverloadAcrossSampleGrid() {
Fixture fixture = createFixture();
@@ -145,6 +165,92 @@ public class IrisDimensionCarvingResolverParityTest {
return new Fixture(engine);
}
private Fixture createMixedDepthFixture() {
IrisBiome rootLowBiome = mock(IrisBiome.class);
IrisBiome rootHighBiome = mock(IrisBiome.class);
IrisBiome childABiome = mock(IrisBiome.class);
IrisBiome childBBiome = mock(IrisBiome.class);
IrisBiome childCBiome = mock(IrisBiome.class);
IrisBiome childDBiome = mock(IrisBiome.class);
IrisBiome childEBiome = mock(IrisBiome.class);
IrisBiome childFBiome = mock(IrisBiome.class);
IrisBiome childGBiome = mock(IrisBiome.class);
IrisBiome fallbackBiome = mock(IrisBiome.class);
IrisBiome surfaceBiome = mock(IrisBiome.class);
doReturn(7).when(rootLowBiome).getRarity();
doReturn(5).when(rootHighBiome).getRarity();
doReturn(2).when(childABiome).getRarity();
doReturn(3).when(childBBiome).getRarity();
doReturn(6).when(childCBiome).getRarity();
doReturn(1).when(childDBiome).getRarity();
doReturn(4).when(childEBiome).getRarity();
doReturn(8).when(childFBiome).getRarity();
doReturn(2).when(childGBiome).getRarity();
doReturn(0).when(fallbackBiome).getCaveMinDepthBelowSurface();
@SuppressWarnings("unchecked")
ResourceLoader<IrisBiome> biomeLoader = mock(ResourceLoader.class);
doReturn(rootLowBiome).when(biomeLoader).load("root-low");
doReturn(rootHighBiome).when(biomeLoader).load("root-high");
doReturn(childABiome).when(biomeLoader).load("child-a");
doReturn(childBBiome).when(biomeLoader).load("child-b");
doReturn(childCBiome).when(biomeLoader).load("child-c");
doReturn(childDBiome).when(biomeLoader).load("child-d");
doReturn(childEBiome).when(biomeLoader).load("child-e");
doReturn(childFBiome).when(biomeLoader).load("child-f");
doReturn(childGBiome).when(biomeLoader).load("child-g");
IrisData data = mock(IrisData.class);
doReturn(biomeLoader).when(data).getBiomeLoader();
IrisDimensionCarvingEntry rootLow = buildEntry("root-low", "root-low", new IrisRange(-64, 120), 7, List.of("child-a", "child-d", "child-e"));
IrisDimensionCarvingEntry rootHigh = buildEntry("root-high", "root-high", new IrisRange(121, 320), 6, List.of("child-b", "child-c", "child-f"));
IrisDimensionCarvingEntry childA = buildEntry("child-a", "child-a", new IrisRange(-4096, 4096), 5, List.of("child-b", "child-g"));
IrisDimensionCarvingEntry childB = buildEntry("child-b", "child-b", new IrisRange(-4096, 4096), 1, List.of("child-c"));
IrisDimensionCarvingEntry childC = buildEntry("child-c", "child-c", new IrisRange(-4096, 4096), 0, List.of());
IrisDimensionCarvingEntry childD = buildEntry("child-d", "child-d", new IrisRange(-4096, 4096), 6, List.of("child-e", "child-f"));
IrisDimensionCarvingEntry childE = buildEntry("child-e", "child-e", new IrisRange(-4096, 4096), 2, List.of("child-a"));
IrisDimensionCarvingEntry childF = buildEntry("child-f", "child-f", new IrisRange(-4096, 4096), 8, List.of("child-g", "child-c"));
IrisDimensionCarvingEntry childG = buildEntry("child-g", "child-g", new IrisRange(-4096, 4096), 3, List.of("child-d"));
KList<IrisDimensionCarvingEntry> carvingEntries = new KList<>();
carvingEntries.add(rootLow);
carvingEntries.add(rootHigh);
carvingEntries.add(childA);
carvingEntries.add(childB);
carvingEntries.add(childC);
carvingEntries.add(childD);
carvingEntries.add(childE);
carvingEntries.add(childF);
carvingEntries.add(childG);
Map<String, IrisDimensionCarvingEntry> index = new HashMap<>();
index.put(rootLow.getId(), rootLow);
index.put(rootHigh.getId(), rootHigh);
index.put(childA.getId(), childA);
index.put(childB.getId(), childB);
index.put(childC.getId(), childC);
index.put(childD.getId(), childD);
index.put(childE.getId(), childE);
index.put(childF.getId(), childF);
index.put(childG.getId(), childG);
IrisDimension dimension = mock(IrisDimension.class);
doReturn(carvingEntries).when(dimension).getCarving();
doReturn(index).when(dimension).getCarvingEntryIndex();
Engine engine = mock(Engine.class, CALLS_REAL_METHODS);
doReturn(dimension).when(engine).getDimension();
doReturn(data).when(engine).getData();
doReturn(new SeedManager(4_627_991_643L)).when(engine).getSeedManager();
doReturn(IrisWorld.builder().minHeight(-64).maxHeight(320).build()).when(engine).getWorld();
doReturn(surfaceBiome).when(engine).getSurfaceBiome(anyInt(), anyInt());
doReturn(fallbackBiome).when(engine).getCaveBiome(anyInt(), anyInt());
return new Fixture(engine);
}
private IrisDimensionCarvingEntry buildEntry(String id, String biome, IrisRange worldRange, int depth, List<String> children) {
IrisDimensionCarvingEntry entry = new IrisDimensionCarvingEntry();
entry.setId(id);
@@ -0,0 +1,208 @@
package art.arcane.iris.util.project.noise;
import art.arcane.volmlib.util.function.NoiseInjector;
import art.arcane.volmlib.util.math.RNG;
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.ArrayList;
import java.util.List;
import java.util.logging.Logger;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
public class CNGInjectorParityTest {
@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 builtInInjectorsMatchLegacyCombineFor1D() {
List<NoiseInjector> injectors = builtInInjectors();
for (NoiseInjector injector : injectors) {
CompositeFixture fixture = createFixture(injector);
for (int x = -300; x <= 300; x += 17) {
double expected = legacyCombined1D(fixture, x);
double actual = fixture.root.noise(x);
assertEquals("injector=" + injector + " x=" + x, expected, actual, 1.0E-12D);
}
}
}
@Test
public void builtInInjectorsMatchLegacyCombineFor2D() {
List<NoiseInjector> injectors = builtInInjectors();
for (NoiseInjector injector : injectors) {
CompositeFixture fixture = createFixture(injector);
for (int x = -160; x <= 160; x += 19) {
for (int z = -160; z <= 160; z += 23) {
double expected = legacyCombined2D(fixture, x, z);
double actual = fixture.root.noise(x, z);
assertEquals("injector=" + injector + " x=" + x + " z=" + z, expected, actual, 1.0E-12D);
}
}
}
}
@Test
public void builtInInjectorsMatchLegacyCombineFor3D() {
List<NoiseInjector> injectors = builtInInjectors();
for (NoiseInjector injector : injectors) {
CompositeFixture fixture = createFixture(injector);
for (int x = -64; x <= 64; x += 11) {
for (int y = -32; y <= 32; y += 13) {
for (int z = -64; z <= 64; z += 17) {
double expected = legacyCombined3D(fixture, x, y, z);
double actual = fixture.root.noise(x, y, z);
assertEquals("injector=" + injector + " x=" + x + " y=" + y + " z=" + z, expected, actual, 1.0E-12D);
}
}
}
}
}
private CompositeFixture createFixture(NoiseInjector injector) {
DeterministicNoiseGenerator rootGenerator = new DeterministicNoiseGenerator(0.17D);
DeterministicNoiseGenerator childGeneratorA = new DeterministicNoiseGenerator(0.43D);
DeterministicNoiseGenerator childGeneratorB = new DeterministicNoiseGenerator(0.79D);
CNG childA = new CNG(new RNG(11L), childGeneratorA, 1.0D, 1).bake();
CNG childB = new CNG(new RNG(12L), childGeneratorB, 1.0D, 1).bake();
CNG root = new CNG(new RNG(9L), rootGenerator, 1.0D, 1).bake();
root.child(childA);
root.child(childB);
root.injectWith(injector);
return new CompositeFixture(root, rootGenerator, childA, childB, injector);
}
private List<NoiseInjector> builtInInjectors() {
List<NoiseInjector> injectors = new ArrayList<>();
injectors.add(CNG.ADD);
injectors.add(CNG.SRC_SUBTRACT);
injectors.add(CNG.DST_SUBTRACT);
injectors.add(CNG.MULTIPLY);
injectors.add(CNG.MAX);
injectors.add(CNG.MIN);
injectors.add(CNG.SRC_MOD);
injectors.add(CNG.SRC_POW);
injectors.add(CNG.DST_MOD);
injectors.add(CNG.DST_POW);
return injectors;
}
private double legacyCombined1D(CompositeFixture fixture, double x) {
double n = fixture.rootGenerator.noise(x, 0D, 0D);
double m = 1D;
double valueA = fixture.childA.noise(x);
double[] combinedA = fixture.injector.combine(n, valueA);
n = combinedA[0];
m += combinedA[1];
double valueB = fixture.childB.noise(x);
double[] combinedB = fixture.injector.combine(n, valueB);
n = combinedB[0];
m += combinedB[1];
return n / m;
}
private double legacyCombined2D(CompositeFixture fixture, double x, double z) {
double n = fixture.rootGenerator.noise(x, z, 0D);
double m = 1D;
double valueA = fixture.childA.noise(x, z);
double[] combinedA = fixture.injector.combine(n, valueA);
n = combinedA[0];
m += combinedA[1];
double valueB = fixture.childB.noise(x, z);
double[] combinedB = fixture.injector.combine(n, valueB);
n = combinedB[0];
m += combinedB[1];
return n / m;
}
private double legacyCombined3D(CompositeFixture fixture, double x, double y, double z) {
double n = fixture.rootGenerator.noise(x, y, z);
double m = 1D;
double valueA = fixture.childA.noise(x, y, z);
double[] combinedA = fixture.injector.combine(n, valueA);
n = combinedA[0];
m += combinedA[1];
double valueB = fixture.childB.noise(x, y, z);
double[] combinedB = fixture.injector.combine(n, valueB);
n = combinedB[0];
m += combinedB[1];
return n / m;
}
private static class CompositeFixture {
private final CNG root;
private final DeterministicNoiseGenerator rootGenerator;
private final CNG childA;
private final CNG childB;
private final NoiseInjector injector;
private CompositeFixture(CNG root, DeterministicNoiseGenerator rootGenerator, CNG childA, CNG childB, NoiseInjector injector) {
this.root = root;
this.rootGenerator = rootGenerator;
this.childA = childA;
this.childB = childB;
this.injector = injector;
}
}
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.013D) + offset;
return 0.2D + (((Math.sin(angle) + 1D) * 0.5D) * 0.6D);
}
@Override
public double noise(double x, double z) {
double angle = (x * 0.011D) + (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.013D) + (z * 0.019D) + offset;
return 0.2D + (((Math.sin(angle) + 1D) * 0.5D) * 0.6D);
}
}
}