Merged wave1 into terrain bake

# Conflicts:
#	core/src/main/java/art/arcane/iris/generation/runtime/IrisComplex.java
#	probe/src/main/java/art/arcane/iris/probe/GenerationProbe.java
This commit is contained in:
Brian Neumann-Fopiano committed 2026-10-22 02:16:40 -04:00
commit d2106806a5
71 files changed
+5882 -948

No files matched your search

+10 -1
View File
@@ -171,10 +171,19 @@ tasks.register('genProbe', JavaExec) {
systemProperty('iris.probe.expectedBackend', providers.gradleProperty('probeExpectedBackend').orElse('').get())
systemProperty('iris.probe.requireNativeSamples', providers.gradleProperty('probeRequireNativeSamples').orElse('false').get())
if (providers.gradleProperty('probeNativeLibrary').isPresent()) {
systemProperty('volmlib.noise.nativeBillowLibrary', providers.gradleProperty('probeNativeLibrary').get())
systemProperty('volmlib.native.library', providers.gradleProperty('probeNativeLibrary').get())
}
if (providers.gradleProperty('probeNativePolicy').isPresent()) {
systemProperty('volmlib.native', providers.gradleProperty('probeNativePolicy').get())
}
systemProperty('iris.probe.threads', providers.gradleProperty('probeThreads').orElse('1').get())
systemProperty('iris.probe.pregenProfile', providers.gradleProperty('probePregenProfile').orElse('false').get())
systemProperty('iris.probe.hydrologyPreplan', providers.gradleProperty('probeHydrologyPreplan').orElse('false').get())
systemProperty('iris.probe.historyRouter', providers.gradleProperty('probeHistoryRouter').orElse('false').get())
systemProperty('iris.probe.productionBuffers', providers.gradleProperty('probeProductionBuffers').orElse('false').get())
String probeCounters = providers.gradleProperty('probeCounters').orElse('false').get()
systemProperty('iris.generation.counters', probeCounters)
systemProperty('volmlib.cache.countContention', probeCounters)
dependsOn(':core:compileJava')
doFirst {
if (!probePack.isPresent() || probePack.get().isBlank()) {
@@ -20,6 +20,7 @@ package art.arcane.iris.probe;
import art.arcane.volmlib.nativelib.terrain.NativeBiome;
import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
import art.arcane.volmlib.nativelib.terrain.NativeBlockVolume;
import art.arcane.volmlib.util.hunk.Hunk;
import java.io.BufferedInputStream;
@@ -84,11 +85,12 @@ public final class ChunkDump {
char[] biomeIndices = new char[size];
PaletteBuilder blockPalette = new PaletteBuilder();
PaletteBuilder biomePalette = new PaletteBuilder();
NativeBlockVolume stored = blocks instanceof NativeBlockVolume volume ? volume : null;
int index = 0;
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
for (int y = 0; y < height; y++) {
NativeBlockState state = blocks.get(x, y, z);
NativeBlockState state = stored == null ? blocks.get(x, y, z) : stored.getStoredRaw(x, y, z);
NativeBiome biome = biomes.get(x, y, z);
blockIndices[index] = blockPalette.indexOf(state, state == null ? NULL_BLOCK_KEY : null);
biomeIndices[index] = biomePalette.indexOf(biome, biome == null ? NULL_BIOME_KEY : null);
@@ -20,6 +20,8 @@ package art.arcane.iris.probe;
import art.arcane.iris.generation.bake.BakePlane;
import art.arcane.iris.generation.bake.GeneratorEvaluations;
import art.arcane.iris.generation.mantle.IrisCaveCarver3D;
import art.arcane.iris.pack.datapack.DataVersion;
import art.arcane.iris.pack.loading.IrisData;
import art.arcane.iris.pack.PackValidationResult;
import art.arcane.iris.pack.PackValidator;
@@ -40,6 +42,9 @@ import art.arcane.iris.generation.hydrology.HydrologyTileCache;
import art.arcane.iris.generation.hydrology.runtime.IrisHydrologyRuntime;
import art.arcane.iris.generation.terrain.IrisDimension;
import art.arcane.iris.world.IrisWorld;
import art.arcane.iris.world.history.GenerationHistory;
import art.arcane.iris.world.history.GenerationHistoryRuntimeRouter;
import art.arcane.iris.world.history.IrisBoundarySignatureSampler;
import art.arcane.iris.world.pregen.PregenPerformanceProfile;
import art.arcane.iris.spi.IrisPlatforms;
import art.arcane.iris.spi.IrisServices;
@@ -63,6 +68,7 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -170,7 +176,8 @@ public final class GenerationProbe {
}
}
record ProbeOptions(int threads, Path dumpFile, boolean pregenProfile) {
record ProbeOptions(int threads, Path dumpFile, boolean pregenProfile, boolean hydrologyPreplan,
boolean historyRouter, boolean productionBuffers, boolean counters) {
ProbeOptions {
if (threads < 1) {
throw new IllegalArgumentException("Thread count must be at least 1.");
@@ -185,7 +192,15 @@ public final class GenerationProbe {
return new ProbeOptions(
Integer.parseInt(System.getProperty("iris.probe.threads", "1").trim()),
dump.isEmpty() ? null : Path.of(dump),
Boolean.parseBoolean(System.getProperty("iris.probe.pregenProfile", "false").trim()));
booleanProperty("iris.probe.pregenProfile"),
booleanProperty("iris.probe.hydrologyPreplan"),
booleanProperty("iris.probe.historyRouter"),
booleanProperty("iris.probe.productionBuffers"),
booleanProperty("iris.generation.counters"));
}
private static boolean booleanProperty(String key) {
return Boolean.parseBoolean(System.getProperty(key, "false").trim());
}
boolean parallel() {
@@ -304,6 +319,7 @@ public final class GenerationProbe {
try {
ProbeResult result = run(configuration, options);
System.out.println(result.machineLine());
System.out.println(IrisCaveCarver3D.cellCounters().machineLine());
exitCode = result.failedChunks() == 0 ? 0 : 1;
} catch (Throwable e) {
System.out.println("[genprobe] FAIL: probe execution failed");
@@ -356,7 +372,7 @@ public final class GenerationProbe {
Throwable executionFailure = null;
try {
File pack = clonePack(configuration.packSource(), workRoot);
configureProbeRuntime(workRoot);
configureProbeRuntime(workRoot, options.historyRouter());
validatePack(pack);
System.out.println("[genprobe] pack: " + configuration.packSource().getAbsolutePath());
@@ -369,29 +385,48 @@ public final class GenerationProbe {
System.out.println("[genprobe] studio: " + configuration.studio());
System.out.println("[genprobe] threads: " + options.threads());
System.out.println("[genprobe] pregen profile: " + options.pregenProfile());
System.out.println("[genprobe] hydrology preplan: " + options.hydrologyPreplan());
System.out.println("[genprobe] history router: " + options.historyRouter());
System.out.println("[genprobe] production buffers: " + options.productionBuffers());
System.out.println("[genprobe] counters: " + options.counters());
System.out.println("[genprobe] dump file: " + (options.dumpFile() == null ? "none" : options.dumpFile()));
long engineStart = System.nanoTime();
File worldFolder = new File(workRoot, "world");
data = IrisData.get(pack);
IrisDimension dimension = data.getDimensionLoader().load(configuration.dimensionKey());
if (dimension == null) {
throw new IllegalStateException("Dimension '" + configuration.dimensionKey()
+ "' did not load from " + pack.getAbsolutePath());
IrisDimension dimension = loadDimension(data, configuration.dimensionKey());
GenerationHistory history = null;
if (options.historyRouter()) {
history = HeadlessGenerationHistorySession.create(new RealPackProbeSupport.HistoryRequest(
worldFolder.toPath(), data, dimension, configuration.seed()), DataVersion.getLatest().get());
data.close();
data = IrisData.openRuntime(history.activePackRoot().toFile());
data.bindGenerationRegistryContract(history.activeEpoch().registryContract());
dimension = loadDimension(data, configuration.dimensionKey());
}
IrisWorld world = IrisWorld.builder()
.platformIdentity("iris:probe")
.name("probe")
.seed(configuration.seed())
.worldFolder(new File(workRoot, "world"))
.worldFolder(worldFolder)
.minHeight(dimension.getMinHeight())
.maxHeight(dimension.getMaxHeight())
.build();
EngineTarget target = new EngineTarget(world, dimension, data);
engine = new IrisEngine(
target,
configuration.studio()
? IrisEngine.InitializationMode.STUDIO
: IrisEngine.InitializationMode.RUNTIME);
IrisEngine.InitializationMode mode = configuration.studio()
? IrisEngine.InitializationMode.STUDIO
: IrisEngine.InitializationMode.RUNTIME;
GenerationHistoryRuntimeRouter router = null;
if (history == null) {
engine = new IrisEngine(target, mode);
} else {
IrisEngine historyEngine = new IrisEngine(target, mode,
history.paths().activationMantleRoot(history.activeActivation().activationId()),
history.activeEpoch().kernelVersion(), null);
engine = historyEngine;
router = GenerationHistoryRuntimeRouter.attach(historyEngine, history, IrisBoundarySignatureSampler.INSTANCE);
router.preloadActiveRuntimes();
}
long engineReadyNanos = System.nanoTime() - engineStart;
List<Throwable> initNoise = settleAndDrain();
@@ -400,9 +435,16 @@ public final class GenerationProbe {
+ " seed=" + engine.getSeedManager().getSeed()
+ " minY=" + engine.getMinHeight() + " maxY=" + engine.getMaxHeight()
+ " timeMs=" + String.format(Locale.ROOT, "%.3f", nanosToMillis(engineReadyNanos)));
System.out.println("IRIS_GENPROBE_MANTLE version=1 radius=" + engine.getMantle().getRadius()
+ " real_radius=" + engine.getMantle().getRealRadius());
ProbeChunkGenerator generator = new ProbeChunkGenerator(engine, router, options.productionBuffers());
long nativeSamplesBefore = FractalBillowSimplexNoise.nativeSampleCount();
GenerationResult generation = generateWithProfile(engine, configuration, options);
GenerationResult generation = generateWithProfile(generator, configuration, options);
verifyBackend(nativeSamplesBefore);
if (options.productionBuffers()) {
System.out.println("IRIS_GENPROBE_BUFFERS version=1 mode=production biome_expanded_chunks="
+ generator.expandedBiomeHunks());
}
String status = generation.failedChunks() == 0 ? "PASS" : "FAIL";
printGenerationFailures(generation);
return new ProbeResult(
@@ -447,7 +489,16 @@ public final class GenerationProbe {
}
}
private static void configureProbeRuntime(File workRoot) {
private static IrisDimension loadDimension(IrisData data, String dimensionKey) {
IrisDimension dimension = data.getDimensionLoader().load(dimensionKey);
if (dimension == null) {
throw new IllegalStateException("Dimension '" + dimensionKey + "' did not load from "
+ data.getDataFolder().getAbsolutePath());
}
return dimension;
}
private static void configureProbeRuntime(File workRoot, boolean generationRegistry) {
REPORTED.clear();
StubPlatform.bindGenerationStateHandlers();
StubPlatform.verbose(false);
@@ -458,7 +509,8 @@ public final class GenerationProbe {
IrisServices.register(EngineEffectsProvider.class, (EngineEffectsProvider) InertEffects::new);
IrisServices.register(EnginePlatformHooks.class, new InertPlatformHooks());
IrisPlatforms.unbind();
IrisPlatforms.bind(new StubPlatform(new File(workRoot, "platform-data")));
IrisPlatforms.bind(new StubPlatform(new File(workRoot, "platform-data"),
generationRegistry ? ProbeGenerationRegistry.INSTANCE : null));
}
private static void validatePack(File pack) {
@@ -475,21 +527,35 @@ public final class GenerationProbe {
System.out.println("[genprobe] offline pack validation: PASS");
}
private static GenerationResult generateWithProfile(Engine engine, ProbeConfiguration configuration,
private static GenerationResult generateWithProfile(ProbeChunkGenerator generator, ProbeConfiguration configuration,
ProbeOptions options) throws Exception {
Engine engine = generator.engine();
if (!options.pregenProfile()) {
return generate(engine, configuration, options);
preplanHydrology(engine, configuration, options);
return generate(generator, configuration, options);
}
PregenPerformanceProfile.apply(engine);
IrisHydrologyRuntime hydrology = engine.getComplex().getHydrologyRuntime();
try (HydrologyTileCache.PregenerationScope ignored = hydrology == null ? null
: hydrology.preparePregeneration(pregenerationArea(configuration))) {
return generate(engine, configuration, options);
preplanHydrology(engine, configuration, options);
return generate(generator, configuration, options);
} finally {
PregenPerformanceProfile.restore();
}
}
private static void preplanHydrology(Engine engine, ProbeConfiguration configuration, ProbeOptions options)
throws Exception {
if (!options.hydrologyPreplan()) {
return;
}
Optional<ProbeHydrologyPreplan> preplan = ProbeHydrologyPreplan.run(engine, configuration);
printDistinctCauses("hydrology preplan reported errors", drainReported());
System.out.println(preplan.map(ProbeHydrologyPreplan::machineLine)
.orElse("IRIS_GENPROBE_HYDROLOGY_PREPLAN version=1 tiles=0 skipped=no-hydrology"));
}
static HydrologyTileCache.PregenerationArea pregenerationArea(ProbeConfiguration configuration) {
List<ChunkCoordinate> coordinates = scheduleCoordinates(
configuration.warmupChunks() + configuration.measuredChunks(),
@@ -533,8 +599,9 @@ public final class GenerationProbe {
return Math.atan2(coordinate.z() - centerChunkZ, coordinate.x() - centerChunkX);
}
private static GenerationResult generate(Engine engine, ProbeConfiguration configuration,
private static GenerationResult generate(ProbeChunkGenerator generator, ProbeConfiguration configuration,
ProbeOptions options) throws Exception {
Engine engine = generator.engine();
int totalChunks = configuration.warmupChunks() + configuration.measuredChunks();
List<ChunkCoordinate> coordinates = scheduleCoordinates(
totalChunks, configuration.centerChunkX(), configuration.centerChunkZ());
@@ -543,31 +610,44 @@ public final class GenerationProbe {
engine.getMinHeight(), height);
try (OutcomeSink sink = new OutcomeSink(options.dumpFile(), header, configuration.measuredChunks())) {
if (options.parallel()) {
generateParallel(engine, configuration, options, coordinates, sink);
generateParallel(generator, configuration, options, coordinates, sink);
} else {
generateSequential(engine, configuration, coordinates, sink);
generateSequential(generator, configuration, options, coordinates, sink);
}
return sink.result(configuration);
}
}
private static void generateSequential(Engine engine, ProbeConfiguration configuration,
List<ChunkCoordinate> coordinates, OutcomeSink sink) throws IOException {
private static void generateSequential(ProbeChunkGenerator generator, ProbeConfiguration configuration,
ProbeOptions options, List<ChunkCoordinate> coordinates,
OutcomeSink sink) throws IOException {
int totalChunks = coordinates.size();
long measuredStartEpochMillis = 0L;
ProbeWindowMetrics measuredStart = null;
GeneratorEvaluations.Snapshot measuredStartEvaluations = GeneratorEvaluations.snapshot();
for (int index = 0; index < totalChunks; index++) {
if (index == configuration.warmupChunks()) {
measuredStartEpochMillis = System.currentTimeMillis();
measuredStartEvaluations = GeneratorEvaluations.snapshot();
measuredStart = ProbeWindowMetrics.capture(generator.engine(), options.counters());
}
drainReported();
ChunkOutcome outcome = generateChunk(engine, index, coordinates.get(index), configuration.multicore());
ChunkOutcome outcome = generator.generate(index, coordinates.get(index), configuration.multicore());
outcome.failures().addAll(drainReported());
sink.accept(outcome, index >= configuration.warmupChunks());
printProgress(configuration, index + 1, totalChunks);
}
printWindow(engine, measuredStartEpochMillis, System.currentTimeMillis(), measuredStartEvaluations);
printWindow(generator.engine(), measuredStartEpochMillis, System.currentTimeMillis(), measuredStartEvaluations);
printWindowMetrics(measuredStart, ProbeWindowMetrics.capture(generator.engine(), options.counters()),
configuration.measuredChunks(), options);
}
private static void printWindowMetrics(ProbeWindowMetrics start, ProbeWindowMetrics end, int chunks,
ProbeOptions options) {
System.out.println(ProbeWindowMetrics.stagesLine(start, end, chunks, options.threads()));
if (options.counters()) {
System.out.println(ProbeWindowMetrics.countersLine(start, end, chunks));
}
}
private static void printWindow(Engine engine, long startEpochMillis, long endEpochMillis, GeneratorEvaluations.Snapshot startEvaluations) {
@@ -585,8 +665,9 @@ public final class GenerationProbe {
System.out.println(planes);
}
private static void generateParallel(Engine engine, ProbeConfiguration configuration, ProbeOptions options,
List<ChunkCoordinate> coordinates, OutcomeSink sink) throws Exception {
private static void generateParallel(ProbeChunkGenerator generator, ProbeConfiguration configuration,
ProbeOptions options, List<ChunkCoordinate> coordinates,
OutcomeSink sink) throws Exception {
ChunkOutcome[] outcomes = new ChunkOutcome[coordinates.size()];
ExecutorService executor = Executors.newFixedThreadPool(options.threads());
long warmupWallNanos;
@@ -599,7 +680,7 @@ public final class GenerationProbe {
warmup.add(index);
}
long warmupStarted = System.nanoTime();
runPhase(executor, engine, configuration, coordinates, warmup, outcomes);
runPhase(executor, generator, configuration, coordinates, warmup, outcomes);
warmupWallNanos = System.nanoTime() - warmupStarted;
unattributed.addAll(drainReported());
System.out.println("[genprobe] warmup complete: " + configuration.warmupChunks() + " chunks on "
@@ -607,12 +688,15 @@ public final class GenerationProbe {
List<Integer> measured = spiralOrder(coordinates, configuration.warmupChunks(),
configuration.centerChunkX(), configuration.centerChunkZ());
ProbeWindowMetrics measuredStart = ProbeWindowMetrics.capture(generator.engine(), options.counters());
long measuredStartEpochMillis = System.currentTimeMillis();
GeneratorEvaluations.Snapshot measuredStartEvaluations = GeneratorEvaluations.snapshot();
long measuredStarted = System.nanoTime();
runPhase(executor, engine, configuration, coordinates, measured, outcomes);
runPhase(executor, generator, configuration, coordinates, measured, outcomes);
measuredWallNanos = System.nanoTime() - measuredStarted;
printWindow(engine, measuredStartEpochMillis, System.currentTimeMillis(), measuredStartEvaluations);
printWindow(generator.engine(), measuredStartEpochMillis, System.currentTimeMillis(), measuredStartEvaluations);
printWindowMetrics(measuredStart, ProbeWindowMetrics.capture(generator.engine(), options.counters()),
configuration.measuredChunks(), options);
unattributed.addAll(drainReported());
System.out.println("[genprobe] measured progress: " + configuration.measuredChunks() + "/"
+ configuration.measuredChunks());
@@ -646,13 +730,13 @@ public final class GenerationProbe {
System.out.println(throughput.machineLine());
}
private static void runPhase(ExecutorService executor, Engine engine, ProbeConfiguration configuration,
List<ChunkCoordinate> coordinates, List<Integer> order,
ChunkOutcome[] outcomes) throws Exception {
private static void runPhase(ExecutorService executor, ProbeChunkGenerator generator,
ProbeConfiguration configuration, List<ChunkCoordinate> coordinates,
List<Integer> order, ChunkOutcome[] outcomes) throws Exception {
List<Future<ChunkOutcome>> futures = new ArrayList<>(order.size());
for (int index : order) {
ChunkCoordinate coordinate = coordinates.get(index);
futures.add(executor.submit(() -> generateChunk(engine, index, coordinate, configuration.multicore())));
futures.add(executor.submit(() -> generator.generate(index, coordinate, configuration.multicore())));
}
for (Future<ChunkOutcome> future : futures) {
ChunkOutcome outcome;
@@ -672,21 +756,6 @@ public final class GenerationProbe {
}
}
private static ChunkOutcome generateChunk(Engine engine, int index, ChunkCoordinate coordinate,
boolean multicore) {
int height = engine.getTarget().getHeight();
List<Throwable> failures = new ArrayList<>();
Hunk<NativeBlockState> blocks = Hunk.newArrayHunk(16, height, 16);
Hunk<NativeBiome> biomes = Hunk.newArrayHunk(16, height, 16);
long started = System.nanoTime();
try {
engine.generate(coordinate.x() << 4, coordinate.z() << 4, blocks, biomes, multicore);
} catch (Throwable e) {
failures.add(e);
}
return new ChunkOutcome(index, coordinate, System.nanoTime() - started, blocks, biomes, failures);
}
private static void printProgress(ProbeConfiguration configuration, int completed, int totalChunks) {
if (completed == configuration.warmupChunks()) {
System.out.println("[genprobe] warmup complete: " + configuration.warmupChunks() + " chunks");
@@ -0,0 +1,99 @@
/*
* Iris is a World Generator for Minecraft 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.probe;
import art.arcane.iris.generation.chunk.SectionBuffer;
import art.arcane.iris.generation.context.IrisContext;
import art.arcane.iris.generation.runtime.Engine;
import art.arcane.iris.generation.runtime.GenerationSessionLease;
import art.arcane.iris.spi.IrisPlatforms;
import art.arcane.iris.world.history.GenerationHistoryRuntimeRouter;
import art.arcane.volmlib.nativelib.terrain.NativeBiome;
import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
import art.arcane.volmlib.util.hunk.Hunk;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
final class ProbeChunkGenerator {
private final Engine engine;
private final GenerationHistoryRuntimeRouter router;
private final boolean productionBuffers;
private final AtomicInteger expandedBiomeHunks = new AtomicInteger();
ProbeChunkGenerator(Engine engine, GenerationHistoryRuntimeRouter router, boolean productionBuffers) {
this.engine = engine;
this.router = router;
this.productionBuffers = productionBuffers;
}
Engine engine() {
return engine;
}
int expandedBiomeHunks() {
return expandedBiomeHunks.get();
}
GenerationProbe.ChunkOutcome generate(int index, GenerationProbe.ChunkCoordinate coordinate, boolean multicore) {
int height = engine.getTarget().getHeight();
List<Throwable> failures = new ArrayList<>();
Hunk<NativeBlockState> blocks = newBlocks(height);
Hunk<NativeBiome> biomes = newBiomes(height);
long started = System.nanoTime();
try {
generateInto(coordinate.x() << 4, coordinate.z() << 4, blocks, biomes, multicore);
} catch (Throwable e) {
failures.add(e);
}
long elapsed = System.nanoTime() - started;
if (biomes instanceof ProductionBiomeHunk production && production.expanded()) {
expandedBiomeHunks.incrementAndGet();
}
return new GenerationProbe.ChunkOutcome(index, coordinate, elapsed, blocks, biomes, failures);
}
private Hunk<NativeBlockState> newBlocks(int height) {
return productionBuffers ? new SectionBuffer(height) : Hunk.newArrayHunk(16, height, 16);
}
private Hunk<NativeBiome> newBiomes(int height) {
return productionBuffers ? new ProductionBiomeHunk(height, ProbeChunkGenerator::plainsBiome)
: Hunk.newArrayHunk(16, height, 16);
}
private void generateInto(int blockX, int blockZ, Hunk<NativeBlockState> blocks, Hunk<NativeBiome> biomes,
boolean multicore) throws Exception {
if (router == null) {
engine.generate(blockX, blockZ, blocks, biomes, multicore);
return;
}
try (GenerationHistoryRuntimeRouter.CoordinateScope historyScope = router.openCoordinateScope(blockX, blockZ);
GenerationSessionLease lease = engine.acquireGenerationLease("probe_terrain_stage");
IrisContext.Scope ignored = IrisContext.open(engine, lease.sessionId(), null)) {
engine.generate(blockX, blockZ, blocks, biomes, multicore);
historyScope.claimGeneratedSemantics();
}
}
private static NativeBiome plainsBiome() {
return IrisPlatforms.get().registries().biome("minecraft:plains");
}
}
@@ -0,0 +1,51 @@
/*
* Iris is a World Generator for Minecraft 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.probe;
import art.arcane.iris.spi.PlatformGenerationRegistry;
import java.util.List;
import java.util.Locale;
final class ProbeGenerationRegistry implements PlatformGenerationRegistry {
static final ProbeGenerationRegistry INSTANCE = new ProbeGenerationRegistry();
private ProbeGenerationRegistry() {
}
@Override
public String runtimeIdentity() {
return "iris-probe";
}
@Override
public List<String> legacyCustomBiomeResourceKeys(String packName, String dimensionKey, String customBiomeId) {
return List.of(dimensionKey.toLowerCase(Locale.ROOT) + ":" + customBiomeId.toLowerCase(Locale.ROOT));
}
@Override
public String dimensionTypeResourceKey(String packName, String dimensionKey, String dimensionTypeKey) {
return "iris:" + dimensionTypeKey.toLowerCase(Locale.ROOT);
}
@Override
public Definition definition(String registryKey, String resourceKey) {
return Definition.resourceIdentity(registryKey, resourceKey);
}
}
@@ -0,0 +1,109 @@
/*
* Iris is a World Generator for Minecraft 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.probe;
import art.arcane.iris.generation.hydrology.HydrologyPlannerSettings;
import art.arcane.iris.generation.hydrology.HydrologyTileCache;
import art.arcane.iris.generation.hydrology.HydrologyTileKey;
import art.arcane.iris.generation.hydrology.runtime.IrisHydrologyRuntime;
import art.arcane.iris.generation.runtime.Engine;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
record ProbeHydrologyPreplan(int tiles, int tileSize, int publicationRadius, int reachChunks, long elapsedNanos) {
private static final int PLANNING_THREADS = 4;
static Optional<ProbeHydrologyPreplan> run(Engine engine, GenerationProbe.ProbeConfiguration configuration)
throws Exception {
IrisHydrologyRuntime hydrology = engine.getComplex().getHydrologyRuntime();
if (hydrology == null) {
return Optional.empty();
}
HydrologyPlannerSettings settings = hydrology.settings();
int tileSize = settings.routing().tileSize();
int publicationRadius = settings.publicationRadius();
int reachChunks = Math.max(engine.getMantle().getRadius(), engine.getMantle().getRealRadius());
List<HydrologyTileKey> keys = tiles(configuration, tileSize, publicationRadius, reachChunks);
long started = System.nanoTime();
ExecutorService executor = Executors.newFixedThreadPool(Math.min(keys.size(), PLANNING_THREADS));
try {
List<Future<?>> plans = new ArrayList<>(keys.size());
for (HydrologyTileKey key : keys) {
plans.add(executor.submit(() -> hydrology.tile(key)));
}
for (Future<?> plan : plans) {
await(plan);
}
} finally {
executor.shutdown();
}
return Optional.of(new ProbeHydrologyPreplan(keys.size(), tileSize, publicationRadius, reachChunks,
System.nanoTime() - started));
}
static List<HydrologyTileKey> tiles(GenerationProbe.ProbeConfiguration configuration, int tileSize,
int publicationRadius, int reachChunks) {
HydrologyTileCache.PregenerationArea area = GenerationProbe.pregenerationArea(configuration);
long reach = (long) reachChunks * 16L + publicationRadius;
int minimumX = Math.toIntExact(Math.floorDiv(area.minimumBlockX() - reach, tileSize)) - 1;
int minimumZ = Math.toIntExact(Math.floorDiv(area.minimumBlockZ() - reach, tileSize)) - 1;
int maximumX = Math.toIntExact(Math.floorDiv(area.maximumBlockX() + reach, tileSize)) + 1;
int maximumZ = Math.toIntExact(Math.floorDiv(area.maximumBlockZ() + reach, tileSize)) + 1;
int centerX = Math.floorDiv(area.centerBlockX(), tileSize);
int centerZ = Math.floorDiv(area.centerBlockZ(), tileSize);
List<HydrologyTileKey> keys = new ArrayList<>((maximumX - minimumX + 1) * (maximumZ - minimumZ + 1));
for (int tileZ = minimumZ; tileZ <= maximumZ; tileZ++) {
for (int tileX = minimumX; tileX <= maximumX; tileX++) {
keys.add(new HydrologyTileKey(tileX, tileZ));
}
}
keys.sort(Comparator.comparingInt((HydrologyTileKey key) ->
Math.max(Math.abs(key.tileX() - centerX), Math.abs(key.tileZ() - centerZ))));
return keys;
}
String machineLine() {
return String.format(Locale.ROOT,
"IRIS_GENPROBE_HYDROLOGY_PREPLAN version=1 tiles=%d tile_size=%d publication_radius=%d reach_chunks=%d planned_ms=%.3f",
tiles, tileSize, publicationRadius, reachChunks, elapsedNanos / 1_000_000D);
}
private static void await(Future<?> plan) throws Exception {
try {
plan.get();
} catch (ExecutionException failure) {
Throwable cause = failure.getCause();
if (cause instanceof Error error) {
throw error;
}
if (cause instanceof Exception exception) {
throw exception;
}
throw new IllegalStateException(cause);
}
}
}
@@ -0,0 +1,67 @@
/*
* Iris is a World Generator for Minecraft 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.probe;
import art.arcane.iris.generation.runtime.Engine;
import art.arcane.iris.generation.runtime.EngineMetrics;
import art.arcane.iris.generation.runtime.GenerationCounter;
import art.arcane.volmlib.util.cache.CacheContention;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
record ProbeWindowMetrics(Map<String, EngineMetrics.StageTotal> stages, Map<String, Long> counters) {
static ProbeWindowMetrics capture(Engine engine, boolean counters) {
return new ProbeWindowMetrics(engine.getMetrics().stageTotals(), counters ? counterValues() : Map.of());
}
static String stagesLine(ProbeWindowMetrics start, ProbeWindowMetrics end, int chunks, int threads) {
StringBuilder line = new StringBuilder(512);
line.append("IRIS_GENPROBE_STAGES version=1 chunks=").append(chunks).append(" threads=").append(threads);
for (Map.Entry<String, EngineMetrics.StageTotal> entry : end.stages().entrySet()) {
EngineMetrics.StageTotal earlier = start.stages().get(entry.getKey());
EngineMetrics.StageTotal window = earlier == null ? entry.getValue() : entry.getValue().since(earlier);
line.append(' ').append(entry.getKey()).append('=')
.append(String.format(Locale.ROOT, "%.3f", window.millis()));
}
return line.toString();
}
static String countersLine(ProbeWindowMetrics start, ProbeWindowMetrics end, int chunks) {
StringBuilder line = new StringBuilder(256);
line.append("IRIS_GENPROBE_COUNTERS version=1 chunks=").append(chunks);
for (Map.Entry<String, Long> entry : end.counters().entrySet()) {
long window = entry.getValue() - start.counters().getOrDefault(entry.getKey(), 0L);
line.append(' ').append(entry.getKey()).append('=').append(window)
.append(' ').append(entry.getKey()).append("_per_chunk=")
.append(String.format(Locale.ROOT, "%.1f", chunks == 0 ? 0D : window / (double) chunks));
}
return line.toString();
}
private static Map<String, Long> counterValues() {
Map<String, Long> values = new LinkedHashMap<>();
for (GenerationCounter counter : GenerationCounter.values()) {
values.put(counter.key(), counter.sum());
}
values.put("cache_duplicate_computations", CacheContention.duplicateComputations());
return values;
}
}
@@ -0,0 +1,112 @@
/*
* Iris is a World Generator for Minecraft 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.probe;
import art.arcane.volmlib.nativelib.terrain.NativeBiome;
import art.arcane.volmlib.util.hunk.HunkMutationSupport;
import art.arcane.volmlib.util.hunk.storage.StorageHunk;
import java.util.function.Supplier;
final class ProductionBiomeHunk extends StorageHunk<NativeBiome> {
private static final int CHUNK_SIZE = 16;
private final NativeBiome[] columns = new NativeBiome[CHUNK_SIZE * CHUNK_SIZE];
private final Supplier<NativeBiome> fallback;
private NativeBiome[] cells;
private volatile NativeBiome defaultBiome;
ProductionBiomeHunk(int height, Supplier<NativeBiome> fallback) {
super(CHUNK_SIZE, height, CHUNK_SIZE);
this.fallback = fallback;
}
@Override
public void set(int x1, int y1, int z1, int x2, int y2, int z2, NativeBiome biome) {
if (x1 == x2 && z1 == z2 && y1 == 0 && y2 == getHeight() - 1) {
fillColumn(x1, z1, biome);
return;
}
HunkMutationSupport.setRangeInclusive(this, x1, y1, z1, x2, y2, z2, biome);
}
@Override
public void setRaw(int x, int y, int z, NativeBiome biome) {
NativeBiome[] storage = cells == null ? expand() : cells;
storage[cellIndex(x, y, z)] = biome;
}
@Override
public NativeBiome getRaw(int x, int y, int z) {
NativeBiome[] storage = cells;
NativeBiome biome = storage == null ? columns[columnIndex(x, z)] : storage[cellIndex(x, y, z)];
if (biome != null) {
return biome;
}
NativeBiome resolved = defaultBiome;
if (resolved == null) {
resolved = fallback.get();
defaultBiome = resolved;
}
return resolved;
}
boolean expanded() {
return cells != null;
}
private void fillColumn(int x, int z, NativeBiome biome) {
NativeBiome[] storage = cells;
if (storage == null) {
columns[columnIndex(x, z)] = biome;
return;
}
int stride = CHUNK_SIZE * CHUNK_SIZE;
int index = columnIndex(x, z);
for (int y = 0; y < getHeight(); y++) {
storage[index] = biome;
index += stride;
}
}
private NativeBiome[] expand() {
NativeBiome[] storage = new NativeBiome[CHUNK_SIZE * getHeight() * CHUNK_SIZE];
int stride = CHUNK_SIZE * CHUNK_SIZE;
for (int column = 0; column < stride; column++) {
NativeBiome biome = columns[column];
if (biome == null) {
continue;
}
for (int index = column; index < storage.length; index += stride) {
storage[index] = biome;
}
}
cells = storage;
return storage;
}
private static int columnIndex(int x, int z) {
return ((z & (CHUNK_SIZE - 1)) * CHUNK_SIZE) + (x & (CHUNK_SIZE - 1));
}
private int cellIndex(int x, int y, int z) {
int clampedY = Math.max(0, Math.min(getHeight() - 1, y));
return (clampedY * CHUNK_SIZE * CHUNK_SIZE) + columnIndex(x, z);
}
}
@@ -26,6 +26,7 @@ import art.arcane.iris.spi.IrisPlatform;
import art.arcane.iris.spi.LogLevel;
import art.arcane.volmlib.nativelib.terrain.NativeBiome;
import art.arcane.iris.spi.PlatformBiomeWriter;
import art.arcane.iris.spi.PlatformGenerationRegistry;
import art.arcane.volmlib.nativelib.terrain.NativeBlockProperty;
import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
import art.arcane.volmlib.nativelib.entity.NativeEntityType;
@@ -78,7 +79,7 @@ public final class StubPlatform implements IrisPlatform {
return StubBlockState.of(key);
}
private final StubRegistries registries = new StubRegistries();
private final StubRegistries registries;
private final StubScheduler scheduler = new StubScheduler();
private final StubStructureHooks structureHooks = new StubStructureHooks();
private final StubBiomeWriter biomeWriter = new StubBiomeWriter();
@@ -89,7 +90,12 @@ public final class StubPlatform implements IrisPlatform {
}
public StubPlatform(File dataFolder) {
this(dataFolder, null);
}
public StubPlatform(File dataFolder, PlatformGenerationRegistry generationRegistry) {
this.dataFolder = dataFolder;
this.registries = new StubRegistries(generationRegistry);
}
private static final class StubBlockState implements NativeBlockState {
@@ -620,6 +626,20 @@ public final class StubPlatform implements IrisPlatform {
}
private static final class StubRegistries implements PlatformRegistries {
private final PlatformGenerationRegistry generationRegistry;
private StubRegistries(PlatformGenerationRegistry generationRegistry) {
this.generationRegistry = generationRegistry;
}
@Override
public PlatformGenerationRegistry generationRegistry() {
if (generationRegistry == null) {
return PlatformRegistries.super.generationRegistry();
}
return generationRegistry;
}
@Override
public NativeBlockState decodeBlockState(String key) {
return blockOrNull(key);
@@ -1,15 +1,20 @@
package art.arcane.iris.probe;
import art.arcane.iris.generation.hydrology.HydrologyTileCache;
import art.arcane.iris.generation.hydrology.HydrologyTileKey;
import art.arcane.iris.generation.runtime.EngineMetrics;
import org.junit.Test;
import java.io.File;
import java.nio.file.Path;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
@@ -128,7 +133,96 @@ public final class GenerationProbeTest {
GenerationProbe.ProbeOptions defaults = GenerationProbe.ProbeOptions.fromSystemProperties();
assertEquals(1, defaults.threads());
assertNull(defaults.dumpFile());
assertThrows(IllegalArgumentException.class, () -> new GenerationProbe.ProbeOptions(0, null, false));
assertThrows(IllegalArgumentException.class,
() -> new GenerationProbe.ProbeOptions(0, null, false, false, false, false, false));
assertFalse(defaults.hydrologyPreplan());
assertFalse(defaults.historyRouter());
assertFalse(defaults.productionBuffers());
assertFalse(defaults.counters());
}
@Test
public void readsHarnessFidelityOptionsFromSystemProperties() {
System.setProperty("iris.probe.hydrologyPreplan", "true");
System.setProperty("iris.probe.historyRouter", "true");
System.setProperty("iris.probe.productionBuffers", "true");
System.setProperty("iris.generation.counters", "true");
try {
GenerationProbe.ProbeOptions options = GenerationProbe.ProbeOptions.fromSystemProperties();
assertTrue(options.hydrologyPreplan());
assertTrue(options.historyRouter());
assertTrue(options.productionBuffers());
assertTrue(options.counters());
assertFalse(options.parallel());
} finally {
System.clearProperty("iris.probe.hydrologyPreplan");
System.clearProperty("iris.probe.historyRouter");
System.clearProperty("iris.probe.productionBuffers");
System.clearProperty("iris.generation.counters");
}
}
@Test
public void stageLineReportsOnlyTheMeasuredWindow() {
Map<String, EngineMetrics.StageTotal> before = new LinkedHashMap<>();
before.put("total", new EngineMetrics.StageTotal(100D, 10L));
before.put("context.prefill.height", new EngineMetrics.StageTotal(0D, 0L));
Map<String, EngineMetrics.StageTotal> after = new LinkedHashMap<>();
after.put("total", new EngineMetrics.StageTotal(350.25D, 20L));
after.put("context.prefill.height", new EngineMetrics.StageTotal(12.5D, 10L));
String line = ProbeWindowMetrics.stagesLine(new ProbeWindowMetrics(before, Map.of()),
new ProbeWindowMetrics(after, Map.of()), 10, 1);
assertEquals("IRIS_GENPROBE_STAGES version=1 chunks=10 threads=1 total=250.250 context.prefill.height=12.500", line);
}
@Test
public void counterLineReportsWindowTotalsAndPerChunkRates() {
Map<String, Long> before = new LinkedHashMap<>();
before.put("generator_evaluations", 1_000L);
before.put("corner_starcasts", 0L);
Map<String, Long> after = new LinkedHashMap<>();
after.put("generator_evaluations", 5_000L);
after.put("corner_starcasts", 330L);
String line = ProbeWindowMetrics.countersLine(new ProbeWindowMetrics(Map.of(), before),
new ProbeWindowMetrics(Map.of(), after), 2);
assertEquals("IRIS_GENPROBE_COUNTERS version=1 chunks=2 generator_evaluations=4000"
+ " generator_evaluations_per_chunk=2000.0 corner_starcasts=330 corner_starcasts_per_chunk=165.0", line);
}
@Test
public void hydrologyPreplanCoversEveryTileAScheduledChunkComposesFromPlusTheNeighbourRing() {
GenerationProbe.ProbeConfiguration configuration = new GenerationProbe.ProbeConfiguration(
new File("/tmp/pack"), "overworld", 256, 1024, 2048, 2048, false, false, 1337L);
int tileSize = 512;
int publicationRadius = 96;
int reachChunks = 13;
List<HydrologyTileKey> keys = ProbeHydrologyPreplan.tiles(configuration, tileSize, publicationRadius, reachChunks);
Set<HydrologyTileKey> planned = new HashSet<>(keys);
assertEquals(keys.size(), planned.size());
for (GenerationProbe.ChunkCoordinate chunk : GenerationProbe.scheduleCoordinates(1280, 2048, 2048)) {
for (int offsetX = -reachChunks; offsetX <= reachChunks; offsetX++) {
for (int offsetZ = -reachChunks; offsetZ <= reachChunks; offsetZ++) {
long minimumX = (long) (chunk.x() + offsetX) * 16L;
long minimumZ = (long) (chunk.z() + offsetZ) * 16L;
int firstX = Math.floorDiv(Math.toIntExact(minimumX - publicationRadius), tileSize) - 1;
int lastX = Math.floorDiv(Math.toIntExact(minimumX + 15L + publicationRadius), tileSize) + 1;
int firstZ = Math.floorDiv(Math.toIntExact(minimumZ - publicationRadius), tileSize) - 1;
int lastZ = Math.floorDiv(Math.toIntExact(minimumZ + 15L + publicationRadius), tileSize) + 1;
for (int tileX = firstX; tileX <= lastX; tileX++) {
for (int tileZ = firstZ; tileZ <= lastZ; tileZ++) {
assertTrue(planned.contains(new HydrologyTileKey(tileX, tileZ)));
}
}
}
}
}
HydrologyTileKey first = keys.getFirst();
assertEquals(new HydrologyTileKey(Math.floorDiv(2048 * 16 + 8, tileSize), Math.floorDiv(2048 * 16 + 8, tileSize)), first);
}
@Test
@@ -0,0 +1,127 @@
package art.arcane.iris.probe;
import art.arcane.iris.generation.chunk.SectionBuffer;
import art.arcane.volmlib.nativelib.terrain.NativeBiome;
import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
import art.arcane.volmlib.util.hunk.Hunk;
import org.junit.Test;
import java.util.List;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
public final class ProductionHunkTest {
private static final int HEIGHT = 24;
@Test
public void blockHunkIgnoresNullWritesAndTracksTheHighestStoredCellPerColumn() {
SectionBuffer blocks = new SectionBuffer(HEIGHT);
NativeBlockState stone = StubPlatform.blockStateForTest("minecraft:stone");
NativeBlockState dirt = StubPlatform.blockStateForTest("minecraft:dirt");
blocks.setRaw(3, 5, 7, stone);
blocks.setRaw(3, 9, 7, dirt);
blocks.setRaw(3, 2, 7, stone);
blocks.setRaw(3, 9, 7, null);
assertSame(dirt, blocks.getStoredRaw(3, 9, 7));
assertSame(stone, blocks.getStoredRaw(3, 5, 7));
assertNull(blocks.getStoredRaw(3, 6, 7));
assertEquals(9, blocks.highestStoredY(3, 7));
assertEquals(-1, blocks.highestStoredY(7, 3));
}
@Test
public void dumpCaptureOfTheBlockHunkMatchesAnArrayHunkHoldingTheSameWrites() {
SectionBuffer production = new SectionBuffer(HEIGHT);
Hunk<NativeBlockState> array = Hunk.newArrayHunk(16, HEIGHT, 16);
Hunk<NativeBiome> biomes = Hunk.newArrayHunk(16, HEIGHT, 16);
NativeBlockState stone = StubPlatform.blockStateForTest("minecraft:stone");
NativeBlockState water = StubPlatform.blockStateForTest("minecraft:water");
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
int top = (x * 7 + z * 3) % HEIGHT;
for (int y = 0; y <= top; y++) {
NativeBlockState state = y == top ? water : stone;
production.setRaw(x, y, z, state);
array.setRaw(x, y, z, state);
}
}
}
ChunkDump.ChunkSnapshot fromProduction = ChunkDump.capture(4, -9, production, biomes, HEIGHT);
ChunkDump.ChunkSnapshot fromArray = ChunkDump.capture(4, -9, array, biomes, HEIGHT);
assertArrayEquals(fromArray.blockPalette(), fromProduction.blockPalette());
assertArrayEquals(fromArray.blocks(), fromProduction.blocks());
assertTrue(List.of(fromProduction.blockPalette()).contains(ChunkDump.NULL_BLOCK_KEY));
}
@Test
public void biomeHunkKeepsWholeColumnWritesPerColumnAndReadsLikeAnArrayHunk() {
ProductionBiomeHunk production = new ProductionBiomeHunk(HEIGHT, () -> biome("minecraft:plains"));
Hunk<NativeBiome> array = Hunk.newArrayHunk(16, HEIGHT, 16);
NativeBiome forest = biome("minecraft:forest");
NativeBiome desert = biome("minecraft:desert");
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
NativeBiome column = ((x + z) & 1) == 0 ? forest : desert;
production.set(x, 0, z, x, HEIGHT - 1, z, column);
array.set(x, 0, z, x, HEIGHT - 1, z, column);
}
}
assertFalse(production.expanded());
assertSameCells(array, production);
production.setRaw(2, 4, 9, desert);
array.setRaw(2, 4, 9, desert);
production.set(5, 0, 5, 5, HEIGHT - 1, 5, desert);
array.set(5, 0, 5, 5, HEIGHT - 1, 5, desert);
assertTrue(production.expanded());
assertSameCells(array, production);
}
@Test
public void biomeHunkReadsTheFallbackOnlyForUnwrittenCells() {
NativeBiome plains = biome("minecraft:plains");
ProductionBiomeHunk production = new ProductionBiomeHunk(HEIGHT, () -> plains);
NativeBiome forest = biome("minecraft:forest");
production.set(1, 0, 1, 1, HEIGHT - 1, 1, forest);
assertSame(forest, production.getRaw(1, HEIGHT - 1, 1));
assertSame(plains, production.getRaw(0, 0, 0));
}
private static void assertSameCells(Hunk<NativeBiome> expected, Hunk<NativeBiome> actual) {
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
for (int y = 0; y < HEIGHT; y++) {
assertSame(expected.get(x, y, z), actual.get(x, y, z));
}
}
}
}
private static NativeBiome biome(String key) {
return new TestBiome(key);
}
private record TestBiome(String key) implements NativeBiome {
@Override
public String namespace() {
return key.substring(0, key.indexOf(':'));
}
@Override
public Object nativeHandle() {
return key;
}
}
}