Merged small stages

- Single-pass deposit footprints with balanced preparation, cached perfection and placement state checks, run-scanned history receipts
This commit is contained in:
Brian Neumann-Fopiano committed 2026-10-22 11:36:07 -04:00
commit 45e00f4794
25 files changed
+1376 -270

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);
@@ -48,7 +48,7 @@ public class IrisOreBandsTest {
}
for (int x = -3; x <= 3; x++) {
assertSame("trial " + trial + " y " + y + " x " + x,
scan(ores, x, y, 5 - x), bands.generate(x, y, 5 - x, RNG_SOURCE, null));
scan(ores, x, y, 5 - x), bands.generate(x, y, 5 - x, RNG_SOURCE, null, null));
}
}
}
@@ -110,10 +110,10 @@ public class IrisOreBandsTest {
ores.add(generator(1D, 57D, 1D, 2L));
ores.add(generator(58D, 58D, 1D, 3L));
IrisOreBands bands = IrisOreBands.of(ores);
assertSame(scan(ores, 0, 0, 0), bands.generate(0, 0, 0, RNG_SOURCE, null));
assertSame(scan(ores, 0, 30, 0), bands.generate(0, 30, 0, RNG_SOURCE, null));
assertSame(scan(ores, 0, 58, 0), bands.generate(0, 58, 0, RNG_SOURCE, null));
assertNull(bands.contains(59) ? bands.generate(0, 59, 0, RNG_SOURCE, null) : null);
assertSame(scan(ores, 0, 0, 0), bands.generate(0, 0, 0, RNG_SOURCE, null, null));
assertSame(scan(ores, 0, 30, 0), bands.generate(0, 30, 0, RNG_SOURCE, null, null));
assertSame(scan(ores, 0, 58, 0), bands.generate(0, 58, 0, RNG_SOURCE, null, null));
assertNull(bands.contains(59) ? bands.generate(0, 59, 0, RNG_SOURCE, null, null) : null);
}
private static final class FixedPalette extends IrisMaterialPalette {
private final NativeBlockState state;
@@ -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))) {
@@ -0,0 +1,140 @@
package art.arcane.iris.world.history;
import org.junit.Test;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Random;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;
import static org.junit.Assert.assertArrayEquals;
public class SavedBiomeStoreEncodingTest {
@Test
public void encodedRecordsMatchTheReferenceEncoding() throws Exception {
Method encode = SavedBiomeStore.class.getDeclaredMethod("encode", SavedBiomeChunk.class);
encode.setAccessible(true);
Random random = new Random(20261022L);
for (int sample = 0; sample < 40; sample++) {
SavedBiomeChunk chunk = randomChunk(random, sample);
assertArrayEquals("sample " + sample, referenceEncode(chunk), (byte[]) encode.invoke(null, chunk));
}
}
private static SavedBiomeChunk randomChunk(Random random, int sample) {
long activation = 1L + random.nextInt(9);
int minimumY = -64;
int height = 384;
List<SavedBiomeChunk.Cell> cells = new ArrayList<>();
int distinctCells = 1 + random.nextInt(sample % 4 == 0 ? 3 : 24);
for (int index = 0; index < distinctCells; index++) {
cells.add(random.nextInt(9) == 0
? SavedBiomeChunk.Cell.unresolved(activation)
: new SavedBiomeChunk.Cell(activation, "biome_" + random.nextInt(12), "region_" + random.nextInt(3)));
}
List<SavedBiomeChunk.Column> shared = new ArrayList<>();
int distinctColumns = 1 + random.nextInt(sample % 3 == 0 ? 4 : 40);
for (int index = 0; index < distinctColumns; index++) {
shared.add(randomColumn(random, cells, minimumY, height));
}
SavedBiomeChunk.Builder builder = SavedBiomeChunk.builder(
new SavedBiomeChunk.Header(3, -7, activation, minimumY, height));
for (int z = 0; z < 16; z++) {
for (int x = 0; x < 16; x++) {
SavedBiomeChunk.Column column = shared.get(random.nextInt(shared.size()));
if (random.nextInt(4) == 0) {
column = new SavedBiomeChunk.Column(column.surface(), column.caveBase(), new ArrayList<>(column.vertical()));
}
builder.column(x, z, column);
}
}
return builder.build();
}
private static SavedBiomeChunk.Column randomColumn(Random random, List<SavedBiomeChunk.Cell> cells,
int minimumY, int height) {
List<SavedBiomeChunk.Span> spans = new ArrayList<>();
int start = minimumY;
int end = minimumY + height;
while (start < end) {
int top = Math.min(end, start + 1 + random.nextInt(96));
spans.add(new SavedBiomeChunk.Span(start, top, cells.get(random.nextInt(cells.size()))));
start = top;
}
return new SavedBiomeChunk.Column(cells.get(random.nextInt(cells.size())),
cells.get(random.nextInt(cells.size())), spans);
}
private static byte[] referenceEncode(SavedBiomeChunk chunk) throws IOException {
LinkedHashMap<SavedBiomeChunk.Cell, Integer> palette = new LinkedHashMap<>();
LinkedHashMap<SavedBiomeChunk.Column, Integer> columns = new LinkedHashMap<>();
for (int z = 0; z < 16; z++) {
for (int x = 0; x < 16; x++) {
SavedBiomeChunk.Column column = chunk.column(x, z);
columns.computeIfAbsent(column, ignored -> columns.size());
palette.computeIfAbsent(column.surface(), ignored -> palette.size());
palette.computeIfAbsent(column.caveBase(), ignored -> palette.size());
for (SavedBiomeChunk.Span span : column.vertical()) {
palette.computeIfAbsent(span.cell(), ignored -> palette.size());
}
}
}
ByteArrayOutputStream raw = new ByteArrayOutputStream();
try (DataOutputStream output = new DataOutputStream(raw)) {
output.writeLong(chunk.activationId());
output.writeInt(chunk.header().minimumY());
output.writeInt(chunk.header().height());
output.writeInt(palette.size());
for (SavedBiomeChunk.Cell cell : palette.keySet()) {
output.writeLong(cell.activationId());
output.writeByte(cell.isResolved() ? 0 : 1);
if (cell.isResolved()) {
output.writeUTF(cell.biomeKey());
output.writeUTF(cell.regionKey());
}
}
output.writeInt(columns.size());
for (SavedBiomeChunk.Column column : columns.keySet()) {
writeUnsigned(output, palette.get(column.surface()));
writeUnsigned(output, palette.get(column.caveBase()));
writeUnsigned(output, column.vertical().size());
for (SavedBiomeChunk.Span span : column.vertical()) {
writeUnsigned(output, span.maximumYExclusive() - chunk.header().minimumY());
writeUnsigned(output, palette.get(span.cell()));
}
}
for (int z = 0; z < 16; z++) {
for (int x = 0; x < 16; x++) {
output.writeByte(columns.get(chunk.column(x, z)));
}
}
}
ByteArrayOutputStream encoded = new ByteArrayOutputStream();
DataOutputStream output = new DataOutputStream(encoded);
output.writeInt(chunk.chunkX());
output.writeInt(chunk.chunkZ());
output.writeInt(raw.size());
Deflater deflater = new Deflater(Deflater.BEST_SPEED);
try (DeflaterOutputStream compressed = new DeflaterOutputStream(output, deflater)) {
raw.writeTo(compressed);
} finally {
deflater.end();
}
return encoded.toByteArray();
}
private static void writeUnsigned(DataOutputStream output, int value) throws IOException {
int remaining = value;
while ((remaining & ~0x7f) != 0) {
output.writeByte((remaining & 0x7f) | 0x80);
remaining >>>= 7;
}
output.writeByte(remaining);
}
}
@@ -56,6 +56,24 @@ public class SavedTerrainCaptureEquivalenceTest {
}
}
@Test
public void runScanningSourcesMatchThePerVoxelReference() throws Exception {
Random random = new Random(20261022L);
for (int sample = 0; sample < 32; sample++) {
RandomSource voxels = new RandomSource(random, sample);
SavedTerrainChunk.VoxelSource runs = new RunScanningSource(voxels, new Random(sample));
for (boolean boundaryOnly : new boolean[]{true, false}) {
SavedTerrainChunk expected = referenceCapture(-3, 7, voxels, boundaryOnly);
SavedTerrainChunk actual = boundaryOnly
? SavedTerrainChunk.captureBoundary(-3, 7, MINIMUM_Y, HEIGHT, "minecraft:noise", runs)
: SavedTerrainChunk.capture(-3, 7, MINIMUM_Y, HEIGHT, "minecraft:noise", runs);
assertSameTerrain(expected, actual);
assertArrayEquals(NativeTerrainReceipt.encode(expected, 1L, "epoch"),
NativeTerrainReceipt.encode(actual, 1L, "epoch"));
}
}
}
@Test
public void geometryMatchesTheReferenceOnWidePalettes() {
Random random = new Random(7L);
@@ -184,6 +202,46 @@ public class SavedTerrainCaptureEquivalenceTest {
return new BoundaryColumnGeometry.Voxel(state, phase, fluid, protectedContent);
}
private static final class RunScanningSource implements SavedTerrainChunk.VoxelSource {
private final RandomSource source;
private final Random splits;
private RunScanningSource(RandomSource source, Random splits) {
this.source = source;
this.splits = splits;
}
@Override
public OptionalInt groundSurface(int localX, int localZ) {
return source.groundSurface(localX, localZ);
}
@Override
public BoundaryColumnGeometry.Voxel voxel(int localX, int worldY, int localZ) {
return source.voxel(localX, worldY, localZ);
}
@Override
public String biome(int localX, int worldY, int localZ) {
return source.biome(localX, worldY, localZ);
}
@Override
public void scanColumn(int localX, int localZ, int minimumY, int height, SavedTerrainChunk.VoxelRuns runs) {
int start = 0;
BoundaryColumnGeometry.Voxel current = source.voxel(localX, minimumY, localZ);
for (int offset = 1; offset < height; offset++) {
BoundaryColumnGeometry.Voxel next = source.voxel(localX, minimumY + offset, localZ);
if (next != current || splits.nextInt(16) == 0) {
runs.add(start, offset, current);
start = offset;
current = next;
}
}
runs.add(start, height, current);
}
}
private static final class RandomSource implements SavedTerrainChunk.VoxelSource {
private final BoundaryColumnGeometry.Voxel[][] voxels = new BoundaryColumnGeometry.Voxel[256][HEIGHT];
private final String[][] biomes = new String[256][HEIGHT];