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
+6
View File
@@ -1,5 +1,11 @@
# Changelog
## 0.1.0-alpha.29 — 2026-09-12
Petites corniches variables sur le relief 3D de la28 ; correction des traversées
et de ladmission des mines natives. Voir [le périmètre](docs/generation-alpha29.md)
et [les essais](docs/testing-alpha29.md). Nouveau monde de test nécessaire.
## 0.1.0-alpha.28 — 2026-09-12
Spéléologie par bruits natifs, déformations volumiques 3D et failles obliques bornées ;
+9 -2
View File
@@ -21,6 +21,13 @@ le relief et les souterrains de la 24, retire la montgolfière et travaille les
marais souterrains et la restauration de vrais modèles d’épaves vanilla. Voir le
[chantier et ses limites](docs/generation-alpha27.md).
## Alpha.29 — corniches et traversées
Peaufinage de la 28 validée sur Windows : petits paliers dans le relief 3D,
correction des traversées manquantes en Grand et des refus excessifs de mines.
Créer un **nouveau monde**. Voir [la génération](docs/generation-alpha29.md),
[les essais](docs/testing-alpha29.md) et [packwiz](docs/packwiz.md).
## Alpha.28 — grottes et lieux proches
Cette passe prolonge la 27 validée en jeu : bruits de grottes vanilla dans
@@ -585,7 +592,7 @@ sera une mise à jour explicite, avec vérification des API et des sauvegardes.
| Fabric API | 0.160.0+26.3 |
| Fabric Loom | 1.17.20 |
| Gradle Wrapper | 9.5.1, distribution vérifiée par SHA-256 |
| Sanctuary / pack | 0.1.0-alpha.28 |
| Sanctuary / pack | 0.1.0-alpha.29 |
Java 25 et Python 3.11 ou plus récent sont nécessaires. Le script pack utilise
uniquement la bibliothèque standard et repère aussi une installation Python
@@ -614,7 +621,7 @@ décrits dans [Validation](docs/testing.md).
Résultats :
- `mods/sanctuary/build/libs/sanctuary-0.1.0-alpha.28.jar` : mod à installer avec
- `mods/sanctuary/build/libs/sanctuary-0.1.0-alpha.29.jar` : mod à installer avec
Fabric API sur la version Minecraft indiquée.
- `build/packwiz/` : pack de développement complet, avec le mod construit et
l'index vérifié. Voir [Installation packwiz](packwiz/README.md).
+10
View File
@@ -978,3 +978,13 @@ Moins de marais ; mines, huttes et donjons vanilla adaptés aux cavités réelle
Les filons natifs doivent aussi alimenter les continents ouverts ensuite.
Aucun retour des salles ellipsoïdales ni des cônes de lalpha.25.
Résultats et limites : [génération](generation-alpha28.md), [essais](testing-alpha28.md).
### WG-31 — Corniches fines et traversées présentes — alpha.29
Branche `codex/terraces-transit-alpha29`, depuis lalpha.28 validée en jeu
sur Windows par le créateur. Reproduire la traversée manquante sur Grand,
graine `-7228211907433324401`, corriger ladmission et la pose des modules.
Ajouter un léger terracement variable au relief3D, préserver les profondeurs,
et vérifier les véritables mines vanilla sur la graine signalée.
Voir [génération](generation-alpha29.md) et [vérifications](testing-alpha29.md).
+56
View File
@@ -0,0 +1,56 @@
# Alpha.29 — corniches fines et traversées présentes
La 28 a été validée visuellement par le créateur sur Windows, avec un chargement
correct. Cette livraison conserve cette base et traite trois observations :
la grande traversée absente en Grand, le relief un peu trop lissé et les mines
vanilla difficiles à rencontrer.
## Témoin signalé
Grand **1 024**, graine **-7228211907433324401**. La reproduction avec le code
28 confirme un plan de transit vide : 419 alignements examinés, 13 000 colonnes,
75,114 secondes de recherche et aucun couple de portiques naturels accepté.
Dans les douze premiers candidats natifs examinés, aucune mine nest admise.
Le résultat ne prétend pas inventorier les parties inconnues de toute l’île.
## Relief
La déformation 3D de la 28 reste en place. Une modulation fine du Y échantillonné
forme de petites corniches avec des ruptures plus franches. Période locale de
6 à 9 blocs et phase variables avec les bruits 3D existants, décalage supplémentaire
borné à ±2,5 blocs. Aucune grille de plateaux horizontaux commune à toute l’île.
Le raccord commence à Y214 et atteint sa pleine amplitude à Y244, à l’écart
de larrivée et du contour. Les densités sous Y214 sont exactement celles de
la 28 ; les grottes, failles, minerais, marais et lieux aériens sont conservés.
Le même champ sert aux colonnes, chunks, biomes et certifications.
## Infrastructures et mines
Le correctif de traversée conserve le génie civil et les galeries industrielles
existants. Les accès doivent sadapter au relief sans transformer la surface en
une grande fondation. Deux accès relient une chaussée et ses trottoirs ; la
branche vers une salle conserve une paroi pleine du côté fermé de sa jonction.
Les modules déjà certifiés ne sont plus refiltrés par un
biome souterrain incompatible avec leur ancien tag de structure.
Pour les mines, les protections sont vérifiées autour des vraies pièces et de
leurs appuis, et non dans tout le rectangle vide entre leurs branches. Le
graphe, les pièces, les rails, coffres, toiles et spawners restent vanilla.
Un sol rocheux portant déjà la galerie est reconnu comme appui natif ; il ne
nécessite pas un second pilier ou une chaîne. Les galeries exposées gardent les
ancrages bornés du vanilla et les réservations protègent aussi leurs appuis.
Les trois essais daltitude sont choisis dans les couches rocheuses réellement
présentes autour des pièces, après un sondage de 32 colonnes au maximum. Les
croisements utilisent le contrat de leur plancher vanilla : une voûte rocheuse
proche nest pas nécessaire à un pont de planches portant sur un corridor ancré.
Les marches creusées et les salles gardent leurs contrôles de couverture.
Les mines restent conditionnelles : le correctif nimpose pas une mine à chaque
graine et ne modifie pas la politique des autres familles de structures.
## Distribution
Mod et pack **0.1.0-alpha.29**, Minecraft **26.3-pre-2**, codec de prototype
`sanctuary:island_v24`, mêmes tailles et bouton Personnaliser. Les options de
structures gardent leur autorité. Créer un nouveau monde ; aucune modification
ou régénération dune sauvegarde personnelle. Les artefacts28 restent accessibles.
Les constats mesurés et les limites sont consignés dans [les essais](testing-alpha29.md).
+109
View File
@@ -0,0 +1,109 @@
# Alpha.29 — vérifications
**Point de reprise local, non publié.** Le chantier se poursuit directement
en alpha.30 à la demande du créateur. Le contrôle supplémentaire Petit512,
graine2026, a refusé la traversée et révélé un témoin côtier aérien sans appui
après génération. Ces limites empêchent de considérer la29 comme livrée.
Les artefacts préparatoires et reçus locaux restent des preuves intermédiaires.
État des preuves au 12 septembre 2026. Les essais natifs ci-dessous ont été
exécutés sur **Mac, en serveur de développement sans rendu client**. Le retour
Windows du créateur concerne lalpha.28 ; aucun essai Windows de la 29 nest
revendiqué. Créer un **nouveau monde** pour tester cette génération.
## Traversées réellement générées et parcourues
| Monde | Départs / chunks FULL | Distance entre accès | Parcours avec collisions natives |
|---|---:|---:|---:|
| Grand 1 024, graine **-7228211907433324401** | 23 / 52 | 340 blocs | 652 pas, 74 points accessibles |
| Moyen 724, graine **0** | 17 / 27 | 238 blocs | 390 pas, 56 points accessibles |
Chaque témoin vérifie les blocs posés, les escaliers, les approches naturelles,
les raccords entre modules, les départs sauvegardables et le locator dentrée.
Les **cinq cellules du côté fermé de la jonction en T** sont contrôlées après
la correction du renderer. Ces recherches nouvrent aucune expansion.
Preuves : `build/alpha29-evidence/user-locate-before-crossing/transit29-native.json`
(PID 55232) et `build/alpha29-evidence/medium-0/` (reçus du PID 56574).
La correction ultérieure de ladmission des mines ne modifie pas le transit.
Sur la même graine Grand, la référence alpha.28 produisait un plan vide après
419 alignements et 13 000 colonnes. Cette reproduction est conservée dans
`build/alpha29-evidence/baseline-user/` ; le succès de ses anciens GameTests
ne démontrait pas la présence dune traversée.
## Relief : comparaison exacte avec la 28
| Monde | Densités inférieures identiques bit à bit | Échantillons supérieurs ayant changé de signe |
|---|---:|---:|
| Grand, graine signalée | 10 140 | 303 / 71 656, soit **0,423 %** |
| Moyen, graine 0 | 7 935 | 166 / 56 074, soit **0,296 %** |
Les densités inférieures sont échantillonnées entre Y32 et Y214. Les deux
coupes à Z0 et Z96, au pas dun bloc dans la bande Y205305, ont été comparées
pour la graine signalée : petites corniches, quelques arêtes plus dentelées,
grandes silhouettes de la 28 conservées. Cela ne remplace pas un essai visuel
dans le client. Le déplacement supplémentaire des coordonnées est borné à
2,5 blocs ; cette borne ne porte pas sur le changement de sommet lorsquune
petite lèvre disparaît.
Reçus `alpha29-terraces-*.json` des dossiers de preuve ; aperçu local :
`build/alpha29-preview/terraces-1024--7228211907433324401.png`.
## Mine vanilla et satellites
**Moyen, graine 0 :** une véritable mine vanilla de **143 pièces acceptées**
est trouvée parmi les candidats normaux. Linspection est partielle :
**16 chunks FULL** sont demandés par le test. Elle observe notamment
244 planches de chêne, 200 barrières, 102 rails et 50 toiles dans les pièces
chargées. Une salle `monster_room` avec spawner de zombies est également
observée. Le test ne prétend pas avoir exploré les 143 pièces, ni avoir
déclenché tous les spawners ou contrôlé tous les butins. La hutte de sorcière
est absente de cet échantillon ; ce nest pas une absence prouvée sur l’île.
Une lecture séparée du monde sauvegardé confirme deux spawners vanilla
`cave_spider` aux positions **156 171 -138** et **140 175 -97**, dans les
corridors correspondants (`sc=1`, `hps=1`). Reçu :
`build/alpha29-evidence/medium-0/mineshaft-spawners-saved.json`.
Sur ce même monde, **quatre satellites**, **deux villageois**, **quatre coffres**
et **une source de cascade** passent la création puis un véritable redémarrage.
Les 47 chunks FULL contrôlés conservent le pillage, les villageois déplacés
et les blocs détruits, sans duplication ni reconstruction des témoins.
Le journal dexpansion reste inchangé. Création PID 56060, rechargement
PID 56574 ; les **23 GameTests natifs** de ce dernier processus passent.
Preuves regroupées dans `build/alpha29-evidence/medium-0/`.
## Tests ciblés et portée
Les smokes terrain, appuis de mines et transit couvrent les bornes, les
orientations et les refus protégés. Le test pur de traversée couvre
0 / 42 / 2026 × 512 / 724 / 1 024 **sur un terrain synthétique portant** :
ce nest pas une matrice de neuf mondes Minecraft réellement générés.
Les contrôles natifs ci-dessus portent sur les témoins explicitement cités.
`./gradlew check build` passe : **86 tâches**, dont les **23 GameTests natifs**,
en 2 min 58 s pour cette exécution. Journal :
`build/alpha29-evidence/medium-0/gradle.log`. Ce temps nest pas un benchmark de génération.
`./gradlew assemblePack` passe également. JAR, MRpack autonome, index packwiz
et ZIP damorçage sont vérifiés localement : versions, dépendances, CRC et
empreintes concordent. Reçus `build/alpha29-artifacts.json` et
`build/alpha29-delivery-local.json`.
Restent en attente de leurs reçus :
- Contrôles natifs de **planification seule** Grand/graine42 et Petit/graine2026 ;
même réussis, ils ne prouveront pas un parcours sur des chunks FULL.
- Publication, canal packwiz et synchronisation Prism.
Pas de preuve dexécution avec structures désactivées propre à lalpha.29,
ni de nouvelle validation visuelle client ou Windows à ce stade.
## Repères pour un essai manuel
Avec les structures activées, sur un nouveau Grand de graine
`-7228211907433324401`, les entrées parcourues sont **128 252 -127** et
**128 257 213**. Sur Moyen/graine0 : **-90 235 -127** et **-90 235 111**.
`/locate structure sanctuary:industrial_transit` recherche une entrée du réseau.
Le repère **152 175 -120** désigne la mine de Moyen/graine0 : lutiliser en
spectateur pour linspection, sans supposer une arrivée sûre en survie.
+2 -2
View File
@@ -8,6 +8,6 @@ loader_version=0.19.5
loom_version=1.17.20
fabric_api_version=0.160.0+26.3
mod_version=0.1.0-alpha.28
pack_version=0.1.0-alpha.28
mod_version=0.1.0-alpha.29
pack_version=0.1.0-alpha.29
maven_group=fr.koka.sanctuary
+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); }
}
+1 -1
View File
@@ -1,6 +1,6 @@
name = "Sanctuary"
author = "KOKA99CAB"
version = "0.1.0-alpha.28"
version = "0.1.0-alpha.29"
pack-format = "packwiz:1.1.0"
[index]