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,82 @@
/*
* Iris is a World Generator for Minecraft Bukkit Servers
* Copyright (c) 2026 Arcane Arts (Volmit Software)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package art.arcane.iris.generation.decoration;
import art.arcane.volmlib.util.math.RNG;
public final class DepositRandom extends RNG {
private static final long MULTIPLIER = 0x5DEECE66DL;
private static final long ADDEND = 0xBL;
private static final long MASK = (1L << 48) - 1L;
private static final int JUMP_TABLE_SIZE = 48;
private static final long[] JUMP_MULTIPLIERS = new long[JUMP_TABLE_SIZE];
private static final long[] JUMP_ADDENDS = new long[JUMP_TABLE_SIZE];
static {
long multiplier = MULTIPLIER;
long addend = ADDEND;
for (int bit = 0; bit < JUMP_TABLE_SIZE; bit++) {
JUMP_MULTIPLIERS[bit] = multiplier;
JUMP_ADDENDS[bit] = addend;
addend = (addend * multiplier + addend) & MASK;
multiplier = (multiplier * multiplier) & MASK;
}
}
private long state;
public DepositRandom() {
super(0L);
}
public DepositRandom(long seed) {
super(seed);
}
@Override
public void setSeed(long seed) {
state = (seed ^ MULTIPLIER) & MASK;
}
@Override
protected int next(int bits) {
long advanced = (state * MULTIPLIER + ADDEND) & MASK;
state = advanced;
return (int) (advanced >>> (48 - bits));
}
public long state() {
return state;
}
public void restore(long state) {
this.state = state & MASK;
}
public void skip(long steps) {
long remaining = steps;
long advanced = state;
for (int bit = 0; remaining != 0L && bit < JUMP_TABLE_SIZE; bit++, remaining >>>= 1) {
if ((remaining & 1L) != 0L) {
advanced = (advanced * JUMP_MULTIPLIERS[bit] + JUMP_ADDENDS[bit]) & MASK;
}
}
state = advanced;
}
}
@@ -58,6 +58,7 @@ import lombok.experimental.Accessors;
public class IrisDepositGenerator {
private static final int FOOTPRINT_MAXIMUM_SIZE = 64;
private static final ThreadLocal<FootprintCells> FOOTPRINT_CELLS = ThreadLocal.withInitial(FootprintCells::new);
private static final double[][] NODE_SINES = nodeSines();
private static final long FNV_OFFSET_BASIS = 0xcbf29ce484222325L;
private static final long FNV_PRIME = 0x100000001b3L;
@@ -283,28 +284,52 @@ public class IrisDepositGenerator {
* only for clumps it keeps. Returns null without drawing when the shape has no footprint path.
*/
public ClumpFootprint sampleClumpFootprint(RNG rng, IrisData rdata) {
if (shape != IrisDepositShape.VANILLA_ELLIPSOID || minSize > FOOTPRINT_MAXIMUM_SIZE
|| maxSize > FOOTPRINT_MAXIMUM_SIZE) {
return null;
}
int paletteSize = getBlockData(rdata).size();
if (paletteSize == 0) {
if (!usesClumpFootprints(rdata)) {
return null;
}
KList<NativeBlockState> resolvedPalette = getBlockData(rdata);
int paletteSize = resolvedPalette.size();
int size = rng.i(minSize, maxSize + 1);
if (size <= 0) {
return ClumpFootprint.EMPTY;
}
FootprintCells cells = FOOTPRINT_CELLS.get();
cells.reset();
vanillaEllipsoidCells(rng, size, cells);
if (cells.count == 0) {
return ClumpFootprint.EMPTY;
if (paletteSize == 1) {
emitEllipsoidRows(rng, size, cells);
if (cells.count == 0) {
return ClumpFootprint.EMPTY;
}
skipUniformPicks(rng, cells.count);
} else {
emitEllipsoidCells(rng, size, cells, cells.scratch);
if (cells.count == 0) {
return ClumpFootprint.EMPTY;
}
for (int cell = 0; cell < cells.count; cell++) {
cells.picks[cell] = rng.i(0, paletteSize);
}
}
for (int cell = 0; cell < cells.count; cell++) {
cells.picks[cell] = rng.i(0, paletteSize);
return new ClumpFootprint(cells.extentX * 2 + 1, cells.minY, cells, cells.stamp, resolvedPalette);
}
public boolean usesClumpFootprints(IrisData rdata) {
return shape == IrisDepositShape.VANILLA_ELLIPSOID && minSize <= FOOTPRINT_MAXIMUM_SIZE
&& maxSize <= FOOTPRINT_MAXIMUM_SIZE && !getBlockData(rdata).isEmpty();
}
public static int lowestFootprintOffset(int size) {
return -4 - (Math.max(0, size) + 15) / 16;
}
private static void skipUniformPicks(RNG rng, int count) {
if (rng instanceof DepositRandom random) {
random.skip(2L * count);
return;
}
for (int cell = 0; cell < count; cell++) {
rng.nextDouble();
}
return new ClumpFootprint(cells.extentX * 2 + 1, cells.minY, cells, cells.stamp, getBlockData(rdata));
}
/**
@@ -313,15 +338,87 @@ public class IrisDepositGenerator {
* test is bit-identical to evaluating {@code nx * nx + ny * ny + nz * nz} in place.
*/
static void vanillaEllipsoidCells(RNG rng, int size, CellSink sink) {
emitEllipsoidCells(rng, size, sink, new EllipsoidScratch());
}
private static void emitEllipsoidCells(RNG rng, int size, CellSink sink, EllipsoidScratch scratch) {
double[] nodes = ellipsoidNodes(rng, size, scratch);
for (int node = 0; node < size; node++) {
if (!scratch.sphere(nodes, node)) {
continue;
}
double[] squaredY = scratch.squaredY;
double[] squaredZ = scratch.squaredZ;
for (int x = scratch.minX; x <= scratch.maxX; x++) {
double nx = (x + 0.5D - scratch.centerX) / scratch.radius;
double squaredX = nx * nx;
if (squaredX >= 1D) {
continue;
}
for (int y = 0; y < scratch.spanY; y++) {
double squaredXY = squaredX + squaredY[y];
if (squaredXY >= 1D) {
continue;
}
for (int z = 0; z < scratch.spanZ; z++) {
if (squaredXY + squaredZ[z] < 1D) {
sink.add(x, scratch.minY + y, scratch.minZ + z);
}
}
}
}
}
}
private static void emitEllipsoidRows(RNG rng, int size, FootprintCells cells) {
EllipsoidScratch scratch = cells.scratch;
double[] nodes = ellipsoidNodes(rng, size, scratch);
for (int node = 0; node < size; node++) {
if (!scratch.sphere(nodes, node)) {
continue;
}
double[] squaredY = scratch.squaredY;
double[] squaredZ = scratch.squaredZ;
int zShift = scratch.minZ + FootprintCells.OFFSET;
for (int x = scratch.minX; x <= scratch.maxX; x++) {
double nx = (x + 0.5D - scratch.centerX) / scratch.radius;
double squaredX = nx * nx;
if (squaredX >= 1D) {
continue;
}
for (int y = 0; y < scratch.spanY; y++) {
double squaredXY = squaredX + squaredY[y];
if (squaredXY >= 1D) {
continue;
}
long row = 0L;
for (int z = 0; z < scratch.spanZ; z++) {
if (squaredXY + squaredZ[z] < 1D) {
row |= 1L << (zShift + z);
}
}
if (row != 0L) {
cells.addRow(x, scratch.minY + y, row);
}
}
}
}
cells.finishRows();
}
private static double[] ellipsoidNodes(RNG rng, int size, EllipsoidScratch scratch) {
float angle = rng.nextFloat() * (float) Math.PI;
float reach = size / 8F;
double startX = Math.sin(angle) * reach;
double endX = -Math.sin(angle) * reach;
double startZ = Math.cos(angle) * reach;
double endZ = -Math.cos(angle) * reach;
double sine = Math.sin(angle);
double cosine = Math.cos(angle);
double startX = sine * reach;
double endX = -sine * reach;
double startZ = cosine * reach;
double endZ = -cosine * reach;
double startY = rng.nextInt(3) - 2;
double endY = rng.nextInt(3) - 2;
double[] nodes = new double[size * 4];
double[] nodes = scratch.nodes(size * 4);
double[] sines = size < NODE_SINES.length ? NODE_SINES[size] : null;
for (int i = 0; i < size; i++) {
float progress = (float) i / size;
@@ -329,7 +426,8 @@ public class IrisDepositGenerator {
nodes[i * 4] = startX + (endX - startX) * progress;
nodes[i * 4 + 1] = startY + (endY - startY) * progress;
nodes[i * 4 + 2] = startZ + (endZ - startZ) * progress;
nodes[i * 4 + 3] = ((Math.sin(Math.PI * progress) + 1D) * radiusNoise + 1D) / 2D;
double arc = sines == null ? Math.sin(Math.PI * progress) : sines[i];
nodes[i * 4 + 3] = ((arc + 1D) * radiusNoise + 1D) / 2D;
}
for (int i = 0; i < size - 1; i++) {
@@ -354,59 +452,20 @@ public class IrisDepositGenerator {
}
}
}
return nodes;
}
double[] squaredY = new double[16];
double[] squaredZ = new double[16];
for (int i = 0; i < size; i++) {
double radius = nodes[i * 4 + 3];
if (radius < 0D) {
continue;
}
double centerX = nodes[i * 4];
double centerY = nodes[i * 4 + 1];
double centerZ = nodes[i * 4 + 2];
int minX = (int) Math.floor(centerX - radius);
int maxX = Math.max((int) Math.floor(centerX + radius), minX);
int minY = (int) Math.floor(centerY - radius);
int maxY = Math.max((int) Math.floor(centerY + radius), minY);
int minZ = (int) Math.floor(centerZ - radius);
int maxZ = Math.max((int) Math.floor(centerZ + radius), minZ);
int spanY = maxY - minY + 1;
int spanZ = maxZ - minZ + 1;
if (spanY > squaredY.length) {
squaredY = new double[spanY];
}
if (spanZ > squaredZ.length) {
squaredZ = new double[spanZ];
}
for (int y = 0; y < spanY; y++) {
double ny = (minY + y + 0.5D - centerY) / radius;
squaredY[y] = ny * ny;
}
for (int z = 0; z < spanZ; z++) {
double nz = (minZ + z + 0.5D - centerZ) / radius;
squaredZ[z] = nz * nz;
}
for (int x = minX; x <= maxX; x++) {
double nx = (x + 0.5D - centerX) / radius;
double squaredX = nx * nx;
if (squaredX >= 1D) {
continue;
}
for (int y = 0; y < spanY; y++) {
double squaredXY = squaredX + squaredY[y];
if (squaredXY >= 1D) {
continue;
}
for (int z = 0; z < spanZ; z++) {
if (squaredXY + squaredZ[z] < 1D) {
sink.add(x, minY + y, minZ + z);
}
}
}
private static double[][] nodeSines() {
double[][] sines = new double[FOOTPRINT_MAXIMUM_SIZE + 1][];
for (int size = 1; size <= FOOTPRINT_MAXIMUM_SIZE; size++) {
double[] values = new double[size];
for (int i = 0; i < size; i++) {
float progress = (float) i / size;
values[i] = Math.sin(Math.PI * progress);
}
sines[size] = values;
}
return sines;
}
IrisObject generateVanillaScattered(RNG rng, IrisData rdata, int size) {
@@ -672,14 +731,16 @@ public class IrisDepositGenerator {
}
public static final class ClumpCells {
static final ClumpCells EMPTY = new ClumpCells(new int[0], new NativeBlockState[0]);
static final ClumpCells EMPTY = new ClumpCells(new int[0], new NativeBlockState[0], null);
private final int[] positions;
private final NativeBlockState[] blocks;
private final NativeBlockState uniform;
private ClumpCells(int[] positions, NativeBlockState[] blocks) {
private ClumpCells(int[] positions, NativeBlockState[] blocks, NativeBlockState uniform) {
this.positions = positions;
this.blocks = blocks;
this.uniform = uniform;
}
public int size() {
@@ -699,7 +760,63 @@ public class IrisDepositGenerator {
}
public NativeBlockState block(int cell) {
return blocks[cell];
return blocks == null ? uniform : blocks[cell];
}
}
private static final class EllipsoidScratch {
private double[] nodes = new double[256];
private double[] squaredY = new double[16];
private double[] squaredZ = new double[16];
private double centerX;
private double radius;
private int minX;
private int maxX;
private int minY;
private int spanY;
private int minZ;
private int spanZ;
private double[] nodes(int length) {
if (nodes.length < length) {
nodes = new double[length];
}
return nodes;
}
private boolean sphere(double[] values, int node) {
double sphereRadius = values[node * 4 + 3];
if (sphereRadius < 0D) {
return false;
}
double sphereX = values[node * 4];
double sphereY = values[node * 4 + 1];
double sphereZ = values[node * 4 + 2];
minX = (int) Math.floor(sphereX - sphereRadius);
maxX = Math.max((int) Math.floor(sphereX + sphereRadius), minX);
minY = (int) Math.floor(sphereY - sphereRadius);
int maxY = Math.max((int) Math.floor(sphereY + sphereRadius), minY);
minZ = (int) Math.floor(sphereZ - sphereRadius);
int maxZ = Math.max((int) Math.floor(sphereZ + sphereRadius), minZ);
spanY = maxY - minY + 1;
spanZ = maxZ - minZ + 1;
if (spanY > squaredY.length) {
squaredY = new double[spanY];
}
if (spanZ > squaredZ.length) {
squaredZ = new double[spanZ];
}
for (int y = 0; y < spanY; y++) {
double ny = (minY + y + 0.5D - sphereY) / sphereRadius;
squaredY[y] = ny * ny;
}
for (int z = 0; z < spanZ; z++) {
double nz = (minZ + z + 0.5D - sphereZ) / sphereRadius;
squaredZ[z] = nz * nz;
}
centerX = sphereX;
radius = sphereRadius;
return true;
}
}
@@ -708,6 +825,7 @@ public class IrisDepositGenerator {
private static final int AXIS = 64;
private final long[] occupied = new long[AXIS * AXIS * AXIS / 64];
private final int[] touched = new int[AXIS * AXIS * AXIS / 64];
private final EllipsoidScratch scratch = new EllipsoidScratch();
private int[] order = new int[256];
private int[] picks = new int[256];
private long[] sortKeys = new long[256];
@@ -715,6 +833,7 @@ public class IrisDepositGenerator {
private int count;
private int extentX;
private int minY;
private boolean uniform;
private long stamp;
private void reset() {
@@ -725,6 +844,7 @@ public class IrisDepositGenerator {
count = 0;
extentX = 0;
minY = Integer.MAX_VALUE;
uniform = false;
stamp++;
}
@@ -751,7 +871,35 @@ public class IrisDepositGenerator {
minY = Math.min(minY, y);
}
private void addRow(int x, int y, long row) {
int word = (x + OFFSET) * AXIS + (y + OFFSET);
long current = occupied[word];
if (current == 0L) {
touched[touchedCount++] = word;
}
occupied[word] = current | row;
}
private void finishRows() {
int cells = 0;
int extent = 0;
int lowest = Integer.MAX_VALUE;
for (int index = 0; index < touchedCount; index++) {
int word = touched[index];
cells += Long.bitCount(occupied[word]);
extent = Math.max(extent, Math.abs(word / AXIS - OFFSET));
lowest = Math.min(lowest, word % AXIS - OFFSET);
}
count = cells;
extentX = extent;
minY = lowest;
uniform = true;
}
private ClumpCells toCells(KList<NativeBlockState> palette) {
if (uniform) {
return toUniformCells(palette.get(0));
}
for (int cell = 0; cell < count; cell++) {
sortKeys[cell] = ((long) order[cell] << 32) | picks[cell];
}
@@ -763,7 +911,22 @@ public class IrisDepositGenerator {
positions[cell] = (int) (key >>> 32);
blocks[cell] = palette.get((int) key);
}
return new ClumpCells(positions, blocks);
return new ClumpCells(positions, blocks, null);
}
private ClumpCells toUniformCells(NativeBlockState block) {
Arrays.sort(touched, 0, touchedCount);
int[] positions = new int[count];
int cell = 0;
for (int index = 0; index < touchedCount; index++) {
int word = touched[index];
long row = occupied[word];
while (row != 0L) {
positions[cell++] = (word << 6) | Long.numberOfTrailingZeros(row);
row &= row - 1L;
}
}
return new ClumpCells(positions, null, block);
}
}
}
@@ -23,6 +23,8 @@ import art.arcane.iris.generation.runtime.IrisEngine;
import art.arcane.iris.generation.runtime.EngineAssignedModifier;
import art.arcane.iris.generation.runtime.DimensionStackLayout;
import art.arcane.iris.generation.biome.IrisBiome;
import art.arcane.iris.generation.chunk.ColumnExtent;
import art.arcane.iris.generation.decoration.DepositRandom;
import art.arcane.iris.generation.decoration.IrisDepositBiomeScope;
import art.arcane.iris.generation.decoration.IrisDepositGenerator;
import art.arcane.iris.generation.decoration.IrisDepositHeightDistribution;
@@ -55,8 +57,8 @@ import java.util.List;
import java.util.function.Predicate;
public class IrisDepositModifier extends EngineAssignedModifier<NativeBlockState> {
private static final int CLUMPS_PER_BATCH = 8;
private static final int PREPARATION_BATCH_COUNT = 4;
private static final int PREPARATION_TASKS = 4;
private static final int MINIMUM_TASK_ATTEMPTS = 96;
private final RNG rng;
private volatile PackScan packScan;
@@ -88,31 +90,67 @@ public class IrisDepositModifier extends EngineAssignedModifier<NativeBlockState
BurstExecutor burst = burst().burst(multicore);
long seed = x * 341873128712L + z * 132897987541L;
int highestSurface = highestTerrainSurface(x, z, null, context);
PreparedDeposit[] prepared = new PreparedDeposit[PREPARATION_BATCH_COUNT];
int pending = 0;
IrisEngine generationEngine = getEngine() instanceof IrisEngine irisEngine
&& irisEngine.hasGenerationRuntimeScope() ? irisEngine : null;
PreparationContext preparation = new PreparationContext(context, generationEngine,
generationEngine == null ? null : generationEngine.captureGenerationRuntimeBinding(), new Throwable[PREPARATION_BATCH_COUNT]);
MantleChunk chunk = getEngine().getMantle().getMantle().useChunk(x, z);
PreparationContext preparation = null;
Throwable callerFailure = null;
try {
List<DepositPlan> plans = new ArrayList<>(generators.size());
AboveTerrainHosts aboveTerrainHosts = new AboveTerrainHosts(terrain, context);
int attempts = 0;
for (int i = 0; i < generators.size(); i++) {
DepositPlan plan = plan(generators.get(i), rng.nextParallelRNG(seed * (i + 1L)), context);
for (int first = 0; first < plan.attempts(); first += CLUMPS_PER_BATCH) {
int firstAttempt = first;
int limit = Math.min(plan.attempts(), first + CLUMPS_PER_BATCH);
int index = pending++;
burst.queue(scopedDepositTask(
() -> prepared[index] = prepare(plan, firstAttempt, limit, x, z, null, context, highestSurface),
preparation, index));
if (pending == prepared.length) {
completeBatches(burst, prepared, chunk, terrain, x, z, preparation);
pending = 0;
if (plan.attempts() == 0 || footprintsMissTerrain(plan, highestSurface)
|| plan.generator().getPlacementScope() == IrisDepositPlacementScope.ABOVE_TERRAIN
&& !aboveTerrainHosts.present(plan.generator().getSurfaceClearance())) {
continue;
}
plans.add(plan);
attempts += plan.attempts();
}
if (attempts == 0) {
return;
}
int tasks = multicore
? Math.max(1, Math.min(PREPARATION_TASKS, Math.ceilDiv(attempts, MINIMUM_TASK_ATTEMPTS)))
: 1;
int attemptsPerTask = Math.ceilDiv(attempts, tasks);
preparation = new PreparationContext(context, generationEngine,
generationEngine == null ? null : generationEngine.captureGenerationRuntimeBinding(), new Throwable[tasks]);
PreparedDeposit[][] prepared = new PreparedDeposit[tasks][];
for (int task = 0; task < tasks; task++) {
int first = task * attemptsPerTask;
int last = Math.min(attempts, first + attemptsPerTask);
if (first >= last) {
break;
}
int index = task;
burst.queue(scopedDepositTask(
() -> prepared[index] = prepareSlice(plans, first, last, x, z, context, highestSurface),
preparation, index));
}
burst.complete();
Throwable failure = preparation.takeFailure(null);
if (failure instanceof Error error) {
throw error;
}
if (failure instanceof RuntimeException exception) {
throw exception;
}
if (failure != null) {
throw new IllegalStateException("Deposit preparation failed", failure);
}
try (IrisContext.Scope chunkScope = IrisContext.open(getEngine(), context.getGenerationSessionId(), context)) {
for (PreparedDeposit[] slice : prepared) {
if (slice == null) {
continue;
}
for (PreparedDeposit deposit : slice) {
place(deposit, chunk, terrain, x, z, null, context);
}
}
}
completeBatches(burst, prepared, chunk, terrain, x, z, preparation);
} catch (Throwable failure) {
callerFailure = failure;
throw failure;
@@ -121,7 +159,7 @@ public class IrisDepositModifier extends EngineAssignedModifier<NativeBlockState
// submitted burst tasks must never write into a released chunk.
try {
burst.complete();
if (callerFailure != null) {
if (callerFailure != null && preparation != null) {
preparation.takeFailure(callerFailure);
}
} finally {
@@ -130,28 +168,22 @@ public class IrisDepositModifier extends EngineAssignedModifier<NativeBlockState
}
}
private void completeBatches(BurstExecutor burst, PreparedDeposit[] prepared, MantleChunk chunk,
Hunk<NativeBlockState> terrain, int x, int z, PreparationContext preparation) {
burst.complete();
Throwable failure = preparation.takeFailure(null);
if (failure instanceof Error error) {
throw error;
}
if (failure instanceof RuntimeException exception) {
throw exception;
}
if (failure != null) {
throw new IllegalStateException("Deposit preparation failed", failure);
}
try (IrisContext.Scope chunkScope = IrisContext.open(getEngine(), preparation.chunk().getGenerationSessionId(), preparation.chunk())) {
for (int i = 0; i < prepared.length; i++) {
PreparedDeposit deposit = prepared[i];
prepared[i] = null;
if (deposit != null) {
place(deposit, chunk, terrain, x, z, null, preparation.chunk());
}
private PreparedDeposit[] prepareSlice(List<DepositPlan> plans, int first, int last, int cx, int cz,
ChunkContext context, int highestSurface) {
List<PreparedDeposit> deposits = new ArrayList<>();
int offset = 0;
for (DepositPlan plan : plans) {
int start = Math.max(first, offset);
int end = Math.min(last, offset + plan.attempts());
if (start < end) {
deposits.add(prepare(plan, start - offset, end - offset, cx, cz, null, context, highestSurface));
}
offset += plan.attempts();
if (offset >= last) {
break;
}
}
return deposits.toArray(new PreparedDeposit[0]);
}
private Runnable scopedDepositTask(Runnable task, PreparationContext preparation, int index) {
@@ -172,10 +204,25 @@ public class IrisDepositModifier extends EngineAssignedModifier<NativeBlockState
public void generate(IrisDepositGenerator k, MantleChunk chunk, Hunk<NativeBlockState> data, RNG rng, int cx, int cz, boolean safe, HeightMap he, ChunkContext context) {
DepositPlan plan = plan(k, rng, context);
for (int first = 0; first < plan.attempts(); first += CLUMPS_PER_BATCH) {
int limit = Math.min(plan.attempts(), first + CLUMPS_PER_BATCH);
place(prepare(plan, first, limit, cx, cz, he, context, Integer.MIN_VALUE), chunk, data, cx, cz, he, context);
if (plan.attempts() == 0) {
return;
}
place(prepare(plan, 0, plan.attempts(), cx, cz, he, context, Integer.MIN_VALUE), chunk, data, cx, cz, he, context);
}
private boolean footprintsMissTerrain(DepositPlan plan, int highestSurface) {
IrisDepositGenerator generator = plan.generator();
if (highestSurface == Integer.MIN_VALUE
|| generator.getPlacementScope() != IrisDepositPlacementScope.TERRAIN
|| !generator.usesClumpFootprints(getData())
|| plan.ore() && packScan().oreScaling()) {
return false;
}
int minimum = generator.getHeightDistribution() == IrisDepositHeightDistribution.CLIPPED_UNIFORM
? Math.max(0, generator.getMinHeight())
: generator.getMinHeight();
long lowest = (long) minimum + IrisDepositGenerator.lowestFootprintOffset(generator.getMaxSize());
return lowest > (long) highestSurface - Math.max(0, generator.getSurfaceClearance());
}
private DepositPlan plan(IrisDepositGenerator generator, RNG rng, ChunkContext context) {
@@ -227,7 +274,7 @@ public class IrisDepositModifier extends EngineAssignedModifier<NativeBlockState
int engineHeight = getEngine().getHeight();
List<PreparedClump> clumps = new ArrayList<>(limit - first);
for (int l = first; l < limit; l++) {
RNG clumpRng = new RNG(clumpSeed(plan.seed(), l));
DepositRandom clumpRng = new DepositRandom(clumpSeed(plan.seed(), l));
if (k.getPerClumpSpawnChance() < clumpRng.d()) {
continue;
}
@@ -628,6 +675,102 @@ public class IrisDepositModifier extends EngineAssignedModifier<NativeBlockState
private record DepositPlan(IrisDepositGenerator generator, long seed, boolean ore, int attempts) {
}
private static final class HostStates {
private static final int SLOTS = 32;
private static final byte BEDROCK_KNOWN = 1;
private static final byte BEDROCK = 2;
private static final byte REPLACEABLE_KNOWN = 4;
private static final byte REPLACEABLE = 8;
private static final byte TARGET_KNOWN = 16;
private static final byte TARGET = 32;
private final IrisDepositGenerator generator;
private final NativeBlockState[] states = new NativeBlockState[SLOTS];
private final byte[] flags = new byte[SLOTS];
private HostStates(IrisDepositGenerator generator) {
this.generator = generator;
}
private boolean target(NativeBlockState state) {
int slot = slot(state);
byte known = flags[slot];
if ((known & TARGET_KNOWN) == 0) {
boolean target = canReplaceDepositTarget(state);
known = (byte) (known | TARGET_KNOWN | (target ? TARGET : 0));
flags[slot] = known;
}
return (known & TARGET) != 0;
}
private boolean bedrock(NativeBlockState state) {
int slot = slot(state);
byte known = flags[slot];
if ((known & BEDROCK_KNOWN) == 0) {
boolean bedrock = IrisProceduralBlocks.materialKey(state).equals("minecraft:bedrock");
known = (byte) (known | BEDROCK_KNOWN | (bedrock ? BEDROCK : 0));
flags[slot] = known;
}
return (known & BEDROCK) != 0;
}
private boolean replaceable(NativeBlockState state) {
int slot = slot(state);
byte known = flags[slot];
if ((known & REPLACEABLE_KNOWN) == 0) {
boolean replaceable = generator.canReplace(state);
known = (byte) (known | REPLACEABLE_KNOWN | (replaceable ? REPLACEABLE : 0));
flags[slot] = known;
}
return (known & REPLACEABLE) != 0;
}
private int slot(NativeBlockState state) {
int slot = System.identityHashCode(state) & (SLOTS - 1);
if (states[slot] != state) {
states[slot] = state;
flags[slot] = 0;
}
return slot;
}
}
private static final class AboveTerrainHosts {
private final Hunk<NativeBlockState> terrain;
private final ChunkContext context;
private int scannedClearance = -1;
private boolean present;
private AboveTerrainHosts(Hunk<NativeBlockState> terrain, ChunkContext context) {
this.terrain = terrain;
this.context = context;
}
private boolean present(int surfaceClearance) {
int clearance = Math.max(0, surfaceClearance);
if (clearance != scannedClearance) {
present = scan(clearance);
scannedClearance = clearance;
}
return present;
}
private boolean scan(int clearance) {
int height = terrain.getHeight();
for (int z = 0; z < terrain.getDepth(); z++) {
for (int x = 0; x < terrain.getWidth(); x++) {
long floor = Math.max(-1L, (long) context.getRoundedHeight(x, z) + clearance);
for (int y = Math.min(height - 1, ColumnExtent.highestStoredY(terrain, x, z)); y > floor; y--) {
if (canReplaceDepositTarget(terrain.get(x, y, z))) {
return true;
}
}
}
}
return false;
}
}
private record PackScan(IrisData data, boolean variants, boolean oreScaling) {
}
@@ -648,11 +791,13 @@ public class IrisDepositModifier extends EngineAssignedModifier<NativeBlockState
private final ChunkContext context;
private final boolean variants;
private final IrisDimension dimension;
private final IrisDimensionCarvingResolver.State carvingState = new IrisDimensionCarvingResolver.State();
private IrisDimensionCarvingResolver.State carvingState;
private final IrisDepositPlacementScope scope;
private final int surfaceClearance;
private final double discardChance;
private final int height;
private final boolean replaceBedrock;
private final HostStates hosts;
private ClumpPlacement(IrisDepositGenerator generator, boolean ore, MantleChunk chunk,
Hunk<NativeBlockState> data, int chunkX, int chunkZ, HeightMap heightMap,
@@ -671,6 +816,17 @@ public class IrisDepositModifier extends EngineAssignedModifier<NativeBlockState
this.surfaceClearance = generator.getSurfaceClearance();
this.discardChance = generator.getDiscardChanceOnAirExposure();
this.height = getEngine().getHeight();
this.replaceBedrock = generator.isReplaceBedrock();
this.hosts = new HostStates(generator);
}
private IrisDimensionCarvingResolver.State carvingState() {
IrisDimensionCarvingResolver.State state = carvingState;
if (state == null) {
state = new IrisDimensionCarvingResolver.State();
carvingState = state;
}
return state;
}
private void place(int nx, int ny, int nz, NativeBlockState block, RNG rng) {
@@ -688,10 +844,10 @@ public class IrisDepositModifier extends EngineAssignedModifier<NativeBlockState
}
NativeBlockState current = data.get(nx, ny, nz);
if (!canReplaceDepositTarget(current)) {
if (current == null || !hosts.target(current)) {
return;
}
if (!generator.isReplaceBedrock() && IrisProceduralBlocks.materialKey(current).equals("minecraft:bedrock")) {
if (!replaceBedrock && hosts.bedrock(current)) {
return;
}
@@ -702,7 +858,9 @@ public class IrisDepositModifier extends EngineAssignedModifier<NativeBlockState
exteriorSurface = generator.hasSurfaceReplaceableBlocks(candidateSurfaceBiome)
&& isTerrainSurface(data, nx, ny, nz, columnSurface);
}
if (!canReplaceDepositHost(generator, current, candidateSurfaceBiome, exteriorSurface)) {
if (exteriorSurface
? !canReplaceDepositHost(generator, current, candidateSurfaceBiome, true)
: !hosts.replaceable(current)) {
return;
}
if (discardChance > 0D) {
@@ -714,7 +872,7 @@ public class IrisDepositModifier extends EngineAssignedModifier<NativeBlockState
if (chunk.get(nx, ny, nz, MatterCavern.class) == null) {
NativeBlockState remapped = variants ? resolveDepositVariant(
chunkX, chunkZ, nx, ny, nz, block, dimension, context, carvingState) : null;
chunkX, chunkZ, nx, ny, nz, block, dimension, context, carvingState()) : null;
data.set(nx, ny, nz, remapped != null ? remapped : B.toDeepSlateOre(current, block));
}
}
@@ -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))) {