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
koka 076c9a6c16 feat: balance native plateaus and 3D mountains for alpha30.3
Build Sanctuary / build (push) Canceled after 0s
2026-09-12 13:31:08 +02:00
40 changed files with 869 additions and 65 deletions
+22
View File
@@ -1,5 +1,27 @@
# 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.
- Répartit de grandes zones de plateaux naturels et de montagnes déformées en 3D,
avec une échelle relative commune à Petit, Moyen et Grand.
- Étend les hauts reliefs vers Y360 sans changer la hauteur du monde ; conserve
les profondeurs, larrivée, les cours deau courbes et les infrastructures.
- Conserve un filtrage des fragments rocheux détachés jusque dans les sommets.
- Adapte la recherche aérienne aux plateaux disponibles : rejet grossier des
emprises trop creuses, contrôle de hauteur précoce et recherche autour de l’île.
- Corrige le veto de biome qui empêchait un sanctuaire minéral pourtant admis
de se poser dans les marais souterrains.
- Nouveau monde nécessaire pour tester le nouveau terrain ; aucun monde régénéré.
## 0.1.0-alpha.30.2
- Corniches locales dont la phase et lespacement suivent un bruit 3D, sur le relief global conservé.
+19 -8
View File
@@ -21,14 +21,25 @@ 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.2corniches et fragments
## Alpha.30.4massifs et ciel constructible
Les grandes masses et arches sont conservées. Une passe locale de corniches
irrégulières casse les pentes répétitives ; les petits composants rocheux
entièrement détachés sont filtrés dans la zone retouchée.
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 [la génération](docs/generation-alpha30.2.md)
et [les vérifications](docs/testing-alpha30.2.md).
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.
De larges provinces de relief font cohabiter les plateaux naturels issus de la
24 et les montagnes déformées en 3D, jusqu’à proximité de Y360. Leur répartition
est proportionnée aux trois tailles. Les petits fragments détachés restent filtrés.
Créer un **nouveau monde**. Voir [la génération](docs/generation-alpha30.3.md)
et [les vérifications](docs/testing-alpha30.3.md).
## Alpha.30.1 — rivières courbes et relief global
@@ -621,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
@@ -650,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).
+20
View File
@@ -1017,3 +1017,23 @@ arches, casser la régularité des pentes par des corniches localisées, et reti
les petits fragments détachés issus du champ de terrain retouché. Comparer les
mêmes volumes natifs avant/après ; contrôler les raccords aux frontières de chunks.
Voir [la génération](generation-alpha30.2.md) et [les essais](testing-alpha30.2.md).
### WG-35 — Plateaux naturels et grandes montagnes — alpha.30.3
Branche `codex/balanced-relief-alpha30-3`. Le rendu de la 30.2 est rejeté en jeu :
les métriques locales de marches ne constituaient pas une validation esthétique.
Retirer ce microrelief, retrouver des plateaux issus de la 24 et réserver une
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).
+59
View File
@@ -0,0 +1,59 @@
# Alpha.30.3 — plateaux et grands reliefs
## Choix retenu
Le créateur a rejeté la 30.2 après essai : la retouche fine produisait un aspect
rongé et trop bruité. Son nombre réduit de longues marches ne garantissait pas
un meilleur paysage. Cette version désactive `surface_302` et `terraces_29`.
La roche reste le champ volumétrique Minecraft de la base alpha.24, accompagné
des grottes et fractures adaptées en 28. Un bruit de grande longueur donde
sélectionne des provinces montagneuses ; il ne dessine ni cônes ni salles.
Les provinces sont échantillonnées dans un repère proportionnel au rayon :
Petit 512, Moyen 724 et Grand 1 024 conservent une distribution comparable,
avec des masses physiquement plus vastes lorsque l’île grandit.
Le seuil médian du champ intérieur conserve une grande part des plateaux ;
la transition vers les provinces hautes est continue. La bande de plateau
utile aux infrastructures reste protégée, ainsi que les 48 blocs autour du
centre. Aucun pavage périodique ou bruit supplémentaire à l’échelle du bloc.
Dans les provinces hautes, le domaine vertical s’étend progressivement
au-dessus de Y170, avec les déplacements signés sur trois axes de la 28.
Cela relève la roche existante et conserve sa structure volumétrique, ses
surplombs et ses vides. Une extinction douce sous Y360 laisse de la place
sous le plafond de construction Y384. La hauteur atteinte dépend du terrain
et de la graine ; aucun sommet nest forcé à une altitude identique.
Le filtre de connexité travaille sur le champ rocheux avant décoration :
composants de 32 blocs au plus, connexions par faces, coordonnées globales
même à cheval sur plusieurs chunks. Il ne crée aucun pilier ni roche de
remplissage. Les grandes masses flottantes naturelles et les îlots aériens
ne sont pas supprimés.
Les grottes sous Y170 conservent exactement leur densité précédente. Les
anciens cours deau courbes et leurs brèches de débordement, la traversée,
le sanctuaire minéral, les secrets et les quatre lieux aériens sont conservés.
Leurs recherches utilisent le terrain courant ; leurs emplacements peuvent
changer. Pas de retour de la montgolfière ou des rivières angulaires.
Le biome `sanctuary:swamp27_caves` rejoint les biomes autorisés des réseaux
secrets : un sanctuaire minéral déjà certifié ne doit plus être annulé au
stade du départ natif simplement parce quil se trouve dans ce marais.
La recherche des îlots refuse immédiatement les volumes trop hauts, dès le
premier obstacle incompatible, et filtre grossièrement les emprises au-dessus
de trop grands trous avant leur certification bloc par bloc. Après le secteur initial, les candidats parcourent tout le tour de l’île.
La recherche reste bornée à 64 candidats par site et 16 384 colonnes au total ;
la certification détaillée des sites acceptés est conservée.
## Essai
Minecraft 26.3-pre-2, mêmes dépendances et bouton natif Personnaliser.
Créer un **nouveau monde Sanctuary**. Le codec reste `sanctuary:island_v24`,
conformément au workflow de mondes de test jetables demandé par le créateur.
Les ressources des trois profils sont mises à jour : ne pas prolonger une
ancienne sauvegarde pour comparer des raccords. Cette livraison nouvre et
ne régénère aucune sauvegarde personnelle.
Les prévisualisations de densité servent à examiner les silhouettes ; elles
ne remplacent pas lessai visuel du paysage décoré dans le client Minecraft.
Voir [les validations réalisées](testing-alpha30.3.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)
+105
View File
@@ -0,0 +1,105 @@
# Vérification alpha.30.3
## Premier témoin natif — Mac
Monde Moyen 724, graine `0`, processus `68244` : 24/24 GameTests réussis.
La comparaison utilise les densités natives enregistrées, pas un terrain simulé.
La correction finale de bornage numérique à -1, sans effet sur les blocs solides,
a ensuite été incluse dans le build de livraison.
| Profil mesuré (même graine) | Hauts terrains proches de la base 24* | Plus haut échantillon |
|---|---:|---:|
| Petit 512 | 330 / 463 (71 %) | Y344 |
| Moyen 724 | 913 / 1 288 (71 %) | Y344 |
| Grand 1 024 | 1 930 / 2 853 (68 %) | Y344 |
*Colonnes échantillonnées tous les 12 blocs, ancienne surface au-dessus de Y200,
écart final de deux blocs au plus. La base de comparaison est le relief 24 repris
en 27. Cela ne mesure ni la surface constructible exacte ni une qualité esthétique.
Les trois profils sont compilés avec le même état aléatoire natif ; seul le
monde Moyen de ce premier essai est parcouru en chunks FULL.
Le volume local contrôlé passe de cinq petits composants détachés en 30.1 à
zéro en 30.3. Le parcours inversé des échantillons donne les mêmes appartenances.
La densité sous Y170 est identique à la 30.1 aux points vérifiés.
La traversée a 19 départs de structure, 29 chunks FULL visités, portée 272.
Le sanctuaire minéral est admis et son sol natif est inspecté. Les deux navires
et deux îlots sont générés ; les tests du Blocodex et des minerais réussissent.
La rivière courbe centrale est présente. Ce témoin ne fournit pas de nouvelle
brèche de berge admissible : il ne constitue donc pas une preuve supplémentaire
de cascade de surface sous ticks, malgré le succès du test des cellules deau.
Preuves ignorées : `build/alpha303-native-medium.log`,
`build/alpha303-evidence/medium-0/`, `build/alpha303-preview/`.
Les cartes et coupes visualisent la roche seule ; aucune capture client ni
validation esthétique du joueur nest revendiquée.
## Build de livraison et témoin Petit — Mac
`./gradlew check build -PsanctuaryTestDiameter=512 -PsanctuaryTestSeed=42
-PsanctuaryTransit29Tests=true -PsanctuaryAerial24Tests=true` : réussite,
24/24 tests natifs et contrôles JVM, 4 min 53 au total sur cette machine Mac.
Ce temps comprend compilation, anciens tests et inspections ; ce nest pas
une mesure du délai de chargement dun client.
Processus `69553`. Les 15 modules de traversée possèdent leurs départs natifs,
32 chunks FULL sont parcourus, portée 204. Le sanctuaire minéral apparaît
réellement dans le marais. Quatre sites aériens, quatre coffres, deux villageois,
une source de cascade sur un îlot ; leurs coffres sont ouverts et les habitants
déplacés dans ce monde jetable par le test. Aucun petit composant détaché dans
le volume contrôlé ; 1 536 échantillons restent identiques en ordre inversé.
Les trois profils sous la graine 42 conservent 60 %, 59 % et 58 % des hauts
terrains à deux blocs au plus de la base 24. Sommets échantillonnés :
Y344 / Y346 / Y346. Une brèche de bassin produit de leau sous ticks réels ;
la région centrale de ce témoin nadmet pas de rivière courbe.
Preuves : `build/alpha303-check-build.log`, `build/alpha303-evidence/small-42/`.
## Témoin Grand signalé par le créateur — Mac
Grand 1024, graine `-7228211907433324401`, processus `69750` : 24/24 tests
natifs réussis. La traversée compte 23 départs, portée 374, avec 52 chunks FULL
parcourus et des accès vérifiés. Localisation témoin : `-187,249,-72`.
Le sanctuaire minéral a un départ valide et un véritable sol central ;
les quatre lieux aériens génèrent leurs quatre coffres et deux marchands.
La rivière courbe centrale est présente ; huit cellules deau sont constatées
sous les ouvertures après propagation native. Le volume local passe de 12
petits composants détachés à zéro. Les1 536 points contrôlés en ordre inversé
restent identiques et les 7 436 échantillons profonds sont inchangés.
Sur cette graine, les plus hauts échantillons des profils 512/724/1024 atteignent
Y322 / Y344 / Y346. Laltitude nest pas forcée à 360 sur toutes les graines.
Preuves : `build/alpha303-native-large.log`, `build/alpha303-evidence/large-user/`.
Les fichiers `verified-run.json` désignent les rapports de chaque processus ;
les autres anciens diagnostics copiés ne comptent pas comme des essais 30.3.
## Défauts corrigés pendant la vérification
Petit 512, graine 42, révélait deux défauts distincts :
- La recherche aérienne épuisait ses colonnes sur des emprises trop hautes ou
trop creuses. Le rejet de hauteur est maintenant immédiat, lappui est filtré
grossièrement avant certification et les candidats de repli couvrent le tour
de l’île. Le plan témoin obtenu emploie 8 058 colonnes, avec quatre sites.
- La chambre minérale était admise en `sanctuary:swamp27_caves`, mais ce biome
manquait au tag `has_structure/forgotten_networks`. Le départ natif était donc
refusé. Le tag inclut désormais ce biome, sans contourner la certification.
Les premiers journaux en échec sont conservés sous `build/alpha303-small42-*failure.log`.
## Limites
Essais exécutés sur Mac ; aucun essai natif Windows par lagent. Les variations
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.2
pack_version=0.1.0-alpha.30.2
mod_version=0.1.0-alpha.30.4
pack_version=0.1.0-alpha.30.4
maven_group=fr.koka.sanctuary
+11
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.
@@ -674,3 +677,11 @@ tasks.register('surfaceRelief302Smoke', JavaExec) {
mainClass = 'fr.koka.sanctuary.worldgen.SurfaceRelief302Smoke'
}
tasks.named('check') { dependsOn('surfaceRelief302Smoke') }
tasks.register('balancedRelief303Smoke', JavaExec) {
group = 'verification'
dependsOn tasks.named('testClasses')
classpath = sourceSets.test.runtimeClasspath
mainClass = 'fr.koka.sanctuary.worldgen.BalancedRelief303Smoke'
}
tasks.named('check') { dependsOn('balancedRelief303Smoke') }
@@ -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,
@@ -16,13 +16,13 @@ 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 {
public void broadProvincesRestorePlateausAndRaiseNativeMasses(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.relief30() && !field.terraces29() && field.surface302(), "Alpha30.2 adds non-periodic local relief to the global field");
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();
@@ -31,25 +31,25 @@ public final class Terraces29WorldGameTests {
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 <= 140; y += 13) {
for (int y = 32; y <= 170; y += 13) {
helper.assertTrue(Float.floatToIntBits(current.sampleValue(context, x, y, z))
== Float.floatToIntBits(previous.sampleValue(context, x, y, z)), "Deep density is exactly alpha30.1");
lowerSamples++;
}
for (int y = 141; y <= 320; y++) {
for (int y = 171; y <= 380; 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 * 3L, "Local surface offsets retain most of the accepted rock volume");
helper.assertTrue(changed > 0, "The upper field now combines broad plateaus and raised rock");
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 >= 145; y--) {
for (int y = 365; y >= 145; 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 ? '#' : '.');
@@ -57,7 +57,7 @@ public final class Terraces29WorldGameTests {
}
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));
sections.put("z" + z, Map.of("before", before, "after", after, "min_x", -radius, "max_y", 365, "step", 1));
}
report.put("sections", sections);
// One block-resolution volume, chosen using the old field's surface transitions.
@@ -76,7 +76,7 @@ public final class Terraces29WorldGameTests {
if(score>bestScore){bestScore=score;bestX=cx;bestZ=cz;}
}
var oldVolume=new ArrayList<String>();var newVolume=new ArrayList<String>();
int size=64,minY=164,maxY=291;
int size=64,minY=171,maxY=362;
for(int y=minY;y<=maxY;y++)for(int z=bestZ-size/2;z<bestZ+size/2;z++){
var a=new StringBuilder();var b=new StringBuilder();
for(int x=bestX-size/2;x<bestX+size/2;x++){
@@ -97,8 +97,33 @@ public final class Terraces29WorldGameTests {
helper.assertTrue((current.sampleValue(reverseContext,x,y,z)>0)==expected,"Rock membership is independent of cache and reverse sample order");reversed++;
}
report.put("reverse_order_samples",reversed);
// Broad top views compare all three supported sizes under the same native noise seed.
var sizes = new ArrayList<Object>();
for (int players : new int[]{5,10,20}) {
var f = (PopulationIslandDensity) level.registryAccess().lookupOrThrow(Registries.DENSITY_FUNCTION)
.getOrThrow(ResourceKey.create(Registries.DENSITY_FUNCTION, SanctuaryMod.id("population_"+players))).value();
var now = random.getSampler(f); var old = random.getSampler(f.legacy27());
var global = random.getSampler(f.legacy301());
int r = (int)Math.round(f.capacity().radius());
var columns = new ArrayList<Object>(); int retained=0,land=0,highest=0;
for(int z=-r;z<=r;z+=12)for(int x=-r;x<=r;x+=12){
int a=0,b=0,c=0;
for(int y=380;y>=32;y-=2){
if(a==0 && old.sampleValue(context,x,y,z)>0)a=y;
if(b==0 && now.sampleValue(context,x,y,z)>0)b=y;
if(c==0 && global.sampleValue(context,x,y,z)>0)c=y;
if(a>0&&b>0&&c>0)break;
}
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<=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/alpha302", "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,48 @@
package fr.koka.sanctuary.worldgen;
import java.util.Arrays;
import net.minecraft.world.level.levelgen.densityfunction.*;
/** Broad geological provinces, sampled once in island-relative coordinates.
* The mask selects where the existing 3D rock field can rise; it never defines a summit shape. */
public final class BalancedRelief303 {
private static final int SIZE = 65;
private final float[] samples = new float[SIZE * SIZE];
private final double radius, low, high;
private final long seed;
public BalancedRelief303(DensitySampler noise, double radius, long seed) {
this.radius = radius; this.seed = seed;
float[] distribution = new float[SIZE * SIZE]; int count = 0;
for (int z = 0; z < SIZE; z++) for (int x = 0; x < SIZE; x++) {
float value = noise.sampleValue(SamplerContext.EMPTY_UNCACHED, x * 2 - 64, 87, z * 2 - 64);
samples[z * SIZE + x] = value;
if (Math.hypot(x - 32, z - 32) <= 26) distribution[count++] = value;
}
Arrays.sort(distribution, 0, count);
low = distribution[count / 2]; high = Math.max(low + .0001, distribution[(int)(count * .82)]);
}
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 n = lerp(fz, lerp(fx, samples[iz*SIZE+ix], samples[iz*SIZE+ix+1]),
lerp(fx, samples[(iz+1)*SIZE+ix], samples[(iz+1)*SIZE+ix+1]));
double distance = Math.hypot(x, z);
return smooth((n - low) / (high - low)) * smooth((distance - 48) / 40)
* (1 - smooth((distance / radius - .68) / .22))
* Relief30.influence(seed, x, z, radius);
}
/** Continuous, strictly increasing vertical domain: no bands, ledges or added fine noise. */
public static double sourceY(int y, double province) {
if (y <= 170 || province <= 0) return y;
double d = y - 170;
// The first 30 blocks blend into untouched lower caves with a matching first derivative.
return 170 + d / (1 + .95 * province * smooth(d / 30));
}
public static double ceilingErosion(int y) { return 2 * smooth((y - 340) / 36.0); }
private static double lerp(double t, double a, double b) { return a + t * (b - a); }
private static double smooth(double t) { t = Math.clamp(t, 0, 1); return t*t*(3-2*t); }
}
@@ -13,11 +13,16 @@ public final class DetachedRock302 implements DensitySampler {
private final DensitySampler input;
private final double radius;
private final long seed;
private final boolean broad;
public DetachedRock302(DensitySampler input, double radius, long seed) {
this.input = input; this.radius = radius; this.seed = seed;
this(input, radius, seed, false);
}
public DetachedRock302(DensitySampler input, double radius, long seed, boolean broad) {
this.input = input; this.radius = radius; this.seed = seed; this.broad = broad;
}
private boolean affected(int x, int y, int z) {
if (broad) return y > 170 && y < 376 && Math.hypot(x, z) > 48 && Math.hypot(x,z) < radius + 32;
return SurfaceRelief302.weight(x,y,z,radius) * Relief30.influence(seed,x,z,radius) > 0;
}
@Override public float sampleValue(SamplerContext context,int x,int y,int z) {
@@ -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);
}
@@ -13,7 +13,7 @@ public record PopulationIslandDensity(DensityFunction terrain, DensityFunction d
DensityFunction sculpt, DensityFunction detail, DensityFunction underside,
DensityFunction riftWarp, DensityFunction riftDetail, IslandCapacity capacity,
DensityFunction caves28, DensityFunction ridges28, DensityFunction erosion28, boolean geology28,
boolean terraces29, boolean relief30, boolean surface302) implements DensityFunction {
boolean terraces29, boolean relief30, boolean surface302, boolean balanced303) 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),
@@ -29,14 +29,15 @@ public record PopulationIslandDensity(DensityFunction terrain, DensityFunction d
Codec.BOOL.optionalFieldOf("geology_28", false).forGetter(PopulationIslandDensity::geology28),
Codec.BOOL.optionalFieldOf("terraces_29", false).forGetter(PopulationIslandDensity::terraces29),
Codec.BOOL.optionalFieldOf("relief_30", false).forGetter(PopulationIslandDensity::relief30),
Codec.BOOL.optionalFieldOf("surface_302", false).forGetter(PopulationIslandDensity::surface302)
Codec.BOOL.optionalFieldOf("surface_302", false).forGetter(PopulationIslandDensity::surface302),
Codec.BOOL.optionalFieldOf("balanced_303", false).forGetter(PopulationIslandDensity::balanced303)
).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, false, false, false);
DensityFunctions.zero(), DensityFunctions.zero(), DensityFunctions.zero(), false, false, false, false, false);
}
public PopulationIslandDensity(DensityFunction terrain, DensityFunction distortion,
@@ -44,7 +45,7 @@ public record PopulationIslandDensity(DensityFunction terrain, DensityFunction d
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, false, false);
caves28, ridges28, erosion28, geology28, false, false, false, false);
}
@Override public DensitySampler compileSampler(CompileContext context) {
@@ -56,6 +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 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) {
@@ -78,16 +80,19 @@ public record PopulationIslandDensity(DensityFunction terrain, DensityFunction d
float coarseValue = coarse.sampleValue(sample, x, localY, z);
float fineValue = fine.sampleValue(sample, x, localY, z);
float terrainValue;
double province = balanced != null ? balanced.province(x, z) : 0;
double mappedY = balanced != null && province > 0 ? HighPlateaus304.sourceY(y, province) : y;
double weight = geology28 ? CavesRelief28.deformationWeight(x, y, z, capacity.radius()) : 0;
if (relief30) weight *= Relief30.influence(seed, x, z, capacity.radius());
if (balanced != null) weight *= province;
else if (relief30) weight *= Relief30.influence(seed, x, z, capacity.radius());
double surfaceWeight = surface302 ? SurfaceRelief302.weight(x, y, z, capacity.radius())
* Relief30.influence(seed, x, z, capacity.radius()) : 0;
if (weight > 0 || surfaceWeight > 0) {
if (weight > 0 || surfaceWeight > 0 || mappedY != y) {
var shift = weight > 0 ? CavesRelief28.warp(weight,
erosion.sampleValue(sample, x + 173, y - 59, z - 89),
ridges.sampleValue(sample, x, y, z),
erosion.sampleValue(sample, x - 211, y + 137, z + 307)) : new CavesRelief28.Warp(0,0,0);
double sourceY = y - 64 - shift.y();
double sourceY = mappedY - 64 - shift.y();
if (surfaceWeight > 0) sourceY = SurfaceRelief302.sourceY(sourceY,surfaceWeight,
coarse.sampleValue(sample,x-233,y+419,z+157));
if (terraces29) sourceY = Terraces29.sourceY(sourceY, coarseValue, fineValue,
@@ -97,7 +102,15 @@ public record PopulationIslandDensity(DensityFunction terrain, DensityFunction d
// Keep the island envelope at actual coordinates, independent from displaced rock.
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);
fineValue, bottom.sampleValue(sample, x, localY, z), 64, 384, capacity, mappedY - 64);
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)));
@@ -116,7 +129,7 @@ public record PopulationIslandDensity(DensityFunction terrain, DensityFunction d
DensitySampler.sampleVolumeNaive(sample, buffer, volume, this);
}
};
return surface302 ? new DetachedRock302(field,capacity.radius(),seed) : field;
return surface302 || balanced303 ? new DetachedRock302(field,capacity.radius(),seed, balanced303) : field;
}
/** Native diagnostics can compile the exact accepted27 field without new adaptations. */
public PopulationIslandDensity legacy27() {
@@ -126,11 +139,11 @@ public record PopulationIslandDensity(DensityFunction terrain, DensityFunction d
/** 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, false, false);
capacity, caves28, ridges28, erosion28, geology28, false, false, false, false);
}
public PopulationIslandDensity legacy301() {
return new PopulationIslandDensity(terrain, distortion, sculpt, detail, underside, riftWarp, riftDetail,
capacity, caves28, ridges28, erosion28, geology28, terraces29, relief30, false);
capacity, caves28, ridges28, erosion28, geology28, terraces29, relief30, false, false);
}
public static long regionSeed(long seed, IslandCapacity.Region region) {
long value = seed ^ ((long) region.x() << 32) ^ (region.z() & 0xffffffffL) ^ 0xA10CA7E1L;
@@ -141,7 +154,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, terraces29, relief30, surface302);
rule.rewrite(caves28), rule.rewrite(ridges28), rule.rewrite(erosion28), geology28, terraces29, relief30, surface302, balanced303);
}
@Override public Interval range() { return Interval.of(-1, 1); }
@Override public int domainAxes() { return ALL_AXES; }
@@ -7,6 +7,12 @@ public final class PopulationIslandShape {
/** Generation v3. The natural field and lateral sculpture are sampled in their original frame. */
public static float raisedDensity(int x, int y, int z, float terrain, float distortion,
float sculpt, float detail, float underside, int offset, int height, IslandCapacity capacity) {
return raisedDensity(x, y, z, terrain, distortion, sculpt, detail, underside, offset, height, capacity, (double)y - offset);
}
/** A raised province relaxes only the old upper fade; lateral and lower coast cuts stay in place. */
public static float raisedDensity(int x, int y, int z, float terrain, float distortion,
float sculpt, float detail, float underside, int offset, int height, IslandCapacity capacity, double upperLocalY) {
double distance = Math.hypot((double) x, (double) z);
if (distance >= capacity.terrainLimit() || y <= 0 || y >= height) return -1.0F;
double localY = (double) y - offset;
@@ -21,7 +27,7 @@ public final class PopulationIslandShape {
erosion += 2.0 * lower * lower * lower * lower;
// Upper silhouette is exactly the old one translated by offset. Absolute-world fades are
// safety margins, outside the normal terrain, rather than the island's visible underside.
erosion += 2.0 * smoothstep((localY - 224.0) / 32.0);
erosion += 2.0 * smoothstep((upperLocalY - 224.0) / 32.0);
erosion += 2.0 * smoothstep((24.0 - y) / 24.0);
erosion += 2.0 * smoothstep((y - (height - 24.0)) / 24.0);
return bounded(terrain, erosion);
@@ -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;
@@ -14,7 +14,7 @@ import fr.koka.sanctuary.worldgen.aerial.AerialPlan24.Site;
/** Fixed work limits, not wall-clock-dependent draws. No chunk is requested or modified by this planner. */
public final class AerialPlanner24 {
public static final int MAX_ATTEMPTS_PER_SITE = 64;
public static final int MAX_COLUMNS = 8192;
public static final int MAX_COLUMNS = 16384;
public static final int ARRIVAL_RADIUS = 96;
public static final int MIN_ISLET_GAP = 16;
private AerialPlanner24() {}
@@ -58,11 +58,15 @@ public final class AerialPlanner24 {
int bodyHeight = merchant ? 18 : 20 + (int) Math.floorMod(mix(siteSeed), 16);
int height = bodyHeight + (merchant ? 0 : AerialPlan24.VEGETATION_HEADROOM);
Site accepted = null;
for (int attempt = 0; attempt < MAX_ATTEMPTS_PER_SITE; attempt++) {
candidates: for (int attempt = 0; attempt < MAX_ATTEMPTS_PER_SITE; attempt++) {
probe.candidate = ids[ordinal] + " attempt=" + attempt;
long draw = mix(siteSeed + attempt * 0x9e3779b97f4a7c15L);
// Keep the four remaining bearings and seeded shapes; the former first sector is simply free.
double angle = initialAngle + (ordinal + 1) * (Math.PI * 2 / 5) + (unit(draw) - .5) * .9
+ (attempt / 16) * .41;
// After the initial sector, cover the whole island instead of repeatedly inspecting
// the same mountain. First-choice locations and all geometry rules remain intact.
if (!merchant && attempt >= 4) angle = initialAngle + (attempt - 4) * 2.399963229728653;
int facing = (int) Math.floorMod(mix(draw), 4);
int worldWidth = facing % 2 == 0 ? width : depth, worldDepth = facing % 2 == 0 ? depth : width;
Shore shore;
@@ -85,13 +89,18 @@ public final class AerialPlanner24 {
cx = (int) Math.round(Math.cos(angle) * distance);
cz = (int) Math.round(Math.sin(angle) * distance);
int top = probe.surface(cx, cz);
if (top < 120 || probe.terrain.reserved(cx, cz, top, top + 3)) continue;
if (top < 120 || top + MIN_ISLET_GAP + 1 + height > 350
|| probe.terrain.reserved(cx, cz, top, top + 3)) continue;
shore = dryViewpoint(probe, cx, cz);
if (shore == null) continue;
int highest = top;
for (int ox = -worldWidth / 2; ox <= worldWidth / 2; ox += 4)
for (int oz = -worldDepth / 2; oz <= worldDepth / 2; oz += 4)
for (int oz = -worldDepth / 2; oz <= worldDepth / 2; oz += 4) {
highest = Math.max(highest, probe.highest(cx + ox, cz + oz));
// One obstruction is enough to refuse a candidate. Do not consume the
// remaining column budget on an island that already cannot fit vertically.
if (highest + MIN_ISLET_GAP + 1 + height > 350) continue candidates;
}
bottom = highest + MIN_ISLET_GAP + 1;
}
if (bottom < 32 || bottom + height > 350) continue;
@@ -104,19 +113,32 @@ public final class AerialPlanner24 {
} else {
// Certify every physical rock column, not only the coarse samples used to
// choose a height. This may raise the whole unchanged island, never the ground.
// A coarse footprint rejects candidates mostly over holes before the full
// per-block certification. Matching the final75% rule avoids exhausting the
// shared budget on the same recessed coast in a small island.
int coarseCount = 0, coarseSupported = 0;
for (int x = 0; x < width; x += 4) for (int z = 0; z < depth; z += 4) {
if (!isletColumn(site, x, z)) continue;
coarseCount++;
if (probe.surface(site.worldX(x,z),site.worldZ(x,z)) >= 120) coarseSupported++;
}
if (coarseSupported * 4 < coarseCount * 3) continue;
int highest = 0, supported = 0, count = 0;
for (int x = 0; x < width; x++) for (int z = 0; z < depth; z++) {
if (!isletColumn(site, x, z)) continue;
int wx = site.worldX(x, z), wz = site.worldZ(x, z);
highest = Math.max(highest, probe.highest(wx, wz));
if (highest + MIN_ISLET_GAP + 1 + height > 350) continue candidates;
if (probe.surface(wx, wz) >= 120) supported++;
count++;
}
if (supported * 4 < count * 3) continue;
if (site.waterfall()) {
var outlet = site.waterfallClaimBounds();
for (int x = outlet.minX(); x <= outlet.maxX(); x++) for (int z = outlet.minZ(); z <= outlet.maxZ(); z++)
highest = Math.max(highest, probe.highest(x, z));
for (int x = outlet.minX(); x <= outlet.maxX(); x++) for (int z = outlet.minZ(); z <= outlet.maxZ(); z++) {
highest = Math.max(highest, probe.highest(x, z));
if (highest + MIN_ISLET_GAP + 1 + height > 350) continue candidates;
}
}
bottom = Math.max(bottom, highest + MIN_ISLET_GAP + 1);
if (bottom + height > 350) continue;
@@ -217,13 +239,14 @@ public final class AerialPlanner24 {
}
private static final class Probe {
final Terrain terrain; final int radius;
String candidate = "unassigned";
final Map<Long, NoiseColumn> columns = new HashMap<>();
Probe(Terrain terrain, int radius) { this.terrain = terrain; this.radius = radius; }
NoiseColumn column(int x, int z) {
if (Math.hypot(x, z) >= radius) return null;
long key = ((long) x << 32) ^ (z & 0xffffffffL);
var present = columns.get(key); if (present != null) return present;
if (columns.size() >= MAX_COLUMNS) throw new IllegalStateException("aerial_sites_unavailable: terrain column budget exhausted");
if (columns.size() >= MAX_COLUMNS) throw new IllegalStateException("aerial_sites_unavailable: terrain column budget exhausted at " + candidate);
var result = terrain.column(x, z);
if (result == null) throw new IllegalStateException("Aerial terrain query returned no column");
columns.put(key, result); return result;
@@ -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"
}
@@ -50,6 +50,7 @@
"sanctuary:woodland_dry_woodland",
"sanctuary:woodland_oak_forest",
"sanctuary:woodland_rocky_heath",
"sanctuary:woodland_sulfur_depths"
"sanctuary:woodland_sulfur_depths",
"sanctuary:swamp27_caves"
]
}
@@ -66,5 +66,6 @@
},
"terraces_29": false,
"relief_30": true,
"surface_302": true
"surface_302": false,
"balanced_303": true
}
@@ -66,5 +66,6 @@
},
"terraces_29": false,
"relief_30": true,
"surface_302": true
"surface_302": false,
"balanced_303": true
}
@@ -66,5 +66,6 @@
},
"terraces_29": false,
"relief_30": true,
"surface_302": true
"surface_302": false,
"balanced_303": true
}
@@ -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": {
@@ -0,0 +1,38 @@
package fr.koka.sanctuary.worldgen;
import net.minecraft.world.level.levelgen.densityfunction.*;
public final class BalancedRelief303Smoke {
private static void require(boolean condition,String message){if(!condition)throw new AssertionError(message);}
public static void main(String[] args){
DensitySampler noise=new DensitySampler(){
@Override public float sampleValue(SamplerContext c,int x,int y,int z){return (float)(Math.sin(x*.05)+Math.cos(z*.04));}
@Override public void sampleVolume(SamplerContext c,DensityBuffer b,DensityVolume v){DensitySampler.sampleVolumeNaive(c,b,v,this);}
};
for(int radius:new int[]{256,362,512}){
var p=new BalancedRelief303(noise,radius,0);int flat=0,mountain=0,n=0;
for(int z=-radius;z<=radius;z+=8)for(int x=-radius;x<=radius;x+=8){
if(Math.hypot(x,z)>radius*.8)continue;
double w=p.province(x,z);if(w==0)flat++;if(w>.5)mountain++;n++;
if(Math.hypot(x,z)<=48)require(w==0,"Natural arrival remains untouched");
}
require(flat>n*.4 && mountain>n*.05,"Every size combines broad natural plateaus and mountains");
}
for(double w=0;w<=1;w+=.05){
double previous=Double.NEGATIVE_INFINITY;
for(int y=0;y<=384;y++){
double now=BalancedRelief303.sourceY(y,w);
require(now>previous,"Vertical mapping cannot fold rock into repeated layers");previous=now;
if(y<=170||w==0)require(now==y,"Zero mountain influence and lower caves preserve original coordinates");
}
}
require(BalancedRelief303.ceilingErosion(358)>=1,"Even fully dense rock fades before Y360");
// New fragment cleanup also covers raised summits, outside the former302 height range.
DensitySampler singleton=new DensitySampler(){
@Override public float sampleValue(SamplerContext c,int x,int y,int z){return x==96&&y==355&&z==100?1:-1;}
@Override public void sampleVolume(SamplerContext c,DensityBuffer b,DensityVolume v){DensitySampler.sampleVolumeNaive(c,b,v,this);}
};
require(new DetachedRock302(singleton,362,0,true).sampleValue(SamplerContext.EMPTY_UNCACHED,96,355,100)<0,"A detached summit block is removed");
System.out.println("BalancedRelief303Smoke: scale balance, untouched arrival/depths, monotonic domains, ceiling and detached summit passed");
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
name = "Sanctuary"
author = "KOKA99CAB"
version = "0.1.0-alpha.30.2"
version = "0.1.0-alpha.30.4"
pack-format = "packwiz:1.1.0"
[index]