Added cave cell kernel

- Decide whole 8x8 adaptive cells as solid or open from lattice corner bounds with an ulp guard
- Evaluate only mixed cells per column with the original bilinear arithmetic
- Share the row kernel between serial and parallel adaptive classification
- Count solid, open and mixed cells and exact rows, printed by the generation probe
- Pin carve write sequences for eight carver configurations with golden hashes
This commit is contained in:
Brian Neumann-Fopiano committed 2026-10-21 19:26:52 -04:00
1 parent e2430500cf
commit 7c4a5e1937
6 files changed
+998 -4

No files matched your search

@@ -66,6 +66,24 @@ final class CaveCarveScratch {
final int[] tileLocalX = new int[4]; final int[] tileLocalX = new int[4];
final int[] tileLocalZ = new int[4]; final int[] tileLocalZ = new int[4];
final int[] tileTopY = new int[4]; final int[] tileTopY = new int[4];
final long[] rowCarveBits = new long[4];
final long[] cellPassBits = new long[4];
final double[] rowLatticeDensity = new double[9];
final CaveDensityGrid.Column[] cellCornerColumns = new CaveDensityGrid.Column[9];
final double[] cellThresholdMin = new double[4];
final double[] cellThresholdMax = new double[4];
final double[] cellThresholdMaxBoost = new double[4];
final int[] cellBreakFloorMin = new int[4];
final int[] cellCeilingFadeStart = new int[4];
final int[] cellTopY = new int[4];
final boolean[] cellProtected = new boolean[4];
final int[] columnProtectStartY = new int[256];
final int[] columnProtectEndY = new int[256];
int[] rowActiveCount = new int[0];
long solidCells;
long openCells;
long mixedCells;
long exactRows;
CaveFieldModuleState[] activeModules = new CaveFieldModuleState[0]; CaveFieldModuleState[] activeModules = new CaveFieldModuleState[0];
double[] activeModuleRemainingMin = new double[0]; double[] activeModuleRemainingMin = new double[0];
double[] activeModuleRemainingMax = new double[0]; double[] activeModuleRemainingMax = new double[0];
@@ -84,5 +102,6 @@ final class CaveCarveScratch {
Arrays.fill(sectionMatter, null); Arrays.fill(sectionMatter, null);
Arrays.fill(sectionSlices, null); Arrays.fill(sectionSlices, null);
Arrays.fill(densityColumns, null); Arrays.fill(densityColumns, null);
Arrays.fill(cellCornerColumns, null);
} }
} }
@@ -0,0 +1,33 @@
package art.arcane.iris.generation.mantle;
final class CaveCellKernel {
static final int NONE = 0;
static final int ALL = 1;
static final int MIXED = 2;
private static final double GUARD_ULPS = 32D;
private static final double MAXIMUM_MAGNITUDE = 0x1p500;
private CaveCellKernel() {
}
static int decide(double d00, double d01, double d10, double d11, double thresholdLow, double thresholdHigh) {
double low = Math.min(Math.min(d00, d01), Math.min(d10, d11));
double high = Math.max(Math.max(d00, d01), Math.max(d10, d11));
double magnitude = Math.max(Math.abs(low), Math.abs(high));
if (!(magnitude < MAXIMUM_MAGNITUDE)) {
return MIXED;
}
double guard = Math.ulp(magnitude) * GUARD_ULPS;
if (high + guard <= thresholdLow) {
return ALL;
}
if (low - guard > thresholdHigh) {
return NONE;
}
return MIXED;
}
static double threshold(double localThreshold, double normalizationFactor, double inverseNormalization) {
return (localThreshold * normalizationFactor) * inverseNormalization;
}
}
@@ -43,6 +43,7 @@ import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinWorkerThread; import java.util.concurrent.ForkJoinWorkerThread;
import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.LongAdder;
public class IrisCaveCarver3D { public class IrisCaveCarver3D {
private static final byte LIQUID_AIR = 0; private static final byte LIQUID_AIR = 0;
@@ -62,6 +63,11 @@ public class IrisCaveCarver3D {
private static final int MAXIMUM_DENSITY_WORKERS = 4; private static final int MAXIMUM_DENSITY_WORKERS = 4;
private static final int[] LATTICE_TILE_OFFSETS = {0, 1, 16, 17}; private static final int[] LATTICE_TILE_OFFSETS = {0, 1, 16, 17};
private static final int STANDALONE_DENSITY_GRID_COLUMNS = 1024; private static final int STANDALONE_DENSITY_GRID_COLUMNS = 1024;
private static final int[] CELL_CORNER_MASKS = {0b000011011, 0b000110110, 0b011011000, 0b110110000};
private static final LongAdder SOLID_CELLS = new LongAdder();
private static final LongAdder OPEN_CELLS = new LongAdder();
private static final LongAdder MIXED_CELLS = new LongAdder();
private static final LongAdder EXACT_ROWS = new LongAdder();
private final Engine engine; private final Engine engine;
private final IrisData data; private final IrisData data;
@@ -155,6 +161,10 @@ public class IrisCaveCarver3D {
densityMaximumY = engine.getWorld().getHeight() - 1; densityMaximumY = engine.getWorld().getHeight() - 1;
} }
public static CellCounters cellCounters() {
return new CellCounters(SOLID_CELLS.sum(), OPEN_CELLS.sum(), MIXED_CELLS.sum(), EXACT_ROWS.sum());
}
public int carve(MantleWriter writer, int chunkX, int chunkZ) { public int carve(MantleWriter writer, int chunkX, int chunkZ) {
CaveCarveScratch scratch = scratchCache.get(); CaveCarveScratch scratch = scratchCache.get();
if (!scratch.fullWeightsInitialized) { if (!scratch.fullWeightsInitialized) {
@@ -504,7 +514,9 @@ public class IrisCaveCarver3D {
int minimumY = pass.minimumY() + index * rows; int minimumY = pass.minimumY() + index * rows;
int maximumY = Math.min(pass.maximumY(), minimumY + rows - 1); int maximumY = Math.min(pass.maximumY(), minimumY + rows - 1);
if (minimumY <= maximumY) { if (minimumY <= maximumY) {
tasks.add(() -> classifyRows(pass, minimumY, maximumY, classified)); tasks.add(pass.cellKernel()
? () -> classifyCellRows(pass, minimumY, maximumY, classified)
: () -> classifyRows(pass, minimumY, maximumY, classified));
} }
} }
runDensityTasks(tasks, pool); runDensityTasks(tasks, pool);
@@ -697,10 +709,25 @@ public class IrisCaveCarver3D {
int[] columns, int[] columnTops, int columnCount, double[] thresholds, int[] columns, int[] columnTops, int columnCount, double[] thresholds,
long[] fluidBoundaries, boolean[] surfaceBreakColumns, int[] surfaceBreakFloors, long[] fluidBoundaries, boolean[] surfaceBreakColumns, int[] surfaceBreakFloors,
double surfaceBreakBoost, double[] verticalFade, CaveCarveScratch sourceScratch, double surfaceBreakBoost, double[] verticalFade, CaveCarveScratch sourceScratch,
int adaptiveStep, double adaptiveMargin, int[] fluidTops, MatterCavern[] matterByY int adaptiveStep, double adaptiveMargin, int[] fluidTops, MatterCavern[] matterByY, boolean cellKernel
) { ) {
} }
public record CellCounters(long solidCells, long openCells, long mixedCells, long exactRows) {
public long decided() {
return solidCells + openCells;
}
public long evaluated() {
return mixedCells;
}
public String machineLine() {
return "IRIS_GENPROBE_CARVE_CELLS solid=" + solidCells + " open=" + openCells + " mixed=" + mixedCells
+ " exact_rows=" + exactRows;
}
}
private static final class DensityTask implements Runnable, ForkJoinPool.ManagedBlocker { private static final class DensityTask implements Runnable, ForkJoinPool.ManagedBlocker {
private final Runnable work; private final Runnable work;
private final AtomicBoolean claimed = new AtomicBoolean(); private final AtomicBoolean claimed = new AtomicBoolean();
@@ -795,7 +822,7 @@ public class IrisCaveCarver3D {
byte[] classified = skipExistingCarved ? null : classifyPassParallel(new DensityPass( byte[] classified = skipExistingCarved ? null : classifyPassParallel(new DensityPass(
x0, z0, minY, passMaxY, activeColumnIndices, activeColumnTopY, activeColumnCount, x0, z0, minY, passMaxY, activeColumnIndices, activeColumnTopY, activeColumnCount,
passThreshold, surfaceFluidBoundaries, surfaceBreakColumn, surfaceBreakFloorY, passThreshold, surfaceFluidBoundaries, surfaceBreakColumn, surfaceBreakFloorY,
surfaceBreakThresholdBoost, verticalEdgeFade, scratch, 0, 0D, fluidMaxY, matterByY)); surfaceBreakThresholdBoost, verticalEdgeFade, scratch, 0, 0D, fluidMaxY, matterByY, false));
for (int sectionIndex = minSection; sectionIndex <= maxSection; sectionIndex++) { for (int sectionIndex = minSection; sectionIndex <= maxSection; sectionIndex++) {
int sectionMinY = Math.max(minY, PowerOfTwoCoordinates.chunkToBlock(sectionIndex)); int sectionMinY = Math.max(minY, PowerOfTwoCoordinates.chunkToBlock(sectionIndex));
@@ -944,12 +971,23 @@ public class IrisCaveCarver3D {
adaptiveSampleStep, adaptiveSampleStep,
effectiveAdaptiveSampleStep effectiveAdaptiveSampleStep
); );
boolean cellKernel = !skipExistingCarved && usesCellKernel(effectiveAdaptiveSampleStep);
if (cellKernel) {
prepareCellPlan(scratch, x0, z0, minY, passMaxY, activeColumnIndices, activeColumnCount,
surfaceFluidBoundaries, surfaceBreakThresholdBoost);
}
byte[] classified = skipExistingCarved ? null : classifyPassParallel(new DensityPass( byte[] classified = skipExistingCarved ? null : classifyPassParallel(new DensityPass(
x0, z0, minY, passMaxY, activeColumnIndices, activeColumnTopY, activeColumnCount, x0, z0, minY, passMaxY, activeColumnIndices, activeColumnTopY, activeColumnCount,
passThreshold, surfaceFluidBoundaries, surfaceBreakColumn, surfaceBreakFloorY, passThreshold, surfaceFluidBoundaries, surfaceBreakColumn, surfaceBreakFloorY,
surfaceBreakThresholdBoost, verticalEdgeFade, scratch, effectiveAdaptiveSampleStep, effectiveAdaptiveThresholdMargin, surfaceBreakThresholdBoost, verticalEdgeFade, scratch, effectiveAdaptiveSampleStep, effectiveAdaptiveThresholdMargin,
fluidMaxY, matterByY)); fluidMaxY, matterByY, cellKernel));
if (cellKernel) {
return emitCellPass(chunk, scratch, classified, x0, z0, minY, passMaxY, minSection, maxSection,
activeColumnIndices, activeColumnTopY, activeColumnCount, surfaceFluidBoundaries,
surfaceBreakThresholdBoost, verticalEdgeFade, fluidMaxY, matterByY, fluidSupportPlan);
}
for (int sectionIndex = minSection; sectionIndex <= maxSection; sectionIndex++) { for (int sectionIndex = minSection; sectionIndex <= maxSection; sectionIndex++) {
int sectionMinY = Math.max(minY, PowerOfTwoCoordinates.chunkToBlock(sectionIndex)); int sectionMinY = Math.max(minY, PowerOfTwoCoordinates.chunkToBlock(sectionIndex));
@@ -1059,6 +1097,432 @@ public class IrisCaveCarver3D {
return adaptiveThresholdMargin + ((effectiveAdaptiveSampleStep - adaptiveSampleStep) * ADAPTIVE_DEEP_MARGIN_BOOST); return adaptiveThresholdMargin + ((effectiveAdaptiveSampleStep - adaptiveSampleStep) * ADAPTIVE_DEEP_MARGIN_BOOST);
} }
private boolean usesCellKernel(int effectiveAdaptiveSampleStep) {
return effectiveAdaptiveSampleStep == ADAPTIVE_DEEP_SAMPLE_STEP
&& verticalDensityStep == 1
&& parallelDensity
&& (hasWarp || !hasModules);
}
private void prepareCellPlan(
CaveCarveScratch plan,
int x0,
int z0,
int minY,
int passMaxY,
int[] activeColumnIndices,
int activeColumnCount,
long[] surfaceFluidBoundaries,
double surfaceBreakThresholdBoost
) {
int rows = passMaxY - minY + 1;
if (plan.rowActiveCount.length < rows + 1) {
plan.rowActiveCount = new int[rows + 1];
}
int[] rowActiveCount = plan.rowActiveCount;
Arrays.fill(rowActiveCount, 0, rows + 1, 0);
Arrays.fill(plan.cellPassBits, 0L);
Arrays.fill(plan.cellThresholdMin, Double.POSITIVE_INFINITY);
Arrays.fill(plan.cellThresholdMax, Double.NEGATIVE_INFINITY);
Arrays.fill(plan.cellThresholdMaxBoost, Double.NEGATIVE_INFINITY);
Arrays.fill(plan.cellBreakFloorMin, Integer.MAX_VALUE);
Arrays.fill(plan.cellCeilingFadeStart, Integer.MAX_VALUE);
Arrays.fill(plan.cellTopY, Integer.MIN_VALUE);
Arrays.fill(plan.cellProtected, false);
prepareAdaptiveGeometry(plan, ADAPTIVE_DEEP_SAMPLE_STEP, 2, 3);
double[] passThreshold = plan.passThreshold;
for (int activeIndex = 0; activeIndex < activeColumnCount; activeIndex++) {
int column = activeColumnIndices[activeIndex];
int cell = ((column >> 7) << 1) | ((column >> 3) & 1);
int top = plan.columnMaxY[column];
double threshold = passThreshold[column];
plan.cellPassBits[column >> 6] |= 1L << column;
plan.cellThresholdMin[cell] = Math.min(plan.cellThresholdMin[cell], threshold);
plan.cellThresholdMax[cell] = Math.max(plan.cellThresholdMax[cell], threshold);
if (plan.surfaceBreakColumn[column]) {
plan.cellThresholdMaxBoost[cell] = Math.max(plan.cellThresholdMaxBoost[cell], threshold + surfaceBreakThresholdBoost);
plan.cellBreakFloorMin[cell] = Math.min(plan.cellBreakFloorMin[cell], plan.surfaceBreakFloorY[column]);
} else {
plan.cellThresholdMaxBoost[cell] = Math.max(plan.cellThresholdMaxBoost[cell], threshold);
}
if (plan.surfaceCeilingColumn[column]) {
plan.cellCeilingFadeStart[cell] = Math.min(plan.cellCeilingFadeStart[cell], top - SURFACE_CEILING_FADE_DEPTH + 1);
}
plan.cellTopY[cell] = Math.max(plan.cellTopY[cell], top);
int protectStart = SurfaceFluidBoundaryPlan.NO_BOUNDARY;
int protectEnd = Integer.MIN_VALUE;
if (surfaceFluidBoundaries != null) {
long boundary = surfaceFluidBoundaries[column];
protectStart = SurfaceFluidBoundaryPlan.startY(boundary);
protectEnd = SurfaceFluidBoundaryPlan.endY(boundary);
}
plan.columnProtectStartY[column] = protectStart;
plan.columnProtectEndY[column] = protectEnd;
rowActiveCount[0]++;
rowActiveCount[top - minY + 1]--;
int protectedFrom = Math.max(protectStart, minY);
int protectedTo = Math.min(protectEnd, top);
if (protectedFrom <= protectedTo) {
rowActiveCount[protectedFrom - minY]--;
rowActiveCount[protectedTo - minY + 1]++;
plan.cellProtected[cell] = true;
}
}
for (int row = 1; row < rows; row++) {
rowActiveCount[row] += rowActiveCount[row - 1];
}
int latticeX = x0 >> CaveDensityGrid.LATTICE_SHIFT;
int latticeZ = z0 >> CaveDensityGrid.LATTICE_SHIFT;
int usedCorners = 0;
for (int cell = 0; cell < 4; cell++) {
if (plan.cellTopY[cell] >= minY) {
usedCorners |= CELL_CORNER_MASKS[cell];
}
}
for (int corner = 0; corner < 9; corner++) {
plan.cellCornerColumns[corner] = (usedCorners & (1 << corner)) == 0 ? null
: densityGrid.column(this, densityGridOwner, densityMaximumY + 1,
latticeX + (corner / 3), latticeZ + (corner % 3));
}
}
private int emitCellPass(
MantleChunk<Matter> chunk,
CaveCarveScratch scratch,
byte[] classified,
int x0,
int z0,
int minY,
int passMaxY,
int minSection,
int maxSection,
int[] activeColumnIndices,
int[] activeColumnTopY,
int activeColumnCount,
long[] surfaceFluidBoundaries,
double surfaceBreakThresholdBoost,
double[] verticalEdgeFade,
int[] fluidMaxY,
MatterCavern[] matterByY,
CaveFluidSupportPlan fluidSupportPlan
) {
int carved = 0;
long[] rowBits = scratch.rowCarveBits;
for (int sectionIndex = minSection; sectionIndex <= maxSection; sectionIndex++) {
int sectionMinY = Math.max(minY, PowerOfTwoCoordinates.chunkToBlock(sectionIndex));
int sectionMaxY = Math.min(passMaxY, PowerOfTwoCoordinates.chunkToBlock(sectionIndex) + 15);
MatterSlice<MatterCavern> cavernSlice = resolveCavernSlice(scratch, chunk, sectionIndex);
for (int y = sectionMinY; y <= sectionMaxY; y++) {
int fadeIndex = y - minY;
if (scratch.rowActiveCount[fadeIndex] == 0) {
continue;
}
MatterCavern verticalMatter = matterByY[fadeIndex];
if (classified != null) {
carved += emitClassifiedRow(cavernSlice, classified, fadeIndex, y, verticalMatter, fluidSupportPlan);
continue;
}
classifyAdaptiveRow(scratch, scratch, x0, z0, y, minY, activeColumnIndices, activeColumnTopY,
activeColumnCount, surfaceFluidBoundaries, surfaceBreakThresholdBoost, verticalEdgeFade, rowBits);
for (int word = 0; word < 4; word++) {
long bits = rowBits[word];
while (bits != 0L) {
int column = (word << 6) | Long.numberOfTrailingZeros(bits);
bits &= bits - 1L;
int localX = PowerOfTwoCoordinates.unpackLocal16X(column);
int localZ = column & 15;
MatterCavern matter = verticalMatter;
if (verticalMatter != carveLava && y <= fluidMaxY[column]) {
matter = resolveMatter(scratch, verticalMatter, x0 + localX, y, z0 + localZ, column, fluidMaxY,
cellThreshold(scratch, column, y, minY, surfaceBreakThresholdBoost, verticalEdgeFade));
}
writeCavern(cavernSlice, localX, y, localZ, matter, fluidSupportPlan);
carved++;
}
}
}
}
flushCellCounters(scratch);
return carved;
}
private int emitClassifiedRow(MatterSlice<MatterCavern> cavernSlice, byte[] classified, int fadeIndex, int y,
MatterCavern verticalMatter, CaveFluidSupportPlan fluidSupportPlan) {
int carved = 0;
int offset = fadeIndex * 256;
for (int column = 0; column < 256; column++) {
byte flags = classified[offset + column];
if ((flags & DENSITY_CARVE) == 0) {
continue;
}
MatterCavern matter = (flags & DENSITY_FLUID) != 0 ? carveFluid : verticalMatter;
writeCavern(cavernSlice, PowerOfTwoCoordinates.unpackLocal16X(column), y, column & 15, matter, fluidSupportPlan);
carved++;
}
return carved;
}
private void classifyCellRows(DensityPass pass, int minimumY, int maximumY, byte[] classified) {
CaveCarveScratch scratch = new CaveCarveScratch();
CaveCarveScratch plan = pass.sourceScratch();
long[] rowBits = scratch.rowCarveBits;
for (int y = minimumY; y <= maximumY; y++) {
int fadeIndex = y - pass.minimumY();
if (plan.rowActiveCount[fadeIndex] == 0) {
continue;
}
classifyAdaptiveRow(scratch, plan, pass.x(), pass.z(), y, pass.minimumY(), pass.columns(), pass.columnTops(),
pass.columnCount(), pass.fluidBoundaries(), pass.surfaceBreakBoost(), pass.verticalFade(), rowBits);
MatterCavern verticalMatter = pass.matterByY()[fadeIndex];
int offset = fadeIndex * 256;
for (int word = 0; word < 4; word++) {
long bits = rowBits[word];
while (bits != 0L) {
int column = (word << 6) | Long.numberOfTrailingZeros(bits);
bits &= bits - 1L;
MatterCavern matter = verticalMatter;
if (verticalMatter != carveLava && y <= pass.fluidTops()[column]) {
matter = resolveMatter(scratch, verticalMatter, pass.x() + PowerOfTwoCoordinates.unpackLocal16X(column),
y, pass.z() + (column & 15), column, pass.fluidTops(),
cellThreshold(plan, column, y, pass.minimumY(), pass.surfaceBreakBoost(), pass.verticalFade()));
}
classified[offset + column] = matter == carveFluid ? DENSITY_CARVE | DENSITY_FLUID : DENSITY_CARVE;
}
}
}
flushCellCounters(scratch);
}
private void classifyAdaptiveRow(
CaveCarveScratch scratch,
CaveCarveScratch plan,
int x0,
int z0,
int y,
int minY,
int[] activeColumnIndices,
int[] activeColumnTopY,
int activeColumnCount,
long[] surfaceFluidBoundaries,
double surfaceBreakThresholdBoost,
double[] verticalEdgeFade,
long[] rowBits
) {
Arrays.fill(rowBits, 0L);
if (plan.rowActiveCount[y - minY] >= ADAPTIVE_MIN_PLANE_COLUMNS) {
classifyCellRow(scratch, plan, x0, z0, y, minY, surfaceBreakThresholdBoost, verticalEdgeFade, rowBits);
return;
}
int[] planeColumnIndices = scratch.planeColumnIndices;
double[] planeThresholdLimit = scratch.planeThresholdLimit;
boolean[] planeCarve = scratch.planeCarve;
int planeCount = 0;
for (int activeIndex = 0; activeIndex < activeColumnCount; activeIndex++) {
if (activeColumnTopY[activeIndex] < y) {
continue;
}
int columnIndex = activeColumnIndices[activeIndex];
if (SurfaceFluidBoundaryPlan.protects(surfaceFluidBoundaries, columnIndex, y)) {
continue;
}
planeColumnIndices[planeCount] = columnIndex;
planeThresholdLimit[planeCount] = cellLocalThreshold(plan, columnIndex, y, minY, surfaceBreakThresholdBoost,
verticalEdgeFade) * normalizationFactor;
planeCount++;
}
classifyDensityPlane(scratch, x0, z0, y, planeColumnIndices, planeThresholdLimit, planeCount, planeCarve);
for (int planeIndex = 0; planeIndex < planeCount; planeIndex++) {
if (planeCarve[planeIndex]) {
int columnIndex = planeColumnIndices[planeIndex];
rowBits[columnIndex >> 6] |= 1L << columnIndex;
}
}
scratch.exactRows++;
}
private void classifyCellRow(
CaveCarveScratch scratch,
CaveCarveScratch plan,
int x0,
int z0,
int y,
int minY,
double surfaceBreakThresholdBoost,
double[] verticalEdgeFade,
long[] rowBits
) {
int activeCells = 0;
int corners = 0;
for (int cell = 0; cell < 4; cell++) {
if (isCellActive(plan, cell, y)) {
activeCells |= 1 << cell;
corners |= CELL_CORNER_MASKS[cell];
}
}
if (activeCells == 0) {
return;
}
double[] density = scratch.rowLatticeDensity;
for (int corner = 0; corner < 9; corner++) {
if ((corners & (1 << corner)) != 0) {
density[corner] = cornerDensity(scratch, plan.cellCornerColumns[corner],
x0 + (corner / 3) * ADAPTIVE_DEEP_SAMPLE_STEP, y, z0 + (corner % 3) * ADAPTIVE_DEEP_SAMPLE_STEP);
}
}
int fadeIndex = y - minY;
double fade = verticalEdgeFade[fadeIndex];
double closure = plan.surfaceClosureThreshold[fadeIndex];
for (int cell = 0; cell < 4; cell++) {
if ((activeCells & (1 << cell)) == 0) {
continue;
}
int corner = ((cell >> 1) * 3) + (cell & 1);
double d00 = density[corner];
double d01 = density[corner + 1];
double d10 = density[corner + 3];
double d11 = density[corner + 4];
double low = plan.cellThresholdMin[cell] - fade;
if (y >= plan.cellCeilingFadeStart[cell]) {
low = Math.min(low, closure);
}
double high = (y >= plan.cellBreakFloorMin[cell] ? plan.cellThresholdMaxBoost[cell] : plan.cellThresholdMax[cell]) - fade;
int decision = CaveCellKernel.decide(d00, d01, d10, d11,
CaveCellKernel.threshold(low, normalizationFactor, inverseNormalization),
CaveCellKernel.threshold(high, normalizationFactor, inverseNormalization));
if (decision == CaveCellKernel.NONE) {
scratch.solidCells++;
} else if (decision == CaveCellKernel.ALL) {
scratch.openCells++;
markActiveCellColumns(plan, cell, y, rowBits);
} else {
scratch.mixedCells++;
classifyMixedCell(plan, cell, y, minY, surfaceBreakThresholdBoost, verticalEdgeFade, d00, d01, d10, d11, rowBits);
}
}
}
private void classifyMixedCell(
CaveCarveScratch plan,
int cell,
int y,
int minY,
double surfaceBreakThresholdBoost,
double[] verticalEdgeFade,
double d00,
double d01,
double d10,
double d11,
long[] rowBits
) {
double[] adaptiveTx = plan.adaptiveTx;
double[] adaptiveTz = plan.adaptiveTz;
int firstLocalX = (cell >> 1) << 3;
int firstLocalZ = (cell & 1) << 3;
for (int localX = firstLocalX; localX < firstLocalX + ADAPTIVE_DEEP_SAMPLE_STEP; localX++) {
int rowColumn = PowerOfTwoCoordinates.packLocal16(localX, firstLocalZ);
for (int column = rowColumn; column < rowColumn + ADAPTIVE_DEEP_SAMPLE_STEP; column++) {
if (!isCellColumnActive(plan, column, y)) {
continue;
}
double tx = adaptiveTx[column];
double tz = adaptiveTz[column];
double dx0 = d00 + ((d10 - d00) * tx);
double dx1 = d01 + ((d11 - d01) * tx);
double predictedDensity = dx0 + ((dx1 - dx0) * tz);
if (predictedDensity <= cellThreshold(plan, column, y, minY, surfaceBreakThresholdBoost, verticalEdgeFade)) {
rowBits[column >> 6] |= 1L << column;
}
}
}
}
private static void markActiveCellColumns(CaveCarveScratch plan, int cell, int y, long[] rowBits) {
int firstLocalX = (cell >> 1) << 3;
int firstLocalZ = (cell & 1) << 3;
for (int localX = firstLocalX; localX < firstLocalX + ADAPTIVE_DEEP_SAMPLE_STEP; localX++) {
int rowColumn = PowerOfTwoCoordinates.packLocal16(localX, firstLocalZ);
for (int column = rowColumn; column < rowColumn + ADAPTIVE_DEEP_SAMPLE_STEP; column++) {
if (isCellColumnActive(plan, column, y)) {
rowBits[column >> 6] |= 1L << column;
}
}
}
}
private static boolean isCellActive(CaveCarveScratch plan, int cell, int y) {
if (y > plan.cellTopY[cell]) {
return false;
}
if (!plan.cellProtected[cell]) {
return true;
}
int firstLocalX = (cell >> 1) << 3;
int firstLocalZ = (cell & 1) << 3;
for (int localX = firstLocalX; localX < firstLocalX + ADAPTIVE_DEEP_SAMPLE_STEP; localX++) {
int rowColumn = PowerOfTwoCoordinates.packLocal16(localX, firstLocalZ);
for (int column = rowColumn; column < rowColumn + ADAPTIVE_DEEP_SAMPLE_STEP; column++) {
if (isCellColumnActive(plan, column, y)) {
return true;
}
}
}
return false;
}
private static boolean isCellColumnActive(CaveCarveScratch plan, int column, int y) {
return (plan.cellPassBits[column >> 6] & (1L << column)) != 0L
&& plan.columnMaxY[column] >= y
&& (y < plan.columnProtectStartY[column] || y > plan.columnProtectEndY[column]);
}
private double cornerDensity(CaveCarveScratch scratch, CaveDensityGrid.Column column, int x, int y, int z) {
if (column == null) {
return samplePlaneDensity(scratch, x, y, z);
}
double cached = column.get(y);
if (!Double.isNaN(cached)) {
return cached;
}
double density = samplePlaneDensity(scratch, x, y, z);
column.set(y, density);
return density;
}
private double cellThreshold(CaveCarveScratch plan, int column, int y, int minY, double surfaceBreakThresholdBoost,
double[] verticalEdgeFade) {
return CaveCellKernel.threshold(cellLocalThreshold(plan, column, y, minY, surfaceBreakThresholdBoost, verticalEdgeFade),
normalizationFactor, inverseNormalization);
}
private double cellLocalThreshold(CaveCarveScratch plan, int column, int y, int minY, double surfaceBreakThresholdBoost,
double[] verticalEdgeFade) {
double localThreshold = plan.passThreshold[column];
if (plan.surfaceBreakColumn[column] && y >= plan.surfaceBreakFloorY[column]) {
localThreshold += surfaceBreakThresholdBoost;
}
localThreshold -= verticalEdgeFade[y - minY];
return applySurfaceCeilingFade(plan, localThreshold, column, y, minY);
}
private static void flushCellCounters(CaveCarveScratch scratch) {
if (scratch.solidCells != 0L) {
SOLID_CELLS.add(scratch.solidCells);
scratch.solidCells = 0L;
}
if (scratch.openCells != 0L) {
OPEN_CELLS.add(scratch.openCells);
scratch.openCells = 0L;
}
if (scratch.mixedCells != 0L) {
MIXED_CELLS.add(scratch.mixedCells);
scratch.mixedCells = 0L;
}
if (scratch.exactRows != 0L) {
EXACT_ROWS.add(scratch.exactRows);
scratch.exactRows = 0L;
}
}
private int carvePassLattice( private int carvePassLattice(
MantleChunk<Matter> chunk, MantleChunk<Matter> chunk,
int x0, int x0,
@@ -0,0 +1,106 @@
package art.arcane.iris.generation.mantle;
import org.junit.Test;
import java.util.SplittableRandom;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public class CaveCellKernelTest {
private static final int TRIALS = 200_000;
@Test
public void decidedCellsAgreeWithEveryInterpolatedColumn() {
SplittableRandom random = new SplittableRandom(91_337L);
long decided = 0L;
for (int trial = 0; trial < TRIALS; trial++) {
double scale = Math.scalb(1D, random.nextInt(-30, 4));
double base = (random.nextDouble() * 2D - 1D) * scale;
double spread = random.nextInt(4) == 0 ? 0D : random.nextDouble() * scale * Math.scalb(1D, -random.nextInt(0, 40));
double d00 = base + spread * (random.nextDouble() * 2D - 1D);
double d01 = random.nextInt(8) == 0 ? d00 : base + spread * (random.nextDouble() * 2D - 1D);
double d10 = base + spread * (random.nextDouble() * 2D - 1D);
double d11 = random.nextInt(8) == 0 ? d10 : base + spread * (random.nextDouble() * 2D - 1D);
double low = Math.min(Math.min(d00, d01), Math.min(d10, d11));
double high = Math.max(Math.max(d00, d01), Math.max(d10, d11));
int mode = random.nextInt(6);
double[] thresholds = new double[64];
for (int column = 0; column < 64; column++) {
thresholds[column] = pickThreshold(random, low, high, mode);
}
double thresholdLow = Double.POSITIVE_INFINITY;
double thresholdHigh = Double.NEGATIVE_INFINITY;
for (double threshold : thresholds) {
thresholdLow = Math.min(thresholdLow, threshold);
thresholdHigh = Math.max(thresholdHigh, threshold);
}
int decision = CaveCellKernel.decide(d00, d01, d10, d11, thresholdLow, thresholdHigh);
if (decision == CaveCellKernel.MIXED) {
continue;
}
decided++;
for (int column = 0; column < 64; column++) {
double tx = (column >> 3) / 8D;
double tz = (column & 7) / 8D;
boolean carved = bilinear(d00, d01, d10, d11, tx, tz) <= thresholds[column];
assertEquals("trial " + trial + " column " + column, decision == CaveCellKernel.ALL, carved);
}
}
assertTrue("decided " + decided, decided > TRIALS / 4);
}
@Test
public void thresholdsAtTheCornerExtremesStayMixed() {
double d00 = 0.1D;
double d01 = -0.2D;
double d10 = 0.3D;
double d11 = 0.05D;
assertEquals(CaveCellKernel.MIXED, CaveCellKernel.decide(d00, d01, d10, d11, 0.3D, 0.3D));
assertEquals(CaveCellKernel.MIXED, CaveCellKernel.decide(d00, d01, d10, d11, Math.nextUp(0.3D), 0.4D));
assertEquals(CaveCellKernel.MIXED, CaveCellKernel.decide(d00, d01, d10, d11, -0.3D, Math.nextDown(-0.2D)));
assertEquals(CaveCellKernel.ALL, CaveCellKernel.decide(d00, d01, d10, d11, 0.3D + 1.0E-12D, 0.5D));
assertEquals(CaveCellKernel.NONE, CaveCellKernel.decide(d00, d01, d10, d11, -0.9D, -0.2D - 1.0E-12D));
}
@Test
public void nonFiniteCornersAreNeverDecided() {
assertEquals(CaveCellKernel.MIXED, CaveCellKernel.decide(Double.NaN, 0D, 0D, 0D, 5D, 5D));
assertEquals(CaveCellKernel.MIXED, CaveCellKernel.decide(0D, Double.POSITIVE_INFINITY, 0D, 0D, -5D, -5D));
assertEquals(CaveCellKernel.MIXED, CaveCellKernel.decide(0D, 0D, Double.NEGATIVE_INFINITY, 0D,
Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY));
assertEquals(CaveCellKernel.MIXED, CaveCellKernel.decide(0D, 0D, 0D, 0D, Double.NaN, 1D));
}
@Test
public void monotoneThresholdMappingPreservesBounds() {
SplittableRandom random = new SplittableRandom(7_771L);
for (int trial = 0; trial < TRIALS; trial++) {
double normalization = 0.25D + random.nextDouble() * 3D;
double inverse = 1D / normalization;
double a = (random.nextDouble() * 2D - 1D) * Math.scalb(1D, random.nextInt(-20, 3));
double b = random.nextInt(4) == 0 ? Math.nextUp(a) : a + random.nextDouble() * Math.scalb(1D, random.nextInt(-50, 0));
assertTrue(CaveCellKernel.threshold(a, normalization, inverse) <= CaveCellKernel.threshold(b, normalization, inverse));
assertEquals(Double.doubleToRawLongBits((a * normalization) * inverse),
Double.doubleToRawLongBits(CaveCellKernel.threshold(a, normalization, inverse)));
}
}
private static double pickThreshold(SplittableRandom random, double low, double high, int mode) {
double width = Math.max(high - low, Math.ulp(Math.max(Math.abs(low), Math.abs(high))));
return switch (mode) {
case 0 -> high + Math.ulp(high) * random.nextInt(0, 64);
case 1 -> low - Math.ulp(low) * random.nextInt(0, 64);
case 2 -> high + width * random.nextDouble() * 0.5D;
case 3 -> low - width * random.nextDouble() * 0.5D;
case 4 -> high + width * (1D + random.nextDouble() * 4D);
default -> low - width * (1D + random.nextDouble() * 4D);
};
}
private static double bilinear(double d00, double d01, double d10, double d11, double tx, double tz) {
double dx0 = d00 + ((d10 - d00) * tx);
double dx1 = d01 + ((d11 - d01) * tx);
return dx0 + ((dx1 - dx0) * tz);
}
}
@@ -0,0 +1,370 @@
package art.arcane.iris.generation.mantle;
import art.arcane.iris.generation.cave.IrisCaveFieldModule;
import art.arcane.iris.generation.cave.IrisCaveProfile;
import art.arcane.iris.generation.noise.IrisGeneratorStyle;
import art.arcane.iris.generation.noise.IrisStyledRange;
import art.arcane.iris.generation.noise.NoiseStyle;
import art.arcane.iris.generation.runtime.Engine;
import art.arcane.iris.generation.runtime.EngineMetrics;
import art.arcane.iris.generation.runtime.SeedManager;
import art.arcane.iris.generation.terrain.IrisDimension;
import art.arcane.iris.pack.loading.IrisData;
import art.arcane.iris.pack.value.IrisRange;
import art.arcane.iris.world.IrisWorld;
import art.arcane.volmlib.util.collection.KList;
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 art.arcane.volmlib.util.matter.slices.CavernMatter;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.withSettings;
public class IrisCaveCarver3DCellKernelTest {
private static final long CARVE_SEED = -54_777_863_784_492_918L;
private static final int WORLD_HEIGHT = 160;
private static final int CHUNK_SPAN = 4;
@Test
public void warpModulesProfileMatchesGoldenWriteSequence() throws Exception {
assertScenario(Scenario.WARP_MODULES, 0xdf617c77d3c3cc5eL, 67798);
}
@Test
public void warpOnlyTerrainProfileMatchesGoldenWriteSequence() throws Exception {
assertScenario(Scenario.WARP_TERRAIN, 0x7e0b63cab5bb3904L, 67036);
}
@Test
public void styledThresholdBlendProfileMatchesGoldenWriteSequence() throws Exception {
assertScenario(Scenario.STYLED_BLEND, 0x25792d49143774faL, 30387);
}
@Test
public void fluidBoundaryProfileMatchesGoldenWriteSequence() throws Exception {
assertScenario(Scenario.FLUID_BOUNDARY, 0xb102145856119afeL, 17584);
}
@Test
public void noWarpModulesProfileMatchesGoldenWriteSequence() throws Exception {
assertScenario(Scenario.NO_WARP_MODULES, 0x5a83dc20c89c6398L, 47534);
}
@Test
public void verticalStepProfileMatchesGoldenWriteSequence() throws Exception {
assertScenario(Scenario.VERTICAL_STEP, 0xac0413031ed65b61L, 48011);
}
@Test
public void latticeProfileMatchesGoldenWriteSequence() throws Exception {
assertScenario(Scenario.LATTICE, 0xc7ffab155e6bdef9L, 72714);
}
@Test
public void floorFluidProfilesSharingChunksMatchGoldenWriteSequence() throws Exception {
assertScenario(Scenario.FLOOR_FLUID_LAYERED, 0xb4c0fb23e071617dL, 68747);
}
@Test
public void cellKernelDecidesCellsWithoutColumnEvaluation() throws Exception {
IrisCaveCarver3D.CellCounters before = IrisCaveCarver3D.cellCounters();
run(Scenario.WARP_MODULES, false);
IrisCaveCarver3D.CellCounters after = IrisCaveCarver3D.cellCounters();
long decided = after.decided() - before.decided();
long evaluated = after.evaluated() - before.evaluated();
assertTrue("decided=" + decided + " evaluated=" + evaluated, decided > 0L && evaluated > 0L);
}
private void assertScenario(Scenario scenario, long expectedHash, int expectedWrites) throws Exception {
Result serial = run(scenario, false);
Result parallel = run(scenario, true);
assertTrue(serial.carved > 0);
assertEquals(serial.hash, parallel.hash);
assertEquals(serial.writes, parallel.writes);
assertEquals(serial.carved, parallel.carved);
assertEquals(expectedHash, serial.hash);
assertEquals(expectedWrites, serial.writes);
}
private Result run(Scenario scenario, boolean parallel) throws Exception {
if (!parallel) {
return carveArea(scenario);
}
ForkJoinPool pool = new ForkJoinPool(4);
try {
return pool.submit(() -> carveArea(scenario)).get(120, TimeUnit.SECONDS);
} finally {
pool.shutdownNow();
}
}
private Result carveArea(Scenario scenario) {
Engine engine = createEngine(scenario);
CaveDensityGrid grid = new CaveDensityGrid(4096);
IrisCaveProfile[] profiles = scenario.profiles();
IrisCaveCarver3D[] carvers = new IrisCaveCarver3D[profiles.length];
for (int index = 0; index < profiles.length; index++) {
carvers[index] = new IrisCaveCarver3D(engine, profiles[index], grid);
}
Capture capture = new Capture();
int carved = 0;
for (int chunkX = -2; chunkX < CHUNK_SPAN - 2; chunkX++) {
for (int chunkZ = -1; chunkZ < CHUNK_SPAN - 1; chunkZ++) {
MantleWriter writer = capture.writer(chunkX, chunkZ);
CaveFluidSupportPlan supportPlan = new CaveFluidSupportPlan();
for (int index = 0; index < carvers.length; index++) {
carved += carvers[index].carve(writer, chunkX, chunkZ, scenario.weights(index, chunkX, chunkZ),
0.08D, 0.24D, scenario.worldYRange(), scenario.surfaceHeights(chunkX, chunkZ),
scenario.boundaries(chunkX, chunkZ), null, supportPlan);
}
capture.resolving = true;
supportPlan.resolve(writer.acquireChunk(chunkX, chunkZ));
capture.finishResolve();
}
}
return new Result(capture.hash, capture.writes, carved);
}
private Engine createEngine(Scenario scenario) {
Engine engine = mock(Engine.class, withSettings().stubOnly());
IrisData data = mock(IrisData.class, withSettings().stubOnly());
IrisDimension dimension = mock(IrisDimension.class, withSettings().stubOnly());
SeedManager seedManager = mock(SeedManager.class, withSettings().stubOnly());
doReturn(data).when(engine).getData();
doReturn(dimension).when(engine).getDimension();
doReturn(seedManager).when(engine).getSeedManager();
doReturn(CARVE_SEED).when(seedManager).getCarve();
doReturn(new EngineMetrics(16)).when(engine).getMetrics();
doReturn(IrisWorld.builder().minHeight(-32).maxHeight(WORLD_HEIGHT - 32).build()).when(engine).getWorld();
doReturn(WORLD_HEIGHT).when(engine).getHeight();
doReturn(110).when(engine).getHeight(anyInt(), anyInt());
doReturn(24).when(dimension).getCaveLavaHeight();
doReturn(70).when(dimension).getFluidHeight();
doReturn(scenario == Scenario.VERTICAL_STEP ? 4 : 1).when(dimension).getCaveDensitySamplingStep();
return engine;
}
private static IrisCaveProfile baseProfile(boolean warp, boolean modules) {
IrisCaveProfile profile = new IrisCaveProfile();
profile.setEnabled(true);
profile.setVerticalRange(new IrisRange(4D, 150D));
profile.setVerticalEdgeFade(14);
profile.setVerticalEdgeFadeStrength(0.21D);
profile.setBaseDensityStyle(new IrisGeneratorStyle(NoiseStyle.SIMPLEX).zoomed(0.21D));
profile.setDetailDensityStyle(new IrisGeneratorStyle(NoiseStyle.SIMPLEX).zoomed(0.43D));
profile.setWarpStyle(new IrisGeneratorStyle(NoiseStyle.SIMPLEX).zoomed(0.37D));
profile.setSurfaceBreakStyle(new IrisGeneratorStyle(NoiseStyle.SIMPLEX).zoomed(0.140625D));
profile.setBaseWeight(1D);
profile.setDetailWeight(0.42D);
profile.setWarpStrength(warp ? 3.7D : 0D);
profile.setDensityThreshold(new IrisStyledRange(-0.18D, -0.18D, new IrisGeneratorStyle(NoiseStyle.FLAT)));
profile.setThresholdBias(0D);
profile.setSampleStep(1);
profile.setSurfaceClearance(5);
profile.setAllowSurfaceBreak(true);
profile.setSurfaceBreakNoiseThreshold(0.3D);
profile.setSurfaceBreakDepth(14);
profile.setSurfaceBreakThresholdBoost(0.21D);
profile.setAllowFluid(false);
profile.setAllowLava(true);
KList<IrisCaveFieldModule> caveModules = new KList<>();
if (modules) {
caveModules.add(new IrisCaveFieldModule(new IrisGeneratorStyle(NoiseStyle.SIMPLEX).zoomed(0.33D),
0.27D, 0.05D, new IrisRange(0D, 90D), false));
caveModules.add(new IrisCaveFieldModule(new IrisGeneratorStyle(NoiseStyle.SIMPLEX).zoomed(0.51D),
0.19D, -0.07D, new IrisRange(40D, 150D), true));
}
profile.setModules(caveModules);
return profile;
}
private enum Scenario {
WARP_MODULES,
WARP_TERRAIN,
STYLED_BLEND,
FLUID_BOUNDARY,
NO_WARP_MODULES,
VERTICAL_STEP,
LATTICE,
FLOOR_FLUID_LAYERED;
IrisCaveProfile[] profiles() {
if (this == FLOOR_FLUID_LAYERED) {
return new IrisCaveProfile[]{
baseProfile(true, true).setAllowFluid(true).setFluidRequiresFloor(true).setFluidMinDepthBelowSurface(4),
baseProfile(true, false).setAllowFluid(true).setFluidRequiresFloor(true).setFluidMinDepthBelowSurface(2)
.setAllowLava(false).setDensityThreshold(new IrisStyledRange(-0.24D, -0.08D,
new IrisGeneratorStyle(NoiseStyle.SIMPLEX).zoomed(0.45D)))
};
}
return new IrisCaveProfile[]{profile()};
}
IrisCaveProfile profile() {
return switch (this) {
case WARP_MODULES, VERTICAL_STEP -> baseProfile(true, true);
case WARP_TERRAIN -> baseProfile(true, false);
case STYLED_BLEND -> baseProfile(false, false).setDensityThreshold(new IrisStyledRange(-0.32D, -0.04D,
new IrisGeneratorStyle(NoiseStyle.SIMPLEX).zoomed(0.6D)));
case FLUID_BOUNDARY -> baseProfile(true, true).setAllowFluid(true).setFluidRequiresFloor(false)
.setFluidMinDepthBelowSurface(6).setAllowLava(false);
case NO_WARP_MODULES -> baseProfile(false, true);
case LATTICE -> baseProfile(true, true).setSampleStep(3);
case FLOOR_FLUID_LAYERED -> baseProfile(true, true);
};
}
double[] weights(int layer, int chunkX, int chunkZ) {
double[] weights = new double[256];
for (int index = 0; index < 256; index++) {
int worldX = (chunkX << 4) + (index >> 4);
int worldZ = (chunkZ << 4) + (index & 15);
double blend = 0.5D + 0.6D * Math.sin(worldX * 0.11D) * Math.cos(worldZ * 0.07D);
weights[index] = switch (this) {
case STYLED_BLEND, FLUID_BOUNDARY -> blend;
case FLOOR_FLUID_LAYERED -> layer == 0 ? blend : 1D - blend;
default -> 1D;
};
}
return weights;
}
int[] surfaceHeights(int chunkX, int chunkZ) {
int[] heights = new int[256];
for (int index = 0; index < 256; index++) {
int worldX = (chunkX << 4) + (index >> 4);
int worldZ = (chunkZ << 4) + (index & 15);
heights[index] = this == WARP_MODULES || this == LATTICE || this == FLOOR_FLUID_LAYERED
? 118
: 96 + (int) Math.round(22D * Math.sin(worldX * 0.09D) + 15D * Math.cos(worldZ * 0.13D));
}
return heights;
}
IrisRange worldYRange() {
return this == FLUID_BOUNDARY || this == FLOOR_FLUID_LAYERED ? new IrisRange(-20D, 100D) : null;
}
long[] boundaries(int chunkX, int chunkZ) {
long[] boundaries = new long[256];
Arrays.fill(boundaries, SurfaceFluidBoundaryPlan.boundary(SurfaceFluidBoundaryPlan.NO_BOUNDARY, Integer.MIN_VALUE));
if (this != FLUID_BOUNDARY && this != WARP_TERRAIN) {
return boundaries;
}
for (int index = 0; index < 256; index++) {
int worldX = (chunkX << 4) + (index >> 4);
if (Math.floorMod(worldX + chunkZ, 5) == 0) {
boundaries[index] = SurfaceFluidBoundaryPlan.boundary(60 + Math.floorMod(index, 7), 82);
}
}
return boundaries;
}
}
private record Result(long hash, int writes, int carved) {
}
private static final class Capture {
private final Map<Long, Map<Integer, Matter>> chunks = new HashMap<>();
private final IdentityHashMap<MatterCavern, Integer> identities = new IdentityHashMap<>();
private final List<Long> resolved = new ArrayList<>();
private long hash = 0xcbf29ce484222325L;
private int writes;
private boolean resolving;
private MantleWriter writer(int chunkX, int chunkZ) {
MantleWriter writer = mock(MantleWriter.class, withSettings().stubOnly());
@SuppressWarnings("unchecked")
Mantle<Matter> mantle = mock(Mantle.class, withSettings().stubOnly());
@SuppressWarnings("unchecked")
MantleChunk<Matter> chunk = mock(MantleChunk.class, withSettings().stubOnly());
long key = ((long) chunkX << 32) ^ (chunkZ & 0xffffffffL);
Map<Integer, Matter> sections = chunks.computeIfAbsent(key, ignored -> new HashMap<>());
doReturn(mantle).when(writer).getMantle();
doReturn(WORLD_HEIGHT).when(mantle).getWorldHeight();
doReturn(chunk).when(writer).acquireChunk(anyInt(), anyInt());
doAnswer(invocation -> sections.get(invocation.getArgument(0, Integer.class))).when(chunk).get(anyInt());
doAnswer(invocation -> sections.computeIfAbsent(invocation.getArgument(0, Integer.class),
sectionIndex -> createSection(chunkX, chunkZ, sectionIndex))).when(chunk).getOrCreate(anyInt());
return writer;
}
private Matter createSection(int chunkX, int chunkZ, int sectionIndex) {
mix(0x5EC7L);
mix(chunkX);
mix(chunkZ);
mix(sectionIndex);
Matter matter = mock(Matter.class, withSettings().stubOnly());
MatterSlice<MatterCavern> slice = new RecordingCavernMatter(this, sectionIndex);
doReturn(slice).when(matter).slice(MatterCavern.class);
doReturn(slice).when(matter).getSlice(MatterCavern.class);
return matter;
}
private void record(int sectionIndex, int x, int y, int z, MatterCavern value) {
writes++;
if (resolving) {
resolved.add(((long) sectionIndex << 20) | ((long) x << 16) | ((long) y << 12) | ((long) z << 8) | value.getLiquid());
return;
}
mix(sectionIndex);
mix((x << 8) | (y << 4) | z);
mix(value == null ? -1 : value.getLiquid());
Integer identity = identities.get(value);
if (identity == null) {
identity = identities.size();
identities.put(value, identity);
}
mix(identity);
}
private void finishResolve() {
resolving = false;
resolved.sort(Long::compare);
for (long entry : resolved) {
mix(entry);
}
resolved.clear();
}
private void mix(long value) {
hash ^= value;
hash *= 0x100000001b3L;
}
}
private static final class RecordingCavernMatter extends CavernMatter {
private final Capture capture;
private final int sectionIndex;
private RecordingCavernMatter(Capture capture, int sectionIndex) {
super(16, 16, 16);
this.capture = capture;
this.sectionIndex = sectionIndex;
}
@Override
public void set(int x, int y, int z, MatterCavern value) {
super.set(x, y, z, value);
capture.record(sectionIndex, x, y, z, value);
}
}
}
@@ -18,6 +18,7 @@
package art.arcane.iris.probe; package art.arcane.iris.probe;
import art.arcane.iris.generation.mantle.IrisCaveCarver3D;
import art.arcane.iris.pack.loading.IrisData; import art.arcane.iris.pack.loading.IrisData;
import art.arcane.iris.pack.PackValidationResult; import art.arcane.iris.pack.PackValidationResult;
import art.arcane.iris.pack.PackValidator; import art.arcane.iris.pack.PackValidator;
@@ -302,6 +303,7 @@ public final class GenerationProbe {
try { try {
ProbeResult result = run(configuration, options); ProbeResult result = run(configuration, options);
System.out.println(result.machineLine()); System.out.println(result.machineLine());
System.out.println(IrisCaveCarver3D.cellCounters().machineLine());
exitCode = result.failedChunks() == 0 ? 0 : 1; exitCode = result.failedChunks() == 0 ? 0 : 1;
} catch (Throwable e) { } catch (Throwable e) {
System.out.println("[genprobe] FAIL: probe execution failed"); System.out.println("[genprobe] FAIL: probe execution failed");