Compare commits

...
Author SHA1 Message Date
koka 2dc70fe905 Refine high plateaus and extend Sanctuary build height for alpha30.4
Build Sanctuary / build (push) Canceled after 0s
2026-09-12 15:56:31 +02:00
koka a49dfa5337 docs: record alpha30.3 publication and Prism synchronization
Build Sanctuary / build (push) Canceled after 0s
2026-09-12 13:34:14 +02:00
30 changed files with 491 additions and 31 deletions
+8
View File
@@ -1,5 +1,13 @@
# Changelog
## 0.1.0-alpha.30.4
- Plateaux bas conservés, abords des massifs élargis et hauts reliefs en 3D ; aucun seuil de découpe latérale, microbruit ou gradin ajouté.
- Replats supérieurs proches de Y360, végétation et recherche supplémentaire de rivières courbes en altitude.
- Monde initial constructible sur 640 blocs (Y0..639), nuages inchangés et terrain toujours sous Y360.
- Conservation des secrets, traversée, navires et îlots ; aucun ajout de larène du destin.
- Nouveau monde de test nécessaire ; aucun monde personnel modifié.
## 0.1.0-alpha.30.3
- Retire les microcorniches de la 30.2, jugées trop bruitées en jeu.
+12 -2
View File
@@ -21,6 +21,16 @@ 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.30.4 — massifs et ciel constructible
Les plateaux bas sont conservés ; les reliefs hauts se raccordent par des
abords plus larges, avec des falaises et des arches issues du bruit 3D. Des replats proches de Y360 peuvent accueillir des rivières courbes.
Le plafond de construction passe à **Y640** (dernier bloc : Y639), sans relever
l’île entière ni déplacer les nuages. Les trois tailles restent inchangées.
Créer un **nouveau monde**. Voir [le terrain](docs/generation-alpha30.4.md)
et [les vérifications](docs/testing-alpha30.4.md).
## Alpha.30.3 — plateaux et grands reliefs
La passe de petites corniches de la 30.2 est retirée après le retour en jeu.
@@ -622,7 +632,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.29 |
| Sanctuary / pack | 0.1.0-alpha.30.4 |
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
@@ -651,7 +661,7 @@ décrits dans [Validation](docs/testing.md).
Résultats :
- `mods/sanctuary/build/libs/sanctuary-0.1.0-alpha.29.jar` : mod à installer avec
- `mods/sanctuary/build/libs/sanctuary-0.1.0-alpha.30.4.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
@@ -1027,3 +1027,13 @@ part importante aux grandes déformations 3D, vers Y360. Équilibrer les trois
tailles par une distribution à l’échelle de l’île. Préserver les profondeurs,
les cours deau et les infrastructures ; limiter les petits fragments flottants.
Voir [le changement](generation-alpha30.3.md) et [les preuves](testing-alpha30.3.md).
### WG-36 — Massifs raccordés et ciel Y640 — alpha.30.4
Branche `codex/high-plateaus-alpha30-4`. Conserver les plateaux bas, retirer
les raccords trop brusques au pied des montagnes, conserver les falaises sans
petites marches répétitives ajoutées, accueillir des rivières
courbes sur les replats supérieurs et libérer de lespace constructible jusqu’à
Y639. Stabiliser un seul relief avant dajouter des personnalisations.
Larène du destin reste une idée, sans structure ajoutée.
Voir [la génération](generation-alpha30.4.md) et [les essais](testing-alpha30.4.md).
+54
View File
@@ -0,0 +1,54 @@
# Alpha.30.4 — massifs, hauts plateaux et ciel constructible
Le retour sur la 30.3 valide les plateaux bas et les intérieurs des montagnes,
mais rejette leurs grandes pentes étirées. Cette passe conserve un seul relief,
sans nouveau réglage de personnalisation. Petit 512, Moyen 724 par défaut et
Grand 1 024 restent disponibles dans le bouton natif Personnaliser.
## Terrain
Les provinces larges continuent de sélectionner les secteurs montagneux. La
base naturelle issue de la 24 garde sa place ; le champ rocheux relevé se
raccorde à cette base par union des volumes. Les abords des massifs s'étendent
sur 64 à 96 blocs selon la taille de l'île. L'élévation suit cette transition,
sans seuil latéral qui sectionnerait la densité en une paroi artificielle.
L'arrivée et le plateau de traversée restent préservés.
Les falaises franches sont voulues. Le retour du créateur vise les petites
marches répétitives et les lambeaux, pas toutes les parois raides. Aucun
microbruit ni gradin périodique n'est ajouté pour masquer les surfaces ; le
champ 3D conserve ses arches. Le relief inférieur à Y170 reste identique.
Le rendu des raccords reste à apprécier en jeu, particulièrement entre les
surfaces végétales et les parois rocheuses.
Les sommets suffisamment massifs portent des replats vers Y354359, avec
une altitude légèrement variable et des vides issus du champ 3D. Les sommets
ne sont pas tous forcés à cette hauteur. Le filtre des petits composants
rocheux détachés reste actif. La végétation peut maintenant s’établir sur ces
replats, avec les mêmes contrôles dappui et despace pour les arbres.
Lhydrologie cherche un cours deau supplémentaire sur les hauts plateaux,
puis conserve la recherche dune rivière sur les plateaux bas. Même tracé
courbe, mêmes lits et sédiments ; le parcours supérieur peut être plus court
pour tenir dans un massif. Sa présence dépend du terrain. Les brèches de
berges et les sources déclenchent toujours les écoulements vanilla.
## Hauteur et périmètre
Le preset actuel utilise le nouveau type `sanctuary:sanctuary_640` :
**Y0 à Y639 constructibles**, limite supérieure exclusive Y640.
Le générateur et ses trois délégués de terrain exposent la même hauteur.
Le terrain reste sous Y360 ; le nouvel espace aérien reste libre. Les nuages
restent à Y352,33. Les dimensions vanilla Nether et End ne changent pas.
Les deux navires, deux îlots, secrets, rails, traversée et expansions restent
présents. Leurs emplacements dépendent du terrain. Larène du destin est
uniquement une idée pour un futur défi en altitude : aucune plateforme,
panneau ni arène nest généré dans cette livraison.
Créer un **nouveau monde**. Conformément au workflow de tests jetables demandé,
les profils courants évoluent avec le codec `sanctuary:island_v24`. Aucun monde
personnel nest ouvert, migré ou régénéré. Ne pas prolonger une ancienne
sauvegarde pour juger les raccords de cette version.
Voir [les essais et leurs limites](testing-alpha30.4.md).
+33
View File
@@ -13,6 +13,39 @@ JAR Sanctuary sont des pièces jointes de releases Gitea. Aucun JAR de mod ou
dépendance téléchargée n'entre dans l'historique Git. Le canal Beta est distinct
de l'ancien pack 26.2.
## Alpha.30.3 publiée — 12 septembre 2026
La [release alpha.30.3](https://git.botsu.net/koka/sanctuary-beta/releases/tag/v0.1.0-alpha.30.3)
est publiée depuis `076c9a6c16e93e46db4a16ed0dd8d6a5a22c5a85`, branche
`codex/balanced-relief-alpha30-3`. Elle retire les microcorniches de la 30.2,
mélange plateaux naturels et grandes provinces montagneuses en 3D et conserve
le filtrage des petits fragments. Les recherches aériennes et le veto de biome
du sanctuaire dans les marais sont corrigés.
`check build` et `assemblePack` réussissent. Les trois profils sont comparés
sur les graines 0, 42 et `-7228211907433324401`. Trois mondes natifs ont terminé
à 24/24 tests ; Petit 42 et Grand signalé comprennent les correctifs finaux de
placement. Voir [le terrain](generation-alpha30.3.md) et [les preuves](testing-alpha30.3.md).
Le [MRpack alpha.30.3](https://git.botsu.net/koka/sanctuary-beta/releases/download/v0.1.0-alpha.30.3/Sanctuary-0.1.0-alpha.30.3.mrpack),
le JAR et le ZIP damorçage ont été vérifiés localement et par téléchargement public.
Canal packwiz : `a68fe0df2f82868100219799db603805f89c56c7`.
Minecraft 26.3-pre-2, Loader 0.19.5 et Fabric API 0.160.0+26.3 restent inchangés.
Deux installations isolées puis deux synchronisations de la même instance
**Sanctuary Beta sur Mac**, jeu fermé, ont réussi. Un seul JAR Sanctuary 30.3
est actif ; les 925 fichiers personnels et réglages suivis gardent leurs empreintes.
Le dossier dinstance reste `Sanctuary-0.1.0-alpha.1`. Sauvegarde ciblée :
`sanctuary-backups/before-0.1.0-alpha.30.3/`.
SHA-256 JAR : `6713622a4ad8034bb230ff11e1bad580d218932da31b3feeddee4c905fd11759`.
SHA-256 MRpack : `bb1172d14a8092d8155edeb61dba943c0a149874580ab4bd21abd8e87db54255`.
Reçus : `build/alpha303-delivery-public.json`, `build/alpha303-isolated-validation.json`,
`build/alpha303-prism-validation.json`.
Créer un **nouveau monde Sanctuary**. Aucun essai natif Windows, mise à jour
Windows ou validation esthétique par le créateur ne sont revendiqués.
## Alpha.30.2 publiée — 12 septembre 2026
La [release alpha.30.2](https://git.botsu.net/koka/sanctuary-beta/releases/tag/v0.1.0-alpha.30.2)
+8
View File
@@ -95,3 +95,11 @@ Essais exécutés sur Mac ; aucun essai natif Windows par lagent. Les variati
de relief exigent un nouveau monde ; aucun monde personnel nest régénéré.
Le filtrage local de connexité nest pas un audit exhaustif de l’île entière.
La présence des mines, huttes et donjons reste conditionnelle au terrain.
## Artefacts et installation
Le JAR, le MRpack et le ZIP Prism ont été contrôlés localement puis téléchargés
publiquement et comparés octet par octet. Deux synchronisations isolées, puis
deux dans la même instance Prism Mac, réussissent. Les 925 fichiers personnels
et réglages suivis conservent leurs empreintes ; un seul JAR Sanctuary 30.3 est actif.
Les reçus et empreintes sont consignés dans [packwiz](packwiz.md).
+90
View File
@@ -0,0 +1,90 @@
# Vérifications alpha.30.4
Essais natifs sur Mac, Minecraft 26.3-pre-2, Java 25. Aucun essai Windows
natif. Les visualisations de densité ne sont pas des captures Minecraft et
ne constituent pas une validation esthétique du paysage décoré.
## Itérations écartées
Les premiers essais Moyen 0 (PID 71154) et Petit 42 (PID 71587) avaient passé
24 tests chacun. Leur retrait latéral produisait toutefois une transition trop
brutale : cette version a été écartée après le retour visuel du créateur.
Leurs mesures de surface ne décrivent donc pas le binaire final.
Le contrôle Grand a ensuite détecté une régression de la densité profonde
lors d'un raccourci de calcul. Le remappage vertical a été corrigé pour rester
strictement identique sous Y170 ; le contrôle n'a pas été affaibli.
## Terrain corrigé
Moyen 724, graine 0, PID 72270 : **24/24 tests natifs**, 4 min 46 s Gradle.
Quatre lieux aériens, quatre coffres et deux villageois. Traversée de 272 blocs,
19 départs, 29 chunks FULL ; sanctuaire minéral présent. Deux rivières courbes,
à Y347 et Y243, un lac à Y250. Sept cellules d'eau coulante observées sous
les brèches ; les surfaces d'eau sont testées sous ticks réels.
Les trois tailles sont échantillonnées dans le même essai : sommets à
Y354/356/354. Respectivement 61/54/59 % des colonnes supérieures comparées
restent à deux blocs au plus de la base 24. Cela conserve des secteurs bas,
sans prétendre qu'il s'agit d'une mesure exhaustive de la surface.
Le volume de contrôle passe de cinq petits composants rocheux détachés à zéro ;
1 536 points sont identiques en ordre inverse. Les 5 819 échantillons profonds
restent identiques. Ces relevés ne garantissent pas chaque bloc de chaque monde.
Le bloc à Y639 est accepté, Y640 refusé ; l'espace supérieur reste initialement vide.
Preuves locales ignorées : `build/alpha304-evidence/medium-0-current/`.
## Client et validation finale
Le client natif valide les mixins de ciel/brouillard, les trois tailles dans
Personnaliser (Valider, Annuler, Échap), le Blocodex et ses échanges réseau.
Le premier protocole de captures a produit des vues inutilisables : il
confondait l'existence théorique d'un chunk client avec sa réception réelle.
Ces premières images ne constituent pas une validation visuelle. Le protocole
corrigé attend de vrais chunks reçus, la caméra et le rendu. Le client passe
en 2 min 20 s (`build/alpha304-client-received-chunks.log`) et fournit deux
captures réelles, sans shaders, distance de rendu 8 et nuages désactivés par
le harnais. Une arche, ses abords végétalisés et une rivière haute avec sa
cascade sont visibles. Des marches subsistent sur certaines faces inclinées :
ce défaut visuel n'est pas déclaré éliminé partout.
Captures locales : `build/alpha304-evidence/client/`. La seconde montre le
massif photographié avec du brouillard de distance ; elle ne représente pas
l'ensemble de l'île ni une validation exhaustive de ses raccords.
`./gradlew check build` réussit en 7 min 53 s sur le terrain final.
Grand 1 024, graine `-7228211907433324401`, PID 73175 : **24/24**.
La traversée mesure 374 blocs, avec 23 départs et 52 chunks FULL.
Quatre lieux aériens, quatre coffres, deux villageois et le sanctuaire minéral
sont présents. Une rivière courbe et huit cellules coulantes sous ses sorties
sont vérifiées. Sur cette graine, une deuxième rivière haute n'est pas admise.
Le plafond Y639 est constructible ; Y640 est refusé.
Les profils 512/724/1024 échantillonnés sur cette graine culminent à
Y330/354/354 ; les maxima ne sont pas imposés à chaque taille. Le volume
contrôlé passe de 12 petits composants détachés à zéro. Les 7 436 échantillons
profonds sont identiques ; 1 536 points restent identiques en ordre inverse.
Preuves : `build/alpha304-check-build-verified.log` et
`build/alpha304-evidence/large-user-current/`.
Un second processus natif, PID 73544, recharge le même Grand : **24/24**
en 3 min 12 s. Le bloc posé à Y639 persiste, les coffres pillés et les
satellites partiellement démolis restent dans cet état ; les occupants déplacés
ne sont pas recréés. Les départs, références et marqueurs d'habitants sont
identiques, le journal d'expansion inchangé.
Preuves : `build/alpha304-reload-large.log` et
`build/alpha304-evidence/large-user-reload/reload1.json`.
`assemblePack` réussit après `check build`, sans relancer les mêmes tests.
Le JAR, le MRpack et le ZIP d'amorçage sont vérifiés localement : versions,
Minecraft exact, codec, hauteur Y640, dépendances épinglées et intégrité ZIP.
Les reçus publics et l'installation Prism seront consignés dans
[la distribution packwiz](packwiz.md).
## Limites
Les rivières restent conditionnelles : un sommet étroit ne reçoit pas un
cours d'eau forcé. Le contrôle des cascades observe leur écoulement local,
pas une simulation exhaustive de chaque chute jusqu'à Y0. Les études de
terrain couvrent des échantillons, et les tests de structures chargent des
emprises ciblées. Aucun monde personnel n'est utilisé. Nouveau monde requis.
+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.30.3
pack_version=0.1.0-alpha.30.3
mod_version=0.1.0-alpha.30.4
pack_version=0.1.0-alpha.30.4
maven_group=fr.koka.sanctuary
+3
View File
@@ -24,6 +24,9 @@ if (clientRenderTests) {
sourceSets.named('gametest') {
java.srcDir('src/clientGametest/java')
}
tasks.named('runClientGameTest') {
systemProperty('sanctuary.test.highland304', providers.gradleProperty('sanctuaryHighland304Visual').getOrElse('false'))
}
}
// Fresh generation by default; the explicit laboratory reload verifies the same saved test world.
@@ -0,0 +1,62 @@
package fr.koka.sanctuary.gametest;
import fr.koka.sanctuary.SanctuaryMod;
import fr.koka.sanctuary.client.SanctuarySky;
import java.nio.file.Path;
import java.util.UUID;
import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext;
import net.minecraft.client.gui.screens.worldselection.WorldCreationUiState;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.level.chunk.status.ChunkStatus;
/** Opt-in photographs of actual decorated terrain, in a fresh ignored development world only. */
final class HighPlateaus304ClientChecks {
static void run(ClientGameTestContext context) {
Path root = Path.of("").toAbsolutePath().normalize();
if (!root.endsWith(Path.of("build", "run", "clientGameTest")))
throw new AssertionError("Visual test must stay in the ignored client development directory");
try (var world = context.worldBuilder().adjustSettings(settings -> {
settings.setName("Sanctuary relief304 " + UUID.randomUUID());
settings.setSeed("0");
var preset = settings.getSettings().worldgenLoadContext().lookupOrThrow(Registries.WORLD_PRESET)
.getOrThrow(ResourceKey.create(Registries.WORLD_PRESET, SanctuaryMod.id("sanctuary")));
settings.setWorldType(new WorldCreationUiState.WorldTypeEntry(preset));
settings.setGameMode(WorldCreationUiState.SelectedGameMode.CREATIVE);
settings.setGenerateStructures(false);
}).create()) {
context.runOnClient(client -> {
client.options.renderDistance().set(8);
client.options.broadcastOptions();
});
world.getServer().runOnServer(server -> {
server.getCommands().performPrefixedCommand(server.createCommandSourceStack(), "gamemode spectator @a");
server.getCommands().performPrefixedCommand(server.createCommandSourceStack(), "time set 6000");
server.getCommands().performPrefixedCommand(server.createCommandSourceStack(), "tp @a 150 288 -60 0 -20");
});
context.waitFor(client -> client.player.isSpectator() && Math.abs(client.player.getY() - 288) < 1, 2400);
context.waitTicks(40);
// ClientLevel.hasChunk() always returns true; require actual received chunks instead.
context.waitFor(client -> client.level.getChunk(9, 0, ChunkStatus.FULL, false) != null
&& client.level.getChunk(11, 3, ChunkStatus.FULL, false) != null, 2400);
world.getConnection().waitForChunksRender(false, 2400);
context.waitTicks(20);
context.runOnClient(client -> {
if (client.level.getHeight() != 640 || !SanctuarySky.isFloatingWorld(client.level))
throw new AssertionError("The connected client needs tall bounds and Sanctuary sky");
});
context.takeScreenshot("sanctuary-alpha304-cliff");
world.getServer().runOnServer(server -> server.getCommands().performPrefixedCommand(
server.createCommandSourceStack(), "tp @a -45 407 30 0 40"));
context.waitFor(client -> Math.abs(client.player.getY() - 407) < 1, 2400);
context.waitTicks(40);
context.waitFor(client -> client.level.getChunk(-6, 8, ChunkStatus.FULL, false) != null
&& client.level.getChunk(-1, 9, ChunkStatus.FULL, false) != null, 2400);
world.getConnection().waitForChunksRender(false, 2400);
context.waitTicks(20);
context.takeScreenshot("sanctuary-alpha304-high-river");
} finally {
context.restoreDefaultGameOptions();
}
}
}
@@ -5,7 +5,7 @@ import java.util.Arrays;
import net.fabricmc.fabric.api.client.gametest.v1.FabricClientGameTest;
import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext;
/** Opt-in check of production render mixins in the actual client, without opening a save. */
/** Opt-in render, UI and network checks in fresh ignored development worlds. */
public final class SanctuaryClientRenderTests implements FabricClientGameTest {
@Override
public void runTest(ClientGameTestContext context) {
@@ -17,6 +17,7 @@ public final class SanctuaryClientRenderTests implements FabricClientGameTest {
SanctuaryMod.LOGGER.info("Client render tests passed: Sanctuary sky and fog mixins applied.");
});
SanctuaryWorldOptionsClientChecks.run(context);
if (Boolean.getBoolean("sanctuary.test.highland304")) HighPlateaus304ClientChecks.run(context);
BlocodexClientChecks.run(context);
BlocodexNetworkClientChecks.run(context);
}
@@ -89,8 +89,15 @@ final class SanctuaryWorldOptionsClientChecks {
require(client.gui.screen() == create && diameter(create) == size.getValue(),
"Done must apply the chosen diameter and return to creation");
var after = create.getUiState().getSettings();
var type = after.selectedDimensions().get(LevelStem.OVERWORLD).orElseThrow().type();
require(type.value().height() == 640 && type.value().minY() == 0,
"Every customized size keeps the 640-block dimension");
require(fr.koka.sanctuary.client.SanctuarySky.isFloatingType(type),
"The tall dimension uses the Sanctuary sky and open-void fog");
require(after.options().equals(before.options()), "Size must preserve seed, structures and bonus chest");
for (var dimension : List.of(LevelStem.NETHER, LevelStem.END)) {
require(!fr.koka.sanctuary.client.SanctuarySky.isFloatingType(
after.selectedDimensions().get(dimension).orElseThrow().type()), "Native sky remains native outside Sanctuary");
require(after.selectedDimensions().get(dimension).equals(before.selectedDimensions().get(dimension)),
"Size must preserve other dimensions");
}
@@ -149,6 +149,23 @@ public final class Aerial24WorldGameTests {
report.put("mutations", previous.get("mutations"));
verifyMutations(helper, level, plan, previous.getAsJsonObject("mutations"), occupants);
}
helper.assertTrue(level.getMinY() == 0 && level.getHeight() == 640
&& generator.getGenDepth() == 640, "Dimension and generator expose Y0..639");
helper.assertTrue(generator.starterContext().generator().getGenDepth() == 640,
"The native terrain delegate also exposes the extended build height");
var ceilingWitness = new BlockPos(8, 639, 8);
if (create) {
for (int y = 384; y < 640; y++)
helper.assertTrue(level.getBlockState(new BlockPos(8,y,8)).isAir(),
"The new build space does not lift the entire island");
helper.assertTrue(level.setBlock(ceilingWitness, Blocks.DIAMOND_BLOCK.defaultBlockState(), 3),
"A normal block can be built at Y639");
helper.assertFalse(level.setBlock(ceilingWitness.above(), Blocks.DIAMOND_BLOCK.defaultBlockState(), 3),
"Y640 is outside the build boundary");
}
helper.assertTrue(level.getBlockState(ceilingWitness).is(Blocks.DIAMOND_BLOCK),
"A block built at Y639 survives saving and a separate JVM reload");
report.put("build_height", 640); report.put("ceiling_block", List.of(8,639,8));
var tags = tags(level, starts);
if (!create) helper.assertTrue(previous.get("native_tags").equals(JSON.toJsonTree(tags)), "Native starts, references and occupant flags survive both reloads");
report.put("native_tags", tags); report.put("full_chunks", chunks.size());
@@ -27,6 +27,9 @@ public final class River30WorldGameTests {
var selected=plan.cells().stream().filter(PopulationHydrology.Cell::hasWater).toList();
var witnesses=new ArrayList<PopulationHydrology.Cell>();
for(int i=0;i<6;i++)witnesses.add(selected.get(i*(selected.size()-1)/5));
for(var feature:plan.features()) if(feature.kind()==PopulationHydrology.Kind.RIVER)
selected.stream().filter(c->c.featureId()==feature.id()).findFirst().ifPresent(witnesses::add);
report.put("tested_water_levels",witnesses.stream().map(PopulationHydrology.Cell::waterY).distinct().sorted().toList());
var falls=plan.spills().stream().filter(f -> plan.features().stream().anyMatch(feature -> feature.kind()==PopulationHydrology.Kind.OUTLET && feature.id()==f.id())).limit(2).toList();
if(Boolean.getBoolean("sanctuary.test.requireCurvedRiverBank"))
helper.assertTrue(falls.stream().anyMatch(f -> plan.features().stream().anyMatch(feature -> feature.id()==f.fromFeatureId() && feature.kind()==PopulationHydrology.Kind.RIVER)),
@@ -121,8 +121,10 @@ public final class SanctuaryWorldGameTests {
var level = helper.getLevel();
var registries = level.registryAccess();
var settings = registries.lookupOrThrow(Registries.NOISE_SETTINGS);
helper.assertTrue(level.getMinY() == 0 && level.getHeight() == 384,
"The new Sanctuary dimension must have exactly 384 buildable blocks, Y0..383");
boolean tall = level.getChunkSource().getGenerator() instanceof fr.koka.sanctuary.worldgen.SanctuaryChunkGenerator sg
&& sg.rootGeneration() == 24;
helper.assertTrue(level.getMinY() == 0 && level.getHeight() == (tall ? 640 : 384),
"Current Sanctuary has 640 buildable blocks; historical presets retain 384");
float cloudHeight = level.environmentAttributes()
.getDimensionValue(net.minecraft.world.attribute.EnvironmentAttributes.CLOUD_HEIGHT);
helper.assertTrue(Math.abs(cloudHeight - 352.33F) < 0.001F,
@@ -22,7 +22,7 @@ public final class Terraces29WorldGameTests {
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.balanced303() && !field.terraces29() && !field.surface302(), "Alpha30.3 removes fine bands and uses broad geological provinces");
helper.assertTrue(field.balanced303() && !field.terraces29() && !field.surface302(), "Alpha30.4 removes fine bands and uses broad geological provinces");
var random = generator.starterContext().random();
var current = random.getSampler(field); var previous = random.getSampler(field.legacy301());
var context = SamplerContext.builder().enableCaches().build();
@@ -117,13 +117,13 @@ public final class Terraces29WorldGameTests {
if(a>=200){land++;if(Math.abs(a-b)<=2)retained++;}
highest=Math.max(highest,b); columns.add(List.of(x,z,a,b,c));
}
helper.assertTrue(highest<=358,"Natural summits leave room below the build ceiling");
helper.assertTrue(highest<=360,"Natural summits leave room below the build ceiling");
helper.assertTrue(retained>land*.25,"Each size retains substantial original uplands, rather than changing every slope");
sizes.add(Map.of("diameter",r*2,"columns",columns,"unchanged_uplands",retained,"uplands",land,"highest_sample",highest));
}
report.put("sizes",sizes);
report.put("passed", true);
Path path = Path.of("diagnostics/alpha303", "relief-" + generator.initialDiameter() + "-" + level.getSeed() + ".json");
Path path = Path.of("diagnostics/alpha304", "relief-" + generator.initialDiameter() + "-" + level.getSeed() + ".json");
Files.createDirectories(path.getParent());
Files.writeString(path, new GsonBuilder().setPrettyPrinting().create().toJson(report));
helper.succeed();
@@ -7,11 +7,16 @@ import net.minecraft.resources.Identifier;
/** Client visuals belonging only to the new floating-world dimension type. */
public final class SanctuarySky {
private static final Identifier FLOATING_WORLD = SanctuaryMod.id("sanctuary_384");
private static final Identifier TALL_FLOATING_WORLD = SanctuaryMod.id("sanctuary_640");
private SanctuarySky() {
}
public static boolean isFloatingWorld(ClientLevel level) {
return level.dimensionTypeRegistration().is(FLOATING_WORLD);
return isFloatingType(level.dimensionTypeRegistration());
}
public static boolean isFloatingType(net.minecraft.core.Holder<net.minecraft.world.level.dimension.DimensionType> type) {
return type.is(FLOATING_WORLD) || type.is(TALL_FLOATING_WORLD);
}
}
@@ -187,7 +187,7 @@ public class ExpansionChunkGenerator extends ChunkGenerator {
}
return column;
}
BlockState[] air = new BlockState[384];
BlockState[] air = new BlockState[getGenDepth()];
java.util.Arrays.fill(air, Blocks.AIR.defaultBlockState());
return new NoiseColumn(0, air);
}
@@ -0,0 +1,61 @@
package fr.koka.sanctuary.worldgen;
import net.minecraft.world.level.levelgen.densityfunction.DensitySampler;
/** Broad foothills around native geological provinces. No density threshold cuts their sides. */
public final class HighPlateaus304 {
private static final int SIZE = 65;
private final float[] foothills = new float[SIZE * SIZE];
private final double radius;
public HighPlateaus304(DensitySampler noise, double radius, long seed) {
this.radius = radius;
var original = new BalancedRelief303(noise, radius, seed);
float[] provinces = new float[SIZE * SIZE];
double step = radius / 32, width = Math.clamp(radius * .25, 64, 96);
for (int z = 0; z < SIZE; z++) for (int x = 0; x < SIZE; x++)
provinces[z * SIZE + x] = (float) original.province(
(int)Math.round((x - 32) * step), (int)Math.round((z - 32) * step));
int reach = (int)Math.ceil(width / step);
for (int z = 0; z < SIZE; z++) for (int x = 0; x < SIZE; x++) {
double value = provinces[z * SIZE + x];
for (int dz = -reach; dz <= reach; dz++) for (int dx = -reach; dx <= reach; dx++) {
int nx = x + dx, nz = z + dz;
if (nx < 0 || nz < 0 || nx >= SIZE || nz >= SIZE) continue;
double peak = provinces[nz * SIZE + nx];
if (peak < .9) continue;
value = Math.max(value, peak * smooth(1 - Math.hypot(dx, dz) * step / width));
}
foothills[z * SIZE + x] = (float)value;
}
}
public double province(int x, int z) {
double gx = Math.clamp((x / radius + 1) * 32, 0, 63.999999);
double gz = Math.clamp((z / radius + 1) * 32, 0, 63.999999);
int ix = (int)gx, iz = (int)gz;
double fx = gx - ix, fz = gz - iz;
double value = lerp(fz, lerp(fx, foothills[iz*SIZE+ix], foothills[iz*SIZE+ix+1]),
lerp(fx, foothills[(iz+1)*SIZE+ix], foothills[(iz+1)*SIZE+ix+1]));
// Keep arrival and the transit plateau; their transitions also have a physical width.
double minor = Math.max(20, radius * .08);
double ellipse = Math.hypot(x / (radius * .62), (z + radius * .25) / minor);
return value * smooth((Math.hypot(x,z) - 48) / 64)
* smooth((ellipse - 1) * minor / 32);
}
public static double sourceY(int y, double province) {
return BalancedRelief303.sourceY(y, province);
}
public static double roofErosion(int y, double regionalNoise) {
double roof = 357 + 2 * Math.tanh(regionalNoise * 2);
return 2 * smooth((y - (roof - 3)) / 6);
}
private static double lerp(double t, double a, double b) { return a + t * (b - a); }
public static double smooth(double t) {
t = Math.clamp(t, 0, 1);
return t * t * (3 - 2 * t);
}
}
@@ -269,7 +269,10 @@ public final class PopulationHydrology {
}
public static Plan create(long seed, Sampler sampler) {
return new Planner(seed, sampler).build();
return new Planner(seed, sampler, false).build();
}
public static Plan withHighPlateaus304(long seed, Sampler sampler) {
return new Planner(seed, sampler, true).build();
}
static Plan additional30(Map<Long, Cell> cells, List<Feature> features, List<Spill> spills, int columns, int samples) {
return new Plan(cells, features, List.of(), Set.of(), columns, samples,
@@ -285,6 +288,7 @@ public final class PopulationHydrology {
private static final class Planner {
private final long seed;
private final Sampler sampler;
private final boolean highPlateaus;
private final Map<Long, Column> columns = new HashMap<>();
private final Map<Long, Integer> coarse = new HashMap<>();
private final Map<Long, Cell> cells = new HashMap<>();
@@ -293,11 +297,14 @@ public final class PopulationHydrology {
private final Set<Position> springFlow = new HashSet<>();
private long samples;
Planner(long seed, Sampler sampler) { this.seed = seed; this.sampler = sampler; }
Planner(long seed, Sampler sampler, boolean highPlateaus) {
this.seed = seed; this.sampler = sampler; this.highPlateaus = highPlateaus;
}
Plan build() {
List<Candidate> candidates = candidates();
addRiver();
if (highPlateaus) addRiver(true);
addRiver(false);
int attempts = 0;
for (Candidate candidate : candidates) {
if (features.size() >= 4 || attempts++ >= 96) break;
@@ -305,7 +312,10 @@ public final class PopulationHydrology {
if (nearWater(center.x(), center.z(), 38)) continue;
Column column = column(center.x(), center.z());
if (column.top() < 64 || column.top() - column.solidBottom() < 7) continue;
Kind kind = features.size() < 2 ? Kind.LAKE : Kind.POND;
// An upper river must not consume the lake's slot merely by incrementing the feature count.
boolean lake = highPlateaus ? features.stream().noneMatch(f -> f.kind() == Kind.LAKE)
: features.size() < 2;
Kind kind = lake ? Kind.LAKE : Kind.POND;
int target = kind == Kind.LAKE ? 700 + (int) (random(seed, center.x(), center.z()) * 400)
: 100 + (int) (random(seed + 1, center.x(), center.z()) * 140);
// Prefer an existing low spot; a shallow cut is the fallback on level terrain.
@@ -331,8 +341,8 @@ public final class PopulationHydrology {
/** A connected catchment is excavated only in a thick natural plateau. The route is
* selected on the actual fissured density, not painted across missing rock. Its two
* terminal pools share the same retained waterline; a separate rock spring feeds it. */
private void addRiver() {
List<Integer> heights = coarse.values().stream().filter(y -> y >= 160).sorted().toList();
private void addRiver(boolean upper) {
List<Integer> heights = coarse.values().stream().filter(y -> upper ? y >= 330 : y >= 160 && (!highPlateaus || y < 330)).sorted().toList();
if (heights.isEmpty()) return;
Set<Integer> levels = new java.util.LinkedHashSet<>();
for (double quantile : new double[]{.55, .35, .75, .15, .90}) {
@@ -351,9 +361,9 @@ public final class PopulationHydrology {
}
for (int attempt = 0; attempt < 4 && allowed.size() > 100; attempt++) {
List<Point> route = longestRoute(allowed, level);
if (route.size() < 36) break;
if (route.size() < (upper ? 18 : 36)) break;
Point first = route.getFirst(), last = route.getLast();
if (Math.hypot(last.x() - first.x(), last.z() - first.z()) < 100) break;
if (Math.hypot(last.x() - first.x(), last.z() - first.z()) < (upper ? 52 : 100)) break;
List<Point> path = smoothRoute(route);
Set<Long> wet = new HashSet<>();
Set<Long> bad = new HashSet<>();
@@ -372,7 +382,7 @@ public final class PopulationHydrology {
boolean pools = true;
for (Point endpoint : List.of(first, last)) {
Set<Long> pool = growRiverPool(endpoint, level);
if (pool.size() < 90) { pools = false; break; }
if (pool.size() < (upper ? 40 : 90)) { pools = false; break; }
wet.addAll(pool);
}
if (!pools) { allowed.remove(key(first.x(), first.z())); allowed.remove(key(last.x(), last.z())); continue; }
@@ -421,6 +431,10 @@ public final class PopulationHydrology {
}
private boolean riverRock(int x, int z, int level) {
if (highPlateaus && !features.isEmpty()) {
for (int dx : new int[]{-4, 0, 4}) for (int dz : new int[]{-4, 0, 4})
if (cells.containsKey(key(x + dx, z + dz))) return false;
}
Column rock = column(x, z);
return rock.top() >= level && rock.top() <= level + 12 && rock.solidBottom() <= level - 8;
}
@@ -52,7 +52,8 @@ public final class PopulationHydrologyRuntime {
return sampler.sampleValue(SamplerContext.EMPTY_UNCACHED, (int) wx, y, (int) wz);
});
long seed = PopulationIslandDensity.regionSeed(state.seed(), region);
var water = PopulationHydrology.create(seed, memo).admitted();
var water = (current30(generator) ? PopulationHydrology.withHighPlateaus304(seed, memo)
: PopulationHydrology.create(seed, memo)).admitted();
if (current30(generator)) {
water = BankOutlets30.openBanks(seed, water, memo, 6);
}
@@ -57,7 +57,7 @@ public record PopulationIslandDensity(DensityFunction terrain, DensityFunction d
var ridges = geology28 ? ridges28.compileSampler(context) : null;
var erosion = geology28 ? erosion28.compileSampler(context) : null;
long seed = context.createRandom(SanctuaryMod.id("population_rifts_alpha10")).nextLong();
var balanced = balanced303 ? new BalancedRelief303(coarse, capacity.radius(), seed) : null;
var balanced = balanced303 ? new HighPlateaus304(coarse, capacity.radius(), seed) : null;
var layouts = new ConcurrentHashMap<IslandCapacity.Region, RiftShape.Layout>();
DensitySampler field = new DensitySampler() {
private float terrainAt(SamplerContext sample, double x, double y, double z) {
@@ -81,7 +81,7 @@ public record PopulationIslandDensity(DensityFunction terrain, DensityFunction d
float fineValue = fine.sampleValue(sample, x, localY, z);
float terrainValue;
double province = balanced != null ? balanced.province(x, z) : 0;
double mappedY = balanced != null ? BalancedRelief303.sourceY(y, province) : y;
double mappedY = balanced != null && province > 0 ? HighPlateaus304.sourceY(y, province) : y;
double weight = geology28 ? CavesRelief28.deformationWeight(x, y, z, capacity.radius()) : 0;
if (balanced != null) weight *= province;
else if (relief30) weight *= Relief30.influence(seed, x, z, capacity.radius());
@@ -103,7 +103,14 @@ public record PopulationIslandDensity(DensityFunction terrain, DensityFunction d
float natural = PopulationIslandShape.raisedDensity(x, y, z, terrainValue,
edge.sampleValue(sample, x, 0, z), coarseValue,
fineValue, bottom.sampleValue(sample, x, localY, z), 64, 384, capacity, mappedY - 64);
if (balanced != null) natural = Math.max(-1, natural - (float) BalancedRelief303.ceilingErosion(y));
if (balanced != null && province > 0 && y > 170) {
float base = PopulationIslandShape.raisedDensity(x, y, z,
raw.sampleValue(sample, x, localY, z), edge.sampleValue(sample, x, 0, z),
coarseValue, fineValue, bottom.sampleValue(sample, x, localY, z), 64, 384, capacity);
double roofNoise = coarse.sampleValue(sample, x / 2, 91, z / 2);
natural = Math.max(base, (float) Math.max(-1, natural
- HighPlateaus304.roofErosion(y, roofNoise)));
}
if (natural <= 0) return natural;
var region = capacity.regionAt(x, z);
var layout = layouts.computeIfAbsent(region, r -> RiftShape.layout(regionSeed(seed, r)));
@@ -178,6 +178,7 @@ public final class SanctuaryChunkGenerator extends ExpansionChunkGenerator {
}
@Override public SanctuaryBiomeSource source() { return (SanctuaryBiomeSource) super.source(); }
@Override protected MapCodec<SanctuaryChunkGenerator> codec() { return generation == 24 ? AERIAL24_CODEC : generation == 23 ? SECRETS23_CODEC : generation == 22 ? SELECTIVE22_CODEC : generation == 21 ? NETWORK21_CODEC : generation == 20 ? ARCHITECTURE20_CODEC : generation == 19 ? HERITAGE19_CODEC : generation == 17 ? RUINS17_CODEC : generation == 16 ? CITY16_CODEC : generation == 15 ? CITY15_CODEC : generation == 14 ? CITY_CODEC : CODEC; }
@Override public int getGenDepth() { return generation == 24 ? 640 : super.getGenDepth(); }
public SanctuaryChunkGenerator withInitialDiameter(int diameter) {
return new SanctuaryChunkGenerator(source().copy(), diameter, generation, experimentalStructures);
}
@@ -17,7 +17,7 @@ public final class WoodlandGroveGeometry {
}
public static boolean naturalFootprint(Sampler sampler, int x, int bedY, int z, int width) {
if (bedY < 32 || bedY > 350) return false;
if (bedY < 32 || bedY > 360) return false;
for (int dx = 0; dx < width; dx++) for (int dz = 0; dz < width; dz++) {
for (int dy = -2; dy <= 0; dy++) if (sampler.sample(x + dx, bedY + dy, z + dz) <= 0) return false;
if (sampler.sample(x + dx, bedY + 1, z + dz) > 0) return false;
@@ -0,0 +1,63 @@
{
"ambient_light": 0.0,
"attributes": {
"minecraft:audio/ambient_sounds": {
"mood": {
"block_search_extent": 8,
"offset": 2.0,
"sound": "minecraft:ambient.cave",
"tick_delay": 6000
}
},
"minecraft:audio/background_music": {
"creative": {
"max_delay": 24000,
"min_delay": 12000,
"sound": "minecraft:music.creative"
},
"default": {
"max_delay": 24000,
"min_delay": 12000,
"sound": "minecraft:music.game"
}
},
"minecraft:gameplay/bed_rule": {
"can_set_spawn": "always",
"can_sleep": "when_dark",
"error_message": {
"translate": "block.minecraft.bed.no_sleep"
}
},
"minecraft:gameplay/nether_portal_spawns_piglin": true,
"minecraft:gameplay/respawn_anchor_works": false,
"minecraft:gameplay/straw_bed_rule": {
"can_set_spawn": "never",
"can_sleep": "when_dark",
"destroy_on_leave": true,
"error_message": {
"translate": "block.minecraft.bed.no_sleep"
}
},
"minecraft:visual/ambient_light_color": "#0a0a0a",
"minecraft:visual/cloud_color": "#ccffffff",
"minecraft:visual/cloud_height": 352.33,
"minecraft:visual/fog_color": "#c0d8ff",
"minecraft:visual/sky_color": "#78a7ff"
},
"coordinate_scale": 1.0,
"default_clock": "minecraft:overworld",
"has_ceiling": false,
"has_ender_dragon_fight": false,
"has_skylight": true,
"height": 640,
"infiniburn": "#minecraft:infiniburn_overworld",
"logical_height": 640,
"min_y": 0,
"monster_spawn_block_light_limit": 0,
"monster_spawn_light_level": {
"type": "minecraft:uniform",
"max_inclusive": 7,
"min_inclusive": 0
},
"timelines": "#minecraft:in_overworld"
}
@@ -5,7 +5,7 @@
"legacy_random_source": true,
"material_rule": "sanctuary:unified_island",
"noise": {
"height": 384,
"height": 640,
"min_y": 0
},
"noise_router": {
@@ -5,7 +5,7 @@
"legacy_random_source": true,
"material_rule": "sanctuary:unified_island",
"noise": {
"height": 384,
"height": 640,
"min_y": 0
},
"noise_router": {
@@ -5,7 +5,7 @@
"legacy_random_source": true,
"material_rule": "sanctuary:unified_island",
"noise": {
"height": 384,
"height": 640,
"min_y": 0
},
"noise_router": {
@@ -1,7 +1,7 @@
{
"dimensions": {
"minecraft:overworld": {
"type": "sanctuary:sanctuary_384",
"type": "sanctuary:sanctuary_640",
"generator": {
"type": "sanctuary:island_v24",
"biome_source": {
+1 -1
View File
@@ -1,6 +1,6 @@
name = "Sanctuary"
author = "KOKA99CAB"
version = "0.1.0-alpha.30.3"
version = "0.1.0-alpha.30.4"
pack-format = "packwiz:1.1.0"
[index]