Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e400f1e2c1 | ||
|
|
2dc70fe905 | ||
|
|
a49dfa5337 | ||
|
|
076c9a6c16 | ||
|
|
2a5b4cb51b | ||
|
|
c5381b939a |
@@ -1,5 +1,34 @@
|
||||
# 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 l’arè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, l’arrivée, les cours d’eau 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 l’espacement suivent un bruit 3D, sur le relief global conservé.
|
||||
- Filtrage borné des petits composants rocheux détachés dans la zone retouchée, indépendant des frontières de chunks.
|
||||
- Rivières courbes, tunnels, sanctuaire minéral et lieux aériens conservés ; nouveau monde pour l’essai.
|
||||
- Minecraft 26.3-pre-2 et les dépendances restent inchangés. Voir `docs/testing-alpha30.2.md` pour les validations effectivement réalisées.
|
||||
|
||||
## 0.1.0-alpha.30.1 — 2026-09-12
|
||||
|
||||
Retrait des rivières sur grille de la 30 ; brèches de cascade sur les anciennes
|
||||
|
||||
@@ -21,6 +21,26 @@ 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.
|
||||
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
|
||||
|
||||
Les rivières angulaires de la 30 sont retirées. Les anciens cours d’eau et
|
||||
@@ -612,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
|
||||
@@ -641,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).
|
||||
|
||||
@@ -1009,3 +1009,31 @@ retrouver la déformation globale de la 28 avec une zone restreinte de plateau,
|
||||
et ouvrir les berges des anciens cours d’eau vers les espaces vides proches.
|
||||
Conserver les recherches de traversées et de sanctuaire améliorées en 30.
|
||||
Voir [le correctif](generation-alpha30.1.md) et [les essais](testing-alpha30.1.md).
|
||||
|
||||
### WG-34 — Corniches irrégulières et fragments — alpha.30.2
|
||||
|
||||
Branche `codex/irregular-relief-alpha30-2`. Conserver les grandes masses et
|
||||
arches, casser la régularité des pentes par des corniches localisées, et retirer
|
||||
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 d’eau 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 l’espace constructible jusqu’à
|
||||
Y639. Stabiliser un seul relief avant d’ajouter des personnalisations.
|
||||
L’arè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).
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
# Alpha.30.2 — corniches irrégulières et petits fragments
|
||||
|
||||
Retour visuel du 12 septembre 2026 : conserver les grandes masses et les arches
|
||||
acceptées, mais réduire leurs contours trop réguliers et les longues pentes
|
||||
en marches répétées. Le rendu des captures sert de contre-exemple ; il ne
|
||||
s’agit pas de remplacer les massifs par de nouvelles formes prédéfinies.
|
||||
|
||||
## Passe locale
|
||||
|
||||
Le relief global de la 28/30.1 reste actif, ainsi que sa zone limitée de plateau.
|
||||
Une adaptation locale du point d’échantillonnage vertical crée de courtes
|
||||
corniches : leur phase et leur espacement suivent un bruit 3D existant, à
|
||||
l’échelle de quelques dizaines de blocs. Les couches ne partagent pas un même
|
||||
niveau horizontal à travers toute l’île. Le déplacement supplémentaire du
|
||||
point d’échantillonnage reste borné à **2,25 blocs**.
|
||||
|
||||
La passe s’atténue sous Y188 et disparaît sous Y140, près de l’arrivée, dans le
|
||||
cœur de la bande de plateau, à l’extrémité du contour et au-dessus du terrain.
|
||||
Les volumes profonds et les paramètres des grottes natives ne sont pas changés.
|
||||
Les rivières courbes, sorties de berge et leurs écoulements gardent leur
|
||||
algorithme. Comme pour toute modification de densité, la recherche d’un lieu
|
||||
compatible peut aboutir à des coordonnées différentes sur une même graine.
|
||||
|
||||
## Petits blocs flottants
|
||||
|
||||
Une recherche de connexion dans le **champ naturel avant décoration** retire
|
||||
les composants détachés de **32 blocs au maximum** dans la zone retouchée.
|
||||
Elle s’arrête dès qu’elle rejoint 33 blocs connectés ou la roche conservée hors
|
||||
retouche. Les six faces servent de voisinage ; les diagonales ne suffisent pas.
|
||||
Les coordonnées sont globales : les bords de chunks ne coupent pas une attache.
|
||||
|
||||
Ce contrôle ne remplit aucun trou, ne rajoute aucun pilier, ne visite aucune
|
||||
sauvegarde personnelle et ne retire pas les îlots aériens. Il précède la pose
|
||||
ultérieure des arbres, structures et fluides. Il ne constitue pas une promesse
|
||||
qu’aucun élément flottant de quelque origine que ce soit ne puisse exister.
|
||||
Le cache est borné, propre au thread et invalidé selon le sampler et son contexte,
|
||||
avec des références faibles aux identités de monde.
|
||||
|
||||
## Livraison
|
||||
|
||||
Version mod et pack **0.1.0-alpha.30.2**, branche
|
||||
`codex/irregular-relief-alpha30-2`. Nouveau champ de codec optionnel
|
||||
`surface_302`, faux lorsqu’il est absent. Activé dans les trois profils actuels
|
||||
512 / 724 / 1 024 ; Moyen reste le choix par défaut. Le prototype garde
|
||||
`sanctuary:island_v24`, Minecraft 26.3-pre-2 et le bouton natif Personnaliser.
|
||||
**Créer un nouveau monde pour essayer le rendu** ; aucune régénération des
|
||||
sauvegardes personnelles. Pas de nouvelle interface nécessitant une traduction.
|
||||
|
||||
Les vérifications et limites sont dans [les essais](testing-alpha30.2.md).
|
||||
La publication et la synchronisation suivent [packwiz](packwiz.md).
|
||||
@@ -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 d’onde
|
||||
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 n’est 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 d’eau 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 qu’il 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 n’ouvre et
|
||||
ne régénère aucune sauvegarde personnelle.
|
||||
|
||||
Les prévisualisations de densité servent à examiner les silhouettes ; elles
|
||||
ne remplacent pas l’essai visuel du paysage décoré dans le client Minecraft.
|
||||
Voir [les validations réalisées](testing-alpha30.3.md).
|
||||
@@ -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 Y354–359, 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 d’appui et d’espace pour les arbres.
|
||||
|
||||
L’hydrologie cherche un cours d’eau supplémentaire sur les hauts plateaux,
|
||||
puis conserve la recherche d’une 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. L’arène du destin est
|
||||
uniquement une idée pour un futur défi en altitude : aucune plateforme,
|
||||
panneau ni arène n’est 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 n’est 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).
|
||||
+102
@@ -13,6 +13,108 @@ 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.4 publiée — 12 septembre 2026
|
||||
|
||||
La [release alpha.30.4](https://git.botsu.net/koka/sanctuary-beta/releases/tag/v0.1.0-alpha.30.4)
|
||||
est publiée depuis `2dc70fe9051ab29996f1047bee6f15b6048910bb`, branche
|
||||
`codex/high-plateaus-alpha30-4`. Elle élargit les abords des massifs, conserve
|
||||
les plateaux bas et cherche des rivières sur les replats supérieurs. Le monde
|
||||
est constructible sur Y0..639 ; terrain sous Y360 et nuages inchangés.
|
||||
Les falaises sont conservées. Certaines faces inclinées restent en marches :
|
||||
leur disparition complète n'est pas revendiquée.
|
||||
|
||||
`check build` et `assemblePack` réussissent. Les mondes Moyen 0 et Grand
|
||||
`-7228211907433324401` passent chacun 24 tests ; le Grand est rechargé dans
|
||||
un second processus, 24/24, avec maintien du bloc à Y639 et des modifications
|
||||
faites aux satellites. Le client natif valide les menus, le Blocodex et le ciel,
|
||||
et fournit deux captures réelles. Voir [le terrain](generation-alpha30.4.md)
|
||||
et [les preuves et limites](testing-alpha30.4.md).
|
||||
|
||||
Le [MRpack alpha.30.4](https://git.botsu.net/koka/sanctuary-beta/releases/download/v0.1.0-alpha.30.4/Sanctuary-0.1.0-alpha.30.4.mrpack),
|
||||
le JAR et le ZIP d'amorçage sont vérifiés localement puis par téléchargement public.
|
||||
Canal packwiz : `664b0c687647ef895c0ffd2b26fa28d7edf1bd34`.
|
||||
Minecraft 26.3-pre-2, Loader 0.19.5 et Fabric API 0.160.0+26.3 restent inchangés.
|
||||
|
||||
Deux synchronisations isolées, puis deux dans la même instance **Sanctuary Beta
|
||||
sur Mac**, jeu fermé, réussissent. Un seul JAR Sanctuary 30.4 est actif ; les
|
||||
**925 fichiers personnels et réglages** suivis conservent leurs empreintes.
|
||||
Le dossier reste `Sanctuary-0.1.0-alpha.1`. Sauvegarde ciblée :
|
||||
`sanctuary-backups/before-0.1.0-alpha.30.4/`.
|
||||
|
||||
SHA-256 JAR : `9f03c1ac7c2adc085f2df1b54abb3903c8832b68df1d19ba530594ea97bf1a92`.
|
||||
SHA-256 MRpack : `8342a4036360416b291b82ef01ec84b273673dfdf7899fdde9c2de7b84bffc79`.
|
||||
Reçus : `build/alpha304-delivery-public.json`, `build/alpha304-isolated-validation.json`,
|
||||
`build/alpha304-prism-validation.json`.
|
||||
|
||||
Créer un **nouveau monde**. Aucun essai ou déploiement Windows natif, ni
|
||||
validation esthétique par le créateur, n'est revendiqué.
|
||||
|
||||
## 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 d’amorç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 d’instance 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)
|
||||
est publiée depuis **`c5381b939ad548511455f5251b51443dba4bf8d5`**, branche
|
||||
`codex/irregular-relief-alpha30-2`. Elle conserve les grandes masses et les
|
||||
arches, ajoute des corniches locales irrégulières et filtre les petits
|
||||
composants rocheux détachés dans la zone retouchée. Voir
|
||||
[la génération](generation-alpha30.2.md) et [les vérifications](testing-alpha30.2.md).
|
||||
|
||||
`check build` et `assemblePack` réussissent sur Mac. Les **24 GameTests** du
|
||||
Moyen 0 et les **24 GameTests** du Grand `-7228211907433324401` passent ; les
|
||||
volumes natifs, l’ordre d’échantillonnage, les cascades et les traversées sont
|
||||
contrôlés. Le rendu complet avec végétation reste à apprécier en jeu.
|
||||
|
||||
Le [MRpack alpha.30.2](https://git.botsu.net/koka/sanctuary-beta/releases/download/v0.1.0-alpha.30.2/Sanctuary-0.1.0-alpha.30.2.mrpack),
|
||||
le JAR et le ZIP d’amorçage sont vérifiés localement puis par téléchargement
|
||||
public. Le canal packwiz est au commit
|
||||
**`b9eac60a25d207540efa5a9f2d2d2087b41c86f4`**.
|
||||
Minecraft 26.3-pre-2, Fabric Loader 0.19.5 et Fabric API 0.160.0+26.3 sont conservés.
|
||||
|
||||
Deux synchronisations isolées, puis deux dans la même instance **Sanctuary Beta**
|
||||
sur Mac, réussissent, jeu fermé. Un seul JAR Sanctuary alpha.30.2 est actif ;
|
||||
les empreintes des **925 fichiers personnels et réglages suivis** sont conservées.
|
||||
Sauvegarde ciblée : `sanctuary-backups/before-0.1.0-alpha.30.2/`.
|
||||
|
||||
SHA-256 JAR : `a11a47f08b052703892fd9a705636863f0c9313e9101641538c1b6f37ff2873a`.
|
||||
SHA-256 MRpack : `f80cd65e10b66851a13e21fa878101c198bec3e115c26676981dc1275c735bfa`.
|
||||
Reçus : `build/alpha302-delivery-public.json`,
|
||||
`build/alpha302-isolated-validation.json`, `build/alpha302-prism-validation.json`.
|
||||
|
||||
Créer un **nouveau monde**. Témoin simple : **Moyen 724, graine 0**.
|
||||
Aucun essai Windows natif ni mise à jour de l’instance Windows n’est revendiqué.
|
||||
|
||||
## Alpha.30.1 publiée — 12 septembre 2026
|
||||
|
||||
La [release alpha.30.1](https://git.botsu.net/koka/sanctuary-beta/releases/tag/v0.1.0-alpha.30.1)
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
# Alpha.30.2 — vérifications et limites
|
||||
|
||||
Essais sur **Mac**, le 12 septembre 2026, dans le serveur de développement.
|
||||
Aucun essai Windows natif de ce correctif n’est revendiqué. Les comparaisons
|
||||
visuelles sont des volumes natifs de roche, sans textures, arbres ou structures,
|
||||
et ne remplacent pas l’essai du rendu complet dans Minecraft.
|
||||
|
||||
## Méthode
|
||||
|
||||
Le témoin principal est **Moyen 724, graine 0**. Le test compare le champ
|
||||
30.1 au champ retouché aux mêmes coordonnées. Il exporte deux coupes et un
|
||||
volume de **64 × 128 × 64 blocs**, choisi uniquement selon les transitions du
|
||||
champ précédent. Le choix du volume ne recherche pas le meilleur résultat de
|
||||
la nouvelle passe. La profondeur sous Y140 et l’arrivée restent inchangées.
|
||||
|
||||
Le volume permet de compter les composants de roche connectés par leurs six
|
||||
faces. Les composants touchant ses plans de coupe sont exclus de l’inventaire
|
||||
des fragments, car leur attache peut continuer hors du volume. Les longues
|
||||
marches sont comptées sur les surfaces supérieures complètes, le long des deux
|
||||
axes : suites d’au moins six différences de hauteur identiques de ±1 bloc.
|
||||
Ces mesures portent sur cet échantillon, pas sur toutes les graines du jeu.
|
||||
|
||||
Deux essais de déplacement plus simple ont été écartés avant publication : le
|
||||
premier introduisait trop de fragments, le second ne réduisait pas les longues
|
||||
marches du témoin. Les aperçus intermédiaires restent dans les dossiers ignorés
|
||||
`build/alpha302-preview-first-pass/` et `build/alpha302-preview-second-pass/`.
|
||||
|
||||
## Contrôle ciblé des fragments
|
||||
|
||||
`surfaceRelief302Smoke` vérifie les petits amas à cheval sur un bord de chunk,
|
||||
la conservation des corps connectés plus grands, l’absence de remplissage du
|
||||
vide, l’isolation des caches entre deux samplers et la limite de déplacement
|
||||
local. Le filtre s’applique au champ de roche retouché, avant les décorations ;
|
||||
il ne modifie pas les constructions ni les sauvegardes personnelles.
|
||||
|
||||
## Témoin Moyen 724, graine 0
|
||||
|
||||
Le processus **67135** passe les **24/24 GameTests** ; `check build` réussit
|
||||
en **5 min 18 s**. Reçus dans `build/alpha302-evidence/medium-0/`.
|
||||
|
||||
Dans le volume natif choisi (**X-154, Y164, Z-218**, 64 × 128 × 64), **96,72 %**
|
||||
des positions restent identiques. La quantité de roche passe de **174 157 à
|
||||
173 881 blocs**, soit une différence de **-0,16 %**. Les pas appartenant aux
|
||||
longues suites de marches passent de **424 à 81**. Le comptage indépendant
|
||||
observe **0 petit composant isolé de huit blocs ou moins**, contre cinq
|
||||
composants réunissant six blocs avant la passe. L’analyse ne compte pas les
|
||||
composants coupés par la frontière du volume. Le filtre lui-même traite les
|
||||
composants de 32 blocs ou moins.
|
||||
|
||||
Deux sorties de l’ancienne rivière sont vérifiées sous ticks réels :
|
||||
**-105 246 -65** et **-24 246 -45**. La traversée possède **19 départs natifs**,
|
||||
**29 chunks FULL** contrôlés, **272 blocs** entre ses accès et **678 pas** dans
|
||||
le parcours testé. Entrées : **-136 234 82** et **136 236 82**. Le sanctuaire
|
||||
possède son départ et son sol central ; les quatre satellites et une mine
|
||||
vanilla sont également observés dans leurs tests respectifs.
|
||||
|
||||
Les mesures de planification sont distinctes du temps de rendu client : la
|
||||
région hydrologique centrale prend **32,1 s**, et la recherche de traversée,
|
||||
qui inclut la préparation de son terrain, **40,8 s**. Ces durées ne s’ajoutent
|
||||
pas simplement. Le filtre a un coût ; cette livraison ne revendique pas un
|
||||
gain de performance. Le test d’export exhaustif du volume bloque ponctuellement
|
||||
le serveur de test ; cet export n’est pas activé dans le mod livré.
|
||||
|
||||
## Témoin Grand 1 024, graine -7228211907433324401
|
||||
|
||||
Le processus **67379** passe les **24/24 GameTests** en **4 min 23 s** pour la
|
||||
commande complète. Le volume natif contient **0 composant détaché de 32 blocs
|
||||
ou moins**, contre **12 avant**. Le test exige de ne pas augmenter ce nombre ;
|
||||
**1 024 points** rééchantillonnés dans l’ordre inverse, avec un autre contexte de
|
||||
cache, conservent le même état roche/air.
|
||||
|
||||
La traversée est réellement posée : **21 départs**, **48 chunks FULL**,
|
||||
**340 blocs entre les accès** et **574 pas** sur le parcours testé. Entrées :
|
||||
**192 238 -195** et **192 224 145**. Le sanctuaire et les quatre satellites
|
||||
passent leurs contrôles. La région centrale admet ici une rivière courbe ;
|
||||
l’écoulement des brèches est contrôlé sous ticks réels dans huit chunks FULL.
|
||||
|
||||
Reçus : `build/alpha302-evidence/large-user/`. Les deux commandes natives
|
||||
activent `sanctuaryAerial24Tests` et `sanctuaryTransit29Tests` ; le témoin Moyen
|
||||
exige aussi `sanctuaryRequireCurvedRiverBank=true`. Les trois tailles conservent
|
||||
leurs profils et le même filtre. Petit n’a pas fait l’objet d’une nouvelle
|
||||
visite native complète dans cette livraison. Les sauvegardes personnelles
|
||||
ne sont pas régénérées ; les scénarios natifs utilisent des mondes jetables.
|
||||
|
||||
Les reçus de distribution sont consignés dans [packwiz](packwiz.md).
|
||||
|
||||
## Distribution
|
||||
|
||||
`assemblePack` passe après la vérification complète ; `:sanctuary:check` est
|
||||
exclu de cette seconde commande pour ne pas répéter la suite inchangée. Le JAR,
|
||||
le MRpack et le ZIP d’amorçage sont vérifiés localement : versions, index,
|
||||
empreintes et contenu concordent. Reçus : `build/alpha302-artifacts.json` et
|
||||
`build/alpha302-delivery-local.json`. Les reçus publics et la mise à jour Prism
|
||||
sont consignés dans [Distribution](packwiz.md).
|
||||
|
||||
La release est publiée et ses trois artefacts sont revérifiés par téléchargement.
|
||||
Deux synchronisations isolées puis deux synchronisations de la même instance
|
||||
Prism sur Mac réussissent, jeu fermé. Les **925 fichiers personnels et réglages
|
||||
suivis** gardent leurs empreintes. Les commits et sommes sont consignés dans
|
||||
[packwiz](packwiz.md#alpha302-publiée--12-septembre-2026).
|
||||
@@ -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 d’eau.
|
||||
|
||||
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 n’est 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 n’est pas
|
||||
une mesure du délai de chargement d’un 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 l’eau sous ticks réels ;
|
||||
la région centrale de ce témoin n’admet 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 d’eau 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. L’altitude n’est 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, l’appui 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 l’agent. Les variations
|
||||
de relief exigent un nouveau monde ; aucun monde personnel n’est régénéré.
|
||||
Le filtrage local de connexité n’est 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).
|
||||
@@ -0,0 +1,92 @@
|
||||
# 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 téléchargements publics sont identiques aux artefacts locaux. Deux
|
||||
synchronisations isolées, puis deux dans la même instance Prism Mac, jeu fermé,
|
||||
réussissent. Les 925 fichiers personnels et réglages suivis gardent leurs
|
||||
empreintes. Reçus et empreintes 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
@@ -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.1
|
||||
pack_version=0.1.0-alpha.30.1
|
||||
mod_version=0.1.0-alpha.30.4
|
||||
pack_version=0.1.0-alpha.30.4
|
||||
maven_group=fr.koka.sanctuary
|
||||
|
||||
@@ -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.
|
||||
@@ -666,3 +669,19 @@ tasks.register('river30Smoke', JavaExec) {
|
||||
mainClass = 'fr.koka.sanctuary.worldgen.River30Smoke'
|
||||
}
|
||||
tasks.named('check') { dependsOn('river30Smoke') }
|
||||
|
||||
tasks.register('surfaceRelief302Smoke', JavaExec) {
|
||||
group = 'verification'
|
||||
dependsOn tasks.named('testClasses')
|
||||
classpath = sourceSets.test.runtimeClasspath
|
||||
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') }
|
||||
|
||||
+62
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -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);
|
||||
}
|
||||
|
||||
+7
@@ -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");
|
||||
}
|
||||
|
||||
+17
@@ -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)),
|
||||
|
||||
+4
-2
@@ -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,
|
||||
|
||||
+93
-11
@@ -16,40 +16,40 @@ 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(), "Alpha30.1 restores global deformation except a narrow plateau");
|
||||
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.legacy28());
|
||||
var current = random.getSampler(field); var previous = random.getSampler(field.legacy301());
|
||||
var context = SamplerContext.builder().enableCaches().build();
|
||||
var report = new LinkedHashMap<String, Object>();
|
||||
report.put("seed", level.getSeed()); report.put("diameter", generator.initialDiameter());
|
||||
report.put("process_id", ProcessHandle.current().pid());
|
||||
int radius = Math.min(400, generator.initialDiameter() / 2), lowerSamples = 0, unchanged = 0, changed = 0;
|
||||
for (int z = -radius; z <= radius; z += 32) for (int x = -radius; x <= radius; x += 32) {
|
||||
for (int y = 32; y <= 180; 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)), "Lower density is exactly alpha28");
|
||||
== Float.floatToIntBits(previous.sampleValue(context, x, y, z)), "Deep density is exactly alpha30.1");
|
||||
lowerSamples++;
|
||||
}
|
||||
for (int y = 181; 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, "A narrow plateau retains most of the accepted global relief");
|
||||
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 >= 205; 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,12 +57,94 @@ 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); report.put("passed", true);
|
||||
Path path = Path.of("diagnostics/alpha301", "relief-" + generator.initialDiameter() + "-" + level.getSeed() + ".json");
|
||||
report.put("sections", sections);
|
||||
// One block-resolution volume, chosen using the old field's surface transitions.
|
||||
// Both versions sample the exact same coordinates, without structures or decoration.
|
||||
int bestX=80,bestZ=80,bestScore=-1;
|
||||
for(int cz=-radius+48;cz<=radius-48;cz+=64)for(int cx=-radius+48;cx<=radius-48;cx+=64){
|
||||
if(Math.hypot(cx,cz)<80||Math.hypot(cx,cz)>generator.capacity().radius()*.78)continue;
|
||||
int score=0;
|
||||
for(int dz=-24;dz<=24;dz+=8)for(int dx=-24;dx<=24;dx+=8){
|
||||
int last=-1;
|
||||
for(int y=290;y>=175;y--){
|
||||
int solid=previous.sampleValue(context,cx+dx,y,cz+dz)>0?1:0;
|
||||
if(last>=0&&last!=solid)score++;last=solid;
|
||||
}
|
||||
}
|
||||
if(score>bestScore){bestScore=score;bestX=cx;bestZ=cz;}
|
||||
}
|
||||
var oldVolume=new ArrayList<String>();var newVolume=new ArrayList<String>();
|
||||
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++){
|
||||
a.append(previous.sampleValue(context,x,y,z)>0?'#':'.');
|
||||
b.append(current.sampleValue(context,x,y,z)>0?'#':'.');
|
||||
}oldVolume.add(a.toString());newVolume.add(b.toString());
|
||||
}
|
||||
report.put("volume",Map.of("min_x",bestX-size/2,"min_z",bestZ-size/2,"min_y",minY,
|
||||
"size_x",size,"size_z",size,"size_y",maxY-minY+1,"before",oldVolume,"after",newVolume));
|
||||
int oldFragments=tinyComponents(oldVolume,size,maxY-minY+1);
|
||||
int newFragments=tinyComponents(newVolume,size,maxY-minY+1);
|
||||
report.put("tiny_components_before",oldFragments);report.put("tiny_components_after",newFragments);
|
||||
helper.assertTrue(newFragments<=oldFragments,"The retouched volume must not introduce extra detached fragments");
|
||||
var reverseContext=SamplerContext.builder().enableCaches().build();int reversed=0;
|
||||
for(int y=maxY;y>=minY;y-=8)for(int z=bestZ+size/2-1;z>=bestZ-size/2;z-=8)
|
||||
for(int x=bestX+size/2-1;x>=bestX-size/2;x-=8){
|
||||
boolean expected=newVolume.get((y-minY)*size+z-(bestZ-size/2)).charAt(x-(bestX-size/2))=='#';
|
||||
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/alpha304", "relief-" + generator.initialDiameter() + "-" + level.getSeed() + ".json");
|
||||
Files.createDirectories(path.getParent());
|
||||
Files.writeString(path, new GsonBuilder().setPrettyPrinting().create().toJson(report));
|
||||
helper.succeed();
|
||||
}
|
||||
private static int tinyComponents(List<String> rows,int size,int height){
|
||||
int plane=size*size,total=plane*height,tiny=0;
|
||||
boolean[] visited=new boolean[total];int[] queue=new int[total];
|
||||
for(int start=0;start<total;start++){
|
||||
if(visited[start]||rows.get(start/size).charAt(start%size)!='#')continue;
|
||||
int head=0,count=1;queue[0]=start;visited[start]=true;boolean boundary=false;
|
||||
while(head<count){
|
||||
int p=queue[head++],x=p%size,z=(p/size)%size,y=p/plane;
|
||||
if(x==0||x==size-1||z==0||z==size-1||y==0||y==height-1)boundary=true;
|
||||
int[] neighbours={x>0?p-1:-1,x<size-1?p+1:-1,z>0?p-size:-1,z<size-1?p+size:-1,y>0?p-plane:-1,y<height-1?p+plane:-1};
|
||||
for(int next:neighbours)if(next>=0&&!visited[next]&&rows.get(next/size).charAt(next%size)=='#'){
|
||||
visited[next]=true;queue[count++]=next;
|
||||
}
|
||||
}
|
||||
if(!boundary&&count<=32)tiny++;
|
||||
}
|
||||
return tiny;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -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); }
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package fr.koka.sanctuary.worldgen;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import net.minecraft.world.level.levelgen.densityfunction.*;
|
||||
|
||||
/** Removes detached components of at most 32 blocks in the locally retouched field.
|
||||
* The flood search reads the unfiltered field and stops on its 33rd connected block.
|
||||
* All coordinates are global: a chunk edge is never treated as the edge of a component. */
|
||||
public final class DetachedRock302 implements DensitySampler {
|
||||
private static final int MIN_BODY = 33;
|
||||
private static final ThreadLocal<Cache> CACHE = ThreadLocal.withInitial(Cache::new);
|
||||
private static final int[][] SIDES = {{1,0,0},{-1,0,0},{0,1,0},{0,-1,0},{0,0,1},{0,0,-1}};
|
||||
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, 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) {
|
||||
if (!affected(x,y,z)) return input.sampleValue(context,x,y,z);
|
||||
var cache=CACHE.get();cache.select(this,context);
|
||||
int slot=cache.get(this,context,x,y,z);float value=cache.values[slot];
|
||||
if(value<=0 || cache.status[slot]==2) return value;
|
||||
if(cache.status[slot]==3) return -0.000001F;
|
||||
int[] xs=new int[MIN_BODY],ys=new int[MIN_BODY],zs=new int[MIN_BODY];xs[0]=x;ys[0]=y;zs[0]=z;
|
||||
int count=1;boolean attached=false;
|
||||
search:for(int head=0;head<count;head++)for(var side:SIDES){
|
||||
int nx=xs[head]+side[0],ny=ys[head]+side[1],nz=zs[head]+side[2];
|
||||
boolean seen=false;
|
||||
for(int i=0;i<count;i++)if(xs[i]==nx&&ys[i]==ny&&zs[i]==nz){seen=true;break;}
|
||||
if(seen)continue;
|
||||
int next=cache.get(this,context,nx,ny,nz);
|
||||
if(cache.values[next]<=0 || cache.status[next]==3)continue;
|
||||
if(cache.status[next]==2 || !affected(nx,ny,nz)){attached=true;break search;}
|
||||
xs[count]=nx;ys[count]=ny;zs[count]=nz;
|
||||
if(++count==MIN_BODY){attached=true;break search;}
|
||||
}
|
||||
for(int i=0;i<count;i++)cache.status[cache.get(this,context,xs[i],ys[i],zs[i])]=(byte)(attached?2:3);
|
||||
return attached?value:-0.000001F;
|
||||
}
|
||||
@Override public void sampleVolume(SamplerContext context,DensityBuffer buffer,DensityVolume volume){
|
||||
DensitySampler.sampleVolumeNaive(context,buffer,volume,this);
|
||||
}
|
||||
/** Bounded primitive cache; weak identities cannot retain a closed world or sample context. */
|
||||
private static final class Cache {
|
||||
private static final int SIZE=1<<16;
|
||||
private WeakReference<DetachedRock302> owner=new WeakReference<>(null);
|
||||
private WeakReference<SamplerContext> context=new WeakReference<>(null);
|
||||
private final int[] xs=new int[SIZE],ys=new int[SIZE],zs=new int[SIZE],stamps=new int[SIZE];
|
||||
private final float[] values=new float[SIZE];
|
||||
private final byte[] status=new byte[SIZE];
|
||||
private int generation;
|
||||
void select(DetachedRock302 selected,SamplerContext selectedContext){
|
||||
if(owner.get()==selected&&context.get()==selectedContext)return;
|
||||
owner=new WeakReference<>(selected);context=new WeakReference<>(selectedContext);
|
||||
if(++generation==0){java.util.Arrays.fill(stamps,0);generation=1;}
|
||||
}
|
||||
int get(DetachedRock302 sampler,SamplerContext sample,int x,int y,int z){
|
||||
int h=x*0x9e3779b9^Integer.rotateLeft(y*0x85ebca6b,11)^Integer.rotateLeft(z*0xc2b2ae35,22);
|
||||
int slot=(h^(h>>>16))&(SIZE-1);
|
||||
if(stamps[slot]!=generation||xs[slot]!=x||ys[slot]!=y||zs[slot]!=z){
|
||||
float value=sampler.input.sampleValue(sample,x,y,z);
|
||||
xs[slot]=x;ys[slot]=y;zs[slot]=z;values[slot]=value;status[slot]=1;stamps[slot]=generation;
|
||||
}
|
||||
return slot;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
+2
-1
@@ -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);
|
||||
}
|
||||
|
||||
+36
-13
@@ -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) 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),
|
||||
@@ -28,14 +28,16 @@ public record PopulationIslandDensity(DensityFunction terrain, DensityFunction d
|
||||
DensityFunction.CODEC.optionalFieldOf("erosion_28", DensityFunctions.zero()).forGetter(PopulationIslandDensity::erosion28),
|
||||
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("relief_30", false).forGetter(PopulationIslandDensity::relief30),
|
||||
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);
|
||||
DensityFunctions.zero(), DensityFunctions.zero(), DensityFunctions.zero(), false, false, false, false, false);
|
||||
}
|
||||
|
||||
public PopulationIslandDensity(DensityFunction terrain, DensityFunction distortion,
|
||||
@@ -43,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);
|
||||
caves28, ridges28, erosion28, geology28, false, false, false, false);
|
||||
}
|
||||
|
||||
@Override public DensitySampler compileSampler(CompileContext context) {
|
||||
@@ -55,8 +57,9 @@ 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>();
|
||||
return new DensitySampler() {
|
||||
DensitySampler field = new DensitySampler() {
|
||||
private float terrainAt(SamplerContext sample, double x, double y, double z) {
|
||||
int ix = (int) Math.floor(x), iy = (int) Math.floor(y), iz = (int) Math.floor(z);
|
||||
double fx = x - ix, fy = y - iy, fz = z - iz;
|
||||
@@ -77,14 +80,21 @@ 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 (weight > 0) {
|
||||
var shift = CavesRelief28.warp(weight,
|
||||
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 || 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));
|
||||
double sourceY = y - 64 - shift.y();
|
||||
erosion.sampleValue(sample, x - 211, y + 137, z + 307)) : new CavesRelief28.Warp(0,0,0);
|
||||
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,
|
||||
Terraces29.weight(y, weight));
|
||||
terrainValue = terrainAt(sample, x - shift.x(), sourceY, z - shift.z());
|
||||
@@ -92,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)));
|
||||
@@ -111,6 +129,7 @@ public record PopulationIslandDensity(DensityFunction terrain, DensityFunction d
|
||||
DensitySampler.sampleVolumeNaive(sample, buffer, volume, this);
|
||||
}
|
||||
};
|
||||
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() {
|
||||
@@ -120,7 +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);
|
||||
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, false);
|
||||
}
|
||||
public static long regionSeed(long seed, IslandCapacity.Region region) {
|
||||
long value = seed ^ ((long) region.x() << 32) ^ (region.z() & 0xffffffffL) ^ 0xA10CA7E1L;
|
||||
@@ -131,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);
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
package fr.koka.sanctuary.worldgen;
|
||||
|
||||
/** Locally phased ledges in the existing rock field, with no shared horizontal level. */
|
||||
public final class SurfaceRelief302 {
|
||||
private SurfaceRelief302() {}
|
||||
|
||||
public static double weight(int x, int y, int z, double radius) {
|
||||
double distance = Math.hypot((double) x, z);
|
||||
return smooth((y - 140) / 48.0) * (1 - smooth((y - 316) / 36.0))
|
||||
* smooth((distance - 48) / 32.0)
|
||||
* (1 - smooth((distance / radius - .86) / .12));
|
||||
}
|
||||
|
||||
/** Coherent patches move together; narrow, continuous transitions form local ledges.
|
||||
* No rounding of Y, shared layer height or per-block random displacement. */
|
||||
public static double sourceY(double originalY, double weight, float noise) {
|
||||
if(weight<=0)return originalY;
|
||||
double period=6.5+1.5*Math.tanh(noise*1.5);
|
||||
double phase=11*Math.tanh(noise);
|
||||
double band=(originalY+phase)/period;
|
||||
double fraction=band-Math.floor(band);
|
||||
double ledge=smooth((fraction-.30)/.40);
|
||||
double offset=Math.clamp((ledge-fraction)*period,-2.25,2.25);
|
||||
return originalY+offset*weight;
|
||||
}
|
||||
|
||||
private static double smooth(double value) {
|
||||
double t = Math.clamp(value, 0, 1);
|
||||
return t * t * (3 - 2 * t);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
+30
-7
@@ -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"
|
||||
}
|
||||
+2
-1
@@ -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"
|
||||
]
|
||||
}
|
||||
|
||||
+3
-1
@@ -65,5 +65,7 @@
|
||||
"y_scale": 2.0
|
||||
},
|
||||
"terraces_29": false,
|
||||
"relief_30": true
|
||||
"relief_30": true,
|
||||
"surface_302": false,
|
||||
"balanced_303": true
|
||||
}
|
||||
|
||||
+3
-1
@@ -65,5 +65,7 @@
|
||||
"y_scale": 2.0
|
||||
},
|
||||
"terraces_29": false,
|
||||
"relief_30": true
|
||||
"relief_30": true,
|
||||
"surface_302": false,
|
||||
"balanced_303": true
|
||||
}
|
||||
|
||||
+3
-1
@@ -65,5 +65,7 @@
|
||||
"y_scale": 2.0
|
||||
},
|
||||
"terraces_29": false,
|
||||
"relief_30": true
|
||||
"relief_30": true,
|
||||
"surface_302": false,
|
||||
"balanced_303": true
|
||||
}
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
"legacy_random_source": true,
|
||||
"material_rule": "sanctuary:unified_island",
|
||||
"noise": {
|
||||
"height": 384,
|
||||
"height": 640,
|
||||
"min_y": 0
|
||||
},
|
||||
"noise_router": {
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
"legacy_random_source": true,
|
||||
"material_rule": "sanctuary:unified_island",
|
||||
"noise": {
|
||||
"height": 384,
|
||||
"height": 640,
|
||||
"min_y": 0
|
||||
},
|
||||
"noise_router": {
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
"legacy_random_source": true,
|
||||
"material_rule": "sanctuary:unified_island",
|
||||
"noise": {
|
||||
"height": 384,
|
||||
"height": 640,
|
||||
"min_y": 0
|
||||
},
|
||||
"noise_router": {
|
||||
|
||||
+1
-1
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package fr.koka.sanctuary.worldgen;
|
||||
|
||||
import java.util.*;
|
||||
import net.minecraft.world.level.levelgen.densityfunction.*;
|
||||
|
||||
public final class SurfaceRelief302Smoke {
|
||||
private record P(int x,int y,int z) {}
|
||||
private static DetachedRock302 filter(Set<P> blocks){
|
||||
return new DetachedRock302(new DensitySampler(){
|
||||
@Override public float sampleValue(SamplerContext c,int x,int y,int z){return blocks.contains(new P(x,y,z))?.5F:-.5F;}
|
||||
@Override public void sampleVolume(SamplerContext c,DensityBuffer b,DensityVolume v){DensitySampler.sampleVolumeNaive(c,b,v,this);}
|
||||
},362,0);
|
||||
}
|
||||
private static void require(boolean value,String message){if(!value)throw new AssertionError(message);}
|
||||
public static void main(String[] args){
|
||||
var context=SamplerContext.EMPTY_UNCACHED;var cube=new HashSet<P>();
|
||||
for(int x=95;x<=96;x++)for(int y=230;y<=231;y++)for(int z=100;z<=101;z++)cube.add(new P(x,y,z));
|
||||
var removed=filter(Set.copyOf(cube));
|
||||
for(var p:cube)require(removed.sampleValue(context,p.x,p.y,p.z)<0,"Eight detached blocks crossing a chunk edge must disappear");
|
||||
for(int x=95;x<=97;x++)for(int y=230;y<=232;y++)for(int z=100;z<=102;z++)cube.add(new P(x,y,z));
|
||||
var clump=filter(Set.copyOf(cube));
|
||||
for(var p:cube)require(clump.sampleValue(context,p.x,p.y,p.z)<0,"A detached 3 by 3 by 3 clump is also removed");
|
||||
for(int z=100;z<=102;z++)for(int x=95;x<=97;x++)cube.add(new P(x,233,z));
|
||||
var retained=filter(Set.copyOf(cube));
|
||||
var order=new ArrayList<>(cube);Collections.reverse(order);
|
||||
for(var p:order)require(retained.sampleValue(context,p.x,p.y,p.z)>0,"A connected body of 36 blocks must remain");
|
||||
require(retained.sampleValue(context,94,230,100)<0,"Cleanup never fills surrounding air");
|
||||
var singleton=filter(Set.of(new P(96,230,100)));
|
||||
require(singleton.sampleValue(context,96,230,100)<0,"A different sampler cannot inherit another body's cached support");
|
||||
var outside=new P(0,64,0);require(filter(Set.of(outside)).sampleValue(context,0,64,0)>.0,"The arrival and deep field are untouched");
|
||||
for(double y=-10;y<400;y+=.125)for(float noise:new float[]{-2,-1,-.25F,0,.25F,1,2}){
|
||||
require(Math.abs(SurfaceRelief302.sourceY(y,1,noise)-y)<=2.25001,"Local ledges must remain bounded");
|
||||
require(SurfaceRelief302.sourceY(y,0,noise)==y,"An excluded region retains its exact field");
|
||||
}
|
||||
System.out.println("SurfaceRelief302Smoke: detached fragments across chunks removed, connected body and void retained, cache isolation and bounded local ledges passed");
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
name = "Sanctuary"
|
||||
author = "KOKA99CAB"
|
||||
version = "0.1.0-alpha.30.1"
|
||||
version = "0.1.0-alpha.30.4"
|
||||
pack-format = "packwiz:1.1.0"
|
||||
|
||||
[index]
|
||||
|
||||
Reference in New Issue
Block a user