Checkpoint alpha29 terrain ledges, transit and native mineshaft corrections

This commit is contained in:
koka
2026-09-12 09:44:30 +02:00
parent 9f82577927
commit 8c90c61c53
31 changed files with 1269 additions and 58 deletions
+26
View File
@@ -56,6 +56,10 @@ tasks.named('runGameTest') {
systemProperty('sanctuary.test.generateStructures', providers.gradleProperty('sanctuaryGenerateStructures').getOrElse('true'))
systemProperty('sanctuary.test.selective22', providers.gradleProperty('sanctuarySelective22Tests').getOrElse('false'))
systemProperty('sanctuary.transit.plannerDiagnostics', providers.gradleProperty('sanctuaryTransitDiagnostics').getOrElse('false'))
systemProperty('sanctuary.test.transit29', providers.gradleProperty('sanctuaryTransit29Tests').getOrElse('false'))
systemProperty('sanctuary.test.transit29.planOnly', providers.gradleProperty('sanctuaryTransit29PlanOnly').getOrElse('false'))
systemProperty('sanctuary.test.requireMineshaft29', providers.gradleProperty('sanctuaryRequireMineshaft29').getOrElse('false'))
systemProperty('sanctuary.expansion.structureDiagnostics', providers.gradleProperty('sanctuaryStructureDiagnostics').getOrElse('false'))
systemProperty('sanctuary.test.heritage21', providers.gradleProperty('sanctuaryHeritage21Tests').getOrElse('false'))
systemProperty('sanctuary.test.lostCity21', providers.gradleProperty('sanctuaryLostCity21Tests').getOrElse('false'))
systemProperty('sanctuary.test.transit21', providers.gradleProperty('sanctuaryTransit21Tests').getOrElse('false'))
@@ -631,3 +635,25 @@ tasks.register('cavesRelief28Smoke', JavaExec) {
mainClass = 'fr.koka.sanctuary.worldgen.CavesRelief28Smoke'
}
tasks.named('check') { dependsOn('cavesRelief28Smoke') }
// Fine ledges retain the alpha28 field below the upper relief.
tasks.register('terraces29Smoke', JavaExec) {
group = 'verification'
dependsOn tasks.named('testClasses')
classpath = sourceSets.test.runtimeClasspath
mainClass = 'fr.koka.sanctuary.worldgen.Terraces29Smoke'
}
tasks.named('check') { dependsOn('terraces29Smoke') }
[
transit29Smoke: 'fr.koka.sanctuary.worldgen.transit.Transit29Smoke',
mineshaftAdmission29Smoke: 'fr.koka.sanctuary.expansion.MineshaftAdmission29Smoke'
].each { taskName, harness ->
tasks.register(taskName, JavaExec) {
group = 'verification'
dependsOn tasks.named('testClasses')
classpath = sourceSets.test.runtimeClasspath
mainClass = harness
}
tasks.named('check') { dependsOn(taskName) }
}
@@ -40,27 +40,64 @@ public final class Alpha28NativeStructuresGameTests {
report.put("scope", "Native structures globally disabled"); report.put("passed", true); write(report); helper.succeed(); return;
}
var root = host.source().islands().stream().filter(island -> island.origin()).findFirst().orElseThrow();
var candidates = ExpansionStructures23.candidates(root).stream().limit(12).toList();
var candidates = ExpansionStructures23.candidates(root).stream().limit(24).toList();
var registry = level.registryAccess().lookupOrThrow(Registries.STRUCTURE);
var found = new LinkedHashMap<String, StructureStart>(); var observed = new TreeMap<String, Integer>();
var firstTwelve = new TreeMap<String, Integer>(); int candidateIndex = 0;
for (var candidate : candidates) {
var chunk = level.getChunk(candidate.chunk().x(), candidate.chunk().z(), ChunkStatus.STRUCTURE_STARTS, true);
for (var start : chunk.getAllStarts().values()) {
if (!start.isValid()) continue;
String id = registry.getKey(start.getStructure()).toString(); observed.merge(id, 1, Integer::sum);
if (candidateIndex < 12) firstTwelve.merge(id, 1, Integer::sum);
if (List.of("minecraft:mineshaft", "minecraft:mineshaft_mesa", "minecraft:swamp_hut").contains(id)) found.putIfAbsent(id, start);
}
candidateIndex++;
}
report.put("candidate_chunks", candidates.size()); report.put("observed_starts", observed);
report.put("first_twelve_starts", firstTwelve);
if (Boolean.getBoolean("sanctuary.test.requireMineshaft29") && found.keySet().stream().noneMatch(id -> id.contains("mineshaft"))) {
var islandsBefore = List.copyOf(host.source().islands());
var wanted = net.minecraft.core.HolderSet.direct(registry.get(Identifier.parse("minecraft:mineshaft")).orElseThrow(),
registry.get(Identifier.parse("minecraft:mineshaft_mesa")).orElseThrow());
// Same finite real-start path as /locate; no injected plan, reference increment or expansion.
var located = ExpansionStructures23.findNearest(level, wanted, BlockPos.ZERO, 8, false);
helper.assertTrue(islandsBefore.equals(host.source().islands()), "Native /locate never adds an expansion or changes the island descriptors");
var locateProof = new LinkedHashMap<String, Object>();
locateProof.put("invoked", true); locateProof.put("found", located != null);
locateProof.put("islands_unchanged", true); locateProof.put("skip_referenced", false);
locateProof.put("root_candidates_available", ExpansionStructures23.candidates(root).size());
report.put("locate_mineshaft", locateProof);
if (located != null) {
String id = located.getSecond().unwrapKey().orElseThrow().identifier().toString();
for (var candidate : ExpansionStructures23.candidates(root)) {
var start = level.getChunk(candidate.chunk().x(), candidate.chunk().z(), ChunkStatus.STRUCTURE_STARTS, true)
.getStartForStructure(located.getSecond().value());
if (start != null && start.isValid() && start.getPieces().getFirst().getLocatorPosition().equals(located.getFirst())) {
found.put(id, start); locateProof.put("start_chunk", List.of(candidate.chunk().x(), candidate.chunk().z())); break;
}
}
helper.assertTrue(found.containsKey(id), "The native /locate result belongs to a real saved start");
locateProof.put("id", id); locateProof.put("position", located.getFirst().toShortString());
}
} else report.put("locate_mineshaft", Map.of("invoked", false));
var full = new LinkedHashSet<ChunkPos>();
// Only a few actual pieces are visited: this is not full-map pregeneration.
// Visit actual corridor bodies (including both endpoint chunks), not just four
// piece centres that could all belong to unadorned rooms. No graph is truncated.
var pieceContext = StructurePieceSerializationContext.fromLevel(level);
int mineFamilies = (int) found.keySet().stream().filter(id -> id.contains("mineshaft")).count();
for (var entry : found.entrySet()) {
full.add(entry.getValue().getChunkPos());
int remaining = entry.getKey().equals("minecraft:swamp_hut") ? 4 : 4;
for (var piece : entry.getValue().getPieces()) {
if (remaining-- <= 0) break;
var b = piece.getBoundingBox(); full.add(new ChunkPos(b.getCenter().getX() >> 4, b.getCenter().getZ() >> 4));
if (entry.getKey().equals("minecraft:swamp_hut")) b.intersectingChunks().forEach(full::add);
var pieces = entry.getValue().getPieces();
if (entry.getKey().contains("mineshaft")) pieces = pieces.stream()
.filter(piece -> piece instanceof net.minecraft.world.level.levelgen.structure.structures.MineshaftPieces.MineShaftCorridor)
.sorted(Comparator.comparingInt(piece -> {
var tag = piece.createTag(pieceContext);
return tag.getBoolean("hr").orElse(false) ? 0 : tag.getBoolean("sc").orElse(false) ? 1 : 2;
})).limit(mineFamilies > 1 ? 2 : 4).toList();
for (var piece : pieces) {
var proposed = new LinkedHashSet<>(full); piece.getBoundingBox().intersectingChunks().forEach(proposed::add);
if (proposed.size() <= 24) full.addAll(proposed);
}
}
// A few additional real generation chunks can contain native monster_room attempts.
@@ -90,6 +127,13 @@ public final class Alpha28NativeStructuresGameTests {
var registry = level.registryAccess().lookupOrThrow(Registries.STRUCTURE);
var context = StructurePieceSerializationContext.fromLevel(level);
var proofs = new LinkedHashMap<String, Object>();
var census = StructureCensusService.snapshot(level.getServer()).evidence().sites().values();
var monsterPositions = new HashSet<BlockPos>();
for (var site : census) {
var key = site.key();
if (key.id().equals(StructureCensusMemory.MONSTER_ROOM) && key.dimension().equals(level.dimension().identifier().toString()))
monsterPositions.add(new BlockPos(key.x(), key.y(), key.z()));
}
for (var entry : found.entrySet()) {
var expected = entry.getValue(); var cp = expected.getChunkPos();
var actual = level.getChunk(cp.x(), cp.z(), ChunkStatus.FULL, true)
@@ -99,14 +143,54 @@ public final class Alpha28NativeStructuresGameTests {
var restored = StructureStart.loadStaticStart(context, tag, level.getSeed());
helper.assertTrue(restored != null && tag.equals(restored.createTag(context, cp)), "Native piece NBT roundtrip: " + entry.getKey());
var counts = new TreeMap<String, Integer>(); var positions = new HashSet<BlockPos>();
var spawners = new ArrayList<Object>(); var corridorFlags = new ArrayList<Object>();
var spawnerTypes = new HashMap<BlockPos, String>();
for (var piece : actual.getPieces()) {
var b = piece.getBoundingBox();
if (piece instanceof net.minecraft.world.level.levelgen.structure.structures.MineshaftPieces.MineShaftCorridor) {
var flags = piece.createTag(context);
corridorFlags.add(Map.of("box", b.toString(), "fully_visited", b.intersectingChunks().allMatch(full::contains),
"rails", flags.getBoolean("hr").orElse(false), "spider_corridor", flags.getBoolean("sc").orElse(false),
"spawner_placed", flags.getBoolean("hps").orElse(false)));
}
for (int x = b.minX(); x <= b.maxX(); x++) for (int z = b.minZ(); z <= b.maxZ(); z++) {
if (!full.contains(new ChunkPos(x >> 4, z >> 4))) continue;
for (int y = b.minY(); y <= b.maxY(); y++) {
for (int y = b.minY() - 1; y <= b.maxY() + 1; y++) {
BlockPos pos = new BlockPos(x, y, z); if (!positions.add(pos)) continue;
var state = level.getBlockState(pos);
if (!state.isAir()) counts.merge(BuiltInRegistries.BLOCK.getKey(state.getBlock()).toString(), 1, Integer::sum);
if (state.is(Blocks.SPAWNER)) {
helper.assertTrue(level.getBlockEntity(pos) instanceof SpawnerBlockEntity, "A mine spawner has its actual native block entity");
var saved = ((SpawnerBlockEntity) level.getBlockEntity(pos)).saveWithFullMetadata(level.registryAccess());
String mob = saved.getCompoundOrEmpty("SpawnData").getCompoundOrEmpty("entity").getStringOr("id", "");
spawnerTypes.put(pos, mob);
String origin = monsterPositions.contains(pos) ? "monster_room" : "mineshaft";
if (entry.getKey().contains("mineshaft")) {
if (origin.equals("monster_room")) helper.assertTrue(List.of("minecraft:zombie", "minecraft:skeleton", "minecraft:spider").contains(mob),
"A recorded vanilla dungeon inside the mine retains its own monster type");
else {
var flags = piece.createTag(context);
helper.assertTrue(mob.equals("minecraft:cave_spider") && flags.getBooleanOr("sc", false) && flags.getBooleanOr("hps", false),
"A native mine spawner agrees with its cave-spider piece flags");
}
}
spawners.add(Map.of("position", pos.toShortString(), "origin", origin, "entity", mob, "nbt", saved.toString()));
}
}
}
if (piece instanceof net.minecraft.world.level.levelgen.structure.structures.MineshaftPieces.MineShaftCorridor) {
var flags = piece.createTag(context);
if (flags.getBooleanOr("sc", false) && flags.getBooleanOr("hps", false) && b.intersectingChunks().allMatch(full::contains)) {
boolean actualSpider = spawnerTypes.entrySet().stream().anyMatch(e -> b.isInside(e.getKey()) && e.getValue().equals("minecraft:cave_spider"));
// A later successful native monster_room may replace a mine's spawner. Its real
// census coordinate must then lie on this corridor's native spawner centreline.
boolean recordedReplacement = false;
int length = piece.getOrientation().getAxis() == net.minecraft.core.Direction.Axis.Z ? b.getZSpan() : b.getXSpan();
for (int z = 1; z < length; z++) {
var at = SwampHutPlacement28.worldPosition(piece,1,0,z);
if (monsterPositions.contains(at) && spawnerTypes.containsKey(at)) recordedReplacement = true;
}
helper.assertTrue(actualSpider || recordedReplacement, "A fully visited sc/hps corridor has its actual cave-spider spawner or an evidenced later vanilla dungeon");
}
}
if (entry.getKey().equals("minecraft:swamp_hut")) for (var pier : SwampHutPlacement28.piers(piece)) {
@@ -118,23 +202,30 @@ public final class Alpha28NativeStructuresGameTests {
.filter(e -> e.getKey().endsWith("_planks") || e.getKey().endsWith("_fence") || e.getKey().equals("minecraft:rail"))
.mapToInt(Map.Entry::getValue).sum() > 0, "A selected mine creates actual native timber/rail geometry");
proofs.put(entry.getKey(), Map.of("start_chunk", List.of(cp.x(), cp.z()), "pieces", actual.getPieces().size(),
"blocks_in_loaded_pieces", counts, "native_tag", tag.toString(), "visit", actual.getPieces().getFirst().getLocatorPosition().toShortString()));
"blocks_in_loaded_pieces", counts, "native_tag", tag.toString(), "visit", actual.getPieces().getFirst().getLocatorPosition().toShortString(),
"corridor_flags", corridorFlags, "spawners", spawners));
}
var rooms = new ArrayList<Object>();
for (var site : StructureCensusService.snapshot(level.getServer()).evidence().sites().values()) {
for (var site : census) {
var key = site.key();
if (!key.id().equals(StructureCensusMemory.MONSTER_ROOM) || !key.dimension().equals(level.dimension().identifier().toString())) continue;
var cp = new ChunkPos(key.x() >> 4, key.z() >> 4);
if (!full.contains(cp)) continue;
var at = new BlockPos(key.x(), key.y(), key.z());
helper.assertTrue(level.getBlockEntity(at) instanceof SpawnerBlockEntity, "Successful vanilla monster_room has its real spawner at " + at);
rooms.add(Map.of("position", List.of(key.x(), key.y(), key.z()), "spawner", true));
var spawnerNbt = ((SpawnerBlockEntity) level.getBlockEntity(at)).saveWithFullMetadata(level.registryAccess());
helper.assertTrue(List.of("minecraft:zombie", "minecraft:skeleton", "minecraft:spider").stream().anyMatch(spawnerNbt.toString()::contains), "Vanilla dungeon retains a real vanilla monster type");
rooms.add(Map.of("position", List.of(key.x(), key.y(), key.z()), "spawner", true, "spawner_nbt", spawnerNbt.toString()));
}
report.put("structures", proofs); report.put("monster_rooms", rooms); report.put("full_chunks", full.size());
report.put("mineshaft_found", found.keySet().stream().anyMatch(id -> id.contains("mineshaft")));
boolean mineFound = found.keySet().stream().anyMatch(id -> id.contains("mineshaft"));
report.put("mineshaft_found", mineFound);
report.put("mineshaft_required", Boolean.getBoolean("sanctuary.test.requireMineshaft29"));
report.put("swamp_hut_found", found.containsKey("minecraft:swamp_hut"));
report.put("monster_room_found", !rooms.isEmpty());
report.put("scope", "At most12 normal start candidates and24 FULL chunks; absence is reported, never replaced by synthetic fixtures");
report.put("scope", "Sample of24 normal start candidates, optional real /locate when a mine is required, and at most24 requested FULL chunks; the first12 remain separately reported. Corridor flags and optional spawners do not impose random-content quotas");
if (Boolean.getBoolean("sanctuary.test.requireMineshaft29"))
helper.assertTrue(mineFound, "The required mine witness has a real native start and inspected FULL timber/rail geometry");
}
private static void write(Map<String, Object> report) throws Exception {
Files.createDirectories(OUT); Files.writeString(OUT.resolve("native.json"), JSON.toJson(report) + "\n");
@@ -0,0 +1,68 @@
package fr.koka.sanctuary.gametest;
import com.google.gson.GsonBuilder;
import fr.koka.sanctuary.SanctuaryMod;
import fr.koka.sanctuary.worldgen.PopulationIslandDensity;
import fr.koka.sanctuary.worldgen.SanctuaryChunkGenerator;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import net.fabricmc.fabric.api.gametest.v1.GameTest;
import net.minecraft.core.registries.Registries;
import net.minecraft.gametest.framework.GameTestHelper;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.level.levelgen.densityfunction.SamplerContext;
/** Compares registered native fields; does not place blocks or load additional chunks. */
public final class Terraces29WorldGameTests {
@GameTest(maxTicks = 2400)
public void fineLedgesPreserveTheAcceptedMass(GameTestHelper helper) throws Exception {
var level = helper.getLevel();
var generator = (SanctuaryChunkGenerator) level.getChunkSource().getGenerator();
var field = (PopulationIslandDensity) level.registryAccess().lookupOrThrow(Registries.DENSITY_FUNCTION)
.getOrThrow(ResourceKey.create(Registries.DENSITY_FUNCTION,
SanctuaryMod.id("population_" + generator.capacity().players()))).value();
helper.assertTrue(field.terraces29(), "Current resources enable the fine ledges");
var random = generator.starterContext().random();
var current = random.getSampler(field); var previous = random.getSampler(field.legacy28());
var context = SamplerContext.builder().enableCaches().build();
var report = new LinkedHashMap<String, Object>();
report.put("seed", level.getSeed()); report.put("diameter", generator.initialDiameter());
report.put("process_id", ProcessHandle.current().pid());
int radius = Math.min(400, generator.initialDiameter() / 2), lowerSamples = 0, unchanged = 0, changed = 0;
for (int z = -radius; z <= radius; z += 32) for (int x = -radius; x <= radius; x += 32) {
for (int y = 32; y <= 214; y += 13) {
helper.assertTrue(Float.floatToIntBits(current.sampleValue(context, x, y, z))
== Float.floatToIntBits(previous.sampleValue(context, x, y, z)), "Lower density is exactly alpha28");
lowerSamples++;
}
for (int y = 215; y <= 320; y++) {
boolean old = previous.sampleValue(context, x, y, z) > 0;
boolean now = current.sampleValue(context, x, y, z) > 0;
if (old == now) unchanged++; else changed++;
if (Math.hypot(x, z) <= 48) helper.assertTrue(old == now, "Arrival silhouette is unchanged");
}
}
helper.assertTrue(changed > 0 && unchanged > changed * 20L, "The edit produces small ledges, not a replacement of the upper mass");
report.put("unchanged_lower_density_samples", lowerSamples);
report.put("unchanged_upper_samples", unchanged); report.put("changed_upper_samples", changed);
var sections = new LinkedHashMap<String, Object>();
for (int z : new int[]{0, 96}) {
var before = new ArrayList<String>(); var after = new ArrayList<String>();
for (int y = 305; y >= 205; y--) {
var a = new StringBuilder(); var b = new StringBuilder();
for (int x = -radius; x <= radius; x++) {
a.append(previous.sampleValue(context, x, y, z) > 0 ? '#' : '.');
b.append(current.sampleValue(context, x, y, z) > 0 ? '#' : '.');
}
before.add(a.toString()); after.add(b.toString());
}
sections.put("z" + z, Map.of("before", before, "after", after, "min_x", -radius, "max_y", 305, "step", 1));
}
report.put("sections", sections); report.put("passed", true);
Path path = Path.of("diagnostics/alpha29", "terraces-" + generator.initialDiameter() + "-" + level.getSeed() + ".json");
Files.createDirectories(path.getParent());
Files.writeString(path, new GsonBuilder().setPrettyPrinting().create().toJson(report));
helper.succeed();
}
}
@@ -342,15 +342,18 @@ public final class Transit21WorldGameTests {
static Map<String, Object> verifyAccess(GameTestHelper helper, TransitPlan21 plan) {
var level = helper.getLevel();
var accesses = plan.modules().stream().filter(m -> m.kind() == TransitPlan21.Kind.ACCESS).toList();
boolean fitted29 = plan.diagnostic().startsWith("accepted29");
int approachHalfWidth = fitted29 ? 1 : 2;
int approachDepth = fitted29 ? 1 : 3;
helper.assertTrue(accesses.size() == 2, "Native transit requires exactly two surface gates");
Predicate<BlockPos> inside = pos -> plan.contains(pos.getX(), pos.getY(), pos.getZ())
|| accesses.stream().anyMatch(m -> pos.getX() >= m.centerX() - 2 && pos.getX() <= m.centerX() + 2
&& pos.getZ() >= m.minZ() - 3 && pos.getZ() < m.minZ()
|| accesses.stream().anyMatch(m -> pos.getX() >= m.centerX() - approachHalfWidth && pos.getX() <= m.centerX() + approachHalfWidth
&& pos.getZ() >= m.minZ() - approachDepth && pos.getZ() < m.minZ()
&& pos.getY() >= m.surfaceY() && pos.getY() <= m.surfaceY() + 2);
var start = TransitPiece21.locator(accesses.getFirst());
Predicate<BlockPos> naturalApproach = pos -> accesses.stream().anyMatch(m ->
pos.getX() >= m.centerX() - 2 && pos.getX() <= m.centerX() + 2
&& pos.getZ() >= m.minZ() - 3 && pos.getZ() < m.minZ()
pos.getX() >= m.centerX() - approachHalfWidth && pos.getX() <= m.centerX() + approachHalfWidth
&& pos.getZ() >= m.minZ() - approachDepth && pos.getZ() < m.minZ()
&& pos.getY() >= m.surfaceY() && pos.getY() <= m.surfaceY() + 2);
var reached = walk(helper, start, inside, naturalApproach); int targets = 0;
var arrivals = new ArrayList<Map<String, Object>>();
@@ -364,7 +367,8 @@ public final class Transit21WorldGameTests {
var gate = TransitPiece21.locator(access);
helper.assertTrue(reached.containsKey(Foot.at(gate)), "The second independent surface gate is not connected by real stairs");
int approaches = 0;
for (int x = access.centerX() - 2; x <= access.centerX() + 2; x++) for (int z = access.minZ() - 3; z < access.minZ(); z++) {
for (int x = access.centerX() - approachHalfWidth; x <= access.centerX() + approachHalfWidth; x++)
for (int z = access.minZ() - approachDepth; z < access.minZ(); z++) {
var high = new BlockPos(x, access.surfaceY() + 1, z);
var low = high.below(); var position = standable(level, high) ? high : low;
helper.assertTrue(standable(level, position) && reached.containsKey(Foot.at(position)),
@@ -0,0 +1,130 @@
package fr.koka.sanctuary.gametest;
import com.google.gson.GsonBuilder;
import fr.koka.sanctuary.SanctuaryMod;
import fr.koka.sanctuary.worldgen.SanctuaryChunkGenerator;
import fr.koka.sanctuary.worldgen.transit.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import net.fabricmc.fabric.api.gametest.v1.GameTest;
import net.minecraft.core.HolderSet;
import net.minecraft.core.registries.Registries;
import net.minecraft.gametest.framework.GameTestHelper;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.chunk.status.ChunkStatus;
import net.minecraft.world.level.levelgen.structure.StructureStart;
import net.minecraft.world.level.levelgen.structure.pieces.StructurePieceSerializationContext;
/** One actual island and its published route. No injected starts, synthetic terrain or expansion. */
public final class Transit29WorldGameTests {
private static final Path OUT = Path.of("diagnostics/transit29");
private static final com.google.gson.Gson JSON = new GsonBuilder().setPrettyPrinting().create();
@GameTest(maxTicks = 12000)
public void throughRouteHasTwoNativeAccesses(GameTestHelper helper) throws Exception {
if (!Boolean.getBoolean("sanctuary.test.transit29")) { helper.succeed(); return; }
var report = new LinkedHashMap<String, Object>();
try {
var level = helper.getLevel();
var generator = (SanctuaryChunkGenerator) level.getChunkSource().getGenerator();
var plan = generator.transitPlan21();
report.put("seed", level.getSeed()); report.put("diameter", generator.initialDiameter());
report.put("process_id", ProcessHandle.current().pid()); report.put("planner", plan.diagnostic());
report.put("modules", plan.modules().size()); report.put("probes", plan.samples());
report.put("generate_structures", level.structureManager().shouldGenerateStructures());
if (!level.structureManager().shouldGenerateStructures()) {
helper.assertTrue(plan.isEmpty(), "Structures OFF leaves no transit reservation");
report.put("passed", true); write(report); helper.succeed(); return;
}
helper.assertFalse(plan.isEmpty(), "A structures-enabled Sanctuary world must retain its main traversal: " + plan.diagnostic());
var accesses = plan.modules().stream().filter(m -> m.kind() == TransitPlan21.Kind.ACCESS).toList();
helper.assertTrue(accesses.size() == 2, "The complete traversal has two independent surface accesses");
int span = Math.max(Math.abs(accesses.getFirst().centerX() - accesses.getLast().centerX()),
Math.abs(accesses.getFirst().centerZ() - accesses.getLast().centerZ()));
helper.assertTrue(span >= generator.initialDiameter() / 4, "The main route spans at least a quarter of the island diameter");
report.put("span", span);
var holder = level.registryAccess().lookupOrThrow(Registries.STRUCTURE).getOrThrow(TransitStructures21.KEY);
var starts = new LinkedHashMap<ChunkPos, Integer>();
var context = StructurePieceSerializationContext.fromLevel(level);
var biomes = new TreeMap<String, Integer>();
var chunks = new LinkedHashSet<ChunkPos>();
for (var module : plan.modules()) {
var cp = new ChunkPos(module.startChunkX(), module.startChunkZ());
if (!starts.containsKey(cp)) {
var chunk = level.getChunk(cp.x(), cp.z(), ChunkStatus.STRUCTURE_STARTS, true);
var start = chunk.getStartForStructure(holder.value());
helper.assertTrue(start != null && start.isValid(), "Every planned local transit start exists: " + cp);
int expected = plan.modulesForChunk(cp.x(), cp.z()).size();
helper.assertTrue(start.getPieces().size() == expected, "No planned module was lost during native structure generation");
var tag = start.createTag(context, cp);
var restored = StructureStart.loadStaticStart(context, tag, level.getSeed());
helper.assertTrue(restored != null && tag.equals(restored.createTag(context, cp)), "Every native transit start roundtrips its full saved geometry");
for (var piece : start.getPieces())
helper.assertFalse(piece instanceof TransitPiece21 transit
&& transit.module().kind() == TransitPlan21.Kind.JUNCTION,
"Junctions close their unopened walls with the corrected renderer");
starts.put(cp, expected);
}
TransitPiece21.bounds(module).intersectingChunks().forEach(chunks::add);
if (module.kind() == TransitPlan21.Kind.ACCESS) {
for (int x = module.centerX() - 2; x <= module.centerX() + 2; x++)
for (int z = module.minZ() - 3; z < module.minZ(); z++) chunks.add(new ChunkPos(x >> 4, z >> 4));
}
}
helper.assertTrue(chunks.size() <= 160, "The native traversal witness stays inside its bounded footprint");
report.put("local_starts", starts.size()); report.put("planned_footprint_chunks", chunks.size());
boolean planOnly = Boolean.getBoolean("sanctuary.test.transit29.planOnly");
report.put("plan_only", planOnly);
if (planOnly) {
report.put("full_chunks", 0);
report.put("surface_accesses", accesses.stream().map(m -> TransitPiece21.locator(m).toShortString()).toList());
report.put("scope", "Published native plan and all STRUCTURE_STARTS with saved-piece roundtrip; no FULL blocks or physical traversal checked in this case");
report.put("passed", true); write(report); helper.succeed(); return;
}
report.put("full_chunks", chunks.size());
var iterator = chunks.iterator(); Runnable[] step = new Runnable[1];
step[0] = () -> {
try {
if (iterator.hasNext()) {
var at = iterator.next(); report.put("stage", "FULL " + at);
level.getChunk(at.x(), at.z(), ChunkStatus.FULL, true);
helper.runAtTickTime(helper.getTick() + 1, () -> step[0].run()); return;
}
for (var module : plan.modules()) {
var at = TransitPiece21.locator(module);
biomes.merge(level.getBiome(at).getRegisteredName(), 1, Integer::sum);
}
int closedWallChecks = 0;
for (var module : plan.modules()) if (module.kind() == TransitPlan21.Kind.JUNCTION)
for (int direction = 0; direction < 4; direction++) if ((module.openings() & 1 << direction) == 0)
for (int across = -2; across <= 2; across++) {
int x = direction == 1 ? module.maxX() : direction == 3 ? module.minX() : module.centerX() + across;
int z = direction == 0 ? module.minZ() : direction == 2 ? module.maxZ() : module.centerZ() + across;
var at = new net.minecraft.core.BlockPos(x, module.minY() + 2, z);
helper.assertTrue(level.getBlockState(at).isSolid(), "An unopened junction wall remains solid at " + at);
closedWallChecks++;
}
report.put("closed_wall_checks", closedWallChecks);
report.put("module_biomes", biomes);
report.put("walk", Transit21WorldGameTests.verifyAccess(helper, plan));
var nearest = TransitStructures21.findNearest(level, HolderSet.direct(holder), net.minecraft.core.BlockPos.ZERO, 128, false);
helper.assertTrue(nearest != null && accesses.stream().anyMatch(m -> TransitPiece21.locator(m).equals(nearest.getFirst())),
"The real structure locator returns one of the walkable surface gates");
report.put("locate", nearest.getFirst().toShortString());
report.put("passed", true); write(report); helper.succeed();
} catch (Exception failure) { fail(report, failure); throw new IllegalStateException("Transit29 native traversal failed", failure); }
};
helper.runAtTickTime(helper.getTick() + 1, () -> step[0].run());
} catch (Exception failure) { fail(report, failure); throw failure; }
}
private static void write(Map<String, Object> report) throws Exception {
Files.createDirectories(OUT); Files.writeString(OUT.resolve("native.json"), JSON.toJson(report) + "\n");
}
private static void fail(Map<String, Object> report, Exception failure) {
report.put("passed", false); report.put("error", failure.toString());
try { Files.createDirectories(OUT); Files.writeString(OUT.resolve("failure.json"), JSON.toJson(report) + "\n"); }
catch (Exception io) { failure.addSuppressed(io); }
SanctuaryMod.LOGGER.error("Transit29 native traversal failed", failure);
}
}
@@ -6,7 +6,7 @@
"environment": "*",
"license": "GPL-3.0-or-later",
"entrypoints": {
"fabric-gametest": ["${gametest_entrypoint}", "fr.koka.sanctuary.gametest.Swamp27WorldGameTests", "fr.koka.sanctuary.gametest.Caves28WorldGameTests", "fr.koka.sanctuary.gametest.Alpha28NativeStructuresGameTests", "fr.koka.sanctuary.gametest.BlockKnowledgeGameTests", "fr.koka.sanctuary.gametest.CensusGameTestsTerrain", "fr.koka.sanctuary.gametest.CensusStockGameTests", "fr.koka.sanctuary.gametest.MaterialActivityGameTests"],
"fabric-gametest": ["${gametest_entrypoint}", "fr.koka.sanctuary.gametest.Swamp27WorldGameTests", "fr.koka.sanctuary.gametest.Caves28WorldGameTests", "fr.koka.sanctuary.gametest.Terraces29WorldGameTests", "fr.koka.sanctuary.gametest.Transit29WorldGameTests", "fr.koka.sanctuary.gametest.Alpha28NativeStructuresGameTests", "fr.koka.sanctuary.gametest.BlockKnowledgeGameTests", "fr.koka.sanctuary.gametest.CensusGameTestsTerrain", "fr.koka.sanctuary.gametest.CensusStockGameTests", "fr.koka.sanctuary.gametest.MaterialActivityGameTests"],
"fabric-client-gametest": ["fr.koka.sanctuary.gametest.SanctuaryClientRenderTests"]
},
"mixins": ["sanctuary-gametest.mixins.json"],
@@ -207,8 +207,11 @@ public final class ExpansionStructures23 {
Structure original = choice.holder().value();
Entry entry = choice.entry();
boolean hut = entry.family().equals("swamp_hut");
boolean layeredMine = island.origin() && island.generation() == 24 && entry.family().equals("mineshaft");
List<Integer> mineHeights = null;
int variants = hut ? hutHeights.size() : entry.habitat() == Habitat.UNDERGROUND || entry.family().equals("ruined_portal") ? 3 : 1;
for (int vertical = 0; vertical < variants; vertical++) {
if (mineHeights != null && vertical >= mineHeights.size()) break;
checkDeadline(deadlineNanos);
int targetY = hut ? hutHeights.get(vertical) : undergroundY - vertical * 20;
ExpansionChunkGenerator nativeView = hut && view instanceof NativeView local
@@ -229,6 +232,16 @@ public final class ExpansionStructures23 {
random, templates, island.seed(), candidate.chunk(), 0, chunk, valid);
}
if (!start.isValid()) { diagnostic(island, candidate, "native-refused", entry.id()); continue; }
if (layeredMine) {
if (mineHeights == null) {
mineHeights = MineshaftHeights29.heights(start, undergroundY, ground.level.getMinY(), ground.level.getMaxY(),
(px, pz) -> ground.at(px, pz).column);
if (DIAGNOSTICS) SanctuaryMod.LOGGER.info("Native29 mine layers: seed {} chunk {} preferred {} chosen {} native pieces {}",
island.seed(), candidate.chunk(), undergroundY, mineHeights, start.getPieces().size());
}
if (vertical >= mineHeights.size()) break;
targetY = mineHeights.get(vertical);
}
// Only buried portal variants need the alternative underground elevations.
if (vertical > 0 && entry.family().equals("ruined_portal") && !undergroundPortal(start)) break;
start = adjustNativeStart(original, start, entry, island, targetY, nativeGround);
@@ -322,19 +335,24 @@ public final class ExpansionStructures23 {
private static String supportRejection(ExpansionChunkGenerator host, ExpansionIsland island, Entry entry,
StructureStart start, GroundSampler ground) {
BoundingBox total = start.getBoundingBox();
boolean nativeMine = island.origin() && island.generation() == 24 && entry.family().equals("mineshaft");
List<BoundingBox> occupied = nativeMine ? MineshaftAdmission29.volumes(start, (x, z) -> ground.at(x, z).column) : null;
if (nativeMine && occupied == null) return "mineshaft-no-native-support";
if (island.origin() && entry.habitat() != Habitat.UNDERGROUND && !undergroundPortal(start)) {
int nearestX = Math.clamp(island.centerX(), total.minX(), total.maxX());
int nearestZ = Math.clamp(island.centerZ(), total.minZ(), total.maxZ());
if (Math.hypot((double) nearestX - island.centerX(), (double) nearestZ - island.centerZ()) < 56) return "natural-arrival-buffer";
}
if (total.minY() < ground.level.getMinY() + 3 || total.maxY() > ground.level.getMaxY() - 4) return "outside-height";
if (!inside(island, total.minX(), total.minZ(), 3) || !inside(island, total.maxX(), total.minZ(), 3)
|| !inside(island, total.minX(), total.maxZ(), 3) || !inside(island, total.maxX(), total.maxZ(), 3)) return "outside-island";
for (BoundingBox footprint : nativeMine ? occupied : List.of(total))
if (!inside(island, footprint.minX(), footprint.minZ(), 3) || !inside(island, footprint.maxX(), footprint.minZ(), 3)
|| !inside(island, footprint.minX(), footprint.maxZ(), 3) || !inside(island, footprint.maxX(), footprint.maxZ(), 3)) return "outside-island";
// Vanilla reference discovery scans eight chunks around the decorating chunk.
if (total.minX() < start.getChunkPos().getMinBlockX() - 128 || total.maxX() > start.getChunkPos().getMaxBlockX() + 128
|| total.minZ() < start.getChunkPos().getMinBlockZ() - 128 || total.maxZ() > start.getChunkPos().getMaxBlockZ() + 128) return "outside-native-reference-range";
if (host instanceof SanctuaryChunkGenerator city) {
if (city.lostCityPlan(island).intersects(total.minX(), total.minZ(), total.maxX(), total.maxZ())) return "occupied-city";
for (BoundingBox volume : nativeMine ? occupied : List.of(total))
if (city.lostCityPlan(island).intersects(volume.minX(), volume.minZ(), volume.maxX(), volume.maxZ())) return "occupied-city";
if (ground.localCity == null) {
var local = new fr.koka.sanctuary.worldgen.city.LostCityProtection16.Builder();
local.add(city.lostCityPlan(island), city.undergroundPlan16(island));
@@ -343,13 +361,23 @@ public final class ExpansionStructures23 {
if (reservation != null) local.addReservation(reservation);
ground.localCity = local.build();
}
if (ground.localCity.intersects(total.minX() - 2, total.minY() - 12, total.minZ() - 2, total.maxX() + 2, total.maxY() + 4, total.maxZ() + 2)
|| city.intersectsCityConstruction15(total.minX() - 2, total.minY() - 12, total.minZ() - 2, total.maxX() + 2, total.maxY() + 4, total.maxZ() + 2)
|| city.intersectsCityConstruction16(total.minX() - 2, total.minY() - 12, total.minZ() - 2, total.maxX() + 2, total.maxY() + 4, total.maxZ() + 2)
|| city.intersectsExperimentalStructures(total.minX() - 2, total.minY() - 12, total.minZ() - 2, total.maxX() + 2, total.maxY() + 4, total.maxZ() + 2)) return "reserved-secret";
var legacyProtection = new BoundingBox(total.minX() - 2, total.minY() - 12, total.minZ() - 2,
total.maxX() + 2, total.maxY() + 4, total.maxZ() + 2);
for (BoundingBox volume : nativeMine ? occupied : List.of(legacyProtection)) {
if (ground.localCity.intersects(volume.minX(), volume.minY(), volume.minZ(), volume.maxX(), volume.maxY(), volume.maxZ())
|| city.intersectsCityConstruction15(volume.minX(), volume.minY(), volume.minZ(), volume.maxX(), volume.maxY(), volume.maxZ())
|| city.intersectsCityConstruction16(volume.minX(), volume.minY(), volume.minZ(), volume.maxX(), volume.maxY(), volume.maxZ())
|| city.intersectsExperimentalStructures(volume.minX(), volume.minY(), volume.minZ(), volume.maxX(), volume.maxY(), volume.maxZ()))
return "reserved-secret";
}
if (island.origin() && !entry.family().equals("swamp_hut")) {
var binding = city.starterContext();
if (fr.koka.sanctuary.worldgen.PopulationHydrologyRuntime.protects(binding.generator(), binding.random(),
if (nativeMine) {
var regions = fr.koka.sanctuary.worldgen.PopulationHydrologyRuntime.plansIntersecting(binding.generator(), binding.random(),
total.minX() - 16, total.minZ() - 16, total.maxX() + 16, total.maxZ() + 16);
for (var volume : occupied) for (var region : regions)
if (MineshaftAdmission29.hydrology(region, volume)) return "protected-hydrology";
} else if (fr.koka.sanctuary.worldgen.PopulationHydrologyRuntime.protects(binding.generator(), binding.random(),
total.getCenter().atY(total.minY()), Math.max(total.getXSpan(), total.getZSpan()) / 2 + 3, total.getYSpan() + 16, 26)) return "protected-hydrology";
}
}
@@ -373,7 +401,12 @@ public final class ExpansionStructures23 {
BoundingBox box = piece.getBoundingBox();
if (entry.family().equals("mineshaft")
&& piece instanceof net.minecraft.world.level.levelgen.structure.structures.MineshaftPieces.MineShaftCorridor) {
if (!MineshaftSupport28.anchored(piece, (x, z) -> ground.at(x, z).column)) return "mineshaft-no-native-support";
if (!nativeMine && !MineshaftSupport28.anchored(piece, (x, z) -> ground.at(x, z).column)) return "mineshaft-no-native-support";
continue;
}
if (nativeMine && piece instanceof net.minecraft.world.level.levelgen.structure.structures.MineshaftPieces.MineShaftCrossing) {
String crossingRefusal = MineshaftCrossing29.rejection(piece, start.getPieces(), (x, z) -> ground.at(x, z).column, ground.level.getMaxY());
if (crossingRefusal != null) return crossingRefusal;
continue;
}
int low = Integer.MAX_VALUE, high = Integer.MIN_VALUE, supported = 0, sampled = 0;
@@ -404,7 +437,12 @@ public final class ExpansionStructures23 {
low = Math.min(low, column.top); high = Math.max(high, column.top);
if (high - low > (entry.family().endsWith("pyramid") ? 5 : 9)) return "surface-slope";
}
if (buriedPiece && supported < Math.ceil(sampled * (entry.family().equals("mineshaft") ? 0.65 : 0.85))) return "cavity-not-enclosed";
if (buriedPiece && supported < Math.ceil(sampled * (entry.family().equals("mineshaft") ? 0.65 : 0.85))) {
if (nativeMine && DIAGNOSTICS) SanctuaryMod.LOGGER.info(
"Native29 mine enclosure: seed {} chunk {} piece {} box {} supported {}/{} required {}",
island.seed(), start.getChunkPos(), piece.getClass().getSimpleName(), box, supported, sampled, (int) Math.ceil(sampled * 0.65));
return "cavity-not-enclosed";
}
if (!buriedPiece && !aquatic) {
siteMin = Math.min(siteMin, low); siteMax = Math.max(siteMax, high);
if (siteMax - siteMin > 20) return "site-slope";
@@ -0,0 +1,60 @@
package fr.koka.sanctuary.expansion;
import fr.koka.sanctuary.worldgen.PopulationHydrologyRuntime;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiFunction;
import net.minecraft.world.level.NoiseColumn;
import net.minecraft.world.level.levelgen.structure.BoundingBox;
import net.minecraft.world.level.levelgen.structure.StructureStart;
import net.minecraft.world.level.levelgen.structure.structures.MineshaftPieces;
/** Occupied native volumes, including potential wooden feet and suspension chains. No connecting-room infill. */
public final class MineshaftAdmission29 {
private MineshaftAdmission29() {}
public static List<BoundingBox> volumes(StructureStart start, BiFunction<Integer, Integer, NoiseColumn> columns) {
var result = new ArrayList<BoundingBox>();
for (var piece : start.getPieces()) {
var box = piece.getBoundingBox();
result.add(body(box));
if (piece instanceof MineshaftPieces.MineShaftCorridor) {
var anchors = MineshaftSupport28.anchorVolumes(piece, columns);
if (anchors == null) return null;
result.addAll(anchors);
}
}
return List.copyOf(result);
}
public static BoundingBox body(BoundingBox box) {
return new BoundingBox(box.minX() - 2, box.minY() - 1, box.minZ() - 2,
box.maxX() + 2, box.maxY() + 2, box.maxZ() + 2);
}
public static boolean intersects(List<BoundingBox> volumes, BoundingBox other) {
return volumes.stream().anyMatch(box -> box.intersects(other));
}
/** Same protected beds, sources and flow margins as SanctuaryDecorationProtection, queried as a rectangle. */
public static boolean hydrology(PopulationHydrologyRuntime.RegionPlan region, BoundingBox box) {
var water = region.water(); var lava = region.lava();
for (int x = box.minX() - 1; x <= box.maxX() + 1; x++) for (int z = box.minZ() - 1; z <= box.maxZ() + 1; z++)
for (var cell : water.cellsAt(x, z))
if (box.minY() <= cell.carveTop() + 1 && box.maxY() >= cell.bedY() - cell.sedimentDepth() - 2) return true;
for (var spring : water.springs()) {
var b = spring.flowBounds();
if (intersects(box, b.minX()-2,b.minY()-2,b.minZ()-2,b.maxX()+2,b.maxY()+2,b.maxZ()+2)) return true;
var p = spring.source();
if (intersects(box,p.x()-3,p.y()-3,p.z()-3,p.x()+3,p.y()+3,p.z()+3)) return true;
}
for (var b : lava.flowBounds())
if (intersects(box,b.minX()-8,b.minY()-8,b.minZ()-8,b.maxX()+8,b.maxY()+8,b.maxZ()+8)) return true;
for (var spill : water.spills()) {
var b = spill.flowBounds();
if (intersects(box,b.minX()-3,b.minY()-3,b.minZ()-3,b.maxX()+3,b.maxY()+3,b.maxZ()+3)) return true;
}
for (var p : lava.cells()) if (intersects(box,p.x()-2,p.y()-2,p.z()-2,p.x()+2,p.y()+2,p.z()+2)) return true;
for (var p : lava.supports()) if (intersects(box,p.x()-2,p.y()-2,p.z()-2,p.x()+2,p.y()+2,p.z()+2)) return true;
return false;
}
private static boolean intersects(BoundingBox b, int minX,int minY,int minZ,int maxX,int maxY,int maxZ) {
return b.minX()<=maxX && b.maxX()>=minX && b.minY()<=maxY && b.maxY()>=minY && b.minZ()<=maxZ && b.maxZ()>=minZ;
}
}
@@ -0,0 +1,51 @@
package fr.koka.sanctuary.expansion;
import java.util.List;
import java.util.function.BiFunction;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.level.EmptyBlockGetter;
import net.minecraft.world.level.NoiseColumn;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.levelgen.structure.BoundingBox;
import net.minecraft.world.level.levelgen.structure.StructurePiece;
import net.minecraft.world.level.levelgen.structure.structures.MineshaftPieces;
/** Native crossings have a plank floor; their optional interior pillars do not require a close rock roof. */
public final class MineshaftCrossing29 {
private MineshaftCrossing29() {}
public static String rejection(StructurePiece crossing, List<StructurePiece> pieces,
BiFunction<Integer, Integer, NoiseColumn> columns, int maxY) {
var box = crossing.getBoundingBox();
boolean needsCarriedConnection = false;
int floorY = box.minY() - 1;
for (int x = box.minX(); x <= box.maxX(); x++) for (int z = box.minZ(); z <= box.maxZ(); z++) {
var column = columns.apply(x, z); var floor = column.getBlock(floorY);
if (!floor.getFluidState().isEmpty()) return "mineshaft-crossing-wet-floor";
if (floor.isFaceSturdy(EmptyBlockGetter.INSTANCE, new BlockPos(x, floorY, z), Direction.UP)) continue;
needsCarriedConnection = true;
// StructurePiece.isInterior tests floorY+1 against OCEAN_FLOOR_WG. Only count
// opaque terrain above the crossing's excavated body, so its own carving cannot remove it.
boolean interior = false;
for (int y = box.maxY() + 1; y < maxY; y++) if (Heightmap.Types.OCEAN_FLOOR_WG.isOpaque().test(column.getBlock(y))) {
interior = true; break;
}
if (!interior) return "mineshaft-crossing-unplaceable-floor";
}
// A wholly natural floor already carries itself. A new plank bridge borrows an actual
// adjacent corridor's certified support, never a distant or merely overlapping box.
if (needsCarriedConnection && pieces.stream().noneMatch(piece -> piece instanceof MineshaftPieces.MineShaftCorridor
&& joins(box, piece) && MineshaftSupport28.anchorVolumes(piece, columns) != null))
return "mineshaft-crossing-no-carried-connection";
return null;
}
private static boolean joins(BoundingBox crossing, StructurePiece corridor) {
var box = corridor.getBoundingBox();
if (box.minY() != crossing.minY()) return false;
if (corridor.getOrientation().getAxis() == Direction.Axis.X)
return (box.maxX() + 1 == crossing.minX() || crossing.maxX() + 1 == box.minX())
&& Math.min(box.maxZ(), crossing.maxZ()) - Math.max(box.minZ(), crossing.minZ()) + 1 >= 3;
return (box.maxZ() + 1 == crossing.minZ() || crossing.maxZ() + 1 == box.minZ())
&& Math.min(box.maxX(), crossing.maxX()) - Math.max(box.minX(), crossing.minX()) + 1 >= 3;
}
}
@@ -0,0 +1,75 @@
package fr.koka.sanctuary.expansion;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.function.BiFunction;
import net.minecraft.world.level.NoiseColumn;
import net.minecraft.world.level.levelgen.structure.StructureStart;
import net.minecraft.world.level.levelgen.structure.structures.MineshaftPieces;
/** Coarse vertical search in the actual native graph, before unchanged detailed admission. */
public final class MineshaftHeights29 {
public static final int MAX_COLUMNS = 32;
public record Probe(NoiseColumn column, int floorOffset, int roofOffset, boolean corridor) {}
private record Score(int y, int covered, int direct, int distance) {}
private MineshaftHeights29() {}
public static List<Integer> heights(StructureStart start, int preferred, int minY, int maxY,
BiFunction<Integer, Integer, NoiseColumn> columns) {
var pieces = start.getPieces(); var total = start.getBoundingBox();
int originY = (total.minY() + total.maxY()) / 2;
int count = Math.min(MAX_COLUMNS, pieces.size());
var probes = new ArrayList<Probe>(count);
for (int i = 0; i < count; i++) {
var piece = pieces.get(count == 1 ? 0 : i * (pieces.size() - 1) / (count - 1));
var box = piece.getBoundingBox(); var center = box.getCenter();
probes.add(new Probe(columns.apply(center.getX(), center.getZ()), box.minY() - 1 - originY,
box.maxY() + 1 - originY, piece instanceof MineshaftPieces.MineShaftCorridor));
}
return choose(probes, preferred, minY + 3 + originY - total.minY(), maxY - 4 - total.maxY() + originY);
}
/** At most96 sampled levels in a384-block world; three native trials, not three new graphs per level. */
public static List<Integer> choose(List<Probe> probes, int preferred, int minY, int maxY) {
var scores = new ArrayList<Score>();
for (int y = Math.floorDiv(minY + 3, 4) * 4; y <= maxY; y += 4) {
int covered = 0, direct = 0;
for (var probe : probes) {
int floor = y + probe.floorOffset(), roof = y + probe.roofOffset();
boolean floorMass = solid(probe.column(), floor), roofMass = solid(probe.column(), roof);
boolean supported = probe.corridor()
? floorMass || anchor(probe.column(), floor - 1, floor - 20, -1)
|| anchor(probe.column(), roof, floor + 50, 1)
: near(probe.column(), floor, 6) && near(probe.column(), roof, 6);
if (supported) covered++;
if (floorMass) direct++;
if (roofMass) direct++;
}
if (covered > 0) scores.add(new Score(y, covered, direct, Math.abs(y - preferred)));
}
scores.sort(Comparator.comparingInt(Score::covered).reversed()
.thenComparing(Comparator.comparingInt(Score::direct).reversed())
.thenComparingInt(Score::distance).thenComparingInt(Score::y));
var chosen = new ArrayList<Integer>(3);
for (var score : scores) {
if (chosen.stream().anyMatch(y -> Math.abs(y - score.y()) < 8)) continue;
chosen.add(score.y()); if (chosen.size() == 3) break;
}
return List.copyOf(chosen);
}
private static boolean solid(NoiseColumn column, int y) {
var state = column.getBlock(y); return state.isSolid() && state.getFluidState().isEmpty();
}
private static boolean near(NoiseColumn column, int y, int reach) {
for (int offset = -reach; offset <= reach; offset++) if (solid(column, y + offset)) return true;
return false;
}
private static boolean anchor(NoiseColumn column, int from, int to, int step) {
for (int y = from; step > 0 ? y <= to : y >= to; y += step) {
if (!column.getBlock(y).getFluidState().isEmpty()) return false;
if (solid(column, y)) return true;
}
return false;
}
}
@@ -8,25 +8,67 @@ import net.minecraft.world.level.levelgen.structure.StructurePiece;
public final class MineshaftSupport28 {
private MineshaftSupport28() {}
public static boolean anchored(StructurePiece piece, BiFunction<Integer, Integer, NoiseColumn> columns) {
return anchorVolumes(piece, columns, false) != null;
}
/** Protect the nearer usable native anchor; downward wins an equal-distance tie. */
public static java.util.List<net.minecraft.world.level.levelgen.structure.BoundingBox> anchorVolumes(StructurePiece piece,
BiFunction<Integer, Integer, NoiseColumn> columns) {
return anchorVolumes(piece, columns, true);
}
private static java.util.List<net.minecraft.world.level.levelgen.structure.BoundingBox> anchorVolumes(StructurePiece piece,
BiFunction<Integer, Integer, NoiseColumn> columns, boolean allowDirectFloor) {
var volumes = new java.util.ArrayList<net.minecraft.world.level.levelgen.structure.BoundingBox>();
var box = piece.getBoundingBox();
int length = piece.getOrientation().getAxis() == net.minecraft.core.Direction.Axis.Z ? box.getZSpan() : box.getXSpan();
for (int z : length > 5 ? new int[]{2, length - 3} : new int[]{2}) for (int x : new int[]{0, 2}) {
for (int z : length > 5 ? new int[]{2, length - 3} : new int[]{2}) {
int sectionAnchors = volumes.size();
for (int x : new int[]{0, 2}) {
var pier = SwampHutPlacement28.worldPosition(piece, x, -1, z);
var column = columns.apply(pier.getX(), pier.getZ()); boolean anchor = false;
var column = columns.apply(pier.getX(), pier.getZ());
var floor = column.getBlock(pier.getY());
// Vanilla setPlanksBlock retains a sturdy natural floor. Only a floor plank subsequently
// invokes placeDoubleLowerOrUpperSupport, so a rock ledge needs no distant anchor.
if (allowDirectFloor && floor.getFluidState().isEmpty()
&& !floor.is(net.minecraft.world.level.block.Blocks.OAK_PLANKS)
&& !floor.is(net.minecraft.world.level.block.Blocks.DARK_OAK_PLANKS)
&& floor.isFaceSturdy(net.minecraft.world.level.EmptyBlockGetter.INSTANCE, pier, net.minecraft.core.Direction.UP)) {
volumes.add(new net.minecraft.world.level.levelgen.structure.BoundingBox(pier));
continue;
}
Integer below = null, above = null;
// Native wooden support searches at most20 below its floor plank.
for (int y = pier.getY() - 1; y >= pier.getY() - 20; y--) {
var state = column.getBlock(y);
if (!state.getFluidState().isEmpty()) break;
if (state.isSolid()) { anchor = true; break; }
if (state.isSolid()) {
below = y; break;
}
}
// Its alternative is a chain up to50 blocks, anchored above the excavated corridor.
if (!anchor) for (int y = box.maxY() + 1; y <= pier.getY() + 50; y++) {
for (int y = box.maxY() + 1; y <= pier.getY() + 50; y++) {
var state = column.getBlock(y);
if (!state.getFluidState().isEmpty()) break;
if (state.isSolid()) { anchor = true; break; }
if (state.isSolid()) {
above = y; break;
}
}
if (!anchor) return false;
if (below == null && above == null) {
if (!allowDirectFloor) return null;
// Vanilla simply omits this optional pillar/chain. The other side of the same
// transverse section can still carry the corridor; both ends must remain anchored.
continue;
}
if (below != null && (above == null || pier.getY() - below <= above - pier.getY()))
volumes.add(new net.minecraft.world.level.levelgen.structure.BoundingBox(pier.getX(), below, pier.getZ(), pier.getX(), pier.getY(), pier.getZ()));
else volumes.add(new net.minecraft.world.level.levelgen.structure.BoundingBox(pier.getX(), pier.getY(), pier.getZ(), pier.getX(), above, pier.getZ()));
}
if (volumes.size() == sectionAnchors) {
if (allowDirectFloor && Boolean.getBoolean("sanctuary.expansion.structureDiagnostics"))
fr.koka.sanctuary.SanctuaryMod.LOGGER.info("Native29 mine unanchored section: piece {} facing {} localZ {} floorY {}",
box, piece.getOrientation(), z, box.minY() - 1);
return null;
}
}
return true;
return java.util.List.copyOf(volumes);
}
}
@@ -12,7 +12,8 @@ import net.minecraft.world.level.levelgen.densityfunction.*;
public record PopulationIslandDensity(DensityFunction terrain, DensityFunction distortion,
DensityFunction sculpt, DensityFunction detail, DensityFunction underside,
DensityFunction riftWarp, DensityFunction riftDetail, IslandCapacity capacity,
DensityFunction caves28, DensityFunction ridges28, DensityFunction erosion28, boolean geology28) implements DensityFunction {
DensityFunction caves28, DensityFunction ridges28, DensityFunction erosion28, boolean geology28,
boolean terraces29) implements DensityFunction {
public static final MapCodec<PopulationIslandDensity> CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group(
DensityFunction.CODEC.fieldOf("terrain").forGetter(PopulationIslandDensity::terrain),
DensityFunction.CODEC.fieldOf("distortion").forGetter(PopulationIslandDensity::distortion),
@@ -25,14 +26,23 @@ public record PopulationIslandDensity(DensityFunction terrain, DensityFunction d
DensityFunction.CODEC.optionalFieldOf("caves_28", DensityFunctions.zero()).forGetter(PopulationIslandDensity::caves28),
DensityFunction.CODEC.optionalFieldOf("ridges_28", DensityFunctions.zero()).forGetter(PopulationIslandDensity::ridges28),
DensityFunction.CODEC.optionalFieldOf("erosion_28", DensityFunctions.zero()).forGetter(PopulationIslandDensity::erosion28),
Codec.BOOL.optionalFieldOf("geology_28", false).forGetter(PopulationIslandDensity::geology28)
Codec.BOOL.optionalFieldOf("geology_28", false).forGetter(PopulationIslandDensity::geology28),
Codec.BOOL.optionalFieldOf("terraces_29", false).forGetter(PopulationIslandDensity::terraces29)
).apply(instance, PopulationIslandDensity::new));
public PopulationIslandDensity(DensityFunction terrain, DensityFunction distortion,
DensityFunction sculpt, DensityFunction detail, DensityFunction underside,
DensityFunction riftWarp, DensityFunction riftDetail, IslandCapacity capacity) {
this(terrain, distortion, sculpt, detail, underside, riftWarp, riftDetail, capacity,
DensityFunctions.zero(), DensityFunctions.zero(), DensityFunctions.zero(), false);
DensityFunctions.zero(), DensityFunctions.zero(), DensityFunctions.zero(), false, false);
}
public PopulationIslandDensity(DensityFunction terrain, DensityFunction distortion,
DensityFunction sculpt, DensityFunction detail, DensityFunction underside,
DensityFunction riftWarp, DensityFunction riftDetail, IslandCapacity capacity,
DensityFunction caves28, DensityFunction ridges28, DensityFunction erosion28, boolean geology28) {
this(terrain, distortion, sculpt, detail, underside, riftWarp, riftDetail, capacity,
caves28, ridges28, erosion28, geology28, false);
}
@Override public DensitySampler compileSampler(CompileContext context) {
@@ -62,6 +72,9 @@ public record PopulationIslandDensity(DensityFunction terrain, DensityFunction d
}
@Override public float sampleValue(SamplerContext sample, int x, int y, int z) {
if (Math.hypot((double) x, z) >= capacity.terrainLimit() || y <= 0 || y >= 384) return -1;
int localY = y - 64;
float coarseValue = coarse.sampleValue(sample, x, localY, z);
float fineValue = fine.sampleValue(sample, x, localY, z);
float terrainValue;
double weight = geology28 ? CavesRelief28.deformationWeight(x, y, z, capacity.radius()) : 0;
if (weight > 0) {
@@ -69,13 +82,15 @@ public record PopulationIslandDensity(DensityFunction terrain, DensityFunction d
erosion.sampleValue(sample, x + 173, y - 59, z - 89),
ridges.sampleValue(sample, x, y, z),
erosion.sampleValue(sample, x - 211, y + 137, z + 307));
terrainValue = terrainAt(sample, x - shift.x(), y - 64 - shift.y(), z - shift.z());
double sourceY = y - 64 - shift.y();
if (terraces29) sourceY = Terraces29.sourceY(sourceY, coarseValue, fineValue,
Terraces29.weight(y, weight));
terrainValue = terrainAt(sample, x - shift.x(), sourceY, z - shift.z());
} else terrainValue = raw.sampleValue(sample, x, y - 64, z);
// Keep the island envelope at actual coordinates, independent from displaced rock.
int localY = y - 64;
float natural = PopulationIslandShape.raisedDensity(x, y, z, terrainValue,
edge.sampleValue(sample, x, 0, z), coarse.sampleValue(sample, x, localY, z),
fine.sampleValue(sample, x, localY, z), bottom.sampleValue(sample, x, localY, z), 64, 384, capacity);
edge.sampleValue(sample, x, 0, z), coarseValue,
fineValue, bottom.sampleValue(sample, x, localY, z), 64, 384, capacity);
if (natural <= 0) return natural;
var region = capacity.regionAt(x, z);
var layout = layouts.computeIfAbsent(region, r -> RiftShape.layout(regionSeed(seed, r)));
@@ -100,6 +115,11 @@ public record PopulationIslandDensity(DensityFunction terrain, DensityFunction d
return new PopulationIslandDensity(terrain, distortion, sculpt, detail, underside, riftWarp, riftDetail,
capacity, DensityFunctions.zero(), DensityFunctions.zero(), DensityFunctions.zero(), false);
}
/** Exact accepted28 deformation and caves, without the fine local ledges. */
public PopulationIslandDensity legacy28() {
return new PopulationIslandDensity(terrain, distortion, sculpt, detail, underside, riftWarp, riftDetail,
capacity, caves28, ridges28, erosion28, geology28, false);
}
public static long regionSeed(long seed, IslandCapacity.Region region) {
long value = seed ^ ((long) region.x() << 32) ^ (region.z() & 0xffffffffL) ^ 0xA10CA7E1L;
value = (value ^ (value >>> 30)) * 0xbf58476d1ce4e5b9L;
@@ -109,7 +129,7 @@ public record PopulationIslandDensity(DensityFunction terrain, DensityFunction d
@Override public DensityFunction rewriteChildren(DfRewriteRule rule) {
return new PopulationIslandDensity(rule.rewrite(terrain), rule.rewrite(distortion), rule.rewrite(sculpt),
rule.rewrite(detail), rule.rewrite(underside), rule.rewrite(riftWarp), rule.rewrite(riftDetail), capacity,
rule.rewrite(caves28), rule.rewrite(ridges28), rule.rewrite(erosion28), geology28);
rule.rewrite(caves28), rule.rewrite(ridges28), rule.rewrite(erosion28), geology28, terraces29);
}
@Override public Interval range() { return Interval.of(-1, 1); }
@Override public int domainAxes() { return ALL_AXES; }
@@ -344,11 +344,13 @@ public final class SanctuaryChunkGenerator extends ExpansionChunkGenerator {
heightAccessor, pos -> protectsCore22(pos) || protectsDecoration(pos) || cityProtection16.contains(pos.getX(), pos.getY(), pos.getZ())
|| ruinsPlan17.protects(pos.getX(), pos.getY(), pos.getZ()) || heritagePlan19.protects(pos.getX(), pos.getY(), pos.getZ()));
long start = System.nanoTime();
transitPlan21 = generation >= 22
transitPlan21 = generation == 24
? fr.koka.sanctuary.worldgen.transit.TransitPlanner29.plan(root.seed(), root.centerX(), root.centerZ(), root.diameter(), sampled)
: generation >= 22
? fr.koka.sanctuary.worldgen.transit.TransitPlanner22.plan(root.seed(), root.centerX(), root.centerZ(), root.diameter(), sampled)
: fr.koka.sanctuary.worldgen.transit.TransitPlanner21.plan(root.seed(), root.centerX(), root.centerZ(), root.diameter(), sampled);
int directColumns = 0;
if ((generation == 22 || (generation == 23 || generation == 24)) && transitPlan21.isEmpty()) {
if ((generation == 22 || generation == 23) && transitPlan21.isEmpty()) {
directColumns = sampled.columnsUsed();
sampled = new fr.koka.sanctuary.worldgen.transit.TransitTerrain21(root, terrain.generator(), terrain.random(),
heightAccessor, pos -> protectsDecoration(pos) || cityProtection16.contains(pos.getX(), pos.getY(), pos.getZ()));
@@ -0,0 +1,29 @@
package fr.koka.sanctuary.worldgen;
/** Small, locally phased ledges on the accepted three-dimensional deformation. */
public final class Terraces29 {
public static final double MAX_OFFSET = 2.5;
public static final int START_Y = 214;
private Terraces29() {}
public static double weight(int y, double deformationWeight) {
return smooth((y - START_Y) / 30.0) * deformationWeight;
}
/** Period and phase both follow 3D noise; there is no shared horizontal layer grid. */
public static double sourceY(double originalY, float coarse, float detail, double weight) {
if (weight <= 0) return originalY;
double period = 7.5 + 1.5 * Math.tanh(coarse * .9);
double phase = 7 * Math.tanh(coarse * .7) + 2 * Math.tanh(detail * .65);
double band = (originalY + phase) / period;
double fraction = band - Math.floor(band);
double shaped = smooth((fraction - .3) / .4);
double offset = Math.clamp((shaped - fraction) * period * .9, -MAX_OFFSET, MAX_OFFSET);
return originalY + offset * Math.clamp(weight, 0, 1);
}
private static double smooth(double value) {
double t = Math.clamp(value, 0, 1);
return t * t * (3 - 2 * t);
}
}
@@ -50,6 +50,9 @@ public record TransitPlan21(List<Module> modules, int samples, String diagnostic
}
public boolean protects(int x, int y, int z) {
if (contains(x, y, z)) return true;
// Intact neighboring rock can carry the continuous masonry walls and roof.
if (x >= minX - 1 && x <= maxX() + 1 && z >= minZ - 1 && z <= maxZ() + 1
&& y >= minY && y <= maxY() + 2) return true;
if (kind == Kind.ACCESS && x >= centerX() - 2 && x <= centerX() + 2
&& z >= minZ - 3 && z < minZ && y >= surfaceY() - 2 && y <= surfaceY() + 3) return true;
// Two untouched layers carry the slab and the certified short piers.
@@ -78,8 +81,8 @@ public record TransitPlan21(List<Module> modules, int samples, String diagnostic
public boolean intersectsProtection(int x0, int y0, int z0, int x1, int y1, int z1) {
for (var m : modules) if (m.kind() == Kind.ACCESS && x0 <= m.centerX() + 2 && x1 >= m.centerX() - 2
&& z0 < m.minZ() && z1 >= m.minZ() - 3 && y0 <= m.surfaceY() + 3 && y1 >= m.surfaceY() - 2) return true;
for (var m : modules) if (x0 <= m.maxX() && x1 >= m.minX() && z0 <= m.maxZ() && z1 >= m.minZ()
&& y0 <= m.maxY() && y1 >= m.boundsMinY() - 2) return true;
for (var m : modules) if (x0 <= m.maxX() + 1 && x1 >= m.minX() - 1 && z0 <= m.maxZ() + 1 && z1 >= m.minZ() - 1
&& y0 <= m.maxY() + 2 && y1 >= m.boundsMinY() - 2) return true;
return false;
}
private static long key(int x, int z) { return (long) x << 32 ^ z & 0xffffffffL; }
@@ -0,0 +1,194 @@
package fr.koka.sanctuary.worldgen.transit;
import fr.koka.sanctuary.worldgen.transit.TransitPlan21.Kind;
import fr.koka.sanctuary.worldgen.transit.TransitPlan21.Module;
import fr.koka.sanctuary.worldgen.transit.TransitPlan21.Support;
import fr.koka.sanctuary.worldgen.transit.TransitPlanner21.Terrain;
import java.util.*;
/** The established road and stair shafts, fitted from their entrances before their depth.
* Only the indispensable traversal uses these civil-engineering bearings. No terrain is added. */
public final class TransitPlanner29 {
public static final int MAX_COLUMNS = 12_000;
private TransitPlanner29() {}
public static TransitPlan21 plan(long seed, int cx, int cz, int diameter, Terrain terrain) {
if (!List.of(512, 724, 1024).contains(diameter)) throw new IllegalArgumentException("Unsupported transit diameter");
var p = new Probe(terrain); int layouts = 0; String reason = "no natural entrance pair";
var entries = new ArrayList<Alignment>();
int mainRadius = Math.max(68, (int) Math.round(diameter * .19 / 17) * 17);
var radii = new LinkedHashSet<Integer>();
for (int radius : new int[]{mainRadius, mainRadius - 17, mainRadius - 34, mainRadius + 17})
if (radius * 2 >= diameter / 4) radii.add(radius);
try {
// Cheap, cached entrance scans precede any expensive underground volume scan.
search: for (int shift : new int[]{-diameter / 8, diameter / 8, -diameter / 16, diameter / 16, 0,
-3 * diameter / 16, 3 * diameter / 16})
for (int advance : new int[]{0, -17, 17, -34, 34, -51, 51})
for (int radius : radii) for (boolean alongX : new boolean[]{true, false}) {
if (++layouts > 320 || p.columns.size() >= 3_000) break search;
int ax = cx + (alongX ? advance - radius : shift), az = cz + (alongX ? shift : advance - radius);
int bx = cx + (alongX ? advance + radius : shift), bz = cz + (alongX ? shift : advance + radius);
int a = entrance(ax, az, p), b = entrance(bx, bz, p);
if (a < 0 || b < 0 || Math.abs(a - b) > 95) continue;
entries.add(new Alignment(ax, az, bx, bz, a, b, alongX));
if (entries.size() >= 24) break search;
}
// Preserve every complete route already accepted by the primary search. Only after
// its failure may an incompatible optional footing be omitted, using other bearings.
for (boolean selectiveFootings : new boolean[]{false, true}) {
p.selectiveFootings = selectiveFootings;
for (var a : entries) {
var floors = new LinkedHashSet<Integer>(); int low = Math.min(a.topA, a.topB), high = Math.max(a.topA, a.topB);
for (int floor : new int[]{low - 26, low - 42, low - 58, low - 74, 200, 184, 160})
if (floor >= 40 && floor <= low - 18 && floor >= high - 140) floors.add(floor);
for (int floor : floors) {
var modules = route(seed, a, floor, p, false);
if (modules == null) { reason = p.reason; continue; }
var complete = new ArrayList<Module>();
for (var module : modules) {
var certified = certify(module, p, true);
if (certified == null) { reason = p.reason; break; }
complete.add(certified);
}
if (complete.size() != modules.size()) continue;
addHall(complete, floor, a.alongX, seed, p);
return new TransitPlan21(complete, p.samples, "accepted29 axis=" + (a.alongX ? "east-west" : "north-south")
+ " floor=" + floor + " span=" + Math.max(Math.abs(a.bx - a.ax), Math.abs(a.bz - a.az))
+ " entries=" + entries.size() + " layouts=" + layouts + " columns=" + p.columns.size()
+ (selectiveFootings ? " selective-footings" : ""));
}
}
}
} catch (Budget exhausted) { reason = "bounded29 search exhausted: " + p.reason; }
return new TransitPlan21(List.of(), p.samples, reason + "; entries=" + entries.size() + "; layouts=" + layouts + "; columns=" + p.columns.size());
}
/** A three-wide doorstep is sufficient; the surrounding hillside need not be a flat platform. */
private static int entrance(int cx, int cz, Probe p) {
long key = key(cx, cz); Integer known = p.entrances.get(key); if (known != null) return known;
int low = 384, high = -1;
for (int x = cx - 1; x <= cx + 1; x++) {
int y = p.surface(x, cz - 9); low = Math.min(low, y); high = Math.max(high, y);
}
boolean valid = low >= 65 && high <= 360 && high - low <= 1;
if (valid) for (int x = cx - 1; x <= cx + 1; x++) {
int y = p.surface(x, cz - 9);
if (p.protectedAt(x, y, cz - 9) || p.protectedAt(x, high + 1, cz - 9)
|| p.protectedAt(x, high + 2, cz - 9)) valid = false;
}
p.entrances.put(key, valid ? high : -1); return valid ? high : -1;
}
private record Alignment(int ax, int az, int bx, int bz, int topA, int topB, boolean alongX) {}
private static void addHall(List<Module> route, int floor, boolean alongX, long seed, Probe p) {
int index = 1 + (route.size() - 2) / 2; var junction = route.get(index);
for (int side : new int[]{-1, 1}) {
try {
int x = alongX ? junction.centerX() - 4 : side < 0 ? junction.minX() - 17 : junction.maxX() + 1;
int z = alongX ? side < 0 ? junction.minZ() - 17 : junction.maxZ() + 1 : junction.centerZ() - 4;
var passage = certify(new Module(Kind.GALLERY, x, floor - 1, z, alongX ? 9 : 17, 10,
alongX ? 17 : 9, alongX ? 0 : 1, alongX ? 5 : 10, seed ^ 0x29B1L ^ side, List.of()), p, true);
if (passage == null || !p.anchored.getOrDefault(passage, false)) continue;
int hx = alongX ? junction.centerX() - 8 : side < 0 ? passage.minX() - 21 : passage.maxX() + 1;
int hz = alongX ? side < 0 ? passage.minZ() - 21 : passage.maxZ() + 1 : junction.centerZ() - 8;
int opening = alongX ? side < 0 ? 4 : 1 : side < 0 ? 2 : 8;
var hall = certify(new Module(Kind.HALL, hx, floor - 1, hz, alongX ? 17 : 21, 10,
alongX ? 21 : 17, alongX ? 0 : 1, opening, seed ^ 0x29A1L ^ side, List.of()), p, true);
if (hall == null) continue;
int branch = alongX ? side < 0 ? 1 : 4 : side < 0 ? 8 : 2;
var joined = certify(new Module(Kind.JUNCTION, junction.minX(), junction.minY(), junction.minZ(),
junction.width(), 10, junction.depth(), junction.facing(), junction.openings() | branch,
junction.seed(), junction.supports()), p, true);
if (joined == null) continue;
route.set(index, joined);
route.add(passage); route.add(hall); return;
} catch (Budget exhausted) { return; }
}
}
private static List<Module> route(long seed, Alignment a, int floor, Probe p, boolean full) {
var result = new ArrayList<Module>();
var first = certify(new Module(Kind.ACCESS, a.ax - 8, floor - 1, a.az - 8, 17, a.topA - floor + 6,
17, 0, a.alongX ? 2 : 4, seed ^ 0x2901L, List.of()), p, full);
if (first == null) return null;
var last = certify(new Module(Kind.ACCESS, a.bx - 8, floor - 1, a.bz - 8, 17, a.topB - floor + 6,
17, 0, a.alongX ? 8 : 1, seed ^ 0x2902L, List.of()), p, full);
if (last == null) return null;
result.add(first); int length = Math.max(Math.abs(a.bx - a.ax), Math.abs(a.bz - a.az)), weak = 0;
for (int d = 17; d < length; d += 17) {
int x = a.ax + (a.alongX ? d : 0), z = a.az + (a.alongX ? 0 : d);
var m = certify(new Module(Kind.GALLERY, x - (a.alongX ? 8 : 4), floor - 1, z - (a.alongX ? 4 : 8),
a.alongX ? 17 : 9, 10, a.alongX ? 9 : 17, a.alongX ? 1 : 0, a.alongX ? 10 : 5,
seed ^ (d * 0x9E3779B97F4A7C15L), List.of()), p, full);
if (m == null) return null;
weak = p.anchored.getOrDefault(m, false) ? 0 : weak + 1;
if (weak > 2) { p.reason = "unsupported span exceeds34 blocks"; return null; }
result.add(m);
}
result.add(last); return result;
}
private static Module certify(Module m, Probe p, boolean full) {
var supports = new ArrayList<Support>(); int quadrants = 0;
for (int x : new int[]{m.minX() + 1, m.centerX(), m.maxX() - 1})
for (int z : new int[]{m.minZ() + 1, m.centerZ(), m.maxZ() - 1}) {
if (!p.owns(x, z)) { p.reason = "outside island"; return null; }
int anchor = m.minY() - 1;
while (anchor >= m.minY() - 17 && !(p.solid(x, anchor, z) && p.solid(x, anchor - 1, z))) anchor--;
if (anchor >= m.minY() - 17) {
boolean compatible = true;
for (int y = anchor - 1; y < m.minY(); y++) if (p.protectedAt(x, y, z)) {
if (!p.selectiveFootings) { p.reason = "protected footing"; return null; }
compatible = false; break;
}
// Do not place this pier, and do not count its quadrant as a bearing.
// The unchanged wall/roof checks and maximum free span still apply.
if (!compatible) continue;
if (anchor < m.minY() - 1) supports.add(new Support(x, anchor + 1, z, m.minY() - 1));
quadrants |= 1 << ((x < m.centerX() ? 0 : 1) + (z < m.centerZ() ? 0 : 2));
}
}
// Existing continuous masonry can also bear on untouched rock beside its walls or roof.
// These contact cells lie in the protected one-block fringe, never inside excavated air.
int wallMask = 0, roofMask = 0;
for (int y : new int[]{m.minY() + 3, m.minY() + 6, m.kind() == Kind.ACCESS ? Math.min(m.maxY() - 5, m.minY() + 18) : m.maxY()}) {
if ((m.openings() & 8) == 0 && p.solid(m.minX() - 1, y, m.centerZ()) && p.solid(m.minX() - 1, y + 1, m.centerZ())) wallMask |= 1;
if ((m.openings() & 2) == 0 && p.solid(m.maxX() + 1, y, m.centerZ()) && p.solid(m.maxX() + 1, y + 1, m.centerZ())) wallMask |= 2;
if ((m.openings() & 1) == 0 && p.solid(m.centerX(), y, m.minZ() - 1) && p.solid(m.centerX(), y + 1, m.minZ() - 1)) wallMask |= 4;
if ((m.openings() & 4) == 0 && p.solid(m.centerX(), y, m.maxZ() + 1) && p.solid(m.centerX(), y + 1, m.maxZ() + 1)) wallMask |= 8;
}
if (m.kind() != Kind.ACCESS) for (int x : new int[]{m.minX() + 1, m.maxX() - 1})
for (int z : new int[]{m.minZ() + 1, m.maxZ() - 1}) if (p.solid(x, m.maxY() + 1, z) && p.solid(x, m.maxY() + 2, z))
roofMask |= 1 << ((x < m.centerX() ? 0 : 1) + (z < m.centerZ() ? 0 : 2));
boolean anchored = quadrants == 15 || roofMask == 15 || (wallMask & 3) == 3 || (wallMask & 12) == 12;
if (!anchored && m.kind() != Kind.GALLERY) { p.reason = "no civil-engineering bearing for " + m.kind(); return null; }
int step = full ? 1 : 4;
for (int x = m.minX(); x <= m.maxX(); x += step) for (int z = m.minZ(); z <= m.maxZ(); z += step) {
if (!p.owns(x, z)) { p.reason = "outside island"; return null; }
for (int y = m.minY(); y <= m.maxY(); y += step) if (p.protectedAt(x, y, z)) {
p.reason = "protected " + m.kind() + " at " + x + "," + y + "," + z; return null;
}
}
var result = new Module(m.kind(), m.minX(), m.minY(), m.minZ(), m.width(), m.height(), m.depth(),
m.facing(), m.openings(), m.seed(), supports);
p.anchored.put(result, anchored); return result;
}
private static long key(int x, int z) { return (long) x << 32 ^ z & 0xffffffffL; }
private static final class Probe {
final Terrain terrain; final Set<Long> columns = new HashSet<>();
final Map<Long, Integer> entrances = new HashMap<>(); final Map<Module, Boolean> anchored = new HashMap<>();
int samples; boolean selectiveFootings; String reason = "budget";
Probe(Terrain terrain) { this.terrain = terrain; }
void visit(int x, int z) {
if (samples >= TransitPlan21.MAX_SAMPLES) throw new Budget(); samples++;
long key = key(x, z);
if (!columns.contains(key) && columns.size() >= MAX_COLUMNS) throw new Budget(); columns.add(key);
}
boolean owns(int x, int z) { visit(x, z); return terrain.owns(x, z); }
boolean solid(int x, int y, int z) { visit(x, z); return terrain.solid(x, y, z); }
boolean protectedAt(int x, int y, int z) { visit(x, z); return terrain.protectedAt(x, y, z); }
int surface(int x, int z) { visit(x, z); return terrain.surfaceY(x, z); }
}
private static final class Budget extends RuntimeException { private static final long serialVersionUID = 1L; }
}
@@ -26,7 +26,8 @@ public final class TransitStructure21 extends Structure {
}
return Optional.of(new GenerationStub(TransitPiece21.locator(modules.getFirst()), pieces ->
modules.forEach(module -> pieces.addPiece((generator.rootGeneration() == 22 || (generator.rootGeneration() == 23 || generator.rootGeneration() == 24))
&& module.kind() == TransitPlan21.Kind.JUNCTION && Integer.bitCount(module.openings()) == 2
&& module.kind() == TransitPlan21.Kind.JUNCTION
&& (generator.rootGeneration() == 24 || Integer.bitCount(module.openings()) == 2)
? new TransitPiece22(module) : new TransitPiece21(module)))));
}
@Override public StructureType<?> type() { return TransitStructures21.TYPE; }
@@ -58,7 +58,9 @@ public final class TransitStructures21 {
var sampler = random.createClimateSampler(SamplerContext.builder().enableCaches().build());
var start = holder.value().generate(holder, level, registries, generator, generator.getBiomeSource(), sampler,
random, templates, island.seed(), chunk.getPos(), previous == null ? 0 : previous.getReferences(),
chunk, holder.value().biomes()::contains);
// The route is already certified as one connected whole. A 3D biome boundary
// must not silently remove a local segment or one of its two surface accesses.
chunk, generator.rootGeneration() == 24 ? biome -> true : holder.value().biomes()::contains);
if (start.isValid()) manager.setStartForStructure(holder.value(), start, chunk);
}
/** Locate selects a saved surface portico rather than teleporting the player into a deep road. */
@@ -63,5 +63,6 @@
"noise": "minecraft:erosion",
"xz_scale": 2.0,
"y_scale": 2.0
}
},
"terraces_29": true
}
@@ -63,5 +63,6 @@
"noise": "minecraft:erosion",
"xz_scale": 2.0,
"y_scale": 2.0
}
},
"terraces_29": true
}
@@ -63,5 +63,6 @@
"noise": "minecraft:erosion",
"xz_scale": 2.0,
"y_scale": 2.0
}
},
"terraces_29": true
}
@@ -0,0 +1,83 @@
package fr.koka.sanctuary.expansion;
import java.util.List;
import net.minecraft.SharedConstants;
import net.minecraft.core.Direction;
import net.minecraft.server.Bootstrap;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.NoiseColumn;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.levelgen.structure.BoundingBox;
import net.minecraft.world.level.levelgen.structure.structures.MineshaftPieces;
import net.minecraft.world.level.levelgen.structure.structures.MineshaftStructure;
/** Thin native branches must not reserve every empty room between them. Native support reach stays bounded. */
public final class MineshaftAdmission29Smoke {
public static void main(String[] args) {
SharedConstants.tryDetectVersion(); Bootstrap.bootStrap();
// Bootstrap does not load datapack tags. This synthetic world contains only STONE/AIR;
// bind its real26.3 heightmap tag so the production native predicate can see its stone ceiling.
var blocks = net.minecraft.core.registries.BuiltInRegistries.BLOCK;
blocks.freeze();
blocks.prepareTagReload(new net.minecraft.tags.TagLoader.LoadResult<>(net.minecraft.core.registries.Registries.BLOCK,
java.util.Map.of(net.minecraft.tags.BlockTags.BLOCKS_MOTION_IN_HEIGHTMAP,
List.of(blocks.wrapAsHolder(Blocks.STONE))))).apply();
require(net.minecraft.world.level.levelgen.Heightmap.Types.OCEAN_FLOOR_WG.isOpaque().test(Blocks.STONE.defaultBlockState()),
"The synthetic stone ceiling is bound to the native heightmap tag");
var branches = List.of(MineshaftAdmission29.body(new BoundingBox(-30,150,-30,-28,152,30)),
MineshaftAdmission29.body(new BoundingBox(28,150,-30,30,152,30)),
MineshaftAdmission29.body(new BoundingBox(-30,150,28,30,152,30)));
var centralSecret = new BoundingBox(-5,145,-5,5,160,5);
require(new BoundingBox(-30,149,-30,30,154,30).intersects(centralSecret), "Old global envelope falsely intersects the central secret");
require(!MineshaftAdmission29.intersects(branches, centralSecret), "Unoccupied room between native branches is not part of the mine");
require(MineshaftAdmission29.intersects(branches, new BoundingBox(-31,151,0,-29,152,1)), "An actual branch remains protected against an intersecting secret");
var layered = column(-1,384);
for (int y = 180; y <= 220; y++) layered.setBlock(y, Blocks.STONE.defaultBlockState());
var layers = MineshaftHeights29.choose(List.of(new MineshaftHeights29.Probe(layered,-6,6,false)),54,16,340);
require(layers.size()==3 && layers.stream().allMatch(y->y>=186 && y<=214), "A low local surface estimate cannot send all mine trials below the real rock mass");
require(Math.abs(layers.get(0)-layers.get(1))>=8 && Math.abs(layers.get(1)-layers.get(2))>=8, "The three native heights remain distinct");
require(MineshaftHeights29.choose(List.of(new MineshaftHeights29.Probe(column(-1,384),-6,6,false)),54,16,340).isEmpty(), "Empty columns never manufacture an underground candidate");
var crossing = new MineshaftPieces.MineShaftCrossing(0,new BoundingBox(0,150,0,4,152,4),Direction.SOUTH,MineshaftStructure.Type.NORMAL);
var approach = new MineshaftPieces.MineShaftCorridor(0,RandomSource.create(42),new BoundingBox(1,150,-15,3,152,-1),Direction.NORTH,MineshaftStructure.Type.NORMAL);
require(MineshaftCrossing29.rejection(crossing,List.of(crossing,approach),(x,z)->z<0?column(140,384):column(-1,240),384)==null,
"An interior native plank crossing can join a carried corridor below a distant cavern ceiling");
require(MineshaftCrossing29.rejection(crossing,List.of(crossing,approach),(x,z)->column(149,384),384)==null,
"A sturdy crossing floor needs no rock roof");
require(MineshaftCrossing29.rejection(crossing,List.of(crossing),(x,z)->column(-1,240),384)!=null,
"An isolated crossing cannot borrow a nonexistent carried corridor");
require(MineshaftCrossing29.rejection(crossing,List.of(crossing),(x,z)->column(149,384),384)==null,
"A wholly natural crossing floor carries itself without an invented extra corridor");
require(MineshaftCrossing29.rejection(crossing,List.of(crossing,approach),(x,z)->z<0?column(140,384):column(-1,384),384)!=null,
"An open-sky crossing over void has no floor that vanilla would place");
for (var direction : List.of(Direction.NORTH,Direction.SOUTH,Direction.EAST,Direction.WEST)) {
boolean z = direction.getAxis() == Direction.Axis.Z;
var piece = new MineshaftPieces.MineShaftCorridor(0, RandomSource.create(42),
new BoundingBox(0,150,0,z?2:14,152,z?14:2),direction,MineshaftStructure.Type.NORMAL);
var below = MineshaftSupport28.anchorVolumes(piece,(x,zz)->column(140,180));
require(below!=null && below.size()==4 && below.stream().allMatch(b->b.minY()==140 && b.maxY()==149), "Rotated wooden feet reach real rock below");
var above = MineshaftSupport28.anchorVolumes(piece,(x,zz)->column(-1,160));
require(above!=null && above.stream().allMatch(b->b.minY()==149 && b.maxY()==160), "Suspension-chain bounds include the real rock anchor");
var ledge = column(-1,384); ledge.setBlock(149, Blocks.STONE.defaultBlockState());
var direct = MineshaftSupport28.anchorVolumes(piece,(x,zz)->ledge);
require(direct!=null && direct.size()==4 && direct.stream().allMatch(b->b.minY()==149 && b.maxY()==149), "A sturdy natural ledge is already the native floor in every rotation");
require(!MineshaftSupport28.anchored(piece,(x,zz)->ledge), "The legacy support-only path remains unchanged");
var oneSide = MineshaftSupport28.anchorVolumes(piece,(x,zz)->
(direction.getAxis()==Direction.Axis.Z ? x==0 : zz==0) ? column(140,384) : column(-1,384));
require(oneSide!=null && oneSide.size()==2, "Native optional opposite pillars may be absent when both transverse ends remain anchored");
var firstEnd = SwampHutPlacement28.worldPosition(piece,0,-1,2);
require(MineshaftSupport28.anchorVolumes(piece,(x,zz)->
(direction.getAxis()==Direction.Axis.Z ? zz==firstEnd.getZ() : x==firstEnd.getX()) ? column(140,384) : column(-1,384))==null,
"A corridor with an entirely unattached end is still refused");
require(MineshaftSupport28.anchorVolumes(piece,(x,zz)->column(-1,220))==null, "A completely unsupported corridor is still refused");
require(MineshaftAdmission29.intersects(above,new BoundingBox(above.getFirst().minX(),158,above.getFirst().minZ(),above.getFirst().maxX(),159,above.getFirst().maxZ())), "A secret along the chain outside the corridor body is detected");
}
System.out.println("MineshaftAdmission29Smoke: native branch volumes, central gaps, four orientations and bounded floor/chain anchors passed.");
}
private static NoiseColumn column(int floor,int ceiling) {
var states=new BlockState[384];
for(int y=0;y<states.length;y++) states[y]=(y<=floor||y>=ceiling?Blocks.STONE:Blocks.AIR).defaultBlockState();
return new NoiseColumn(0,states);
}
private static void require(boolean condition,String message) {if(!condition)throw new AssertionError(message);}
}
@@ -0,0 +1,36 @@
package fr.koka.sanctuary.worldgen;
public final class Terraces29Smoke {
public static void main(String[] args) {
double largest = 0;
int positive = 0, negative = 0, varied = 0;
for (int y = 0; y < 384; y++) {
if (y <= Terraces29.START_Y) require(Terraces29.weight(y, 1) == 0, "lower caves stay unchanged");
require(Terraces29.weight(y, 0) == 0, "arrival and outer coast remain unchanged");
}
for (float coarse : new float[]{-1, -.5F, 0, .5F, 1})
for (float detail : new float[]{-1, -.4F, 0, .6F, 1}) {
double previous = Terraces29.sourceY(0, coarse, detail, 1);
for (int step = 1; step <= 4000; step++) {
double y = step * .1;
double mapped = Terraces29.sourceY(y, coarse, detail, 1);
double offset = mapped - y;
require(Math.abs(offset) <= Terraces29.MAX_OFFSET + 1e-10, "fine terraces stay within 2.5 blocks");
require(mapped > previous && mapped - previous < .5, "fixed local phase remains continuous and monotone");
require(Terraces29.sourceY(y, coarse, detail, 0) == y, "disabled terraces are exactly neutral");
if (offset > .5) positive++;
if (offset < -.5) negative++;
if (Math.abs(mapped - Terraces29.sourceY(y, coarse + .1F, detail - .1F, 1)) > .1) varied++;
largest = Math.max(largest, Math.abs(offset));
previous = mapped;
}
}
require(positive > 0 && negative > 0 && largest > 1.5, "fine relief has both directions at a visible but small amplitude");
require(varied > 0, "3D phase and period do not produce a common horizontal grid");
System.out.println("Terraces29Smoke: bounded local ledges, varied phase, continuous sampling and protected depths passed; max=" + largest);
}
private static void require(boolean condition, String message) {
if (!condition) throw new AssertionError(message);
}
}
@@ -0,0 +1,65 @@
package fr.koka.sanctuary.worldgen.transit;
import java.util.*;
/** Pure geometry controls; actual terrain, starts and walkability are checked by Transit29WorldGameTests. */
public final class Transit29Smoke {
public static void main(String[] args) {
var terrain = new TransitPlanner21.Terrain() {
public boolean owns(int x, int z) { return Math.hypot(x, z) < 510; }
public boolean solid(int x, int y, int z) { return y >= 55 && y <= surfaceY(x, z); }
public boolean protectedAt(int x, int y, int z) { return false; }
// This three-wide natural threshold is walkable without flattening a five-wide platform.
public int surfaceY(int x, int z) { return 250 + Math.floorMod(x, 6) / 2; }
};
for (int diameter : new int[]{512, 724, 1024}) for (long seed : new long[]{0, 42, 2026}) {
var plan = TransitPlanner29.plan(seed, 0, 0, diameter, terrain);
require(!plan.isEmpty(), "Supported hillside must admit a traversal: " + plan.diagnostic());
require(plan.equals(TransitPlanner29.plan(seed, 0, 0, diameter, terrain)), "Planning must be deterministic");
var accesses = plan.modules().stream().filter(m -> m.kind() == TransitPlan21.Kind.ACCESS).toList();
int span = Math.max(Math.abs(accesses.getFirst().centerX() - accesses.getLast().centerX()),
Math.abs(accesses.getFirst().centerZ() - accesses.getLast().centerZ()));
require(span >= diameter / 4, "A small local gallery is not the main traversal");
require(plan.modules().stream().anyMatch(m -> m.kind() == TransitPlan21.Kind.HALL), "Keep the established maintenance room where it fits");
var reached = new HashSet<TransitPlan21.Module>(); reached.add(accesses.getFirst());
for (int pass = 0; pass < plan.modules().size(); pass++) for (var a : List.copyOf(reached)) for (var b : plan.modules())
if (connected(a, b)) reached.add(b);
require(reached.size() == plan.modules().size(), "Every module and both accesses must connect through opposing doors");
}
var blocked = new TransitPlanner21.Terrain() {
public boolean owns(int x, int z) { return true; }
public boolean solid(int x, int y, int z) { return y <= 250; }
public boolean protectedAt(int x, int y, int z) { return true; }
public int surfaceY(int x, int z) { return 250; }
};
require(TransitPlanner29.plan(0, 0, 0, 1024, blocked).isEmpty(), "Protected fluids and structures cannot be overwritten");
var protectedBed = new TransitPlanner21.Terrain() {
public boolean owns(int x, int z) { return Math.hypot(x, z) < 510; }
public boolean solid(int x, int y, int z) { return y >= 55 && y <= 250; }
public boolean protectedAt(int x, int y, int z) { return y == 222; }
public int surfaceY(int x, int z) { return 250; }
};
var rescued = TransitPlanner29.plan(2026, 0, 0, 512, protectedBed);
require(!rescued.isEmpty() && rescued.diagnostic().contains("selective-footings"),
"After the complete primary search fails, valid walls may carry a route above a protected bed");
for (var m : rescued.modules()) {
require(m.minY() > 222, "The rescued route must not excavate the protected layer");
for (var support : m.supports()) require(support.minY() > 222 || support.maxY() < 222,
"No retained footing may write through the protected layer");
}
System.out.println("Transit29 pure geometry: nine seed/size cases, narrow natural thresholds, connected halls, bounded protected refusal.");
}
private static boolean connected(TransitPlan21.Module a, TransitPlan21.Module b) {
if (a.floorY() != b.floorY()) return false;
if (a.centerX() == b.centerX()) {
if (a.maxZ() + 1 == b.minZ()) return (a.openings() & 4) != 0 && (b.openings() & 1) != 0;
if (b.maxZ() + 1 == a.minZ()) return (b.openings() & 4) != 0 && (a.openings() & 1) != 0;
}
if (a.centerZ() == b.centerZ()) {
if (a.maxX() + 1 == b.minX()) return (a.openings() & 2) != 0 && (b.openings() & 8) != 0;
if (b.maxX() + 1 == a.minX()) return (b.openings() & 2) != 0 && (a.openings() & 8) != 0;
}
return false;
}
private static void require(boolean value, String message) { if (!value) throw new AssertionError(message); }
}