Deposit clump fast paths

- Clump randoms use a plain-field LCG with the RNG sequence and a step jump
- Single-block palettes build footprints from row masks and jump the pick draws
- Ellipsoid nodes, row offsets and node sines reuse thread scratch and size tables
- Terrain-scoped footprint plans that cannot reach the chunk surface are skipped
- Above-terrain plans are skipped when no solid host sits above the surface
- Preparation runs as at most four balanced slices with one join per chunk
- Placement caches target, bedrock and replaceable checks per block state
This commit is contained in:
Brian Neumann-Fopiano committed 2026-10-22 07:28:34 -04:00
1 parent af90765a92
commit 9cd470c7c8
8 files changed
+743 -142

No files matched your search

@@ -0,0 +1,65 @@
package art.arcane.iris.generation.decoration;
import art.arcane.volmlib.util.math.RNG;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class DepositRandomTest {
@Test
public void everyDrawMatchesTheRngSequence() {
int[] bounds = {1, 2, 3, 5, 7, 16, 100, 1 << 20, Integer.MAX_VALUE};
for (long seed : new long[]{0L, 1L, -1L, 42L, 0x9E3779B97F4A7C15L, Long.MIN_VALUE}) {
RNG reference = new RNG(seed);
DepositRandom random = new DepositRandom(seed);
assertEquals(reference.getSeed(), random.getSeed());
for (int draw = 0; draw < 4000; draw++) {
int bound = bounds[draw % bounds.length];
switch (draw % 9) {
case 0 -> assertEquals(reference.nextInt(), random.nextInt());
case 1 -> assertEquals(reference.nextInt(bound), random.nextInt(bound));
case 2 -> assertEquals(reference.nextLong(), random.nextLong());
case 3 -> assertEquals(Double.doubleToRawLongBits(reference.nextDouble()),
Double.doubleToRawLongBits(random.nextDouble()));
case 4 -> assertEquals(Float.floatToRawIntBits(reference.nextFloat()),
Float.floatToRawIntBits(random.nextFloat()));
case 5 -> assertEquals(reference.nextBoolean(), random.nextBoolean());
case 6 -> assertEquals(Double.doubleToRawLongBits(reference.d()),
Double.doubleToRawLongBits(random.d()));
case 7 -> assertEquals(reference.i(-bound / 2, bound), random.i(-bound / 2, bound));
default -> assertEquals(reference.i(0, 1), random.i(0, 1));
}
}
}
}
@Test
public void skipAdvancesExactlyLikeRepeatedSteps() {
for (long seed : new long[]{3L, -11L, 0x5DEECE66DL}) {
for (long steps : new long[]{0L, 1L, 2L, 3L, 7L, 64L, 65L, 1000L, 123457L}) {
DepositRandom stepped = new DepositRandom(seed);
DepositRandom jumped = new DepositRandom(seed);
for (long step = 0L; step < steps; step++) {
stepped.nextInt();
}
jumped.skip(steps);
assertEquals("seed " + seed + " steps " + steps, stepped.state(), jumped.state());
assertEquals(stepped.nextLong(), jumped.nextLong());
}
}
}
@Test
public void reseedingAndRestoringReproduceTheSequence() {
DepositRandom random = new DepositRandom();
for (long seed : new long[]{5L, 77L, -9001L}) {
random.setSeed(seed);
RNG reference = new RNG(seed);
long saved = random.state();
long first = random.nextLong();
assertEquals(reference.nextLong(), first);
random.restore(saved);
assertEquals(first, random.nextLong());
}
}
}
@@ -260,6 +260,56 @@ public class IrisDepositTuningTest {
assertNull(generator.sampleClumpFootprint(new RNG(1L), data));
}
@Test
public void uniformEllipsoidFootprintCellsMatchTheBuiltClumpForEitherRandom() {
IrisData data = mock(IrisData.class);
IrisDepositGenerator generator = generatorWithState(data, true);
generator.setShape(IrisDepositShape.VANILLA_ELLIPSOID);
for (int size : new int[]{0, 1, 2, 3, 4, 9, 17, 20, 25, 48, 64}) {
generator.setMinSize(size).setMaxSize(size);
for (long seed = 0; seed < 300; seed++) {
RNG built = new RNG(seed);
IrisObject clump = generator.getClump(null, built, data);
long expectedNext = built.nextLong();
List<String> expected = new ArrayList<>();
VectorMap<NativeBlockState>.Cursor cursor = clump.getBlocks().cursor();
while (cursor.next()) {
expected.add(cursor.key().getBlockX() + "," + cursor.key().getBlockY() + ","
+ cursor.key().getBlockZ() + "=" + cursor.value().key());
}
for (RNG sampled : new RNG[]{new RNG(seed), new DepositRandom(seed)}) {
IrisDepositGenerator.ClumpFootprint footprint = generator.sampleClumpFootprint(sampled, data);
assertEquals(clump.getW(), footprint.width());
IrisDepositGenerator.ClumpCells cells = footprint.cells();
List<String> actual = new ArrayList<>();
for (int cell = 0; cell < cells.size(); cell++) {
actual.add(cells.x(cell) + "," + cells.y(cell) + "," + cells.z(cell) + "="
+ cells.block(cell).key());
}
assertEquals("size " + size + " seed " + seed, expected, actual);
assertEquals("size " + size + " seed " + seed, expectedNext, sampled.nextLong());
}
}
}
}
@Test
public void footprintOffsetBoundNeverExceedsTheLowestEllipsoidCell() {
IrisData data = mock(IrisData.class);
IrisDepositGenerator generator = generatorWithState(data, true);
generator.setShape(IrisDepositShape.VANILLA_ELLIPSOID);
for (int size = 1; size <= 64; size++) {
generator.setMinSize(size).setMaxSize(size);
int bound = IrisDepositGenerator.lowestFootprintOffset(size);
for (long seed = 0; seed < 400; seed++) {
IrisDepositGenerator.ClumpFootprint footprint = generator.sampleClumpFootprint(new DepositRandom(seed), data);
if (!footprint.empty()) {
assertTrue("size " + size + " seed " + seed, footprint.minY() >= bound);
}
}
}
}
@Test
public void ellipsoidFootprintCellsMatchTheBuiltClumpInPlacementOrder() {
IrisData data = mock(IrisData.class);
@@ -54,8 +54,8 @@ public class IrisDepositModifierContextTest {
try (Fixture fixture = new Fixture(engine, true)) {
fixture.generateAndCheckCleanup();
verify(engine, times(1)).captureGenerationRuntimeBinding();
verify(engine, times(4)).openGenerationRuntimeScope(fixture.binding);
assertEquals(4, fixture.closedScopes.get());
verify(engine, times(1)).openGenerationRuntimeScope(fixture.binding);
assertEquals(1, fixture.closedScopes.get());
}
}
@@ -84,13 +84,13 @@ public class IrisDepositModifierContextTest {
assertSame(failure, assertThrows(IllegalStateException.class, () ->
fixture.modifier.modify(32, -48, Hunk.newArrayHunk(16, 16, 16), true, fixture.context)));
verify(fixture.chunk).release();
assertEquals(4, fixture.closedScopes.get());
assertEquals(1, fixture.closedScopes.get());
assertNull(IrisContext.get());
}
}
@Test
public void callerPlanFailureStillDrainsWorkersAndRestoresContext() throws Exception {
public void callerPlanFailureQueuesNoWorkAndRestoresContext() throws Exception {
try (Fixture fixture = new Fixture(mock(IrisEngine.class, RETURNS_DEEP_STUBS), true)) {
IllegalStateException failure = new IllegalStateException("caller deposit plan failed");
doThrow(failure).when(fixture.biomeDeposit).getSpawnChance();
@@ -102,25 +102,24 @@ public class IrisDepositModifierContextTest {
assertEquals(19L, IrisContext.require().getGenerationSessionId());
}
verify(fixture.chunk).release();
assertEquals(3, fixture.closedScopes.get());
assertEquals(10, fixture.completed.get());
assertEquals(0, fixture.closedScopes.get());
assertEquals(0, fixture.completed.get());
assertNull(IrisContext.get());
}
}
@Test
public void callerFailureRetainsDistinctWorkerFailuresAfterDraining() throws Exception {
public void distinctWorkerFailuresAreRetainedAfterDraining() throws Exception {
try (Fixture fixture = new Fixture(mock(IrisEngine.class, RETURNS_DEEP_STUBS), true)) {
IllegalStateException caller = new IllegalStateException("caller failure");
IllegalArgumentException firstWorker = new IllegalArgumentException("first worker failure");
UnsupportedOperationException secondWorker = new UnsupportedOperationException("second worker failure");
when(fixture.dimensionDeposit.getMinPerChunk()).thenReturn(200);
when(fixture.dimensionDeposit.getMaxPerChunk()).thenReturn(200);
doThrow(firstWorker, secondWorker).when(fixture.dimensionDeposit).getClump(any(), any(), any());
doThrow(caller).when(fixture.biomeDeposit).getSpawnChance();
assertSame(caller, assertThrows(IllegalStateException.class, () ->
assertSame(firstWorker, assertThrows(IllegalArgumentException.class, () ->
fixture.modifier.modify(32, -48, Hunk.newArrayHunk(16, 16, 16), true, fixture.context)));
assertEquals(2, caller.getSuppressed().length);
assertSame(firstWorker, caller.getSuppressed()[0]);
assertSame(secondWorker, caller.getSuppressed()[1]);
assertEquals(1, firstWorker.getSuppressed().length);
assertSame(secondWorker, firstWorker.getSuppressed()[0]);
verify(fixture.chunk).release();
assertEquals(3, fixture.closedScopes.get());
assertNull(IrisContext.get());
@@ -79,16 +79,16 @@ public class IrisDepositModifierOrderingTest {
@Test
public void reverseWorkerCompletionKeepsDimensionRegionBiomePlacementOrder() throws Exception {
try (Fixture fixture = new Fixture(true, 1)) {
try (Fixture fixture = new Fixture(true, 96, true)) {
fixture.generate();
assertEquals(List.of("biome", "region", "dimension"), fixture.completed);
assertEquals(List.of("biome", "region", "dimension"), fixture.firstCompletions());
fixture.assertBiomeWins();
}
}
@Test
public void inlinePreparationUsesTheSameOverlappingHostRules() throws Exception {
try (Fixture fixture = new Fixture(false, 1)) {
try (Fixture fixture = new Fixture(false, 1, false)) {
fixture.generate();
assertEquals(List.of("dimension", "region", "biome"), fixture.completed);
fixture.assertBiomeWins();
@@ -107,7 +107,7 @@ public class IrisDepositModifierOrderingTest {
Collections.sort(expectedSamples);
for (boolean parallel : new boolean[]{false, true}) {
try (Fixture fixture = new Fixture(parallel, 25)) {
try (Fixture fixture = new Fixture(parallel, 25, false)) {
fixture.generate();
Collections.sort(fixture.samples);
assertEquals(expectedSamples, fixture.samples);
@@ -290,11 +290,13 @@ public class IrisDepositModifierOrderingTest {
private final MantleChunk<Matter> chunk;
private final boolean parallel;
private final int attempts;
private final boolean reverse;
@SuppressWarnings("unchecked")
private Fixture(boolean parallel, int attempts) {
private Fixture(boolean parallel, int attempts, boolean reverse) {
this.parallel = parallel;
this.attempts = attempts;
this.reverse = reverse;
IrisBiome biome = mock(IrisBiome.class, withSettings().stubOnly());
IrisRegion region = mock(IrisRegion.class, withSettings().stubOnly());
ChunkedDataCache<IrisBiome> biomes = new ChunkedDataCache<>(
@@ -348,11 +350,9 @@ public class IrisDepositModifierOrderingTest {
}
}
when(generator.getClump(any(), any(), any())).thenAnswer(invocation -> {
assertTrue("Placement must release the first clump window before more are prepared",
samples.size() < 32 || output.get(0, 4, 0) != stone);
if (parallel && attempts == 1 && name.equals("dimension")) {
if (reverse && name.equals("dimension")) {
assertTrue(regionReady.await(10, TimeUnit.SECONDS));
} else if (parallel && attempts == 1 && name.equals("region")) {
} else if (reverse && name.equals("region")) {
assertTrue(biomeReady.await(10, TimeUnit.SECONDS));
}
RNG rng = invocation.getArgument(1);
@@ -376,6 +376,18 @@ public class IrisDepositModifierOrderingTest {
verify(chunk).release();
}
private List<String> firstCompletions() {
List<String> order = new ArrayList<>();
synchronized (completed) {
for (String name : completed) {
if (!order.contains(name)) {
order.add(name);
}
}
}
return order;
}
private void assertBiomeWins() {
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
@@ -8,6 +8,7 @@ import art.arcane.iris.generation.biome.IrisBiome;
import art.arcane.iris.generation.decoration.IrisDepositBiomeScope;
import art.arcane.iris.generation.decoration.IrisDepositGenerator;
import art.arcane.iris.generation.decoration.IrisDepositHeightDistribution;
import art.arcane.iris.generation.decoration.IrisDepositPlacementScope;
import art.arcane.iris.generation.decoration.IrisDepositShape;
import art.arcane.iris.generation.decoration.IrisDepositVariant;
import art.arcane.iris.generation.terrain.IrisDimension;
@@ -129,6 +130,56 @@ public class IrisDepositModifierParityTest {
assertEquals(0, clumps.get());
}
@Test
public void terrainFootprintsAboveTheHighestSurfaceAreSkippedWithoutChangingOutput() {
long[] expected = {-681153659952315909L, 784L, 2118L, 53L, 67L, 10L};
for (boolean multicore : new boolean[]{false, true}) {
AtomicInteger skipped = new AtomicInteger();
long[] withSkipped = generateConfigured(101L, multicore, true, false, new AtomicInteger(), null, true,
skyDeposit(47, skipped));
assertEquals(Arrays.toString(expected), Arrays.toString(withSkipped));
assertEquals(0, skipped.get());
AtomicInteger sampled = new AtomicInteger();
long[] withSampled = generateConfigured(101L, multicore, true, false, new AtomicInteger(), null, true,
skyDeposit(46, sampled));
assertEquals(Arrays.toString(expected), Arrays.toString(withSampled));
assertTrue(sampled.get() > 0);
}
}
@Test
public void aboveTerrainDepositsWithoutHostsAreSkippedWithoutChangingOutput() {
long[] expected = {-681153659952315909L, 784L, 2118L, 53L, 67L, 10L};
for (boolean multicore : new boolean[]{false, true}) {
AtomicInteger vacant = new AtomicInteger();
long[] withoutHosts = generateConfigured(101L, multicore, true, false, new AtomicInteger(), null, true,
floatingDeposit(vacant), false);
assertEquals(Arrays.toString(expected), Arrays.toString(withoutHosts));
assertEquals(0, vacant.get());
AtomicInteger hosted = new AtomicInteger();
generateConfigured(101L, multicore, true, false, new AtomicInteger(), null, true,
floatingDeposit(hosted), true);
assertTrue(hosted.get() > 0);
}
}
private IrisDepositGenerator floatingDeposit(AtomicInteger clumps) {
return new CountingPaletteGenerator(state("andesite"), clumps)
.setShape(IrisDepositShape.VANILLA_ELLIPSOID).setMinSize(9).setMaxSize(9)
.setMinPerChunk(40).setMaxPerChunk(40).setMinHeight(0).setMaxHeight(47)
.setHeightDistribution(IrisDepositHeightDistribution.UNIFORM).setSurfaceClearance(0)
.setPlacementScope(IrisDepositPlacementScope.ABOVE_TERRAIN)
.setPerClumpSpawnChance(1D).setReplaceableBlocks(new KList<>("stone"));
}
private IrisDepositGenerator skyDeposit(int minimumHeight, AtomicInteger clumps) {
return new CountingPaletteGenerator(state("andesite"), clumps)
.setShape(IrisDepositShape.VANILLA_ELLIPSOID).setMinSize(17).setMaxSize(17)
.setMinPerChunk(60).setMaxPerChunk(60).setMinHeight(minimumHeight).setMaxHeight(400)
.setHeightDistribution(IrisDepositHeightDistribution.UNIFORM).setSurfaceClearance(0)
.setPerClumpSpawnChance(1D).setReplaceableBlocks(new KList<>("stone"));
}
private long[] generateConfigured(long seed, boolean multicore) {
return generateConfigured(seed, multicore, true, false, new AtomicInteger());
}
@@ -143,10 +194,25 @@ public class IrisDepositModifierParityTest {
return generateConfigured(seed, multicore, caveVariant, enumerablePack, caveBiomeLookups, unmatchableClumps, true);
}
@SuppressWarnings("unchecked")
private long[] generateConfigured(long seed, boolean multicore, boolean caveVariant, boolean enumerablePack,
AtomicInteger caveBiomeLookups, AtomicInteger unmatchableClumps,
boolean oreScaling) {
return generateConfigured(seed, multicore, caveVariant, enumerablePack, caveBiomeLookups, unmatchableClumps,
oreScaling, null, false);
}
private long[] generateConfigured(long seed, boolean multicore, boolean caveVariant, boolean enumerablePack,
AtomicInteger caveBiomeLookups, AtomicInteger unmatchableClumps,
boolean oreScaling, IrisDepositGenerator extraSurfaceDeposit) {
return generateConfigured(seed, multicore, caveVariant, enumerablePack, caveBiomeLookups, unmatchableClumps,
oreScaling, extraSurfaceDeposit, false);
}
@SuppressWarnings("unchecked")
private long[] generateConfigured(long seed, boolean multicore, boolean caveVariant, boolean enumerablePack,
AtomicInteger caveBiomeLookups, AtomicInteger unmatchableClumps,
boolean oreScaling, IrisDepositGenerator extraSurfaceDeposit,
boolean floatingHost) {
Engine engine = mock(Engine.class, withSettings().stubOnly().defaultAnswer(RETURNS_DEEP_STUBS));
ChunkContext context = mock(ChunkContext.class, withSettings().stubOnly().defaultAnswer(RETURNS_DEEP_STUBS));
IrisDimension dimension = new IrisDimension();
@@ -189,6 +255,9 @@ public class IrisDepositModifierParityTest {
.setIncludedBiomes(new KList<>("elsewhere"));
surface.getDeposits().add(unmatchable);
}
if (extraSurfaceDeposit != null) {
surface.getDeposits().add(extraSurfaceDeposit);
}
when(engine.getHeight()).thenReturn(48);
when(engine.getMinHeight()).thenReturn(-64);
when(engine.getSeedManager().getDeposit()).thenReturn(seed);
@@ -241,6 +310,9 @@ public class IrisDepositModifierParityTest {
}
}
}
if (floatingHost) {
terrain.set(8, 44, 8, state("stone"));
}
MultiBurst burst = new MultiBurst("Deposit parity", () -> 4);
when(engine.burst()).thenReturn(burst);
try (MockedStatic<B> blocks = mockStatic(B.class, withSettings().stubOnly().defaultAnswer(CALLS_REAL_METHODS))) {