Reworked terrain transitions

- New terrain heights meet saved boundaries while retaining local relief
- Surface water levels approach saved fluid heights across the band
- Cave and physical biome matching use a four-block seam patch
- Terrain generates once without speculative neighbor containment
- Bounded caches serve transition membership and boundary lookups
- Saved biome writes honor the selected durability policy
- Release labels and presentation code leave generation identity unchanged
- Unfinished objects retain blocks and metadata across pack changes
- Whole structure assemblies preserve pending excavation and foundations
- New placements keep pending historical pieces clear
- Transition probes cover cutovers and retained object fragments
This commit is contained in:
Brian Neumann-Fopiano
2026-09-27 15:31:31 -04:00
parent 9eb05fd987
commit 8c3e5d547b
159 changed files with 6092 additions and 1905 deletions
+26 -1
View File
@@ -37,6 +37,7 @@ dependencies {
runtimeOnly(libs.fastutil)
runtimeOnly(libs.caffeine)
runtimeOnly(libs.lz4)
runtimeOnly('com.fulcrumgenomics:jlibdeflate:0.2.0')
runtimeOnly(libs.zip)
runtimeOnly(libs.guava)
runtimeOnly(libs.oshi)
@@ -291,7 +292,31 @@ tasks.register('riverTransectProbe', JavaExec) {
}
}
['hydrologyProbe', 'genProbe', 'hydrologyPackProbe', 'generationOrderProbe', 'riverTransectProbe', 'terrain3DProbe', 'hydrologyBlockProbe'].each { String taskName ->
tasks.register('transitionProbe', JavaExec) {
group = 'verification'
description = 'Exercises a bounded two-pack Studio cutover, transition routing, receipts and semantics with a vanilla-biome pack.'
mainClass = 'art.arcane.iris.probe.TransitionProbe'
workingDir = rootProject.projectDir
classpath = sourceSets.main.runtimeClasspath
jvmArgs('--add-modules', 'jdk.incubator.vector')
timeout = java.time.Duration.ofMinutes(12)
doFirst {
Provider<String> replacement = providers.gradleProperty('probeReplacementPack')
if (!probePack.isPresent() || !replacement.isPresent() || !probeDimension.isPresent() || !probeOutput.isPresent()) {
throw new GradleException('transitionProbe requires probePack, probeReplacementPack, probeDimension and probeOutput')
}
java.nio.file.Path output = new File(probeOutput.get()).canonicalFile.toPath()
if (output.startsWith(rootProject.projectDir.canonicalFile.toPath())) {
throw new GradleException('probeOutput must point outside the repository')
}
args(probePack.get(), replacement.get(), probeDimension.get(), probeSeed.getOrElse('1337'),
providers.gradleProperty('probeTransitionWidth').getOrElse('32'),
providers.gradleProperty('probeTransitionChunks').getOrElse('2'), output.toString(),
providers.gradleProperty('probeTransitionReverse').getOrElse('false'))
}
}
['transitionProbe', 'hydrologyProbe', 'genProbe', 'hydrologyPackProbe', 'generationOrderProbe', 'riverTransectProbe', 'terrain3DProbe', 'hydrologyBlockProbe'].each { String taskName ->
tasks.named(taskName, JavaExec).configure { JavaExec task ->
classpath += configurations.studioProbeApi
doFirst {
@@ -206,7 +206,10 @@ public final class StubPlatform implements IrisPlatform {
@Override
public boolean isStorageChest() {
return false;
return switch (blockKey()) {
case "minecraft:chest", "minecraft:trapped_chest", "minecraft:barrel" -> true;
default -> false;
};
}
@Override
@@ -0,0 +1,405 @@
package art.arcane.iris.probe;
import art.arcane.iris.generation.runtime.Engine;
import art.arcane.iris.generation.runtime.IrisEngine;
import art.arcane.iris.generation.terrain.IrisDimension;
import art.arcane.iris.pack.loading.IrisData;
import art.arcane.iris.spi.IrisPlatform;
import art.arcane.iris.world.history.ChunkGenerationOwnership;
import art.arcane.iris.world.history.ChunkGenerationSemantics;
import art.arcane.iris.world.history.GenerationActivation;
import art.arcane.iris.world.history.GenerationEpochContractFactory;
import art.arcane.iris.world.history.GenerationHistory;
import art.arcane.iris.world.history.GenerationHistoryRuntimeRouter;
import art.arcane.iris.world.history.GenerationPackFingerprint;
import art.arcane.iris.world.history.GenerationRegistryContract;
import art.arcane.iris.world.history.NativeTerrainReceipt;
import art.arcane.iris.world.history.SavedTerrainChunk;
import art.arcane.iris.world.history.SavedBiomeChunk;
import art.arcane.iris.world.history.TerrainBoundarySignature;
import art.arcane.iris.world.history.TransitionGenerationPlan;
import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
import art.arcane.volmlib.nativelib.terrain.NativeTerrainReceiptStorage;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.HexFormat;
import java.nio.file.Files;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitOption;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.Path;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.CompletableFuture;
public final class TransitionProbe {
private TransitionProbe() {
}
public static void main(String[] arguments) throws Exception {
Configuration configuration = Configuration.parse(arguments);
Files.createDirectory(configuration.output());
long deadline = System.nanoTime() + Duration.ofMinutes(10).toNanos();
Bindings bindings = new Bindings();
Evidence evidence = new Evidence(
new ArrayList<>(List.of("phase,chunk_x,chunk_z,activation,band,elapsed_ns,receipt_bytes,blocks_sha256,biomes_sha256,receipt_sha256,metadata_sha256")),
new ArrayList<>(List.of("phase,x,z,ground_y,fluid_y,solid_spans,fluid_gaps")),
new ArrayList<>(List.of("phase,x,z,minimum_y,maximum_y_exclusive,block")),
new ArrayList<>(List.of("phase,chunk_x,chunk_z,block,count")));
try (RealPackProbeSupport.Workspace workspace = RealPackProbeSupport.openWorkspace(
new RealPackProbeSupport.WorkspaceOptions(configuration.packA().toFile(), configuration.dimension(),
"[transition-probe]", bindings, Path.of(System.getProperty("java.io.tmpdir"))));
RealPackProbeSupport.EngineSession session = workspace.openEngine(configuration.seed(), true, "transition")) {
IrisEngine engine = (IrisEngine) session.engine();
GenerationHistoryRuntimeRouter router = engine.getGenerationHistoryRuntimeRouter().orElseThrow();
GenerationHistory history = router.history();
long originalActivation = history.activeActivation().activationId();
for (int x = 0; x < 2; x++) {
for (int z = 0; z < 2; z++) {
generate(new GenerationRequest(engine, bindings, x, z, "original", false, deadline), evidence);
}
}
Path world = engine.getWorld().worldFolder().toPath();
Map<Long, byte[]> historicalReceipts = new HashMap<>();
for (int x = 0; x < 2; x++) {
for (int z = 0; z < 2; z++) {
historicalReceipts.put(ChunkGenerationOwnership.packChunk(x, z), SavedTerrainChunk.readReceipt(world, x, z));
}
}
Path replacementPack = workspace.pack().toPath().getParent().resolve("replacement-pack");
copyPack(configuration.packB(), replacementPack);
SavedBiomeChunk historicalBiomes = router.biomes().snapshot(0, 0).orElseThrow();
IrisData replacement = IrisData.openRuntime(replacementPack.toFile());
try {
IrisDimension dimension = replacement.getDimensionLoader().load(configuration.dimension());
if (dimension == null) {
throw new IOException("Replacement dimension did not load");
}
requireVanillaBiomes(replacement, dimension);
history.stageUpdate(replacementPack, GenerationPackFingerprint.compute(replacementPack,
GenerationPackFingerprint.CURRENT_VERSION),
GenerationEpochContractFactory.create(dimension, configuration.dimension(), "iris:transition_probe"),
GenerationRegistryContract.empty(), configuration.width());
} finally {
replacement.close();
}
try (GenerationHistoryRuntimeRouter.StudioCutover cutover = router.beginStudioCutover(30_000)) {
GenerationActivation activated = cutover.promotePending();
require(activated.activationId() != originalActivation, "Pack update did not create another activation");
}
require(historicalBiomes.equals(router.biomes().snapshot(0, 0).orElseThrow()),
"Historical biome data changed during promotion");
require(history.resolveActivation(0, 0).activationId() == originalActivation,
"Historical chunk ownership changed");
require(history.semantics(0, 0).orElseThrow().activationId() == originalActivation,
"Historical semantic ownership changed");
for (int sequence = 0; sequence < configuration.chunks(); sequence++) {
int index = configuration.reverse() ? configuration.chunks() - 1 - sequence : sequence;
generate(new GenerationRequest(engine, bindings, 2 + index, 0, "transition", true, deadline), evidence);
generate(new GenerationRequest(engine, bindings, 2 + configuration.width() / 16 + index, 0,
"distant", false, deadline), evidence);
}
for (int x = 0; x < 2; x++) {
for (int z = 0; z < 2; z++) {
require(Arrays.equals(historicalReceipts.get(ChunkGenerationOwnership.packChunk(x, z)),
SavedTerrainChunk.readReceipt(world, x, z)), "Historical disk receipt changed after generation");
}
}
require(historicalBiomes.equals(router.biomes().snapshot(0, 0).orElseThrow()),
"Historical biome data changed after generation");
for (Throwable failure : RealPackProbeSupport.settleAndDrain()) {
throw new IllegalStateException("Generation reported a failure", failure);
}
require(bindings.captures > 0, "Promotion did not capture historical terrain");
Files.write(configuration.output().resolve("chunks.csv"), evidence.chunks());
Files.write(configuration.output().resolve("transect.csv"), evidence.transect());
Files.write(configuration.output().resolve("sections.csv"), evidence.sections());
Files.write(configuration.output().resolve("materials.csv"), evidence.materials());
System.out.println("[transition-probe] PASS generated=" + bindings.terrain.size()
+ " boundaryChunkCaptures=" + bindings.captures
+ " timing=diagnostic-only platform=stub cutover=studio output=" + configuration.output());
}
}
private static void generate(GenerationRequest request, Evidence evidence) throws Exception {
require(System.nanoTime() < request.deadline(), "Probe exceeded ten-minute workload deadline");
long key = ChunkGenerationOwnership.packChunk(request.x(), request.z());
require(!request.bindings().terrain.containsKey(key), "Probe scheduled a duplicate chunk");
GenerationHistoryRuntimeRouter router = request.engine().getGenerationHistoryRuntimeRouter().orElseThrow();
try (GenerationHistoryRuntimeRouter.RuntimeRoute route = router.openRoute(request.x(), request.z());
GenerationHistoryRuntimeRouter.RuntimeRoute.RuntimeScope scope = route.openRuntimeScope()) {
TransitionGenerationPlan plan = route.transitionPlan();
boolean band = plan != null && plan.hasTransitionAtChunk(request.x(), request.z());
require(band == request.band(), "Unexpected transition membership at " + request.x() + "," + request.z());
long started = System.nanoTime();
RealPackProbeSupport.GeneratedChunk generated = RealPackProbeSupport.generateChunk(
request.engine(), request.x(), request.z());
require(route.claimGeneratedSemantics((x, y, z) -> {
NativeBlockState state = generated.blocks().getRaw(x & 15, y, z & 15);
return state == null || state.isAir();
}), "Generated semantic claim was rejected");
SavedTerrainChunk terrain = route.naturalTerrain().orElseThrow();
byte[] receipt = NativeTerrainReceipt.encode(terrain, route.activation().activationId(), route.epoch().epochId());
NativeTerrainReceipt.Decoded decoded = NativeTerrainReceipt.decode(receipt, "minecraft:full");
require(decoded.activationId() == route.activation().activationId()
&& decoded.epochId().equals(route.epoch().epochId()), "Receipt changed generation ownership");
for (int localX = 0; localX < 16; localX++) {
for (int localZ = 0; localZ < 16; localZ++) {
if (!terrain.hasColumn(localX, localZ)) {
continue;
}
TerrainBoundarySignature original = terrain.column(request.x() * 16 + localX, request.z() * 16 + localZ);
TerrainBoundarySignature restored = decoded.terrain().column(request.x() * 16 + localX, request.z() * 16 + localZ);
require(original.column().equals(restored.column()) && original.geometry().equals(restored.geometry()),
"Receipt changed captured terrain");
}
}
request.bindings().terrain.put(key, terrain);
writeReceipt(request.engine().getWorld().worldFolder().toPath(), request.x(), request.z(), receipt);
require(router.history().semantics(request.x(), request.z()).orElseThrow().activationId()
== route.activation().activationId(), "Generated semantics lost activation ownership");
require(router.biomes().snapshot(request.x(), request.z()).orElseThrow().activationId()
== route.activation().activationId(), "Generated saved biomes lost activation ownership");
long elapsed = System.nanoTime() - started;
GenerationOrderProbe.ChunkHash hash = GenerationOrderProbe.hashChunk(
new GenerationOrderProbe.ChunkCoordinate(request.x(), request.z()),
generated.blocks(), generated.biomes(), generated.height());
String metadataHash = metadataHash(router.biomes().snapshot(request.x(), request.z()).orElseThrow(),
router.history().semantics(request.x(), request.z()).orElseThrow());
for (Map.Entry<String, Integer> material : new TreeMap<>(hash.blockCounts()).entrySet()) {
evidence.materials().add(request.phase() + "," + request.x() + "," + request.z() + ","
+ material.getKey() + "," + material.getValue());
}
evidence.chunks().add(request.phase() + "," + request.x() + "," + request.z() + ","
+ route.activation().activationId() + "," + band + "," + elapsed + "," + receipt.length
+ "," + hash.blocks() + "," + hash.biomes() + ","
+ HexFormat.of().formatHex(MessageDigest.getInstance("SHA-256").digest(receipt)) + "," + metadataHash);
if (request.z() == 0) {
for (int x = 0; x < 16; x++) {
int ground = -1;
int fluid = -1;
int solidSpans = 0;
boolean solidBelow = false;
String stateBelow = null;
int spanStart = 0;
for (int y = 0; y < generated.height(); y++) {
NativeBlockState state = generated.blocks().getRaw(x, y, 8);
String stateKey = state == null ? "minecraft:air" : state.key();
if (!stateKey.equals(stateBelow)) {
if (stateBelow != null) {
evidence.sections().add(request.phase() + "," + (request.x() * 16 + x) + ",8,"
+ (spanStart + request.engine().getMinHeight()) + ","
+ (y + request.engine().getMinHeight()) + "," + stateBelow);
}
stateBelow = stateKey;
spanStart = y;
}
boolean solid = state != null && !state.isAir() && !isFluid(state);
if (solid && !solidBelow) {
solidSpans++;
}
solidBelow = solid;
if (state != null && !state.isAir()) {
if (isFluid(state)) {
fluid = y + request.engine().getMinHeight();
} else {
ground = y + request.engine().getMinHeight();
}
}
}
evidence.sections().add(request.phase() + "," + (request.x() * 16 + x) + ",8,"
+ (spanStart + request.engine().getMinHeight()) + ","
+ (generated.height() + request.engine().getMinHeight()) + "," + stateBelow);
int fluidGaps = 0;
for (int y = ground + 1; y < fluid; y++) {
NativeBlockState state = generated.blocks().getRaw(x, y - request.engine().getMinHeight(), 8);
if (state == null || state.isAir()) {
fluidGaps++;
}
}
evidence.transect().add(request.phase() + "," + (request.x() * 16 + x) + ",8," + ground + "," + fluid
+ "," + solidSpans + "," + fluidGaps);
}
}
}
}
private static boolean isFluid(NativeBlockState state) {
return state.isWater() || state.key().startsWith("minecraft:lava");
}
private static String metadataHash(SavedBiomeChunk biomes, ChunkGenerationSemantics semantics) throws Exception {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
digest.update(biomes.column(x, z).toString().getBytes(StandardCharsets.UTF_8));
}
}
for (Collection<?> values : List.of(semantics.surfaceBiomeKeys(), semantics.caveBiomeKeys(),
semantics.regionKeys(), semantics.riverProfileKeys(), semantics.objectKeys(), semantics.riverFeatures(),
semantics.pointsOfInterest(), semantics.structures())) {
digest.update(values.stream().map(Object::toString).sorted().toList().toString().getBytes(StandardCharsets.UTF_8));
}
return HexFormat.of().formatHex(digest.digest());
}
static void writeReceipt(Path world, int chunkX, int chunkZ, byte[] receipt) throws IOException {
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
try (DataOutputStream nbt = new DataOutputStream(bytes)) {
nbt.writeByte(10);
nbt.writeUTF("");
nbt.writeByte(3);
nbt.writeUTF("xPos");
nbt.writeInt(chunkX);
nbt.writeByte(3);
nbt.writeUTF("zPos");
nbt.writeInt(chunkZ);
nbt.writeByte(8);
nbt.writeUTF("Status");
nbt.writeUTF("minecraft:full");
nbt.writeByte(7);
nbt.writeUTF(NativeTerrainReceiptStorage.NBT_KEY);
nbt.writeInt(receipt.length);
nbt.write(receipt);
nbt.writeByte(0);
}
Path region = Files.createDirectories(world.resolve("region")).resolve(
"r." + (chunkX >> 5) + "." + (chunkZ >> 5) + ".mca");
byte[] payload = bytes.toByteArray();
int sectors = (payload.length + 5 + 4095) / 4096;
require(sectors <= 255, "Receipt exceeds MCA sector limit");
try (RandomAccessFile output = new RandomAccessFile(region.toFile(), "rw")) {
if (output.length() == 0) {
output.setLength(8192);
}
int offset = Math.toIntExact((output.length() + 4095) / 4096);
output.setLength((long) (offset + sectors) * 4096);
output.seek((long) offset * 4096);
output.writeInt(payload.length + 1);
output.writeByte(3);
output.write(payload);
output.seek((long) ((chunkX & 31) + (chunkZ & 31) * 32) * 4);
output.writeInt(offset << 8 | sectors);
}
}
private static void copyPack(Path source, Path destination) throws IOException {
Files.walkFileTree(source, EnumSet.of(FileVisitOption.FOLLOW_LINKS), Integer.MAX_VALUE,
new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(Path directory, BasicFileAttributes attributes) throws IOException {
Files.createDirectory(destination.resolve(source.relativize(directory)));
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attributes) throws IOException {
Files.copy(file, destination.resolve(source.relativize(file)));
return FileVisitResult.CONTINUE;
}
});
}
private static void requireVanillaBiomes(IrisData data, IrisDimension dimension) {
require(!dimension.getAllBiomes(() -> data).stream().anyMatch(biome -> biome.isCustom()),
"Transition probe requires vanilla physical biomes");
}
private static void require(boolean condition, String message) {
if (!condition) {
throw new IllegalStateException(message);
}
}
record Configuration(Path packA, Path packB, String dimension, long seed, int width, int chunks, Path output, boolean reverse) {
static Configuration parse(String[] arguments) {
if (arguments.length != 7 && arguments.length != 8) {
throw new IllegalArgumentException("Expected packA packB dimension seed width chunks output [reverse]");
}
int width = Integer.parseInt(arguments[4]);
int chunks = Integer.parseInt(arguments[5]);
if (width < 16 || width > 128 || width % 16 != 0 || chunks < 1 || chunks > 8 || chunks * 16 > width) {
throw new IllegalArgumentException("Width must be 16..128 in multiples of 16; chunks must be 1..8 within width");
}
Path packA = Path.of(arguments[0]).toAbsolutePath().normalize();
Path packB = Path.of(arguments[1]).toAbsolutePath().normalize();
if (!Files.isDirectory(packA) || !Files.isDirectory(packB)) {
throw new IllegalArgumentException("Both source packs must be existing directories");
}
Path output = Path.of(arguments[6]).toAbsolutePath().normalize();
if (Files.exists(output) || !Files.isDirectory(output.getParent())) {
throw new IllegalArgumentException("Output must be a new directory beneath an existing parent");
}
if (arguments[2].isBlank()) {
throw new IllegalArgumentException("Dimension cannot be blank");
}
return new Configuration(packA, packB, arguments[2], Long.parseLong(arguments[3]),
width, chunks, output, arguments.length == 8
&& RealPackProbeSupport.parseBoolean(arguments[7], "reverse"));
}
}
private record Evidence(List<String> chunks, List<String> transect, List<String> sections, List<String> materials) {
}
private record GenerationRequest(IrisEngine engine, Bindings bindings, int x, int z, String phase,
boolean band, long deadline) {
}
private static final class Bindings implements RealPackProbeSupport.RuntimeBindings {
private final Map<Long, SavedTerrainChunk> terrain = new HashMap<>();
private int captures;
@Override
public IrisPlatform create(File platformRoot) {
return new StubPlatform(platformRoot);
}
@Override
public void prepare(IrisData data, IrisDimension dimension) {
requireVanillaBiomes(data, dimension);
}
@Override
public GenerationHistory createHistory(RealPackProbeSupport.HistoryRequest request) throws IOException {
Files.createDirectories(request.worldRoot());
Path pack = request.data().getDataFolder().toPath();
return GenerationHistory.create(request.worldRoot(), pack,
GenerationPackFingerprint.compute(pack, GenerationPackFingerprint.CURRENT_VERSION), request.seed(),
GenerationEpochContractFactory.create(request.dimension(), request.dimension().getLoadKey(),
"iris:transition_probe"), GenerationRegistryContract.empty());
}
@Override
public CompletableFuture<SavedTerrainChunk> captureSavedTerrainChunk(Engine engine, int chunkX, int chunkZ) {
captures++;
SavedTerrainChunk chunk = terrain.get(ChunkGenerationOwnership.packChunk(chunkX, chunkZ));
return chunk == null ? CompletableFuture.failedFuture(new IOException("Missing generated historical chunk"))
: CompletableFuture.completedFuture(chunk);
}
@Override
public CompletableFuture<Void> flushSavedTerrainCapture(Engine engine) {
return CompletableFuture.completedFuture(null);
}
@Override
public void close() {
terrain.clear();
}
}
}
@@ -0,0 +1,414 @@
package art.arcane.iris.probe;
import art.arcane.iris.generation.decoration.tree.TreeBlockMaterial;
import art.arcane.iris.generation.runtime.Engine;
import art.arcane.iris.generation.runtime.EngineTarget;
import art.arcane.iris.generation.runtime.IrisEngine;
import art.arcane.iris.generation.terrain.IrisDimension;
import art.arcane.iris.pack.loading.IrisData;
import art.arcane.iris.spi.IrisPlatform;
import art.arcane.iris.structure.placement.StructurePlacementMarker;
import art.arcane.iris.testsupport.IrisRuntimeState;
import art.arcane.iris.world.IrisWorld;
import art.arcane.iris.world.history.GenerationEpochContractFactory;
import art.arcane.iris.world.history.GenerationHistory;
import art.arcane.iris.world.history.GenerationHistoryRuntimeRouter;
import art.arcane.iris.world.history.GenerationPackFingerprint;
import art.arcane.iris.world.history.GenerationRegistryContract;
import art.arcane.iris.world.history.IrisBoundarySignatureSampler;
import art.arcane.iris.world.history.NativeTerrainReceipt;
import art.arcane.iris.world.history.SavedTerrainChunk;
import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
import art.arcane.volmlib.util.matter.Matter;
import org.junit.AfterClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Stream;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
public final class ObjectContinuationProbeTest {
private static final long SEED = 1338;
private static final Fixture TREE = new Fixture("boundary-tree", "trees/boundary", Set.of("minecraft:oak_log", "minecraft:oak_leaves"), true);
private static final Fixture STRUCTURE = new Fixture("boundary-structure", "buildings/boundary", Set.of("minecraft:bricks", "minecraft:oak_planks", "minecraft:chest"), false);
@Rule
public TemporaryFolder temporary = new TemporaryFolder();
@AfterClass
public static void resetRuntime() {
IrisRuntimeState.reset();
}
@Test(timeout = 120_000)
public void removedTreeCompletesOnlyItsPublishedPlacementsAfterRestart() throws Exception {
verifyCompletion(false, TREE);
}
@Test(timeout = 120_000)
public void originalTreeSurvivesTwoUpdatesBeforeItsTailGenerates() throws Exception {
verifyCompletion(true, TREE);
}
@Test(timeout = 120_000)
public void removedStructureCompletesItsCarveAndFoundationAfterRestart() throws Exception {
verifyCompletion(false, STRUCTURE);
}
private void verifyCompletion(boolean secondUpdate, Fixture fixture) throws Exception {
Map<Position, ObjectVoxel> controlOld;
Map<Position, ObjectVoxel> controlNew;
GeneratedSnapshot nativeSource;
GeneratedSnapshot nativeDestination;
try (RealPackProbeSupport.Workspace workspace = RealPackProbeSupport.openWorkspace(
fixture(fixture.pack()).toFile(), "transition", "[object-control]");
RealPackProbeSupport.EngineSession session = workspace.openEngine(SEED, true, "control")) {
nativeSource = generatedObjects(session.engine(), 0, fixture);
controlOld = nativeSource.objects();
nativeDestination = generatedObjects(session.engine(), 1, fixture);
controlNew = nativeDestination.objects();
}
Set<String> historicalPlacements = new HashSet<>();
for (ObjectVoxel voxel : controlOld.values()) {
historicalPlacements.add(voxel.marker());
}
Map<Position, ObjectVoxel> expectedTail = new HashMap<>();
Map<Position, ObjectVoxel> unrelated = new HashMap<>();
for (Map.Entry<Position, ObjectVoxel> entry : controlNew.entrySet()) {
(historicalPlacements.contains(entry.getValue().marker()) ? expectedTail : unrelated)
.put(entry.getKey(), entry.getValue());
}
assertFalse("Fixture must have old object blocks in the saved chunk", controlOld.isEmpty());
assertFalse("Fixture must have an old placement crossing into the next chunk", expectedTail.isEmpty());
if (fixture.tree()) {
assertFalse("Fixture must include old placements wholly inside the new area", unrelated.isEmpty());
Map<String, Position> centers = new HashMap<>();
Map<Position, ObjectVoxel> control = new HashMap<>(controlOld);
control.putAll(controlNew);
int trunkY = control.keySet().stream().mapToInt(Position::y).min().orElseThrow();
for (Map.Entry<Position, ObjectVoxel> entry : control.entrySet()) {
if (entry.getKey().y() == trunkY) {
centers.put(entry.getValue().marker(), entry.getKey());
}
}
boolean crossingFromNewSource = false;
boolean unrelatedFromNewSource = false;
for (Map.Entry<String, Position> entry : centers.entrySet()) {
if (entry.getValue().x() >= 16 && entry.getValue().x() < 32) {
crossingFromNewSource |= historicalPlacements.contains(entry.getKey());
unrelatedFromNewSource |= !historicalPlacements.contains(entry.getKey());
}
}
assertTrue("Fixture must distinguish two placements from the same new source chunk: " + centers,
crossingFromNewSource && unrelatedFromNewSource);
} else {
assertTrue("Structure fixture must place foundations", nativeDestination.foundations() > 0);
assertTrue("Structure fixture must carve solid ground", nativeDestination.carvedGround() > 0);
}
Bindings bindings = new Bindings();
try (RealPackProbeSupport.Workspace workspace = RealPackProbeSupport.openWorkspace(
new RealPackProbeSupport.WorkspaceOptions(fixture(fixture.pack()).toFile(), "transition",
"[object-update]", bindings, temporary.getRoot().toPath()))) {
IrisWorld world;
byte[] oldReceipt;
long originalActivation;
try (RealPackProbeSupport.EngineSession session = workspace.openEngine(SEED, true, "original")) {
IrisEngine engine = (IrisEngine) session.engine();
GenerationHistoryRuntimeRouter router = engine.getGenerationHistoryRuntimeRouter().orElseThrow();
originalActivation = router.history().activeActivation().activationId();
assertEquals(controlOld, generateClaimed(engine, bindings, 0, fixture).objects());
world = engine.getWorld();
oldReceipt = SavedTerrainChunk.readReceipt(world.worldFolder().toPath(), 0, 0);
Path replacement = workspace.pack().toPath().getParent().resolve("replacement");
copyPack(fixture("lowland"), replacement);
IrisData replacementData = IrisData.openRuntime(replacement.toFile());
try {
IrisDimension dimension = replacementData.getDimensionLoader().load("transition");
assertNotNull(dimension);
router.history().stageUpdate(replacement,
GenerationPackFingerprint.compute(replacement, GenerationPackFingerprint.CURRENT_VERSION),
GenerationEpochContractFactory.create(dimension, "transition", "iris:object_probe"),
GenerationRegistryContract.empty(), 32);
} finally {
replacementData.close();
}
try (GenerationHistoryRuntimeRouter.StudioCutover cutover = router.beginStudioCutover(30_000)) {
assertTrue(cutover.promotePending().activationId() > originalActivation);
}
}
GenerationHistory reopened = GenerationHistory.open(world.worldFolder().toPath(), SEED);
if (secondUpdate) {
Path third = workspace.pack().toPath().getParent().resolve("third-pack");
copyPack(fixture("mountain"), third);
IrisData thirdData = IrisData.openRuntime(third.toFile());
try {
IrisDimension dimension = thirdData.getDimensionLoader().load("transition");
assertNotNull(dimension);
reopened.stageUpdate(third,
GenerationPackFingerprint.compute(third, GenerationPackFingerprint.CURRENT_VERSION),
GenerationEpochContractFactory.create(dimension, "transition", "iris:object_probe"),
GenerationRegistryContract.empty(), 32);
} finally {
thirdData.close();
}
reopened.prepareCurrentGenerator(32);
reopened = GenerationHistory.open(world.worldFolder().toPath(), SEED);
assertTrue(reopened.activeActivation().activationId() > originalActivation + 1);
}
IrisData data = IrisData.openRuntime(reopened.activePackRoot().toFile());
try {
data.bindGenerationRegistryContract(reopened.activeEpoch().registryContract());
IrisDimension dimension = data.getDimensionLoader().load("transition");
assertNotNull(dimension);
IrisEngine engine = new IrisEngine(new EngineTarget(world, dimension, data),
IrisEngine.InitializationMode.RUNTIME,
reopened.paths().activationMantleRoot(reopened.activeActivation().activationId()),
reopened.activeEpoch().kernelVersion(), reopened.transitionPlan(reopened.activeActivation().activationId()));
try {
GenerationHistoryRuntimeRouter router = GenerationHistoryRuntimeRouter.attach(
engine, reopened, IrisBoundarySignatureSampler.INSTANCE);
router.preloadActiveRuntimes();
assertEquals("Old object blocks and metadata must reload before neighboring generation",
controlOld, historicalObjects(engine, fixture));
assertHistoricalEdits(engine, nativeSource.edits());
GeneratedSnapshot result = generateClaimed(engine, bindings, 1, fixture);
Map<Position, ObjectVoxel> completed = result.objects();
if (!fixture.tree()) {
for (Map.Entry<Position, String> edit : nativeDestination.edits().entrySet()) {
assertEquals("Structure carve/foundation at " + edit.getKey(), edit.getValue(),
result.blocks().get(edit.getKey()));
}
assertTrue("Completed storage blocks must retain structure-aware placement markers",
completed.values().stream().anyMatch(voxel -> voxel.marker() != null));
}
assertEquals("Complete only placements with a published old portion", expectedTail, completed);
for (Map.Entry<Position, ObjectVoxel> entry : unrelated.entrySet()) {
assertFalse("Unrelated old placement was imported at " + entry.getKey(),
completed.containsKey(entry.getKey()));
}
assertEquals("Old geometry and metadata must survive neighboring generation",
controlOld, historicalObjects(engine, fixture));
assertHistoricalEdits(engine, nativeSource.edits());
assertArrayEquals(oldReceipt, SavedTerrainChunk.readReceipt(world.worldFolder().toPath(), 0, 0));
assertEquals(originalActivation, reopened.resolveActivation(0, 0).activationId());
assertTrue(reopened.semantics(1, 0).orElseThrow().objectKeys().contains(fixture.objectKey()));
assertTrue("Generation must not report swallowed errors", RealPackProbeSupport.settleAndDrain().isEmpty());
} finally {
engine.close();
}
} finally {
data.close();
}
}
}
private static GeneratedSnapshot generateClaimed(IrisEngine engine, Bindings bindings, int chunkX, Fixture fixture)
throws Exception {
GenerationHistoryRuntimeRouter router = engine.getGenerationHistoryRuntimeRouter().orElseThrow();
try (GenerationHistoryRuntimeRouter.RuntimeRoute route = router.openRoute(chunkX, 0);
GenerationHistoryRuntimeRouter.RuntimeRoute.RuntimeScope scope = route.openRuntimeScope()) {
GeneratedSnapshot result = generatedObjects(engine, chunkX, fixture);
assertTrue(route.claimGeneratedSemantics());
SavedTerrainChunk terrain = route.naturalTerrain().orElseThrow();
bindings.terrain.put(chunkX, terrain);
TransitionProbe.writeReceipt(engine.getWorld().worldFolder().toPath(), chunkX, 0,
NativeTerrainReceipt.encode(terrain, route.activation().activationId(), route.epoch().epochId()));
return result;
}
}
private static GeneratedSnapshot generatedObjects(Engine engine, int chunkX, Fixture fixture) {
RealPackProbeSupport.GeneratedChunk chunk = RealPackProbeSupport.generateChunk(engine, chunkX, 0);
Map<Position, ObjectVoxel> result = objectMetadata(engine, chunkX, fixture);
for (Map.Entry<Position, ObjectVoxel> entry : result.entrySet()) {
Position position = entry.getKey();
NativeBlockState state = chunk.blocks().getRaw(position.x() & 15, position.y(), position.z());
assertNotNull("Object metadata must correspond to a generated block", state);
assertEquals(entry.getValue().block(), state.key());
}
Map<Position, String> blocks = new HashMap<>();
Map<Position, String> edits = new HashMap<>();
int objectBlocks = 0;
int foundations = 0;
int carvedGround = 0;
for (int x = 0; x < 16; x++) {
for (int y = 0; y < chunk.height(); y++) {
for (int z = 0; z < 16; z++) {
NativeBlockState state = chunk.blocks().getRaw(x, y, z);
if (state != null && fixture.materials().contains(state.key().split("\\[", 2)[0])) {
objectBlocks++;
}
if (!fixture.tree()) {
Position position = new Position(chunkX * 16 + x, y, z);
String block = state == null || state.isAir() ? "minecraft:air" : state.key();
blocks.put(position, block);
if (state != null && state.key().contains("cobblestone")) {
foundations++;
edits.put(position, block);
}
if (y > 1 && y <= 72 && (state == null || state.isAir())) {
carvedGround++;
edits.put(position, block);
}
}
}
}
}
assertEquals("Every generated object block must keep placement metadata", objectBlocks, result.size());
return new GeneratedSnapshot(result, blocks, edits, foundations, carvedGround);
}
private static Map<Position, ObjectVoxel> historicalObjects(IrisEngine engine, Fixture fixture) throws Exception {
GenerationHistoryRuntimeRouter router = engine.getGenerationHistoryRuntimeRouter().orElseThrow();
try (GenerationHistoryRuntimeRouter.SavedChunkMantle saved = router.openSavedChunkMantle(0, 0)) {
return objectMetadata(saved.mantle().getChunk(0, 0), 0, engine.getHeight(), fixture);
}
}
private static void assertHistoricalEdits(IrisEngine engine, Map<Position, String> edits) throws Exception {
if (edits.isEmpty()) {
return;
}
GenerationHistoryRuntimeRouter router = engine.getGenerationHistoryRuntimeRouter().orElseThrow();
try (GenerationHistoryRuntimeRouter.SavedChunkMantle saved = router.openSavedChunkMantle(0, 0)) {
MantleChunk<Matter> chunk = saved.mantle().getChunk(0, 0).use();
try {
for (Map.Entry<Position, String> edit : edits.entrySet()) {
Position position = edit.getKey();
NativeBlockState block = chunk.get(position.x(), position.y(), position.z(), NativeBlockState.class);
assertNotNull("Historical carve/foundation must reload at " + position, block);
assertEquals("Historical carve/foundation changed at " + position, edit.getValue(),
block.isAir() ? "minecraft:air" : block.key());
}
} finally {
chunk.release();
}
}
}
private static Map<Position, ObjectVoxel> objectMetadata(Engine engine, int chunkX, Fixture fixture) {
return objectMetadata(engine.getMantle().getMantle().getChunk(chunkX, 0), chunkX, engine.getHeight(), fixture);
}
private static Map<Position, ObjectVoxel> objectMetadata(MantleChunk<Matter> chunk, int chunkX, int height, Fixture fixture) {
Map<Position, ObjectVoxel> result = new HashMap<>();
chunk.use();
try {
for (int x = 0; x < 16; x++) {
for (int y = 0; y < height; y++) {
for (int z = 0; z < 16; z++) {
String marker = chunk.get(x, y, z, String.class);
StructurePlacementMarker.Decoded decoded = StructurePlacementMarker.decode(marker);
NativeBlockState block = chunk.get(x, y, z, NativeBlockState.class);
boolean objectBlock = block != null && fixture.materials().contains(block.key().split("\\[", 2)[0]);
if (decoded != null && decoded.objectKey().equals(fixture.objectKey()) || !fixture.tree() && objectBlock) {
TreeBlockMaterial material = chunk.get(x, y, z, TreeBlockMaterial.class);
assertNotNull("Placement marker must retain its object block", block);
if (fixture.tree()) {
assertNotNull("Tree material metadata must survive", material);
assertTrue(material.matches(block.key()));
} else if (block.isStorageChest()) {
assertNotNull("Structure storage blocks must have placement metadata", decoded);
assertEquals("boundary", decoded.structureKey());
}
result.put(new Position(chunkX * 16 + x, y, z), new ObjectVoxel(block.key(), marker, material));
}
}
}
}
} finally {
chunk.release();
}
return result;
}
private static Path fixture(String name) throws Exception {
URL resource = Objects.requireNonNull(ObjectContinuationProbeTest.class.getResource("/transition-packs/" + name));
return Path.of(resource.toURI());
}
private static void copyPack(Path source, Path destination) throws IOException {
try (Stream<Path> paths = Files.walk(source)) {
for (Path path : paths.toList()) {
Path target = destination.resolve(source.relativize(path));
if (Files.isDirectory(path)) {
Files.createDirectories(target);
} else {
Files.copy(path, target);
}
}
}
}
private record Fixture(String pack, String objectKey, Set<String> materials, boolean tree) {
}
private record GeneratedSnapshot(Map<Position, ObjectVoxel> objects, Map<Position, String> blocks,
Map<Position, String> edits, int foundations, int carvedGround) {
}
private record Position(int x, int y, int z) {
}
private record ObjectVoxel(String block, String marker, TreeBlockMaterial material) {
}
private static final class Bindings implements RealPackProbeSupport.RuntimeBindings {
private final Map<Integer, SavedTerrainChunk> terrain = new HashMap<>();
@Override
public IrisPlatform create(File platformRoot) {
return new StubPlatform(platformRoot);
}
@Override
public void prepare(IrisData data, IrisDimension dimension) {
}
@Override
public GenerationHistory createHistory(RealPackProbeSupport.HistoryRequest request) throws IOException {
Files.createDirectories(request.worldRoot());
Path pack = request.data().getDataFolder().toPath();
return GenerationHistory.create(request.worldRoot(), pack,
GenerationPackFingerprint.compute(pack, GenerationPackFingerprint.CURRENT_VERSION), request.seed(),
GenerationEpochContractFactory.create(request.dimension(), "transition", "iris:object_probe"),
GenerationRegistryContract.empty());
}
@Override
public CompletableFuture<SavedTerrainChunk> captureSavedTerrainChunk(Engine engine, int chunkX, int chunkZ) {
SavedTerrainChunk chunk = chunkZ == 0 ? terrain.get(chunkX) : null;
return chunk == null ? CompletableFuture.failedFuture(new IOException("Missing historical chunk"))
: CompletableFuture.completedFuture(chunk);
}
@Override
public CompletableFuture<Void> flushSavedTerrainCapture(Engine engine) {
return CompletableFuture.completedFuture(null);
}
@Override
public void close() {
terrain.clear();
}
}
}
@@ -0,0 +1,347 @@
package art.arcane.iris.probe;
import art.arcane.iris.testsupport.IrisRuntimeState;
import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
import org.junit.AfterClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
import java.util.Arrays;
import java.util.TreeMap;
import java.util.stream.Stream;
import java.util.Objects;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertTrue;
public final class TransitionFixtureProbeTest {
@Rule
public TemporaryFolder temporary = new TemporaryFolder();
@AfterClass
public static void resetRuntime() {
IrisRuntimeState.reset();
}
@Test(timeout = 120_000)
public void lowlandToMountainPreservesHistoricalTrackingAndActivatesTheNewPack() throws Exception {
run(new Scenario("lowland", "mountain", 72, -1, 104, -1));
}
@Test(timeout = 120_000)
public void oceanToMountainRetainsOldWaterAndProducesDryDistantTerrain() throws Exception {
run(new Scenario("ocean", "mountain", 24, 40, 104, -1));
}
@Test(timeout = 120_000)
public void mountainToOceanProducesTheReplacementWaterTable() throws Exception {
run(new Scenario("mountain", "ocean", 104, -1, 24, 40));
}
@Test(timeout = 120_000)
public void waterLevelChangeKeepsTheAuthoredOceanFloor() throws Exception {
run(new Scenario("ocean", "ocean-highwater", 24, 40, 24, 48));
}
private void run(Scenario scenario) throws Exception {
Path output = temporary.newFolder().toPath().resolve("output");
Path originalPack = fixture(scenario.original());
Path replacementPack = fixture(scenario.replacement());
Map<Path, byte[]> originalSource = snapshot(originalPack);
Map<Path, byte[]> replacementSource = snapshot(replacementPack);
TransitionProbe.main(new String[]{originalPack.toString(), replacementPack.toString(),
"transition", "1337", "32", "2", output.toString()});
assertSnapshotEquals(originalSource, snapshot(originalPack));
assertSnapshotEquals(replacementSource, snapshot(replacementPack));
List<String> chunks = Files.readAllLines(output.resolve("chunks.csv"));
assertEquals(9, chunks.size());
long originalActivation = Long.parseLong(chunks.get(1).split(",")[3]);
int original = 0;
int transition = 0;
int distant = 0;
for (String row : chunks.subList(1, chunks.size())) {
String[] cells = row.split(",");
long activation = Long.parseLong(cells[3]);
boolean band = Boolean.parseBoolean(cells[4]);
assertTrue(Long.parseLong(cells[6]) > 0);
switch (cells[0]) {
case "original" -> {
original++;
assertEquals(originalActivation, activation);
assertTrue(!band);
}
case "transition" -> {
transition++;
assertTrue(activation > originalActivation);
assertTrue(band);
}
case "distant" -> {
distant++;
assertTrue(activation > originalActivation);
assertTrue(!band);
}
default -> throw new AssertionError("Unexpected phase " + cells[0]);
}
}
assertEquals(4, original);
assertEquals(2, transition);
assertEquals(2, distant);
List<String> transect = Files.readAllLines(output.resolve("transect.csv"));
assertEquals(97, transect.size());
assertFlatSeam(scenario, transect);
for (String row : transect.subList(1, transect.size())) {
String[] cells = row.split(",");
if (cells[0].equals("original")) {
assertEquals(row, scenario.originalGround(), Integer.parseInt(cells[3]));
assertEquals(row, scenario.originalFluid(), Integer.parseInt(cells[4]));
} else if (cells[0].equals("distant")) {
assertEquals(row, scenario.replacementGround(), Integer.parseInt(cells[3]));
assertEquals(row, scenario.replacementFluid(), Integer.parseInt(cells[4]));
}
}
}
@Test(timeout = 120_000)
public void slopedTransitionsKeepChunkOutputAndTrackingIndependentOfGenerationOrder() throws Exception {
compareOrders("slope-lowland", "slope-mountain", false);
}
@Test(timeout = 120_000)
public void volumetricTransitionsKeepChunkOutputIndependentOfGenerationOrder() throws Exception {
compareOrders("volumetric-lowland", "volumetric-mountain", true);
}
@Test(timeout = 120_000)
public void nearSeamDisplacementRetainsNewTerrainRippleCurvature() throws Exception {
Path original = fixture("lowland");
Path replacement = fixture("ripple-mountain");
Path output = temporary.newFolder().toPath().resolve("ripple");
TransitionProbe.main(new String[]{original.toString(), replacement.toString(), "transition", "1337",
"64", "4", output.toString()});
Map<Integer, int[]> blended = columns(Files.readAllLines(output.resolve("transect.csv")));
Map<Integer, Integer> nativeHeights = new HashMap<>();
try (RealPackProbeSupport.Workspace workspace = RealPackProbeSupport.openWorkspace(
replacement.toFile(), "transition", "[transition-ripple-control]");
RealPackProbeSupport.EngineSession session = workspace.openEngine(1337, true, "native-control")) {
Map<String, List<String>> displacedChunks = chunkContents(output);
for (int chunkX : new int[]{2, 3, 6, 7, 8, 9}) {
RealPackProbeSupport.GeneratedChunk chunk = RealPackProbeSupport.generateChunk(session.engine(), chunkX, 0);
if (chunkX >= 6) {
GenerationOrderProbe.ChunkHash nativeHash = GenerationOrderProbe.hashChunk(
new GenerationOrderProbe.ChunkCoordinate(chunkX, 0), chunk.blocks(), chunk.biomes(), chunk.height());
assertEquals("Transition must not alter distant voxels", displacedChunks.get(chunkX + ",0").get(0), nativeHash.blocks());
assertEquals("Transition must not alter distant physical biomes", displacedChunks.get(chunkX + ",0").get(1), nativeHash.biomes());
continue;
}
for (int x = 0; x < 16; x++) {
int ground = -1;
for (int y = 0; y < chunk.height(); y++) {
NativeBlockState block = chunk.blocks().getRaw(x, y, 8);
if (block != null && !block.isAir() && !block.isWater()) {
ground = y;
}
}
nativeHeights.put(chunkX * 16 + x, ground);
}
}
}
int strongRipples = 0;
for (int x = 40; x <= 60; x++) {
int nativeCurvature = nativeHeights.get(x - 3) - 2 * nativeHeights.get(x) + nativeHeights.get(x + 3);
if (Math.abs(nativeCurvature) >= 6) {
strongRipples++;
int blendedCurvature = blended.get(x - 3)[0] - 2 * blended.get(x)[0] + blended.get(x + 3)[0];
assertTrue("New terrain relief was attenuated near the seam at x=" + x
+ " native=" + nativeCurvature + " blended=" + blendedCurvature,
Math.abs(nativeCurvature - blendedCurvature) <= 3);
}
}
assertTrue("Control fixture must contain enough measurable ripples", strongRipples >= 5);
}
@Test(timeout = 120_000)
public void transitionSolidsUseTheNewPaletteWithoutHistoricalMaterialDither() throws Exception {
Path original = fixture("granite-lowland");
Path replacement = fixture("calcite-mountain");
Path output = temporary.newFolder().toPath().resolve("materials");
TransitionProbe.main(new String[]{original.toString(), replacement.toString(), "transition", "1337",
"32", "2", output.toString()});
int oldSolid = 0;
int newSolid = 0;
List<String> rows = Files.readAllLines(output.resolve("materials.csv"));
for (String row : rows.subList(1, rows.size())) {
String[] cells = row.split(",");
if (cells[3].equals("minecraft:air") || cells[3].equals("minecraft:cave_air")) {
continue;
}
if (cells[0].equals("original")) {
assertEquals(row, "minecraft:granite", cells[3]);
oldSolid += Integer.parseInt(cells[4]);
} else {
assertEquals(row, "minecraft:calcite", cells[3]);
newSolid += Integer.parseInt(cells[4]);
}
}
assertTrue(oldSolid > 0);
assertTrue(newSolid > 0);
}
@Test(timeout = 120_000)
public void oldCaveOpeningContinuesAtTheSeamAndStopsWithinFourBlocks() throws Exception {
Path original = fixture("cave-lowland");
Path replacement = fixture("lowland");
Path output = temporary.newFolder().toPath().resolve("cave");
TransitionProbe.main(new String[]{original.toString(), replacement.toString(), "transition", "1337",
"32", "2", output.toString()});
Map<Integer, String[]> sections = sections(Files.readAllLines(output.resolve("sections.csv")));
int opening = 0;
for (int y = 32; y <= 47; y++) {
if (sections.get(31)[y].equals("minecraft:air") || sections.get(31)[y].equals("minecraft:cave_air")) {
opening++;
assertTrue("Historical cave mouth must continue at the first seam column y=" + y,
sections.get(32)[y].equals("minecraft:air") || sections.get(32)[y].equals("minecraft:cave_air"));
}
assertEquals("Cave patch exceeded four blocks at y=" + y, "minecraft:stone", sections.get(35)[y]);
assertEquals("Cave must not be extruded across the transition at y=" + y, "minecraft:stone", sections.get(48)[y]);
}
assertTrue("Fixture must expose an actual cave mouth at the historical edge", opening >= 12);
assertEquals("Cave mouth requires a floor", "minecraft:stone", sections.get(31)[31]);
assertEquals("Cave mouth requires a roof", "minecraft:stone", sections.get(31)[48]);
}
private static Map<Integer, String[]> sections(List<String> rows) {
Map<Integer, String[]> result = new HashMap<>();
for (String row : rows.subList(1, rows.size())) {
String[] cells = row.split(",", 6);
int x = Integer.parseInt(cells[1]);
String[] column = result.computeIfAbsent(x, ignored -> new String[128]);
String blockKey = cells[5].contains(":") ? cells[5] : "minecraft:" + cells[5];
Arrays.fill(column, Integer.parseInt(cells[3]), Integer.parseInt(cells[4]), blockKey);
}
return result;
}
private void compareOrders(String original, String replacement, boolean volumetric) throws Exception {
Path originalPack = fixture(original);
Path replacementPack = fixture(replacement);
Path forward = temporary.newFolder().toPath().resolve("forward");
Path reverse = temporary.newFolder().toPath().resolve("reverse");
for (Path output : List.of(forward, reverse)) {
TransitionProbe.main(new String[]{originalPack.toString(), replacementPack.toString(), "transition", "1337",
"64", "4", output.toString(), Boolean.toString(output.equals(reverse))});
}
assertEquals(chunkContents(forward), chunkContents(reverse));
Map<Integer, int[]> transect = columns(Files.readAllLines(forward.resolve("transect.csv")));
assertEquals("Transect must cover both joins without missing columns", 160, transect.size());
if (volumetric) {
assertTrue("Volumetric fixture must contain more than one solid span",
transect.values().stream().anyMatch(column -> column[2] > 1));
} else {
assertTrue("Old-side fixture must have a non-flat slope", transect.get(31)[0] != transect.get(0)[0]);
assertTrue("New-side fixture must have a non-flat slope", transect.get(159)[0] != transect.get(96)[0]);
assertTrue("Height must meet historical ground at the seam", Math.abs(transect.get(32)[0] - transect.get(31)[0]) <= 2);
assertTrue("Height must meet native ground at the end of the band", Math.abs(transect.get(96)[0] - transect.get(95)[0]) <= 2);
for (int x = 32; x < 96; x++) {
assertTrue("Slope step at x=" + x, Math.abs(transect.get(x)[0] - transect.get(x - 1)[0]) <= 3);
}
}
}
private static Map<String, List<String>> chunkContents(Path output) throws Exception {
Map<String, List<String>> result = new HashMap<>();
List<String> rows = Files.readAllLines(output.resolve("chunks.csv"));
for (String row : rows.subList(1, rows.size())) {
String[] cells = row.split(",");
result.put(cells[1] + "," + cells[2], Arrays.asList(cells).subList(7, 11));
}
return result;
}
private static void assertFlatSeam(Scenario scenario, List<String> rows) {
Map<Integer, int[]> columns = columns(rows);
assertEquals("Transect must cover both joins without missing columns", 96, columns.size());
int difference = scenario.replacementGround() - scenario.originalGround();
int maximumStep = (int) Math.ceil(Math.abs(difference) * 1.875 / 32) + 1;
assertTrue("First new column must meet historical ground: old=" + columns.get(31)[0]
+ " new=" + columns.get(32)[0], Math.abs(columns.get(32)[0] - columns.get(31)[0]) <= 1);
assertTrue("Band must reach native new ground: band=" + columns.get(63)[0]
+ " native=" + columns.get(64)[0], Math.abs(columns.get(64)[0] - columns.get(63)[0]) <= 1);
for (int x = 32; x < 64; x++) {
int[] column = columns.get(x);
int step = column[0] - columns.get(x - 1)[0];
assertTrue("Excessive terrain step at x=" + x + " step=" + step + " maximum=" + maximumStep, Math.abs(step) <= maximumStep);
assertTrue("Terrain overshoot at x=" + x, column[0] >= Math.min(scenario.originalGround(), scenario.replacementGround())
&& column[0] <= Math.max(scenario.originalGround(), scenario.replacementGround()));
assertEquals("Floating water or a dry gap below water at x=" + x, 0, column[3]);
if (difference == 0) {
assertEquals("Fluid-level transition must not create a stone dam at x=" + x, scenario.originalGround(), column[0]);
assertTrue("Fluid-level transition must remain wet at x=" + x, column[1] > column[0]);
assertTrue("Water level must stay within the old/new range at x=" + x,
column[1] >= Math.min(scenario.originalFluid(), scenario.replacementFluid())
&& column[1] <= Math.max(scenario.originalFluid(), scenario.replacementFluid()));
assertTrue("Water level must rise monotonically at x=" + x, column[1] >= columns.get(x - 1)[1]);
assertTrue("Water step at x=" + x, column[1] - columns.get(x - 1)[1] <= 1);
} else {
assertTrue("Transition must not create an isolated wall or pit at x=" + x + " step=" + step,
difference > 0 ? step >= 0 : step <= 0);
}
}
}
private static Map<Integer, int[]> columns(List<String> rows) {
Map<Integer, int[]> result = new TreeMap<>();
for (String row : rows.subList(1, rows.size())) {
String[] cells = row.split(",");
int[] values = {Integer.parseInt(cells[3]), Integer.parseInt(cells[4]),
Integer.parseInt(cells[5]), Integer.parseInt(cells[6])};
assertEquals("Duplicate transect column", null, result.put(Integer.parseInt(cells[1]), values));
}
return result;
}
private static Map<Path, byte[]> snapshot(Path root) throws Exception {
Map<Path, byte[]> entries = new TreeMap<>();
try (Stream<Path> paths = Files.walk(root)) {
for (Path path : paths.toList()) {
entries.put(root.relativize(path), Files.isDirectory(path) ? null : Files.readAllBytes(path));
}
}
return entries;
}
private static void assertSnapshotEquals(Map<Path, byte[]> expected, Map<Path, byte[]> actual) {
assertEquals(expected.keySet(), actual.keySet());
for (Map.Entry<Path, byte[]> entry : expected.entrySet()) {
assertArrayEquals(entry.getKey().toString(), entry.getValue(), actual.get(entry.getKey()));
}
}
private Path fixture(String name) throws Exception {
URL resource = Objects.requireNonNull(TransitionFixtureProbeTest.class.getClassLoader()
.getResource("transition-packs/" + name), "Transition fixture " + name);
Path source = Path.of(resource.toURI());
Path pack = temporary.newFolder(name).toPath();
try (Stream<Path> paths = Files.walk(source)) {
for (Path file : paths.filter(path -> Files.isRegularFile(path) && path.toString().endsWith(".json")
&& !source.relativize(path).toString().startsWith(".")).toList()) {
Path target = pack.resolve(source.relativize(file));
Files.createDirectories(target.getParent());
Files.copy(file, target);
}
}
Files.write(pack.resolve(".source-content"), new byte[]{0, 17, -128, 127});
return pack;
}
private record Scenario(String original, String replacement, int originalGround, int originalFluid,
int replacementGround, int replacementFluid) {
}
}
@@ -0,0 +1,76 @@
package art.arcane.iris.probe;
import art.arcane.iris.world.history.BoundaryColumnGeometry;
import art.arcane.iris.world.history.NativeTerrainReceipt;
import art.arcane.iris.world.history.SavedTerrainChunk;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import java.nio.file.Path;
import java.nio.file.Files;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThrows;
public final class TransitionProbeTest {
@Rule
public TemporaryFolder temporary = new TemporaryFolder();
@Test
public void rejectsWorkloadsOutsideTheBandAndExistingOutputs() throws Exception {
Path root = temporary.newFolder().toPath();
String[] arguments = {root.toString(), root.toString(), "overworld", "1337", "32", "2", root.resolve("result").toString()};
assertEquals(2, TransitionProbe.Configuration.parse(arguments).chunks());
arguments[5] = "3";
assertThrows(IllegalArgumentException.class, () -> TransitionProbe.Configuration.parse(arguments));
arguments[5] = "2";
arguments[4] = "8192";
assertThrows(IllegalArgumentException.class, () -> TransitionProbe.Configuration.parse(arguments));
arguments[4] = "32";
arguments[6] = root.toString();
assertThrows(IllegalArgumentException.class, () -> TransitionProbe.Configuration.parse(arguments));
}
@Test
public void missingSourcesAreRejectedWithoutCreatingSourceOrOutputDirectories() throws Exception {
Path root = temporary.newFolder().toPath();
for (int missingIndex : new int[]{0, 1}) {
Path missing = root.resolve("missing-" + missingIndex);
Path output = root.resolve("output-" + missingIndex);
String[] arguments = {root.toString(), root.toString(), "transition", "1337", "32", "2", output.toString()};
arguments[missingIndex] = missing.toString();
assertThrows(IllegalArgumentException.class, () -> TransitionProbe.main(arguments));
assertFalse(Files.exists(missing));
assertFalse(Files.exists(output));
}
}
@Test
public void persistsActualReceiptsAcrossNegativeRegionBoundariesAndMultipleChunks() throws Exception {
Path world = temporary.newFolder().toPath();
SavedTerrainChunk.VoxelSource source = new SavedTerrainChunk.VoxelSource() {
@Override
public BoundaryColumnGeometry.Voxel voxel(int localX, int worldY, int localZ) {
return worldY <= 5
? new BoundaryColumnGeometry.Voxel("minecraft:stone", BoundaryColumnGeometry.Phase.SOLID, "", false)
: new BoundaryColumnGeometry.Voxel("minecraft:air", BoundaryColumnGeometry.Phase.AIR, "", false);
}
@Override
public String biome(int localX, int worldY, int localZ) {
return "minecraft:plains";
}
};
for (int x : new int[]{-33, -32, -31, 0, 1, 32}) {
SavedTerrainChunk chunk = SavedTerrainChunk.captureBoundary(x, -1, 0, 16, "minecraft:full", source);
TransitionProbe.writeReceipt(world, x, -1, NativeTerrainReceipt.encode(chunk, 7, "epoch"));
}
for (int x : new int[]{-33, -32, -31, 0, 1, 32}) {
assertEquals("minecraft:full", SavedTerrainChunk.readStatus(world, x, -1));
assertEquals(5, SavedTerrainChunk.read(world, x, -1, 0, 16).column(x * 16, -16).oceanFloorHeight());
assertEquals(7, NativeTerrainReceipt.decode(SavedTerrainChunk.readReceipt(world, x, -1), "minecraft:full").activationId());
}
}
}
@@ -0,0 +1,21 @@
{
"name": "Terrain",
"derivative": "minecraft:plains",
"vanillaDerivative": "minecraft:plains",
"layers": [
{
"palette": [
{
"block": "minecraft:stone"
}
]
}
],
"generators": [
{
"generator": "flat",
"min": 32,
"max": 32
}
]
}
@@ -0,0 +1,49 @@
{
"name": "Boundary structure transition fixture",
"version": 1,
"mode": {
"type": "OVERWORLD"
},
"regions": [
"terrain"
],
"environment": "NORMAL",
"dimensionHeight": {
"min": 0,
"max": 128
},
"logicalHeight": 128,
"fluidHeight": 40,
"caveProfile": {
"enabled": false
},
"structures": [
{
"structures": [
"boundary"
],
"distribution": "RANDOM_SPREAD",
"spacing": 4,
"separation": 3,
"anchor": "HEIGHT_BAND",
"minHeight": 48,
"maxHeight": 64,
"terrain": {
"mode": "BORE",
"horizontalPadding": 2,
"floorPadding": 10,
"ceilingPadding": 2
},
"stilt": {
"maxDepth": 20,
"palette": {
"palette": [
{
"block": "minecraft:cobblestone"
}
]
}
}
}
]
}
@@ -0,0 +1,15 @@
{
"seed": 310,
"interpolator": {
"function": "NONE",
"horizontalScale": 1
},
"composite": [
{
"seed": 310,
"style": {
"style": "FLAT"
}
}
]
}
@@ -0,0 +1,4 @@
{
"object": "buildings/boundary",
"rotatable": false
}
@@ -0,0 +1,8 @@
{
"pieces": [
{
"piece": "boundary",
"weight": 1
}
]
}
@@ -0,0 +1,12 @@
{
"name": "Terrain",
"landBiomes": [
"terrain"
],
"seaBiomes": [
"terrain"
],
"shoreBiomes": [
"terrain"
]
}
@@ -0,0 +1,5 @@
{
"startPool": "boundary",
"maxDepth": 1,
"maxSizeChunks": 2
}
@@ -0,0 +1,38 @@
{
"name": "Terrain",
"derivative": "minecraft:plains",
"vanillaDerivative": "minecraft:plains",
"layers": [
{
"palette": [
{
"block": "minecraft:stone"
}
]
}
],
"generators": [
{
"generator": "flat",
"min": 32,
"max": 32
}
],
"objects": [
{
"place": [
"trees/boundary"
],
"chance": 1,
"density": 2,
"rotation": {
"enabled": false
},
"forcePlace": true,
"requireSurfaceSupport": false,
"translate": {
"y": 4
}
}
]
}
@@ -0,0 +1,20 @@
{
"name": "Boundary tree transition fixture",
"version": 1,
"mode": {
"type": "OVERWORLD"
},
"regions": [
"terrain"
],
"environment": "NORMAL",
"dimensionHeight": {
"min": 0,
"max": 128
},
"logicalHeight": 128,
"fluidHeight": 40,
"caveProfile": {
"enabled": false
}
}
@@ -0,0 +1,15 @@
{
"seed": 310,
"interpolator": {
"function": "NONE",
"horizontalScale": 1
},
"composite": [
{
"seed": 310,
"style": {
"style": "FLAT"
}
}
]
}
@@ -0,0 +1,12 @@
{
"name": "Terrain",
"landBiomes": [
"terrain"
],
"seaBiomes": [
"terrain"
],
"shoreBiomes": [
"terrain"
]
}
@@ -0,0 +1,21 @@
{
"name": "Terrain",
"derivative": "minecraft:plains",
"vanillaDerivative": "minecraft:plains",
"layers": [
{
"palette": [
{
"block": "minecraft:calcite"
}
]
}
],
"generators": [
{
"generator": "flat",
"min": 64,
"max": 64
}
]
}
@@ -0,0 +1,28 @@
{
"name": "Mountain transition fixture",
"version": 1,
"mode": {
"type": "OVERWORLD"
},
"regions": [
"terrain"
],
"environment": "NORMAL",
"dimensionHeight": {
"min": 0,
"max": 128
},
"logicalHeight": 128,
"fluidHeight": 40,
"caveProfile": {
"enabled": false
},
"rockPalette": {
"palette": [
{
"block": "minecraft:calcite"
}
]
},
"bedrock": false
}
@@ -0,0 +1,15 @@
{
"seed": 310,
"interpolator": {
"function": "NONE",
"horizontalScale": 1
},
"composite": [
{
"seed": 310,
"style": {
"style": "FLAT"
}
}
]
}
@@ -0,0 +1,12 @@
{
"name": "Terrain",
"landBiomes": [
"terrain"
],
"seaBiomes": [
"terrain"
],
"shoreBiomes": [
"terrain"
]
}
@@ -0,0 +1,21 @@
{
"name": "Terrain",
"derivative": "minecraft:plains",
"vanillaDerivative": "minecraft:plains",
"layers": [
{
"palette": [
{
"block": "minecraft:stone"
}
]
}
],
"generators": [
{
"generator": "flat",
"min": 32,
"max": 32
}
]
}
@@ -0,0 +1,46 @@
{
"name": "Lowland transition fixture",
"version": 1,
"mode": {
"type": "OVERWORLD"
},
"regions": [
"terrain"
],
"environment": "NORMAL",
"dimensionHeight": {
"min": 0,
"max": 128
},
"logicalHeight": 128,
"fluidHeight": 40,
"caveProfile": {
"enabled": true,
"verticalRange": {
"min": 32,
"max": 47
},
"verticalEdgeFade": 0,
"baseDensityStyle": {
"style": "FLAT"
},
"detailWeight": 0,
"densityThreshold": {
"min": 1,
"max": 1
},
"thresholdBias": 0,
"adaptiveSampling": false,
"allowSurfaceBreak": false,
"allowFluid": false,
"allowLava": false
},
"rockPalette": {
"palette": [
{
"block": "minecraft:stone"
}
]
},
"bedrock": false
}
@@ -0,0 +1,15 @@
{
"seed": 310,
"interpolator": {
"function": "NONE",
"horizontalScale": 1
},
"composite": [
{
"seed": 310,
"style": {
"style": "FLAT"
}
}
]
}
@@ -0,0 +1,12 @@
{
"name": "Terrain",
"landBiomes": [
"terrain"
],
"seaBiomes": [
"terrain"
],
"shoreBiomes": [
"terrain"
]
}
@@ -0,0 +1,21 @@
{
"name": "Terrain",
"derivative": "minecraft:plains",
"vanillaDerivative": "minecraft:plains",
"layers": [
{
"palette": [
{
"block": "minecraft:granite"
}
]
}
],
"generators": [
{
"generator": "flat",
"min": 32,
"max": 32
}
]
}
@@ -0,0 +1,28 @@
{
"name": "Lowland transition fixture",
"version": 1,
"mode": {
"type": "OVERWORLD"
},
"regions": [
"terrain"
],
"environment": "NORMAL",
"dimensionHeight": {
"min": 0,
"max": 128
},
"logicalHeight": 128,
"fluidHeight": 40,
"caveProfile": {
"enabled": false
},
"rockPalette": {
"palette": [
{
"block": "minecraft:granite"
}
]
},
"bedrock": false
}
@@ -0,0 +1,15 @@
{
"seed": 310,
"interpolator": {
"function": "NONE",
"horizontalScale": 1
},
"composite": [
{
"seed": 310,
"style": {
"style": "FLAT"
}
}
]
}
@@ -0,0 +1,12 @@
{
"name": "Terrain",
"landBiomes": [
"terrain"
],
"seaBiomes": [
"terrain"
],
"shoreBiomes": [
"terrain"
]
}
@@ -0,0 +1,21 @@
{
"name": "Terrain",
"derivative": "minecraft:plains",
"vanillaDerivative": "minecraft:plains",
"layers": [
{
"palette": [
{
"block": "minecraft:stone"
}
]
}
],
"generators": [
{
"generator": "flat",
"min": 32,
"max": 32
}
]
}
@@ -0,0 +1,20 @@
{
"name": "Lowland transition fixture",
"version": 1,
"mode": {
"type": "OVERWORLD"
},
"regions": [
"terrain"
],
"environment": "NORMAL",
"dimensionHeight": {
"min": 0,
"max": 128
},
"logicalHeight": 128,
"fluidHeight": 40,
"caveProfile": {
"enabled": false
}
}
@@ -0,0 +1,15 @@
{
"seed": 310,
"interpolator": {
"function": "NONE",
"horizontalScale": 1
},
"composite": [
{
"seed": 310,
"style": {
"style": "FLAT"
}
}
]
}
@@ -0,0 +1,12 @@
{
"name": "Terrain",
"landBiomes": [
"terrain"
],
"seaBiomes": [
"terrain"
],
"shoreBiomes": [
"terrain"
]
}
@@ -0,0 +1,21 @@
{
"name": "Terrain",
"derivative": "minecraft:plains",
"vanillaDerivative": "minecraft:plains",
"layers": [
{
"palette": [
{
"block": "minecraft:stone"
}
]
}
],
"generators": [
{
"generator": "flat",
"min": 64,
"max": 64
}
]
}
@@ -0,0 +1,20 @@
{
"name": "Mountain transition fixture",
"version": 1,
"mode": {
"type": "OVERWORLD"
},
"regions": [
"terrain"
],
"environment": "NORMAL",
"dimensionHeight": {
"min": 0,
"max": 128
},
"logicalHeight": 128,
"fluidHeight": 40,
"caveProfile": {
"enabled": false
}
}
@@ -0,0 +1,15 @@
{
"seed": 310,
"interpolator": {
"function": "NONE",
"horizontalScale": 1
},
"composite": [
{
"seed": 310,
"style": {
"style": "FLAT"
}
}
]
}
@@ -0,0 +1,12 @@
{
"name": "Terrain",
"landBiomes": [
"terrain"
],
"seaBiomes": [
"terrain"
],
"shoreBiomes": [
"terrain"
]
}
@@ -0,0 +1,21 @@
{
"name": "Terrain",
"derivative": "minecraft:plains",
"vanillaDerivative": "minecraft:plains",
"layers": [
{
"palette": [
{
"block": "minecraft:stone"
}
]
}
],
"generators": [
{
"generator": "flat",
"min": -24,
"max": -24
}
]
}
@@ -0,0 +1,20 @@
{
"name": "Ocean-highwater transition fixture",
"version": 1,
"mode": {
"type": "OVERWORLD"
},
"regions": [
"terrain"
],
"environment": "NORMAL",
"dimensionHeight": {
"min": 0,
"max": 128
},
"logicalHeight": 128,
"fluidHeight": 48,
"caveProfile": {
"enabled": false
}
}
@@ -0,0 +1,15 @@
{
"seed": 310,
"interpolator": {
"function": "NONE",
"horizontalScale": 1
},
"composite": [
{
"seed": 310,
"style": {
"style": "FLAT"
}
}
]
}
@@ -0,0 +1,12 @@
{
"name": "Terrain",
"landBiomes": [
"terrain"
],
"seaBiomes": [
"terrain"
],
"shoreBiomes": [
"terrain"
]
}
@@ -0,0 +1,21 @@
{
"name": "Terrain",
"derivative": "minecraft:plains",
"vanillaDerivative": "minecraft:plains",
"layers": [
{
"palette": [
{
"block": "minecraft:stone"
}
]
}
],
"generators": [
{
"generator": "flat",
"min": -16,
"max": -16
}
]
}
@@ -0,0 +1,20 @@
{
"name": "Ocean transition fixture",
"version": 1,
"mode": {
"type": "OVERWORLD"
},
"regions": [
"terrain"
],
"environment": "NORMAL",
"dimensionHeight": {
"min": 0,
"max": 128
},
"logicalHeight": 128,
"fluidHeight": 40,
"caveProfile": {
"enabled": false
}
}
@@ -0,0 +1,15 @@
{
"seed": 310,
"interpolator": {
"function": "NONE",
"horizontalScale": 1
},
"composite": [
{
"seed": 310,
"style": {
"style": "FLAT"
}
}
]
}
@@ -0,0 +1,12 @@
{
"name": "Terrain",
"landBiomes": [
"terrain"
],
"seaBiomes": [
"terrain"
],
"shoreBiomes": [
"terrain"
]
}
@@ -0,0 +1,21 @@
{
"name": "Terrain",
"derivative": "minecraft:plains",
"vanillaDerivative": "minecraft:plains",
"layers": [
{
"palette": [
{
"block": "minecraft:stone"
}
]
}
],
"generators": [
{
"generator": "flat",
"min": 48,
"max": 80
}
]
}
@@ -0,0 +1,20 @@
{
"name": "Mountain transition fixture",
"version": 1,
"mode": {
"type": "OVERWORLD"
},
"regions": [
"terrain"
],
"environment": "NORMAL",
"dimensionHeight": {
"min": 0,
"max": 128
},
"logicalHeight": 128,
"fluidHeight": 40,
"caveProfile": {
"enabled": false
}
}
@@ -0,0 +1,3 @@
{
"expression": "0.5 + 0.125 * sin(x / 2)"
}
@@ -0,0 +1,15 @@
{
"seed": 310,
"interpolator": {
"function": "NONE",
"horizontalScale": 1
},
"composite": [
{
"seed": 310,
"style": {
"expression": "ripple"
}
}
]
}
@@ -0,0 +1,12 @@
{
"name": "Terrain",
"landBiomes": [
"terrain"
],
"seaBiomes": [
"terrain"
],
"shoreBiomes": [
"terrain"
]
}
@@ -0,0 +1,21 @@
{
"name": "Terrain",
"derivative": "minecraft:plains",
"vanillaDerivative": "minecraft:plains",
"layers": [
{
"palette": [
{
"block": "minecraft:stone"
}
]
}
],
"generators": [
{
"generator": "flat",
"min": 8,
"max": 40
}
]
}
@@ -0,0 +1,20 @@
{
"name": "Lowland transition fixture",
"version": 1,
"mode": {
"type": "OVERWORLD"
},
"regions": [
"terrain"
],
"environment": "NORMAL",
"dimensionHeight": {
"min": 0,
"max": 128
},
"logicalHeight": 128,
"fluidHeight": 40,
"caveProfile": {
"enabled": false
}
}
@@ -0,0 +1,3 @@
{
"expression": "0.5 + 0.25 * sin(x / 64) + 0.125 * sin(y / 48)"
}
@@ -0,0 +1,15 @@
{
"seed": 310,
"interpolator": {
"function": "NONE",
"horizontalScale": 1
},
"composite": [
{
"seed": 310,
"style": {
"expression": "slope"
}
}
]
}
@@ -0,0 +1,12 @@
{
"name": "Terrain",
"landBiomes": [
"terrain"
],
"seaBiomes": [
"terrain"
],
"shoreBiomes": [
"terrain"
]
}
@@ -0,0 +1,21 @@
{
"name": "Terrain",
"derivative": "minecraft:plains",
"vanillaDerivative": "minecraft:plains",
"layers": [
{
"palette": [
{
"block": "minecraft:stone"
}
]
}
],
"generators": [
{
"generator": "flat",
"min": 40,
"max": 72
}
]
}
@@ -0,0 +1,20 @@
{
"name": "Mountain transition fixture",
"version": 1,
"mode": {
"type": "OVERWORLD"
},
"regions": [
"terrain"
],
"environment": "NORMAL",
"dimensionHeight": {
"min": 0,
"max": 128
},
"logicalHeight": 128,
"fluidHeight": 40,
"caveProfile": {
"enabled": false
}
}
@@ -0,0 +1,3 @@
{
"expression": "0.5 + 0.30 * sin(x / 40 + 1) + 0.10 * sin(y / 48) + 0.03 * sin(x / 5)"
}
@@ -0,0 +1,15 @@
{
"seed": 310,
"interpolator": {
"function": "NONE",
"horizontalScale": 1
},
"composite": [
{
"seed": 310,
"style": {
"expression": "slope"
}
}
]
}
@@ -0,0 +1,12 @@
{
"name": "Terrain",
"landBiomes": [
"terrain"
],
"seaBiomes": [
"terrain"
],
"shoreBiomes": [
"terrain"
]
}
@@ -0,0 +1,31 @@
{
"name": "Terrain",
"derivative": "minecraft:plains",
"vanillaDerivative": "minecraft:plains",
"layers": [
{
"palette": [
{
"block": "minecraft:stone"
}
]
}
],
"generators": [
{
"generator": "flat",
"min": 96,
"max": 96
}
],
"terrain3D": {
"amplitude": 64,
"horizontalScale": 96,
"verticalScale": 16,
"minimumSlope": 0,
"slopeFade": 1,
"fluidClearance": 0,
"fluidFade": 1,
"crackDepth": 0
}
}
@@ -0,0 +1,20 @@
{
"name": "Lowland transition fixture",
"version": 1,
"mode": {
"type": "OVERWORLD"
},
"regions": [
"terrain"
],
"environment": "NORMAL",
"dimensionHeight": {
"min": 0,
"max": 256
},
"logicalHeight": 256,
"fluidHeight": 0,
"caveProfile": {
"enabled": false
}
}
@@ -0,0 +1,15 @@
{
"seed": 310,
"interpolator": {
"function": "NONE",
"horizontalScale": 1
},
"composite": [
{
"seed": 310,
"style": {
"style": "FLAT"
}
}
]
}
@@ -0,0 +1,12 @@
{
"name": "Terrain",
"landBiomes": [
"terrain"
],
"seaBiomes": [
"terrain"
],
"shoreBiomes": [
"terrain"
]
}
@@ -0,0 +1,31 @@
{
"name": "Terrain",
"derivative": "minecraft:plains",
"vanillaDerivative": "minecraft:plains",
"layers": [
{
"palette": [
{
"block": "minecraft:stone"
}
]
}
],
"generators": [
{
"generator": "flat",
"min": 128,
"max": 128
}
],
"terrain3D": {
"amplitude": 64,
"horizontalScale": 96,
"verticalScale": 16,
"minimumSlope": 0,
"slopeFade": 1,
"fluidClearance": 0,
"fluidFade": 1,
"crackDepth": 0
}
}
@@ -0,0 +1,20 @@
{
"name": "Mountain transition fixture",
"version": 1,
"mode": {
"type": "OVERWORLD"
},
"regions": [
"terrain"
],
"environment": "NORMAL",
"dimensionHeight": {
"min": 0,
"max": 256
},
"logicalHeight": 256,
"fluidHeight": 0,
"caveProfile": {
"enabled": false
}
}
@@ -0,0 +1,15 @@
{
"seed": 310,
"interpolator": {
"function": "NONE",
"horizontalScale": 1
},
"composite": [
{
"seed": 310,
"style": {
"style": "FLAT"
}
}
]
}
@@ -0,0 +1,12 @@
{
"name": "Terrain",
"landBiomes": [
"terrain"
],
"seaBiomes": [
"terrain"
],
"shoreBiomes": [
"terrain"
]
}