Compare commits
11
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e94c1c539 | ||
|
|
40168e2054 | ||
|
|
ebcdb17a11 | ||
|
|
40463329f1 | ||
|
|
ddc43d716b | ||
|
|
2932411349 | ||
|
|
27df2da2a6 | ||
|
|
ae8c2235ef | ||
|
|
85320f3b1e | ||
|
|
624e7e6e60 | ||
|
|
e48de1b400 |
@@ -0,0 +1,15 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
[*.{json,yml,yaml,toml}]
|
||||
indent_size = 2
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
@@ -0,0 +1,9 @@
|
||||
* text=auto
|
||||
*.java text eol=lf
|
||||
*.json text eol=lf
|
||||
*.toml text eol=lf
|
||||
*.md text eol=lf
|
||||
gradlew text eol=lf
|
||||
gradlew.bat text eol=crlf
|
||||
*.png binary
|
||||
*.jar binary
|
||||
@@ -0,0 +1,41 @@
|
||||
---
|
||||
name: Bug
|
||||
about: Signaler un comportement incorrect et le reproduire
|
||||
title: "[Bug] "
|
||||
---
|
||||
|
||||
## Résultat observé
|
||||
|
||||
Décrire le problème et son impact sur le jeu.
|
||||
|
||||
## Résultat attendu
|
||||
|
||||
Décrire le comportement correct.
|
||||
|
||||
## Étapes de reproduction
|
||||
|
||||
1. Ouvrir…
|
||||
2. Faire…
|
||||
3. Observer…
|
||||
|
||||
## Environnement
|
||||
|
||||
- Version de Minecraft :
|
||||
- Version de Sanctuary ou commit :
|
||||
- Version de Fabric Loader et Fabric API :
|
||||
- Client seul ou serveur dédié :
|
||||
- Autres mods pertinents :
|
||||
- Fréquence : toujours / parfois / une fois :
|
||||
|
||||
## Monde concerné
|
||||
|
||||
Pour un problème de terrain, d'entités ou de position :
|
||||
|
||||
- Seed :
|
||||
- Dimension et coordonnées :
|
||||
- Monde nouvellement créé ou sauvegarde existante :
|
||||
- Type de monde et paramètres Sanctuary :
|
||||
|
||||
## Logs et captures utiles
|
||||
|
||||
Ajouter uniquement les éléments nécessaires à la reproduction. Retirer les secrets et informations personnelles. Indiquer si le problème survient aussi dans une sauvegarde de développement neuve, si ce test a été fait.
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
name: Fonctionnalité
|
||||
about: Proposer un incrément jouable de Sanctuary
|
||||
title: "[Feature] "
|
||||
---
|
||||
|
||||
## Ce que le joueur doit pouvoir faire
|
||||
|
||||
Décrire le résultat souhaité et son utilité en quelques phrases.
|
||||
|
||||
## Périmètre
|
||||
|
||||
Indiquer les systèmes concernés et les dépendances éventuelles. Faire un ticket distinct pour les idées qui peuvent être livrées séparément.
|
||||
|
||||
## Critères d'acceptation
|
||||
|
||||
- [ ] Un comportement observable et vérifiable.
|
||||
- [ ] Un cas limite important, si nécessaire.
|
||||
|
||||
## Contexte et références
|
||||
|
||||
Exemples, croquis, liens vers la vision, ancien fichier ou commit utile. Pour la génération : seed, coordonnées, dimensions et configuration si elles sont connues.
|
||||
|
||||
## Décisions encore ouvertes
|
||||
|
||||
Valeurs d'équilibrage, règles, interface ou contraintes qui doivent être fixées pendant le ticket.
|
||||
@@ -0,0 +1,17 @@
|
||||
name: Build Sanctuary
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: '25'
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.12'
|
||||
- name: Verify and build
|
||||
run: ./gradlew --no-daemon check build assemblePack
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
.DS_Store
|
||||
.gradle/
|
||||
**/build/
|
||||
**/run/
|
||||
**/out/
|
||||
.idea/
|
||||
*.iml
|
||||
*.jar
|
||||
!gradle/wrapper/gradle-wrapper.jar
|
||||
*.mrpack
|
||||
logs/
|
||||
crash-reports/
|
||||
saves/
|
||||
screenshots/
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
@@ -0,0 +1,19 @@
|
||||
# Sanctuary Beta — travail par tickets
|
||||
|
||||
Ce dépôt contient le pack Sanctuary et ses mods. Lire `README.md`, puis le ticket concerné.
|
||||
La vision est dans `docs/vision.md` ; elle décrit aussi des fonctionnalités futures.
|
||||
|
||||
- Avant une modification, lire l'état Git et préserver les changements existants.
|
||||
- Un ticket correspond à un résultat jouable ou vérifiable et à une branche `codex/<sujet>`.
|
||||
- Garder les changements ciblés. Le dépôt voisin `26.2` est une référence historique, pas une cible de modification ou de déploiement.
|
||||
- Les sources Java sont dans `mods/<mod>/`, le manifeste du pack dans `packwiz/`.
|
||||
- Les règles de jeu et sauvegardes font autorité côté serveur. Les mods autonomes gardent leur propre responsabilité.
|
||||
- Ne pas modifier un monde existant, régénérer des chunks, changer un format de sauvegarde ou activer une expansion sans contrat de migration explicite.
|
||||
- Garder les identifiants `sanctuary:*` stables et documenter toute évolution de la génération avec graine et version.
|
||||
- Ajouter les libellés FR/EN des nouvelles interfaces. Vérifier les dépendances pour la version Minecraft exacte ; aucune compatibilité supposée à partir du nom d'un mod.
|
||||
- Lancer `./gradlew check build` pour livrer du code, et `./gradlew assemblePack` si la distribution change. Ajouter seulement les tests utiles au comportement touché.
|
||||
- Pour chaque nouvelle livraison du mod, incrémenter `mod_version` et `pack_version` dans `gradle.properties`, synchroniser `packwiz/pack.toml` et documenter le changement. Une simple modification documentaire n'incrémente pas les binaires.
|
||||
- Le pack Beta suit un canal packwiz stable et une seule instance Prism. Pour une mise à jour demandée, suivre `docs/packwiz.md` : publier un artefact vérifié et immuable, avancer le canal, puis synchroniser l'instance existante en conservant ses sauvegardes et réglages.
|
||||
- Ne pas versionner de secrets, mondes, JAR générés ou dépendances téléchargées. Le wrapper Gradle fait exception.
|
||||
- Ne pas déployer dans une installation de jeu ou un serveur personnel sans demande correspondante. Les serveurs de test restent dans les dossiers de développement ignorés.
|
||||
- Une livraison explique le résultat, les vérifications effectuées et les limites encore ouvertes. Ne pas marquer une intention comme implémentée.
|
||||
@@ -0,0 +1,84 @@
|
||||
# Changelog
|
||||
|
||||
## 0.1.0-alpha.4 — 2026-09-08
|
||||
|
||||
- Première hydrologie de surface : étangs, petits lacs et cours d’eau calmes
|
||||
adaptés au relief, avec vérification du fond et des berges.
|
||||
- Fonds de sable, gravier et argile, berges sableuses et canne à sucre
|
||||
placée après la végétation lorsque ses conditions vanilla sont réunies.
|
||||
- Hauteur, ciel et densité du terrain alpha.3 conservés ; nouveaux paramètres
|
||||
dédiés pour préserver la génération des anciennes sauvegardes.
|
||||
- Plan déterministe indépendant de l’ordre de génération des chunks, sans
|
||||
digue ajoutée, remplissage de cavité souterraine ni mer globale.
|
||||
|
||||
Créer un **nouveau monde Sanctuary** pour tester l’hydrologie. Le pack continue
|
||||
à se mettre à jour dans la même instance Prism. Les grandes rivières en pente,
|
||||
cascades, océans et eaux des futurs continents restent à développer.
|
||||
|
||||
Validation : 10/10 tests serveur réussis sur chacune des graines 0, 42 et
|
||||
8675309, avec eau contenue après simulation réelle, continuité du cours d’eau
|
||||
et canne à sucre valide. Tests de forme et de déterminisme également réussis.
|
||||
Les cartes de blocs sont inspectées ; l’aspect dans le client reste à tester.
|
||||
|
||||
## 0.1.0-alpha.3 — 2026-09-08
|
||||
|
||||
- Nouveaux mondes Sanctuary sur 384 blocs de hauteur, avec l'île relevée de
|
||||
64 blocs et un dessous sculpté pour remplacer l'ancien fondu horizontal.
|
||||
- Nuages relevés et suppression du disque sombre de l'horizon et du
|
||||
noircissement d'altitude propres au vide dans ces nouveaux mondes.
|
||||
- Anciens paramètres de monde conservés : aucune translation de blocs ou
|
||||
modification de la hauteur des sauvegardes existantes.
|
||||
- Premier champ de recherche climatique documenté : nord froid, sud chaud,
|
||||
ouest sec, est humide et leurs combinaisons dans les quatre diagonales.
|
||||
- Relevé de développement des blocs et ressources après génération, avec
|
||||
emprise et complétude explicites. Aucun quota n'influence le terrain.
|
||||
|
||||
Les étangs, lacs de surface, rivière, berges à canne à sucre et plages de l'île
|
||||
tempérée restent à implémenter. Le contrat de cette hydrologie et de l'expansion
|
||||
est dans [Expansion et ressources](docs/expansion.md).
|
||||
|
||||
Créer un **nouveau monde Sanctuary** pour la hauteur et le ciel de l'alpha.3.
|
||||
Le pack se met à jour dans la même instance Prism.
|
||||
|
||||
Validation : build et pack réussis, 8 tests serveur réussis sur chacune des
|
||||
graines 0, 42 et 8675309, injections de rendu vérifiées dans le vrai client.
|
||||
Le relevé complet de la graine 0 et les limites des essais sont documentés dans
|
||||
[Validation](docs/testing.md) et [Expansion et ressources](docs/expansion.md).
|
||||
|
||||
## 0.1.0-alpha.2 — 2026-09-08
|
||||
|
||||
- Suppression du noyau elliptique qui ajoutait une masse artificielle au centre.
|
||||
- Sculpture progressive du contour par érosion en trois dimensions ; retrait
|
||||
des faces hautes et basses pour réduire les grandes tranches verticales.
|
||||
- Spawn recherché sur le terrain naturel, avec une surface de 3 × 3 privilégiée.
|
||||
- Canal packwiz stable et release versionnée, pour mettre à jour la même
|
||||
instance Prism avant chaque lancement.
|
||||
|
||||
Validation : `./gradlew check build assemblePack`, tests de densité compilée,
|
||||
coupes du générateur et tests serveur sur plusieurs graines. Voir
|
||||
[Validation](docs/testing.md) et [Distribution Prism](docs/packwiz.md).
|
||||
|
||||
Tester la génération dans un **nouveau monde Sanctuary**. Les chunks déjà
|
||||
générés conservent leur ancien terrain ; les sauvegardes ne sont pas régénérées.
|
||||
Minecraft reste en 26.3-pre-2, avec Fabric Loader 0.19.5.
|
||||
|
||||
## 0.1.0-alpha.1 — 2026-09-08
|
||||
|
||||
Première base indépendante de Sanctuary Beta pour Minecraft 26.3-pre-2 / Fabric.
|
||||
|
||||
- Construction Java 25 / Gradle reproductible, CI Gitea et modèles de tickets.
|
||||
- Vision du projet conservée et audit du générateur 26.2 documenté.
|
||||
- Preset Sanctuary : île flottante finie issue du bruit historique, forêt de
|
||||
départ utilisant les ressources vanilla et extérieur vide.
|
||||
- Spawn initial près de l'origine, recherché sur une surface pleine et libre.
|
||||
- Tests de forme et tests sur un vrai serveur Minecraft de développement.
|
||||
- Manifeste packwiz avec Fabric API épinglé et assemblage du mod local.
|
||||
|
||||
Validation : `./gradlew check build assemblePack` réussit. Le serveur de test
|
||||
valide le spawn `(0, 118, 0)`, 12 chunks extérieurs entièrement vides et le
|
||||
déterminisme du bruit compilé. Voir [Validation](docs/testing.md) pour les seeds,
|
||||
les limites des tests et les vérifications manuelles restantes.
|
||||
|
||||
Cette version prépare le terrain. L'hydrologie, les continents déverrouillables,
|
||||
TerraMix, la progression, l'économie et les autres dimensions restent à venir.
|
||||
Les mondes joués de 26.2 ne sont pas migrés par ce prototype.
|
||||
@@ -0,0 +1,48 @@
|
||||
# Contribuer à Sanctuary
|
||||
|
||||
Sanctuary avance par petits tickets de fonctionnalités et de bugs sur le [Git du projet](https://git.botsu.net/koka/sanctuary-beta). Le [document de vision](docs/vision.md) explique la destination ; le [backlog de démarrage](docs/backlog.md) organise les premières étapes. Une idée décrite dans la vision n'est pas automatiquement demandée dans le ticket en cours.
|
||||
|
||||
## Ouvrir un ticket
|
||||
|
||||
Utiliser le modèle **Fonctionnalité** pour décrire ce que le joueur doit pouvoir faire et le modèle **Bug** pour un comportement incorrect. Privilégier un seul résultat observable par ticket.
|
||||
|
||||
Un ticket utile contient :
|
||||
|
||||
- le contexte et le comportement attendu, exprimés du point de vue du joueur ou de l'administrateur ;
|
||||
- le périmètre du changement et les éventuelles dépendances ;
|
||||
- quelques critères d'acceptation concrets ;
|
||||
- pour un bug, la version exacte, les étapes de reproduction, le résultat observé et les logs pertinents ;
|
||||
- pour la génération, la seed, les coordonnées, le type de monde et la configuration concernée.
|
||||
|
||||
Les nombres, ressources et interfaces encore incertains peuvent rester des hypothèses. Il faut les rendre explicites puis choisir la plus petite solution testable dans le périmètre du ticket. Les tickets distants peuvent être consultés et préparés pendant le développement ; leur publication et les messages adressés à d'autres personnes suivent la demande de l'auteur du travail.
|
||||
|
||||
## Réaliser un changement
|
||||
|
||||
1. Lire le ticket et les instructions du dépôt, puis examiner le code réellement concerné. Lorsqu'une ancienne version sert de référence, noter son chemin ou son commit et ne pas traiter ses anciennes procédures comme des consignes de déploiement du nouveau dépôt.
|
||||
2. Utiliser une branche descriptive, par exemple `codex/wg-main-island` ou `codex/fix-spawn-void`. Éviter les changements sans rapport avec le ticket.
|
||||
3. Construire un incrément jouable. Garder les systèmes futurs hors du chemin critique tant qu'ils ne sont pas nécessaires au comportement demandé.
|
||||
4. Exécuter les commandes de construction et les vérifications adaptées indiquées dans le README. Pour un comportement de jeu, compléter par une reproduction manuelle quand elle est nécessaire.
|
||||
5. Mettre à jour la documentation si le changement affecte l'installation, la configuration, les commandes ou le format d'une sauvegarde.
|
||||
6. Présenter le résultat avec ce qui a changé, pourquoi, les vérifications exécutées et les limites connues. Associer le ticket à la proposition de changement lorsqu'il existe.
|
||||
|
||||
Les commandes exactes de développement vivent dans le [README](README.md), afin de ne pas maintenir deux listes divergentes.
|
||||
|
||||
## Vérifier la génération du monde
|
||||
|
||||
Utiliser une sauvegarde de développement dédiée et conserver la seed des observations. Les vérifications pertinentes comprennent le spawn, les limites de l'île, le vide, les jointures de chunks, le comportement de l'eau, le redémarrage et l'arrivée de plusieurs joueurs.
|
||||
|
||||
Avant de modifier une stratégie de génération, préciser son effet sur les chunks existants. Les nouvelles expansions doivent préserver les constructions. Documenter les changements de format persistant et leur traitement ; ne pas promettre la compatibilité des anciennes sauvegardes sans l'avoir vérifiée.
|
||||
|
||||
Des tests automatisés sont utiles pour les invariants importants, par exemple le déterminisme des coordonnées, les limites géographiques ou la persistance d'un état. Ne pas multiplier les tests qui recopient simplement l'implémentation ou les vérifications sans rapport avec le changement.
|
||||
|
||||
## Ressources et intégrations
|
||||
|
||||
Avant de reprendre du code, des textures, de la musique, des modèles ou des configurations de l'historique et de la communauté, conserver leur provenance et respecter leur licence. Une ressource installée localement n'est pas automatiquement redistribuable dans le modpack.
|
||||
|
||||
Sanctuary, son modpack, It's Alive !, Only Fun et Master Key ont des responsabilités distinctes. Une intégration commence par un ticket qui précise la version supportée, la dépendance réelle et le comportement en son absence. L'ajout d'une dépendance ou d'un module doit répondre à un besoin livré.
|
||||
|
||||
## Signaler les résultats
|
||||
|
||||
Une description de changement doit être compréhensible sans lire la conversation de développement. Pour un bug, donner si possible un exemple avant/après. Indiquer ce qui a été testé réellement ; une compilation réussie ne prouve pas à elle seule qu'une génération est agréable ni qu'une session multijoueur fonctionne.
|
||||
|
||||
Les captures, logs et sauvegardes partagées doivent être limités au contexte utile et ne pas inclure de jetons, données d'authentification ou informations personnelles inutiles. Ne pas ajouter les répertoires d'exécution, les caches ou les mondes complets au dépôt par défaut.
|
||||
@@ -0,0 +1,676 @@
|
||||
Sanctuary 26.2 is licensed under GPL-3.0-or-later.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
@@ -1,75 +1,156 @@
|
||||
# Sanctuary — historique des notes de livraison
|
||||
# Sanctuary
|
||||
|
||||
Publication rétrospective des livraisons locales après **alpha.30.7**, jusqu’à **beta.064**.
|
||||
Sanctuary est une extension gratuite de Minecraft qui transforme le jeu en un
|
||||
monde flottant d'exploration, de production et de progression collective.
|
||||
Les joueurs commencent ensemble sur Sanctuary Island et construisent les
|
||||
infrastructures qui permettront progressivement d'ouvrir de nouveaux continents.
|
||||
|
||||
Cette branche contient uniquement des documents. Les tags `notes/beta.xxx` sont des repères documentaires ; leurs archives Gitea ne sont pas les sources historiques du mod. Les posts beta.060 et beta.061 utilisent les tags de code exacts déjà publiés. Les tags alpha et les artefacts existants restent immuables.
|
||||
> Sanctuary is a free expansion of Minecraft that reshapes the game around a
|
||||
> floating world. Players begin together on Sanctuary Island, isolated in the
|
||||
> void. By exploring, building infrastructure and producing resources, they
|
||||
> progressively unlock new floating continents, each with its own geography.
|
||||
> Rather than a linear campaign, the server itself becomes a world that grows
|
||||
> through the action of its players.
|
||||
|
||||
**63 livraisons** sont recensées. Le chantier beta.021 a été finalisé en beta.023, sans livraison indépendante. L’alpha.30.7 possède déjà son [post de release](https://git.botsu.net/koka/sanctuary-beta/releases/tag/v0.1.0-alpha.30.7).
|
||||
Ce dépôt démarre la nouvelle base Fabric. La [vision complète](docs/vision.md)
|
||||
conserve les intentions ; le [backlog](docs/backlog.md) prépare les premiers
|
||||
tickets. Les systèmes d'expansion, d'économie, de progression et les dimensions
|
||||
décrits dans la vision ne sont pas encore implémentés.
|
||||
|
||||
Chaque texte décrit sa version historique ; les livraisons suivantes ont pu modifier les mêmes règles. Le détail des vérifications et limites reste accessible depuis chaque post. La préparation de ces notes ne rejoue pas les tests de jeu et ne publie pas les archives locales.
|
||||
## Premier incrément
|
||||
|
||||
| Version | Notes |
|
||||
- Mod `sanctuary` indépendant et pack Sanctuary construit avec packwiz.
|
||||
- Preset **Sanctuary** sélectionnable à la création d'un monde, avec une île
|
||||
flottante finie, un terrain naturel et un extérieur vide.
|
||||
- Forêt de départ avec végétation, minerais et animaux vanilla. Les formes de
|
||||
terrain reprennent l'approche du générateur 26.2, adaptée à l'API 26.3.
|
||||
- Spawn recherché sur le sol de l'île lors de la création. Les mondes ordinaires
|
||||
et les choix ultérieurs de spawn ne sont pas remplacés.
|
||||
- Nouveaux mondes sur 384 blocs (`Y=0..383`), île relevée de 64 blocs,
|
||||
dessous sculpté et nuages au-dessus du terrain. Les anciennes sauvegardes
|
||||
conservent leurs paramètres de génération.
|
||||
- Première hydrologie de surface : étangs, petits lacs et cours d’eau calmes,
|
||||
avec des berges de sable et de la canne à sucre lorsque le terrain s’y prête.
|
||||
|
||||
L'île a une emprise nominale de 512 blocs de diamètre, avec une bordure sculptée
|
||||
par des bruits liés à la seed. Aucune masse centrale n'est ajoutée pour forcer
|
||||
le terrain : le spawn cherche un emplacement naturel. Le catalogue
|
||||
TerraMix, les continents déverrouillables, les grandes rivières et cascades, les ruines et
|
||||
les autres fonctionnalités restent à construire par tickets.
|
||||
|
||||
Le premier champ de recherche climatique est défini : nord froid, sud chaud,
|
||||
ouest sec et est humide, avec leurs combinaisons diagonales. Un outil de
|
||||
développement relève les ressources après génération ; le système d'expansion
|
||||
en jeu reste à construire. Voir [Expansion et ressources](docs/expansion.md).
|
||||
|
||||
Les détails du terrain et de ses limites sont dans [Génération](docs/worldgen.md),
|
||||
et la provenance dans [Audit 26.2](docs/migration-26.2.md).
|
||||
|
||||
## Versions et prérequis
|
||||
|
||||
Au 8 septembre 2026, la cible disponible est **26.3-pre-2**. Le dépôt ne prétend
|
||||
pas cibler une version finale 26.3 déjà sortie. Le passage à la version finale
|
||||
sera une mise à jour explicite, avec vérification des API et des sauvegardes.
|
||||
|
||||
| Composant | Version fixée |
|
||||
| --- | --- |
|
||||
| beta.001 | [Les quatre anciennes expéditions](notes/beta.001.md) |
|
||||
| beta.002 | [Biomes boréaux, corail et temples](notes/beta.002.md) |
|
||||
| beta.003 | [Créer son habitant et commencer la progression](notes/beta.003.md) |
|
||||
| beta.004 | [Cadre Blocodex et sprint](notes/beta.004.md) |
|
||||
| beta.005 | [Carte native et noms de couleurs](notes/beta.005.md) |
|
||||
| beta.006 | [Brouillard, grille et Demeure](notes/beta.006.md) |
|
||||
| beta.007 | [Carte immersive](notes/beta.007.md) |
|
||||
| beta.008 | [Minage et vein mining](notes/beta.008.md) |
|
||||
| beta.009 | [Construction et vein building](notes/beta.009.md) |
|
||||
| beta.010 | [Inventaire jusqu'à six rangées](notes/beta.010.md) |
|
||||
| beta.011 | [Factions, prestige et New Game+](notes/beta.011.md) |
|
||||
| beta.012 | [Inventaire et progression lisible](notes/beta.012.md) |
|
||||
| beta.013 | [Progression, prestige et repères visuels](notes/beta.013.md) |
|
||||
| beta.014 | [Rangement, hotbar mobile et préfixe de faction](notes/beta.014.md) |
|
||||
| beta.015 | [Icônes de progression communes](notes/beta.015.md) |
|
||||
| beta.016 | [Les premiers gestes rapportent de l’XP](notes/beta.016.md) |
|
||||
| beta.017 | [Nage, S’allonger, Se reposer et fabrication manuelle](notes/beta.017.md) |
|
||||
| beta.018 | [Familiers, cape et cosmétique de tête](notes/beta.018.md) |
|
||||
| beta.019 | [Pouvoirs et déplacements des familiers](notes/beta.019.md) |
|
||||
| beta.020 | [Œufs singuliers, portage et plongeon](notes/beta.020.md) |
|
||||
| beta.022 | [Création de monde plus rapide](notes/beta.022.md) |
|
||||
| beta.023 | [Cosmétiques directement sur leur case](notes/beta.023.md) |
|
||||
| beta.024 | [Recettes progressives dans le Blocodex](notes/beta.024.md) |
|
||||
| beta.025 | [Barres d’XP des compétences](notes/beta.025.md) |
|
||||
| beta.026 | [Cache d’affichage Demeure sur la carte](notes/beta.026.md) |
|
||||
| beta.027 | [Piles de joueurs](notes/beta.027.md) |
|
||||
| beta.028 | [Plongeon jusqu’à collision](notes/beta.028.md) |
|
||||
| beta.029 | [Cri et recul des familiers](notes/beta.029.md) |
|
||||
| beta.030 | [Lumières portées et objets jetés](notes/beta.030.md) |
|
||||
| beta.031 | [Apparition galactique](notes/beta.031.md) |
|
||||
| beta.032 | [Refonte des 88 familiers](notes/beta.032.md) |
|
||||
| beta.033 | [Menus directs et profil de test rapide](notes/beta.033.md) |
|
||||
| beta.034 | [Fil de découvertes et hotbar agrandie](notes/beta.034.md) |
|
||||
| beta.035 | [Collections Minecraft](notes/beta.035.md) |
|
||||
| beta.036 | [Notifications et suivi choisi](notes/beta.036.md) |
|
||||
| beta.037 | [Temps réel et suivi commun](notes/beta.037.md) |
|
||||
| beta.038 | [Nourriture, têtes-tombes et factions](notes/beta.038.md) |
|
||||
| beta.039 | [Hello World, savoir alimentaire et inventaires de mort](notes/beta.039.md) |
|
||||
| beta.040 | [Mode opérateur, fiche Habitant et ciel partagé](notes/beta.040.md) |
|
||||
| beta.041 | [Portage des bébés](notes/beta.041.md) |
|
||||
| beta.042 | [Joueurs assis sur la tête](notes/beta.042.md) |
|
||||
| beta.043 | [Blocs portés et icône officielle](notes/beta.043.md) |
|
||||
| beta.044 | [Pouvoirs natifs des familiers](notes/beta.044.md) |
|
||||
| beta.045 | [Menu principal](notes/beta.045.md) |
|
||||
| beta.046 | [Constellations, vol et zoom](notes/beta.046.md) |
|
||||
| beta.047 | [Étoiles à la longue-vue et boutons](notes/beta.047.md) |
|
||||
| beta.048 | [Bateaux collectifs et moteurs animaux](notes/beta.048.md) |
|
||||
| beta.049 | [Bateaux rectangulaires et chapeaux actifs](notes/beta.049.md) |
|
||||
| beta.050 | [Bateaux compacts et redstone portée](notes/beta.050.md) |
|
||||
| beta.051 | [Distributeur porté orienté par le regard](notes/beta.051.md) |
|
||||
| beta.052 | [Nom du familier de départ](notes/beta.052.md) |
|
||||
| beta.053 | [Brume progressive et saturation](notes/beta.053.md) |
|
||||
| beta.054 | [Duels, mises et niveaux des familiers](notes/beta.054.md) |
|
||||
| beta.055 | [Force et portage de joueurs](notes/beta.055.md) |
|
||||
| beta.056 | [Étoiles dispersées et saturation](notes/beta.056.md) |
|
||||
| beta.057 | [Météo quotidienne et nouvelles ambiances](notes/beta.057.md) |
|
||||
| beta.058 | [Altitude de la carte](notes/beta.058.md) |
|
||||
| beta.059 | [Bannières et cartes au trésor](notes/beta.059.md) |
|
||||
| beta.060 | [Objets et blocs sur les mobs](notes/beta.060.md) |
|
||||
| beta.061 | [Musique d’arrivée et montures familières](notes/beta.061.md) |
|
||||
| beta.062 | [Soleil et chapeaux des familiers](notes/beta.062.md) |
|
||||
| beta.063 | [Chargement et règle de jeu Sanctuary](notes/beta.063.md) |
|
||||
| beta.064 | [Cache et étapes réelles de préparation](notes/beta.064.md) |
|
||||
| Minecraft Java | 26.3-pre-2 |
|
||||
| Java JDK | 25 |
|
||||
| Fabric Loader | 0.19.5 |
|
||||
| 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.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
|
||||
3.11–3.14 si `python3` désigne l'ancien Python fourni par macOS. Gradle est fourni
|
||||
par le wrapper. `packwiz` est utile pour modifier les dépendances ou servir le
|
||||
pack ; il n'est pas nécessaire pour le construire.
|
||||
|
||||
Références vérifiées : [Minecraft 26.3-pre-2](https://www.minecraft.net/en-us/article/minecraft-26-3-pre-release-2),
|
||||
[Fabric](https://fabricmc.net/develop/),
|
||||
[version Fabric API](https://modrinth.com/mod/fabric-api/version/o9uChmGq).
|
||||
|
||||
## Construire et lancer
|
||||
|
||||
```sh
|
||||
git clone https://git.botsu.net/koka/sanctuary-beta.git
|
||||
cd sanctuary-beta
|
||||
./gradlew check build assemblePack
|
||||
```
|
||||
|
||||
`check` inclut les tests de forme et le serveur de test automatisé de Fabric,
|
||||
qui charge le vrai preset Sanctuary dans un monde de développement neuf.
|
||||
Les scénarios couverts et les essais multijoueurs restant à effectuer sont
|
||||
décrits dans [Validation](docs/testing.md).
|
||||
|
||||
Résultats :
|
||||
|
||||
- `mods/sanctuary/build/libs/sanctuary-0.1.0-alpha.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).
|
||||
|
||||
Le pack peut aussi être exporté en `.mrpack` avec packwiz pour une importation
|
||||
dans un lanceur compatible ; la commande est indiquée dans ce même guide.
|
||||
|
||||
Pour les essais réguliers, la même instance Prism suit un canal packwiz stable
|
||||
et se synchronise avant chaque lancement. La procédure de publication et de
|
||||
raccordement est dans [Distribution Prism](docs/packwiz.md).
|
||||
|
||||
Lancer le client de développement :
|
||||
|
||||
```sh
|
||||
./gradlew :sanctuary:runClient
|
||||
```
|
||||
|
||||
Créer un **nouveau monde**, puis sélectionner le type de monde **Sanctuary**.
|
||||
L'installation du mod seule ne transforme pas un monde Minecraft ordinaire.
|
||||
|
||||
Pour un serveur de développement, lancer `./gradlew :sanctuary:runServer`, puis
|
||||
suivre les indications du jeu concernant son EULA. Utiliser un dossier de monde
|
||||
neuf et choisir dans `mods/sanctuary/run/server.properties` :
|
||||
|
||||
```properties
|
||||
level-type=sanctuary:sanctuary
|
||||
level-name=sanctuary-dev
|
||||
level-seed=42
|
||||
```
|
||||
|
||||
Relancer ensuite la même commande. Ne pas remplacer le générateur d'une
|
||||
sauvegarde 26.2 : aucun outil de migration de ses chunks n'est livré ici.
|
||||
|
||||
## Organisation
|
||||
|
||||
| Chemin | Responsabilité |
|
||||
| --- | --- |
|
||||
| `mods/sanctuary/` | Code, ressources et tests du mod Sanctuary |
|
||||
| `packwiz/` | Manifeste source, dépendances distantes et hashes |
|
||||
| `ressources-pack/` | Sources graphiques existantes et futurs resource packs |
|
||||
| `shaders-pack/` | Sources et réglages de shaders à venir |
|
||||
| `docs/` | Vision, backlog, migration, génération et validation |
|
||||
| `.gitea/ISSUE_TEMPLATE/` | Modèles de tickets fonctionnalités et bugs |
|
||||
| `.gitea/workflows/` | Vérification sur un runner Gitea compatible |
|
||||
|
||||
It's Alive !, Only Fun et Master Key restent des modules autonomes prévus par la
|
||||
vision. Leurs sources 26.2 ne sont pas copiées dans ce socle. Fabric API est la
|
||||
seule dépendance de gameplay actuellement distribuée avec le mod.
|
||||
|
||||
## Travailler par tickets
|
||||
|
||||
Un ticket décrit un comportement à obtenir ou un bug à reproduire. On livre
|
||||
un petit incrément, on le vérifie en jeu si nécessaire, puis on ajuste avec
|
||||
le ticket suivant. Pour le terrain, joindre la seed et les coordonnées aide
|
||||
à retrouver exactement le problème. Voir [Contribuer](CONTRIBUTING.md).
|
||||
|
||||
Le [backlog initial](docs/backlog.md) contient des propositions locales, pas des
|
||||
issues déjà publiées. Le prochain travail après ce socle est le réglage de
|
||||
l'île, puis le placement de continents et leur ouverture sans écraser les
|
||||
constructions des joueurs.
|
||||
|
||||
## Licence et crédits
|
||||
|
||||
Sanctuary conserve la licence **GPL-3.0-or-later** du projet 26.2. Voir
|
||||
[LICENSE](LICENSE) et [provenance et crédits](THIRD_PARTY_NOTICES.md).
|
||||
Projet communautaire indépendant de Mojang Studios et Microsoft.
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
# Provenance et crédits
|
||||
|
||||
Le code Sanctuary est distribué sous GPL-3.0-or-later, comme la version 26.2 dont
|
||||
la génération est reprise. Voir `LICENSE` et `docs/migration-26.2.md` pour la
|
||||
provenance détaillée et le périmètre du portage.
|
||||
|
||||
Minecraft appartient à Mojang Studios / Microsoft. Sanctuary est un projet
|
||||
communautaire indépendant. Le dépôt ne redistribue pas le jeu.
|
||||
|
||||
Le biome de départ reprend la configuration de la forêt vanilla 26.3-pre-2,
|
||||
adaptée pour une île finie. Les références de blocs, végétation et minerais
|
||||
sont résolues par Minecraft ; leurs textures et modèles ne sont pas inclus.
|
||||
|
||||
Fabric Loader : Apache-2.0. Fabric API : Apache-2.0. Fabric Loom et le wrapper
|
||||
Gradle : Apache-2.0. Ces projets conservent leurs auteurs, notices et licences.
|
||||
Les dépendances sont résolues depuis leurs distributions officielles.
|
||||
|
||||
Le fichier préexistant `ressources-pack/helloworld/assets/minecraft_title.png`
|
||||
est conservé comme source graphique fournie par le propriétaire du dépôt.
|
||||
Il n'est pas encore installé dans le pack généré. Sa provenance et son adaptation
|
||||
à l'écran titre 26.3 seront précisées dans le ticket d'identité visuelle.
|
||||
|
||||
Les resource packs, shaders et mods communautaires envisagés dans la vision
|
||||
ne sont pas inclus automatiquement. Chaque ajout aura une version, une source,
|
||||
un hash et les crédits de sa distribution.
|
||||
@@ -0,0 +1,20 @@
|
||||
plugins {
|
||||
id 'base'
|
||||
id 'net.fabricmc.fabric-loom' version "${loom_version}" apply false
|
||||
}
|
||||
|
||||
tasks.named('build') { dependsOn(':sanctuary:build') }
|
||||
tasks.named('check') { dependsOn(':sanctuary:check', 'verifyPack') }
|
||||
|
||||
tasks.register('verifyPack', Exec) {
|
||||
group = 'verification'
|
||||
description = 'Verify pinned pack dependencies, versions and index hashes.'
|
||||
commandLine('python3', 'scripts/pack.py', 'check')
|
||||
}
|
||||
|
||||
tasks.register('assemblePack', Exec) {
|
||||
group = 'distribution'
|
||||
description = 'Assemble a local packwiz pack including the built Sanctuary mod.'
|
||||
dependsOn(':sanctuary:build', 'verifyPack')
|
||||
commandLine('python3', 'scripts/pack.py', 'assemble')
|
||||
}
|
||||
+166
@@ -0,0 +1,166 @@
|
||||
# Backlog de démarrage
|
||||
|
||||
Ce fichier prépare les premiers tickets à publier sur le Git du projet. **Aucun identifiant ci-dessous n'est un numéro d'issue distante et aucun ticket n'est présumé publié.** Les identifiants `BOOT-*` et `WG-*` servent seulement à relier les travaux tant que les issues n'existent pas.
|
||||
|
||||
Le [document de vision](vision.md) conserve les idées à long terme. Ce backlog organise uniquement le socle et les premiers incréments de génération. Une fonctionnalité n'est considérée comme livrée que lorsque son résultat et sa validation figurent dans le dépôt ou dans son issue.
|
||||
|
||||
## Premier jalon : un monde Sanctuary jouable
|
||||
|
||||
État au 8 septembre 2026 : **BOOT-01 et WG-01 sont réalisés** dans le premier
|
||||
commit. Le prototype WG-02 et l'initialisation du spawn WG-03 sont implémentés
|
||||
et passent les tests serveur décrits dans [Validation](testing.md). Les essais
|
||||
visuels, multijoueurs et de redémarrage indiqués dans ce document restent à
|
||||
faire avant de clôturer tous leurs critères. WG-04 à WG-07 restent proposés.
|
||||
|
||||
Résultat visé : un client et un serveur Fabric compatibles peuvent ouvrir un monde Sanctuary, plusieurs joueurs y arrivent sur une même île sûre et les chunks extérieurs restent vides. La génération est reproductible et ses limites sont documentées.
|
||||
|
||||
### BOOT-01 — Initialiser le dépôt et la construction Fabric
|
||||
|
||||
**But :** disposer d'une base clonable et vérifiable pour travailler par tickets.
|
||||
|
||||
**Critères d'acceptation :**
|
||||
|
||||
- Les versions réellement utilisées de Minecraft, Java, Fabric Loader, Fabric API et de l'outillage sont fixées et documentées.
|
||||
- La cible souhaitée 26.3 est distinguée de la version exécutée si sa disponibilité oblige à utiliser une version de développement ou à différer la migration.
|
||||
- Une commande reproductible construit le mod ; le README explique le lancement et les prérequis.
|
||||
- Les métadonnées identifient Sanctuary sans annoncer les fonctionnalités de la vision comme déjà présentes.
|
||||
- Le dépôt contient les consignes de contribution et des modèles de tickets ; les binaires, caches et sauvegardes de jeu ne sont pas suivis par accident.
|
||||
|
||||
### WG-01 — Auditer le générateur Sanctuary 26.2
|
||||
|
||||
**But :** identifier précisément le code et les ressources utiles avant de les adapter.
|
||||
|
||||
**Critères d'acceptation :**
|
||||
|
||||
- Les fichiers, commits ou références historiques consultés sont cités dans une note d'audit.
|
||||
- La note explique l'algorithme de l'île, le vide, le point d'apparition, les dépendances et les paramètres structurants.
|
||||
- Les éléments réutilisables, les défauts connus et les changements d'API de la cible sont séparés.
|
||||
- La réutilisation de ressources est accompagnée de leur origine et de leur licence connue ; les inconnues sont explicitement notées.
|
||||
- L'audit n'installe ni ne déploie l'ancienne version et ne modifie pas ses sauvegardes.
|
||||
|
||||
### WG-02 — Enregistrer un monde Sanctuary avec île principale et vide
|
||||
|
||||
**Dépendances :** BOOT-01, WG-01.
|
||||
|
||||
**But :** créer le premier terrain identifiable comme Sanctuary.
|
||||
|
||||
**Critères d'acceptation :**
|
||||
|
||||
- Une procédure documentée permet de créer un monde utilisant le générateur Sanctuary sur la version testée.
|
||||
- Une île principale est générée au point de départ prévu ; ses coordonnées, son altitude et ses dimensions sont explicites.
|
||||
- Au-delà de son emprise, les chunks de contrôle sont vides, sans fondation ni terrain vanilla inattendu.
|
||||
- Une même seed et une même configuration donnent les mêmes blocs aux positions de contrôle.
|
||||
- Les jointures entre chunks voisins ne créent pas de fissures ou de parois artificielles dues à une discontinuité du calcul.
|
||||
- Un monde Minecraft ordinaire reste créable sans sélectionner Sanctuary.
|
||||
|
||||
### WG-03 — Assurer une arrivée commune et un redémarrage sûr
|
||||
|
||||
**Dépendance :** WG-02.
|
||||
|
||||
**But :** éviter qu'un nouveau joueur apparaisse dans le vide et vérifier le comportement multijoueur.
|
||||
|
||||
**Critères d'acceptation :**
|
||||
|
||||
- Le spawn du monde se situe sur une surface stable de l'île, avec l'espace nécessaire au joueur.
|
||||
- Deux nouveaux joueurs arrivent dans la zone de départ commune, sans traverser le sol ni apparaître hors de l'île.
|
||||
- Le comportement après une mort sans lit est vérifié ; l'ajout ultérieur des Backrooms n'est pas requis pour ce ticket.
|
||||
- Après sauvegarde et redémarrage, le générateur, la seed et le spawn sont conservés.
|
||||
- Un scénario manuel reproductible couvre le client et le serveur dédié, avec la version et la seed utilisées.
|
||||
|
||||
## Deuxième jalon : des continents d'essai reproductibles
|
||||
|
||||
Ce jalon fournit un outil de développement du terrain. L'interface d'expansion et son coût collectif viendront dans des tickets distincts, lorsque la génération sera satisfaisante.
|
||||
|
||||
### WG-04 — Décrire et placer un continent d'essai
|
||||
|
||||
**Dépendance :** WG-03.
|
||||
|
||||
**But :** pouvoir générer une terre suspendue à une direction, une distance et une taille explicites.
|
||||
|
||||
**Critères d'acceptation :**
|
||||
|
||||
- Un schéma décrit au minimum l'identifiant, le centre ou la direction et la distance, les dimensions, l'altitude et la seed du continent.
|
||||
- Le premier champ de recherche utilise huit directions climatiques : nord froid,
|
||||
sud chaud, ouest sec, est humide ; les diagonales combinent ces tendances.
|
||||
Chaque candidat expose sa signature et sa distribution de température/humidité.
|
||||
- La sélection climatique ne force ni la forme du terrain ni des quotas de
|
||||
ressources. Un relevé après génération identifie les stocks réellement produits,
|
||||
son emprise, sa version et son niveau de complétude. Voir [le contrat](expansion.md).
|
||||
- Une configuration ou commande de développement documentée crée un continent reproductible.
|
||||
- Les paramètres invalides et les chevauchements interdits sont rejetés avec un message compréhensible.
|
||||
- Des limites de taille et de coût de génération sont définies à partir d'une mesure réelle.
|
||||
- Le continent et ses paramètres restent identiques après rechargement du monde.
|
||||
|
||||
### WG-05 — Ouvrir une expansion sans écraser l'existant
|
||||
|
||||
**Dépendance :** WG-04.
|
||||
|
||||
**But :** garantir que l'évolution du monde préserve les constructions et l'exploration.
|
||||
|
||||
**Critères d'acceptation :**
|
||||
|
||||
- La politique envers les chunks déjà générés est décidée et documentée avant l'implémentation : refus, réservation préalable ou mécanisme explicite de modification.
|
||||
- Une nouvelle expansion ne remplace aucun bloc joueur silencieusement.
|
||||
- Répéter la même demande ne crée pas de duplicata et ne décale pas les continents existants.
|
||||
- L'état des expansions survit à un redémarrage et contient une version de format permettant de prévoir les migrations.
|
||||
- Une interruption entre réservation et génération est simulée ; la reprise ou le refus reste cohérent et expliqué.
|
||||
|
||||
### WG-06 — Ajouter reliefs, perforations et eaux retenues
|
||||
|
||||
**Dépendance :** WG-04 ; combiner avec WG-05 avant l'usage sur une sauvegarde jouée.
|
||||
|
||||
**But :** donner aux continents une géographie reconnaissable au-delà d'une simple masse de pierre.
|
||||
|
||||
**Critères d'acceptation :**
|
||||
|
||||
- Un jeu de seeds de référence montre des reliefs, montagnes, ravins et perforations traversantes.
|
||||
- Des bassins accueillent lacs ou océans, et un premier type de rivière flottante est démontré.
|
||||
- Pour l'île initiale tempérée et légèrement humide : privilégier petits étangs,
|
||||
lacs de surface, rivière et berges propices à la canne à sucre, avec quelques
|
||||
plages plus sèches. Pas de grands bassins souterrains remplis d'eau.
|
||||
- L'eau ne se répand pas de manière incontrôlée dans le vide lors du chargement et des mises à jour de blocs du scénario testé.
|
||||
- Les profils du dessous et les bords du continent sont inspectés visuellement depuis les airs.
|
||||
- La génération est mesurée sur une emprise et une machine indiquées ; les valeurs observées sont consignées, sans annoncer un objectif de performance non mesuré.
|
||||
|
||||
**Incrément île initiale — alpha.4 :** première hydrologie de surface, documentée
|
||||
dans [Génération](worldgen.md). Ce travail prépare les eaux des continents sans
|
||||
clore WG-06 : océans, grandes rivières et cascades restent à développer.
|
||||
|
||||
### WG-07 — Introduire un premier biome distinct et une structure
|
||||
|
||||
**Dépendance :** WG-06.
|
||||
|
||||
**But :** valider les points d'extension avant d'ajouter un grand catalogue de contenus.
|
||||
|
||||
**Critères d'acceptation :**
|
||||
|
||||
- Le ticket choisit un premier biome, par exemple le Black Desert, avec des règles de surface et d'ambiance explicites.
|
||||
- Une petite structure de référence se place sur un terrain compatible, sans flotter accidentellement ni détruire une construction existante.
|
||||
- Le lien entre biome, végétation, ressources et règles de placement est documenté.
|
||||
- La reprise du catalogue TerraMix natif d'Another World 26.2 est évaluée séparément ; « plus de cent biomes » reste une ambition tant que son adaptation n'est pas vérifiée.
|
||||
- Les limites des futures zones Lost Cities et des structures uniques sont identifiées sans imposer leur livraison dans ce ticket.
|
||||
|
||||
## Réserve de thèmes futurs
|
||||
|
||||
Ces thèmes servent à retrouver la vision, pas à demander leur implémentation immédiate. On en extrait un ticket seulement lorsqu'il devient utile au prochain incrément jouable.
|
||||
|
||||
| Thème | Contenus à découper plus tard |
|
||||
| --- | --- |
|
||||
| Distribution et apparence | packwiz, mods communautaires, resource packs, shaders, icône, chargement, crédits et attributions |
|
||||
| Expansion collective | deposit boxes, objectifs de production, ordinateur d'expansion, ouvertures persistantes |
|
||||
| Progression | capacités, XP, inventaire, prestiges débloquant des slots de factions, recettes et advancements, capes et familiers |
|
||||
| Économie | gemmes, mailbox, shop, offres horaires, bourse du navet (achat à la loterie du dimanche, revente au shop pendant la semaine), black market, catalogue, coffre-fort et drill |
|
||||
| Groupes et métiers | couleurs, équipes temporaires, factions et slots liés aux prestiges, cloches et bannières, villageois et copper golems |
|
||||
| Production et construction | convoyeurs, stockage, terminaux, ordinateur 8 bits, vein mining/building, plans et prefabs |
|
||||
| Dimensions | cavernes, Alpha, Backrooms, indoors, salles secrètes et récupération des objets perdus |
|
||||
| Faune et combats | zombies, fantômes, baleine, creepers, poules rares, Mooblooms, noms, armes et explosifs |
|
||||
| Mobilité | waystones, téléporteurs, ziplines, grappin, aéronefs, Magic Carpet et interactions physiques |
|
||||
| Temps et histoire | temps réel, calendrier, événements, loterie, étoiles, constellations, cube et sept boules |
|
||||
| Objets et surprises | caméra, œil d'araignée révélant les niveaux de lumière, disque blanc, chunky, particuleur, lucky blocks et lootboxes |
|
||||
| It's Alive ! | agriculture localisée, ustensiles, recettes, fermentation, affinage et pages secrètes |
|
||||
| Only Fun | interactions potaches, chanvre, anniversaires et intégration aux événements |
|
||||
| Master Key | permissions communes, configuration, diagnostic et réparation du serveur |
|
||||
|
||||
## Définition pratique d'un ticket terminé
|
||||
|
||||
Un ticket contient un résultat observable, un périmètre limité et des critères d'acceptation vérifiables. Sa conclusion indique le comportement livré, la version testée, les vérifications réellement exécutées et les limitations qui subsistent. Les nouveaux besoins découverts deviennent de nouveaux tickets plutôt que des ajouts implicites à tous les systèmes.
|
||||
@@ -0,0 +1,202 @@
|
||||
# Climats, expansion et ressources
|
||||
|
||||
Ce document fixe les décisions de conception pour la suite de Sanctuary. Le
|
||||
registre persistant des continents, leur déblocage et leur génération dans les
|
||||
huit directions restent à implémenter. Le relevé de ressources décrit plus bas
|
||||
est un outil de développement séparé, exécuté dans le monde jetable des GameTests.
|
||||
|
||||
## Les huit directions climatiques
|
||||
|
||||
Sanctuary Island constitue le point de départ **tempéré, légèrement humide**.
|
||||
Le nord conduit vers le froid, le sud vers le chaud, l'ouest vers le sec et
|
||||
l'est vers l'humide. Les diagonales combinent ces tendances.
|
||||
|
||||
| Direction | Axe Minecraft depuis Sanctuary | Tendance climatique |
|
||||
| --- | --- | --- |
|
||||
| Nord | `−Z` | Froid |
|
||||
| Nord-est | `+X, −Z` | Froid et humide |
|
||||
| Est | `+X` | Humide |
|
||||
| Sud-est | `+X, +Z` | Chaud et humide |
|
||||
| Sud | `+Z` | Chaud |
|
||||
| Sud-ouest | `−X, +Z` | Chaud et sec |
|
||||
| Ouest | `−X` | Sec |
|
||||
| Nord-ouest | `−X, −Z` | Froid et sec |
|
||||
|
||||
Ces directions définissent des familles de climats, pas huit biomes uniformes.
|
||||
Le relief, l'altitude, les variations locales et la graine doivent conserver
|
||||
des géographies distinctes à l'intérieur de chaque continent. Les amplitudes
|
||||
de température et d'humidité, leur évolution avec la distance et les seuils
|
||||
de sélection des biomes feront partie du ticket de génération des continents.
|
||||
Les ressources et cultures futures, notamment celles d'It's Alive !, pourront
|
||||
ainsi demander l'exploration de plusieurs climats.
|
||||
|
||||
L'île initiale utilise encore un biome de départ spécifique. Une température
|
||||
ou une humidité du biome ne constitue pas à elle seule une carte climatique
|
||||
directionnelle : cette carte et la sélection des biomes des continents restent
|
||||
des travaux distincts.
|
||||
|
||||
## Eau de surface et exploration
|
||||
|
||||
L'île de départ doit pouvoir accueillir naturellement des étangs et lacs de
|
||||
surface, des berges propices à la canne à sucre et, lorsque le relief s'y prête,
|
||||
des rivières. L'eau s'inscrit dans les creux et les chemins du terrain existant.
|
||||
Les rivières flottantes et les cascades doivent tenir compte de la continuité
|
||||
de leur lit et du vide sous les îles.
|
||||
|
||||
Le contrat ne demande ni un bassin souterrain imposé pour obtenir un stock
|
||||
d'eau, ni une cuvette ajoutée systématiquement à une position fixe. Une surface
|
||||
d'eau doit reposer sur un fond naturel et disposer de berges cohérentes ; un
|
||||
volume rocheux fermé sous terre ne devient pas un lac de surface. Les plantations
|
||||
de canne à sucre doivent respecter leurs conditions de placement, sans modifier
|
||||
la carte pour atteindre une quantité prédéterminée.
|
||||
|
||||
Un étang, un lac, une rivière et un bassin souterrain sont donc des résultats
|
||||
différents à vérifier. Un prototype d'étangs ne valide pas automatiquement les
|
||||
rivières ou l'ensemble de l'hydrologie. Le détail effectivement livré à chaque
|
||||
version reste décrit dans [Génération](worldgen.md), avec ses limites.
|
||||
|
||||
## Observer les ressources après génération
|
||||
|
||||
La génération produit le monde ; l'économie apprend ensuite ce que le monde
|
||||
contient. On ne force pas un nombre de blocs de pierre, de bûches ou de minerais
|
||||
pour faire correspondre la géographie à un budget. Un relevé peut constater
|
||||
qu'une ressource est absente, rare ou abondante pour une graine donnée. Ce constat
|
||||
ne déclenche pas de remplissage ni de remplacement de blocs.
|
||||
|
||||
Le relevé initial servira de référence pour préparer l'économie et la progression
|
||||
collective. Il distingue les blocs présents, les ressources réellement accessibles
|
||||
aux joueurs, les ressources renouvelables et les objets obtenus en les récoltant.
|
||||
Un bloc de minerai n'est pas un nombre garanti d'objets : outils, enchantements,
|
||||
butins et transformations relèvent d'autres règles. Les blocs de bois ne comptent
|
||||
pas des arbres, et un comptage de blocs d'eau ne mesure pas un débit de rivière.
|
||||
|
||||
Une observation dans une petite zone reste une observation locale. On ne peut
|
||||
pas multiplier son résultat pour annoncer la quantité totale de l'île. Les
|
||||
rapports de continents devront conserver leur propre graine, version, emprise
|
||||
et état de génération, afin d'additionner seulement des régions connues et
|
||||
disjointes lorsque le besoin économique sera défini.
|
||||
|
||||
## Relevé de développement disponible
|
||||
|
||||
`ResourceSurveyDiagnostics`, dans `src/gametest`, lit les chunks au statut
|
||||
`FULL`, après le terrain, les matériaux et les décorations. Il ne figure pas
|
||||
dans le JAR Sanctuary distribué et n'ajoute aucune commande joueur ou admin.
|
||||
La tâche repart d'un monde GameTest neuf, jamais d'une sauvegarde personnelle.
|
||||
|
||||
Le relevé standard inspecte **neuf chunks**, soit un carré de 48×48 blocs autour
|
||||
du chunk du spawn naturel, sur toute la hauteur de la dimension. Il prépare
|
||||
d'abord ces chunks et un halo voisin, puis compte exactement chaque cellule des
|
||||
palettes de leurs sections. La grille éloignée du framework est exclue et sa
|
||||
distance est vérifiée. Aucun objet posé, inventaire, contenu de coffre ou mob
|
||||
n'est inclus dans ces comptes de blocs.
|
||||
|
||||
```sh
|
||||
# Contrôle standard : neuf chunks inspectés, total local uniquement.
|
||||
./gradlew :sanctuary:runGameTest -PsanctuaryResourceSurvey=sample --console=plain
|
||||
|
||||
# Relevé de toute l'enveloppe déclarée de Sanctuary, sur un monde jetable neuf.
|
||||
./gradlew :sanctuary:runGameTest -PsanctuaryTestSeed=0 -PsanctuaryResourceSurvey=island --console=plain
|
||||
|
||||
# Désactiver seulement le relevé pour un autre diagnostic.
|
||||
./gradlew :sanctuary:runGameTest -PsanctuaryResourceSurvey=off --console=plain
|
||||
```
|
||||
|
||||
Les modes sont transmis à la propriété JVM de test
|
||||
`sanctuary.test.resourceSurvey`. Le mode `island` couvre **1 444 chunks** :
|
||||
`X` et `Z` de **−304 inclus à 304 exclu**, correspondant à l'enveloppe de terrain
|
||||
de rayon 288 et une marge de décoration de 16 blocs. Un halo d'un chunk porte
|
||||
la préparation à **1 600 chunks FULL** ; seuls les 1 444 chunks demandés sont
|
||||
comptés. Ce relevé complet est plus coûteux et ne s'exécute pas par défaut.
|
||||
La marge devra être revue si une future génération peut produire des structures
|
||||
ou décorations au-delà de cette enveloppe.
|
||||
|
||||
Le JSON est écrit dans
|
||||
`mods/sanctuary/build/run/gameTest/diagnostics/resource-survey-seed-<graine>-<mode>.json`.
|
||||
Il contient :
|
||||
|
||||
- La graine, la version Minecraft, le preset et les réglages de génération,
|
||||
ainsi que `generator.version = sanctuary@<version du mod>`.
|
||||
- Le spawn naturel, les bornes exactes en blocs et chunks, les hauteurs de la
|
||||
dimension et du bruit, les coordonnées de chaque chunk inspecté au statut
|
||||
`FULL`, les dates UTC et les durées de préparation et de comptage.
|
||||
- Le total de toutes les cellules, les cellules d'air, les blocs non vides,
|
||||
puis les comptes par identifiant de registre et par état complet, propriétés
|
||||
comprises, par exemple `minecraft:oak_log[axis=y]`.
|
||||
- Des agrégats explicites pour la pierre, les bûches, les feuilles, les minerais
|
||||
de charbon, fer, cuivre, or, redstone, lapis, diamant et émeraude, l'eau, la
|
||||
lave, le sable, le gravier, l'argile, la canne à sucre et les sols.
|
||||
- La complétude dans les bornes demandées, la couverture ou non de l'enveloppe
|
||||
déclarée de l'île et l'absence d'extrapolation. Aucun mode ne prétend inventorier
|
||||
tout le monde Minecraft, ses autres dimensions ou ses futures expansions.
|
||||
|
||||
Les variantes ordinaires et deepslate des minerais sont détaillées. Les agrégats
|
||||
fondés sur des tags indiquent leur définition et les membres observés. Certains
|
||||
groupes se recouvrent, comme `stone` et `base_stone_overworld`, ou les blocs
|
||||
d'eau et les blocs contenant un état d'eau : ils ne doivent pas être additionnés.
|
||||
Les sommes des identifiants et des états doivent chacune être exactement égales
|
||||
au volume inspecté, y compris l'air ; une incohérence fait échouer le GameTest.
|
||||
|
||||
Ce rapport est un instantané après génération, avec l'heure du jeu observée.
|
||||
Il ne prédit pas les pousses, les récoltes, les pertes ni la production future.
|
||||
Le format JSON est versionné pour l'outil de test ; ce n'est pas encore un
|
||||
format de sauvegarde serveur ou un registre persistant de l'économie.
|
||||
|
||||
## Référence mesurée — graine 0, alpha.3
|
||||
|
||||
Le **8 septembre 2026**, un relevé complet de l'enveloppe déclarée a été effectué
|
||||
sur un monde neuf de graine **0**, avec `sanctuary@0.1.0-alpha.3` et les réglages
|
||||
`sanctuary:sanctuary_384`. Il couvre **1 444 chunks FULL**, de **−304 inclus à
|
||||
304 exclu en X et Z**, et de **0 inclus à 384 exclu en Y**. Le volume inspecté
|
||||
est donc exactement **608 × 608 × 384 = 141 950 976 cellules**.
|
||||
|
||||
| Ressource observée | Nombre de blocs |
|
||||
| --- | ---: |
|
||||
| Pierre (`minecraft:stone`) | 7 355 184 |
|
||||
| Bûches, tag `minecraft:logs` | 10 946 |
|
||||
| Minerai de charbon | 101 399 |
|
||||
| Minerai de fer | 67 198 |
|
||||
| Minerai de cuivre | 2 352 |
|
||||
| Minerai de diamant | 6 |
|
||||
| Minerais d'or, redstone, lapis et émeraude | 0 chacun |
|
||||
| Gravier | 121 044 |
|
||||
| Terre et blocs d'herbe | 728 630 |
|
||||
| Feuilles | 117 735 |
|
||||
| Eau, y compris les blocs contenant de l'eau | 0 |
|
||||
| Canne à sucre | 0 |
|
||||
| Sable, argile et lave | 0 chacun |
|
||||
|
||||
Les bûches comprennent **8 765 bûches de chêne** et **2 181 de bouleau**.
|
||||
Les minerais présents sont ici leurs variantes ordinaires ; aucune variante
|
||||
deepslate n'a été observée. Toutes catégories confondues, le relevé compte
|
||||
**8 584 514 blocs non vides** et **133 366 462 cellules d'air**. Ce sont les
|
||||
quantités observées dans cette graine et cette version, sans quota ni correction
|
||||
du terrain. Les zéros d'eau et de canne à sucre confirment que l'hydrologie
|
||||
demandée reste à réaliser ; ils ne constituent pas une cible à conserver.
|
||||
|
||||
La source détaillée est le fichier local non versionné
|
||||
`mods/sanctuary/build/run/gameTest/diagnostics/resource-survey-seed-0-island.json`,
|
||||
terminé à **11:34:08 UTC**. Une lecture indépendante du JSON a confirmé les
|
||||
1 444 coordonnées de chunks distinctes, les sommes par chunk, les agrégats et
|
||||
l'égalité exacte entre le volume, le compte par identifiant et le compte par
|
||||
état complet. Les huit tests requis du serveur ont réussi pendant cette exécution.
|
||||
|
||||
La préparation des chunks a pris **782 659 ms**, soit **13 min 2,659 s** ; le
|
||||
comptage et la préparation des données à sérialiser ont pris **135 ms**. Cette
|
||||
mesure locale a été effectuée sur un **Apple M1 avec 8 Go de mémoire**, avec un
|
||||
essai client concurrent pendant environ 23 secondes. Elle décrit une machine
|
||||
chargée et cette exécution, pas une performance garantie sur d'autres machines.
|
||||
Le temps de préparation est distinct du coût du comptage par palettes.
|
||||
|
||||
## Contrat du prochain ticket d'expansion
|
||||
|
||||
Avant toute activation de continent, enregistrer côté serveur son identifiant,
|
||||
sa direction parmi les huit, son centre, son emprise, sa graine dérivée, sa
|
||||
version de génération, son profil climatique et son état de déblocage.
|
||||
Le relevé des ressources intervient après la génération effective de sa région,
|
||||
avec une complétude explicite, indépendamment des contributions qui l'ont ouverte.
|
||||
|
||||
Les zones déjà explorées dans le vide peuvent contenir des chunks sauvegardés
|
||||
ou des constructions. L'activation exige donc un contrat de réservation ou de
|
||||
matérialisation qui les protège ; elle ne peut pas régénérer silencieusement
|
||||
les chunks existants. Les règles de contribution, distances, tailles,
|
||||
réservations, migrations et prix ne sont pas décidées par ce diagnostic.
|
||||
@@ -0,0 +1,154 @@
|
||||
# Réutiliser la génération Sanctuary 26.2
|
||||
|
||||
Audit de source du 8 septembre 2026, effectué en lecture seule dans le dépôt voisin
|
||||
`../26.2`, à la révision `da61a3b1ec4fe161a9b5ffaab0fa3111238a35b6`
|
||||
(pack `26.2.0-alpha.242`). Les chemins ci-dessous sont relatifs à ce dépôt historique.
|
||||
Ce document décrit ce qui existe dans 26.2 ; il ne prétend pas que ces éléments sont
|
||||
déjà portés dans Sanctuary Beta.
|
||||
|
||||
## Conclusion pour le premier lot
|
||||
|
||||
La base de terrain est réutilisable : du bruit Minecraft lié à la seed, une enveloppe
|
||||
verticale d'îles flottantes, puis des masques de densité. En revanche, la génération
|
||||
26.2 n'est pas un monde initialement vide avec des continents déverrouillables.
|
||||
L'archipel extérieur est généré automatiquement au-delà d'un petit anneau de vide.
|
||||
Le premier lot doit donc borner explicitement l'île centrale et garantir un départ
|
||||
sûr, avant de construire le système persistant d'expansion collective.
|
||||
|
||||
## Carte des sources récupérables
|
||||
|
||||
| Élément | Source 26.2 | Réutilisation |
|
||||
| --- | --- | --- |
|
||||
| Bruit de terrain | `sanctuary/src/main/resources/data/sanctuary/worldgen/density_function/base_3d_noise.json` | `minecraft:old_blended_noise`, dépendant de la seed ; aucun bloc propre au mod. |
|
||||
| Enveloppe flottante | `sanctuary/src/main/resources/data/sanctuary/worldgen/density_function/floating_archipelago.json` | Gradients verticaux combinés au bruit précédent. |
|
||||
| Composition finale | `sanctuary/src/main/resources/data/sanctuary/worldgen/density_function/final_density.json` | Référence pour l'interpolation et le mélange ; remplacer le comportement extérieur infini. |
|
||||
| Masques et relief | `sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/worldgen/SanctuaryDensityFunctions.java` | `AnnularVoidMask`, `TectonicMassifDensity`, `TectonicVoidDensity` ; séparer les fonctions utiles du code inactif. |
|
||||
| Réglages de dimension | `sanctuary/src/main/resources/data/sanctuary/dimension_type/sanctuary.json` | Plage verticale Y=0–383, lumière et ciel ; vérifier les codecs de la version cible. |
|
||||
| Réglages de bruit | `sanctuary/src/main/resources/data/sanctuary/worldgen/noise_settings/sanctuary.json` | Air comme fluide par défaut, aquifères désactivés, niveau marin -64 ; ne pas importer ses règles de surface en bloc. |
|
||||
| Preset | `sanctuary/src/main/resources/data/sanctuary/worldgen/world_preset/sanctuary.json` | Générateur `minecraft:noise`, biomes `minecraft:multi_noise` ; garder seulement les dimensions nécessaires au lot. |
|
||||
| Recherche du spawn | `sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/world/SanctuarySpawn.java` | Recherche d'un sol naturel autour de l'origine ; corriger les limites décrites plus bas. |
|
||||
| Lacs | `sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/worldgen/feature/SanctuaryWaterLakeFeature.java` | Extension de `LakeFeature`, avec quelques cannes à sucre ; utilisable dans un lot hydrologie. |
|
||||
| Ravins | `sanctuary/src/main/resources/data/sanctuary/worldgen/configured_carver/tectonic_ravine.json` | Configuration de carver ; revalider sur des îles finies. |
|
||||
| Prévisualisation | `sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/client/worldgen/SanctuaryBiomePreviewScreen.java` | Référence ultérieure pour visualiser la seed et la distribution des biomes. |
|
||||
|
||||
Le graphe actif 26.2 est approximativement :
|
||||
|
||||
```text
|
||||
old_blended_noise -> floating_archipelago + central_halo_mask
|
||||
|
|
||||
max(tectonic_massifs)
|
||||
|
|
||||
min(void_basins)
|
||||
|
|
||||
blend_density -> interpolated -> squeeze
|
||||
```
|
||||
|
||||
`IslandSpawnDensity` et `RadialVoidMask` restent enregistrés en Java mais ne font
|
||||
plus partie du graphe actif. L'ancienne forme sculptée utilise des bruits de hachage
|
||||
sans seed de monde passée à la fonction ; elle n'est pas un substitut fidèle au
|
||||
terrain procédural actuel.
|
||||
|
||||
## Terramix et Black Desert
|
||||
|
||||
Dans ce dépôt, **Terramix est le nom du catalogue natif de 100 biomes d'Another
|
||||
World**, répartis en dix familles de dix. Ce n'est pas une dépendance Fabric externe
|
||||
à ajouter automatiquement au pack.
|
||||
|
||||
- Catalogue auteur : `anotherworld/src/main/resources/data/anotherworld/sanctuary_biome_catalog.json`.
|
||||
- Lecture et familles climatiques : `anotherworld/src/main/java/fr/koka99cab/sanctuary26/anotherworld/registry/SanctuaryBiomeCatalog.java`.
|
||||
- Fabrication des biomes, végétations et géologies : `anotherworld/build.gradle`,
|
||||
notamment la section commençant par « Terramix is a native Sanctuary catalogue ».
|
||||
- Mélange avec les biomes vanilla :
|
||||
`anotherworld/src/main/java/fr/koka99cab/sanctuary26/anotherworld/mixin/MultiNoiseBiomeSourceParameterListPresetMixin.java`.
|
||||
- Black Desert est un biome historique distinct, `anotherworld:black_desert`, avec
|
||||
du sable noir et une géologie sombre. Ses règles de surface se trouvent aussi dans
|
||||
`sanctuary/.../worldgen/noise_settings/sanctuary.json`.
|
||||
|
||||
Le port du seul JSON de bruit complet échouerait sans les blocs, tags et biomes
|
||||
`anotherworld:*` qu'il référence. Préférer d'abord une surface et des biomes vanilla,
|
||||
puis un lot dédié qui préserve les identifiants du catalogue. Le mixin climatique
|
||||
historique modifie le preset Overworld global : son périmètre doit être redéfini
|
||||
pour éviter d'affecter d'autres mondes par simple installation du mod.
|
||||
|
||||
Le changelog indique que Terralith a été retiré en alpha.143 et remplacé par le
|
||||
catalogue natif en alpha.145. Ne pas confondre ces deux noms ni restaurer les anciennes
|
||||
ressources Terralith lors d'un port de Terramix.
|
||||
|
||||
## Limites et défauts à traiter
|
||||
|
||||
1. **L'extérieur n'est pas vide.** Le masque annulaire actif protège un rayon nominal
|
||||
de 96 blocs, entre dans le halo sur 160 blocs et quitte celui-ci à partir de
|
||||
256 blocs sur 96 blocs, avec une distorsion de 32 blocs. Il retrouve donc
|
||||
l'archipel normal vers 352 blocs nominalement. Ce masque ne borne pas un monde.
|
||||
2. **Aucune activation de continents trouvée.** La recherche des sources Java
|
||||
Sanctuary, Another World et Master Key n'a trouvé ni registre de continents
|
||||
déverrouillés, ni commande d'expansion directionnelle. Les « continents » du
|
||||
changelog désignent le relief tectonique naturel. Un format de sauvegarde,
|
||||
des limites de taille et une politique pour les chunks déjà explorés restent
|
||||
à concevoir.
|
||||
3. **Spawn non garanti.** `SanctuarySpawn` ne cherche que dans un rayon de trois
|
||||
chunks autour de l'origine. En cas d'échec il conserve la suggestion vanilla,
|
||||
sans créer de sol sûr. Il sélectionne à nouveau le spawn global à chaque
|
||||
`SERVER_STARTED`, ce qui peut écraser le choix d'un administrateur.
|
||||
4. **Reliefs extérieurs très éloignés.** Les massifs et bassins de vide actifs ont
|
||||
`protected_radius=10000` et `protection_fade=2000`. Les copier autour d'une petite
|
||||
île ne produit donc pas de montagne proche du départ.
|
||||
5. **L'eau n'est pas une hydrologie continentale.** Le réglage emploie de l'air et
|
||||
désactive les aquifères. Les lacs sont des features ponctuelles. Aucun réseau
|
||||
de rivières flottantes ou d'océans suspendus contrôlés n'a été identifié dans
|
||||
les sources examinées.
|
||||
6. **La densité seule n'assure pas le vide final.** Les carvers, décorations et
|
||||
structures constituent des étapes distinctes. Vérifier les chunks terminés,
|
||||
y compris aux frontières de l'île, et borner aussi les placements qui pourraient
|
||||
traverser la limite.
|
||||
7. **Ordre des features sensible.** Le changelog alpha.105 rapporte un blocage
|
||||
« Loading terrain » corrigé en ordonnant la végétation lush de plafond avant
|
||||
celle au sol. Revalider les dépendances entre features lors du port des biomes.
|
||||
|
||||
Ces constats sont issus de la lecture du code et du changelog. Aucun serveur 26.2
|
||||
n'a été lancé ni modifié pendant cet audit.
|
||||
|
||||
## Historique exploitable
|
||||
|
||||
L'historique Git local n'est pas shallow, mais commence par l'import
|
||||
`94644a2` (« Import Sanctuary 26.2 collaboration workspace »). Le générateur principal
|
||||
actuel est déjà présent dans cet import : les étapes plus anciennes ne correspondent
|
||||
donc pas à des commits individuels disponibles dans ce dépôt. Leurs descriptions
|
||||
restent dans `CHANGELOG.md` et `pack/migrations/`.
|
||||
|
||||
| Repère | Intérêt |
|
||||
| --- | --- |
|
||||
| Alpha.104–106 | Introduction des massifs, lacs, ravins et hauteur de dimension 384. |
|
||||
| Alpha.108–111 | Expériences de relief extrême, puis réduction des plateaux et pentes. |
|
||||
| Alpha.116–120 | Protection centrale, passage à l'île procédurale et réduction du halo. |
|
||||
| Alpha.143–148 | Retrait de Terralith, Terramix natif, correction des transitions et prévisualisation. |
|
||||
| Alpha.189 | Provinces climatiques à deux échelles, points MultiNoise vanilla conservés. |
|
||||
| Commit `9c09664` | Port de l'île Indev finie pour la dimension Alpha ; sujet distinct du monde Sanctuary. |
|
||||
|
||||
Les profils extrêmes sont réellement présents dans
|
||||
`sanctuary/design/worldgen/alpha108_mega_tectonics/` et
|
||||
`sanctuary/design/worldgen/alpha110_steep_massifs/`. Les chemins `backups/` cités par
|
||||
le changelog sont des repères historiques ; leur disponibilité n'a pas été vérifiée.
|
||||
|
||||
## Provenance et licences déclarées
|
||||
|
||||
Le `LICENSE` racine et le manifeste Fabric Sanctuary déclarent
|
||||
`GPL-3.0-or-later`. Garder cette provenance avec les sources effectivement reprises.
|
||||
La dimension Alpha possède en plus une notice BSD 3-Clause pour l'algorithme Indev
|
||||
dans `sanctuary/THIRD_PARTY_NOTICES.md` ; elle concerne ce port spécifique.
|
||||
|
||||
`anotherworld/THIRD_PARTY_NOTICES.md` signale des textures Flower Cows dont la
|
||||
permission de redistribution publique est à clarifier. Elles ne sont pas nécessaires
|
||||
au générateur minimal et ne doivent pas être importées avec un lot de terrain.
|
||||
|
||||
## Lots proposés après l'initialisation
|
||||
|
||||
1. Île principale finie dépendante de la seed, vide extérieur, spawn durable et sûr,
|
||||
preset explicite ; validation sur plusieurs seeds et redémarrage serveur.
|
||||
2. Modèle persistant de continents : identifiant, seed, centre, orientation, taille,
|
||||
état verrouillé/déverrouillé et version de génération ; définir le traitement
|
||||
des chunks vides déjà visités avant toute commande d'activation.
|
||||
3. Hydrologie fermée et reliefs : bassins, lacs, rivières, cavités et ravins ; vérifier
|
||||
les bordures de chunks, l'écoulement et la conservation des constructions.
|
||||
4. Terramix et Black Desert avec leur géologie, leurs ressources et une distribution
|
||||
climatique mesurable ; puis structures procédurales et continents thématiques.
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
# Distribution packwiz et Prism
|
||||
|
||||
Sanctuary Beta utilise une seule instance Prism, synchronisée avant chaque
|
||||
lancement par packwiz. Le canal reste à l'adresse :
|
||||
|
||||
```text
|
||||
https://git.botsu.net/koka/sanctuary-beta/raw/branch/packwiz/pack.toml
|
||||
```
|
||||
|
||||
Les sources sont sur la branche de travail du ticket. La branche `packwiz`
|
||||
contient uniquement les manifestes de distribution produits par le build ; les
|
||||
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.
|
||||
|
||||
## Livrer une mise à jour
|
||||
|
||||
Après les vérifications, le commit et le push de la branche du ticket, le script
|
||||
de publication réalise les étapes de release et de canal ci-dessous :
|
||||
|
||||
```sh
|
||||
python3 scripts/publish_pack.py --notes-file chemin/vers/notes.md --asset build/Sanctuary-0.1.0-alpha.4.mrpack
|
||||
```
|
||||
|
||||
Les options sont facultatives. Ce script **publie** sur le Git configuré dans
|
||||
`origin` ; il exige un arbre propre, un HEAD déjà poussé et un JAR dont la version
|
||||
correspond aux sources. Il utilise les identifiants du gestionnaire Git en
|
||||
mémoire, refuse de remplacer une release ou un artefact différent portant la
|
||||
même version, puis vérifie les téléchargements publics et les métadonnées du
|
||||
canal. Il préserve l'historique de `packwiz` et n'utilise pas de push forcé. Il
|
||||
ne lance pas les tests et ne modifie aucune instance de jeu : les vérifications
|
||||
de livraison restent à exécuter avant cette commande.
|
||||
|
||||
La procédure complète, également utilisable manuellement :
|
||||
|
||||
1. Modifier ensemble les versions de `gradle.properties` et `packwiz/pack.toml`.
|
||||
2. Exécuter `./gradlew check build assemblePack` et les vérifications du ticket.
|
||||
3. Préparer les métadonnées de la livraison, avec l'URL exacte du futur JAR :
|
||||
|
||||
```sh
|
||||
python3 scripts/pack.py release https://git.botsu.net/koka/sanctuary-beta/releases/download/v0.1.0-alpha.4/sanctuary-0.1.0-alpha.4.jar
|
||||
```
|
||||
|
||||
4. Pousser le commit source vérifié. Créer une release Gitea correspondant à ce
|
||||
commit et joindre le JAR construit, sans remplacer les artefacts d'une release
|
||||
antérieure. Vérifier que le téléchargement public possède le SHA-256 indiqué
|
||||
dans `build/packwiz-release/mods/sanctuary.pw.toml`.
|
||||
5. Publier le contenu de `build/packwiz-release/` à la racine de la branche
|
||||
`packwiz` dans un seul commit, puis vérifier les fichiers et hashes depuis
|
||||
l'URL publique. Préserver l'historique de ce canal et ne jamais y copier le
|
||||
dossier `build/packwiz/`, qui contient le JAR de développement.
|
||||
6. Tester une installation puis une seconde synchronisation avec le véritable
|
||||
`packwiz-installer` dans un dossier de développement neuf. Le second passage
|
||||
doit laisser le pack à jour sans dupliquer Sanctuary.
|
||||
|
||||
`mods/sanctuary.pw.toml` conserve le même chemin entre versions ; seul son nom de
|
||||
JAR, son URL et son hash changent. Packwiz peut ainsi retirer l'ancien JAR qu'il
|
||||
gère quand il installe le nouveau. Une première installation depuis un `.mrpack`
|
||||
n'a pas encore ce suivi : voir la procédure ci-dessous.
|
||||
|
||||
Un ZIP d'amorçage Prism se prépare avec :
|
||||
|
||||
```sh
|
||||
python3 scripts/pack.py prism https://git.botsu.net/koka/sanctuary-beta/raw/branch/packwiz/pack.toml
|
||||
```
|
||||
|
||||
Il contient les versions exactes Minecraft/Fabric, la commande de lancement et
|
||||
`packwiz-installer-bootstrap` v0.0.3. Le script vérifie le SHA-256 du bootstrap
|
||||
officiel avant d'écrire `build/Sanctuary-Prism-auto-update.zip`. Ce ZIP est réservé
|
||||
aux nouvelles installations ; les instances déjà branchées sur packwiz suivent
|
||||
le canal sans être réimportées.
|
||||
|
||||
## Raccorder l'instance Prism existante
|
||||
|
||||
Fermer Minecraft avant de modifier ses mods. Fermer également Prism avant de
|
||||
modifier directement `instance.cfg`, pour éviter que son état en mémoire écrase
|
||||
les changements. Dans l'instance Sanctuary Beta :
|
||||
|
||||
1. Sauvegarder `instance.cfg` et les anciens JAR Sanctuary dans un dossier de
|
||||
sauvegarde hors de `mods/`. Vérifier que l'instance utilise Minecraft
|
||||
`26.3-pre-2` et Fabric `0.19.5` ; ne pas convertir une instance 26.2.
|
||||
2. Mettre le bootstrap issu du ZIP dans son dossier `minecraft/` (ou `.minecraft/`
|
||||
selon l'installation).
|
||||
3. Dans **Modifier → Paramètres → Commandes personnalisées**, activer les
|
||||
commandes et renseigner la commande avant lancement ci-dessous.
|
||||
4. Pour une instance issue d'un ancien `.mrpack`, déplacer seulement l'ancien
|
||||
JAR Sanctuary non géré dans la sauvegarde préparée, puis faire la première
|
||||
synchronisation packwiz. Conserver les autres mods et données personnelles.
|
||||
Si la synchronisation échoue, remettre ce JAR et les réglages sauvegardés.
|
||||
5. Vérifier qu'il reste un seul mod Sanctuary actif et que `packwiz.json` est
|
||||
présent dans le dossier Minecraft. Les prochaines mises à jour sont suivies
|
||||
par ce manifeste local : ne pas le supprimer entre les versions.
|
||||
|
||||
Commande à coller dans l'interface Prism :
|
||||
|
||||
```text
|
||||
"$INST_JAVA" -jar "$INST_MC_DIR/packwiz-installer-bootstrap.jar" --bootstrap-main-jar "$INST_MC_DIR/packwiz-installer.jar" --pack-folder "$INST_MC_DIR" --multimc-folder "$INST_DIR" https://git.botsu.net/koka/sanctuary-beta/raw/branch/packwiz/pack.toml
|
||||
```
|
||||
|
||||
Une modification directe de `instance.cfg` doit échapper les guillemets au format
|
||||
Qt INI (`\"`) et activer `OverrideCommands=true`. Ces clés et
|
||||
`PreLaunchCommand` doivent être dans la section **`[General]`**, jamais ajoutées
|
||||
après la section `[UI]`. Le ZIP préparé le fait déjà. Rouvrir les commandes
|
||||
personnalisées dans Prism pour vérifier que la commande est effectivement lue.
|
||||
Une instance importée d'un `.mrpack` peut être marquée `ManagedPack=true` :
|
||||
dès que packwiz en assure les mises à jour, utiliser `ManagedPack=false` pour
|
||||
éviter deux mécanismes de gestion sur le même pack.
|
||||
|
||||
## Données locales et mondes
|
||||
|
||||
Le pack livré possède uniquement les fichiers déclarés dans son index. Il ne
|
||||
déclare ni `saves/`, ni `options.txt`, `servers.dat`, `screenshots/`, ni les packs
|
||||
graphiques personnels. L'installation de packwiz et une mise à jour des mods
|
||||
n'autorisent pas à supprimer ou régénérer un monde.
|
||||
|
||||
La hauteur de 384 blocs, le dessous sculpté et le ciel de l'alpha.3 se testent
|
||||
dans un **nouveau monde** avec le preset **Sanctuary**. Les anciens mondes gardent
|
||||
leurs identifiants de génération et leurs hauteurs. L’hydrologie de l’alpha.4
|
||||
nécessite elle aussi un nouveau monde Sanctuary ; la mise à jour de l’instance
|
||||
n’ajoute pas d’eau aux sauvegardes existantes. Les détails des versions de génération sont dans
|
||||
[Génération](worldgen.md). Les changements de version Minecraft ou de format
|
||||
de sauvegarde demandent leur propre ticket et contrat de migration.
|
||||
|
||||
Références officielles : [installation packwiz](https://packwiz.infra.link/tutorials/installing/packwiz-installer/),
|
||||
[commandes Prism](https://prismlauncher.org/wiki/help-pages/custom-commands/),
|
||||
[bootstrap v0.0.3](https://github.com/packwiz/packwiz-installer-bootstrap/releases/tag/v0.0.3).
|
||||
+317
@@ -0,0 +1,317 @@
|
||||
# Vérifier Sanctuary
|
||||
|
||||
Utiliser Java 25 et les dépendances épinglées dans `gradle.properties`.
|
||||
|
||||
```sh
|
||||
./gradlew check build assemblePack --console=plain
|
||||
```
|
||||
|
||||
`check` inclut les tests GameTest. Pour un diagnostic ciblé de la génération seule,
|
||||
utiliser `./gradlew :sanctuary:runGameTest --console=plain`.
|
||||
|
||||
Pour générer de vrais chunks et vérifier le spawn sur d'autres graines :
|
||||
|
||||
```sh
|
||||
./gradlew :sanctuary:runGameTest -PsanctuaryTestSeed=42 --console=plain
|
||||
./gradlew :sanctuary:runGameTest -PsanctuaryTestSeed=8675309 --console=plain
|
||||
```
|
||||
|
||||
La propriété ne s'applique qu'au mod de test, jamais au mod distribué ni à une
|
||||
sauvegarde de joueur. Chaque commande repart d'un monde de développement neuf.
|
||||
|
||||
## Tests de forme
|
||||
|
||||
La tâche `:sanctuary:worldgenSmoke`, incluse dans `check`, vérifie les invariants de
|
||||
l'enveloppe de l'île : absence de noyau imposé, conservation des trous naturels,
|
||||
sculpture du bord, limite extérieure, hauteur et stabilité de la fonction
|
||||
géométrique. Elle complète les tests en jeu ; elle ne charge pas à elle
|
||||
seule les ressources de génération de Minecraft.
|
||||
|
||||
La tâche `:sanctuary:hydrologySmoke`, également incluse dans `check`, vérifie
|
||||
le planificateur sur des reliefs contrôlés : reproductibilité, variation de la
|
||||
graine, limites de profondeur et d’excavation, support naturel, parois, ruisseau
|
||||
sur plateau compatible et partition indépendante de l’ordre des chunks. Les
|
||||
scénarios vides, percés ou trop minces ne reçoivent aucun bassin de secours.
|
||||
|
||||
## Tests dans le moteur Minecraft
|
||||
|
||||
`runGameTest` démarre le serveur de test headless officiel de Fabric dans
|
||||
`mods/sanctuary/build/run/gameTest/`. La tâche `cleanGameTestWorld` supprime uniquement
|
||||
son monde jetable avant chaque exécution afin de générer des chunks neufs.
|
||||
Il ne déploie rien dans un serveur ou une installation de jeu personnels.
|
||||
La configuration garde `eula=false` : aucun fichier d'acceptation n'est écrit par
|
||||
Loom. Fabric dispose d'un chemin de démarrage propre à ses tests automatisés.
|
||||
|
||||
Le source set `gametest` est un mod de test séparé, exclu du JAR Sanctuary distribué.
|
||||
Deux adaptations y sont nécessaires, vérifiées contre les classes Minecraft
|
||||
`26.3-pre-2` :
|
||||
|
||||
- `GameTestServer` sélectionne normalement `minecraft:flat_all_dimensions`.
|
||||
Le mixin de test sélectionne directement le preset de production
|
||||
`sanctuary:sanctuary`, sans recopier ses JSON.
|
||||
- Le framework désactive normalement les structures. Le test les active dans
|
||||
`WorldOptions` pour examiner les chunks après toutes les étapes de génération.
|
||||
La seed du serveur de test est `0` par défaut ; `-PsanctuaryTestSeed` est transmis
|
||||
à la propriété JVM `sanctuary.test.seed` utilisée uniquement dans ce mixin.
|
||||
|
||||
Le framework déplace aussi le spawn vers une grille de tests située loin du centre.
|
||||
Le test capture donc le spawn juste avant ce déplacement, après l'initialisation
|
||||
normale du monde, et examine les coordonnées absolues de l'île.
|
||||
|
||||
Les neuf tests Sanctuary vérifient :
|
||||
|
||||
1. Le vrai générateur Sanctuary est chargé et le spawn collectif repose sur une
|
||||
surface déjà présente dans la densité naturelle, avec un sol plein de 3×3
|
||||
blocs et deux blocs libres et secs pour les graines de régression.
|
||||
2. Douze chunks entièrement générés sont vides dans les quatre directions, juste
|
||||
après l'enveloppe de décoration puis à environ 512 et 4 096 blocs. Cela couvre
|
||||
notamment l'ancien retour automatique de l'archipel au loin.
|
||||
3. La densité du datapack réellement chargé, compilée par `RandomState`, est
|
||||
reproductible pour la seed `0` et change pour la seed `8675309`.
|
||||
4. La densité compilée des graines `0`, `42` et `8675309` ne crée aucune matière
|
||||
là où le terrain source est vide. Le relief supérieur reste celui de l'alpha.2
|
||||
translaté de 64 blocs et le dessous varie avec le terrain et les bruits.
|
||||
5. Le nouveau monde et son bruit utilisent bien 384 blocs, à partir de Y=0,
|
||||
tandis que les anciens paramètres restent à 256 blocs. Les nuages sont
|
||||
au-dessus de l'île, à Y=352,33.
|
||||
6. Les chunks complets inspectés gardent une marge vide sous l'île, à Y=0..15.
|
||||
7. Le relevé après génération compte exactement tous les états des blocs des
|
||||
chunks demandés, avec des totaux cohérents et une complétude explicite.
|
||||
Voir [Expansion et ressources](expansion.md) pour le mode complet facultatif.
|
||||
8. Le plan hydrologique se retrouve dans les vrais chunks après décoration :
|
||||
eau retenue, fonds naturels sur trois blocs, parois continues, ouverture au
|
||||
ciel, raccords entre chunks et canne à sucre pouvant survivre. Après des
|
||||
mises à jour réelles de fluides, chaque bloc d’eau doit rester dans ce plan.
|
||||
9. Les paramètres et le biome des anciennes sauvegardes n’activent ni le
|
||||
traitement hydrologique ni la nouvelle décoration des berges.
|
||||
|
||||
La recherche de spawn parcourt l'île finie, par anneaux de quatre blocs jusqu'à
|
||||
288 blocs du centre. Elle examine d'abord la hauteur brute pour éviter de
|
||||
générer entièrement les colonnes vides, puis vérifie le sol et les dégagements
|
||||
après décoration. Elle préfère une zone naturelle de 3×3 ; si aucune n'est trouvée,
|
||||
elle utilise le premier emplacement naturel sûr d'une colonne avec deux blocs
|
||||
libres. Elle ne pose aucun bloc. Une graine pathologique sans emplacement sûr
|
||||
échantillonné produit une erreur explicite, sans plate-forme de secours.
|
||||
|
||||
Le passage avec la graine de serveur `0` produit aussi, pour les trois graines,
|
||||
des PNG et CSV dans `mods/sanctuary/build/run/gameTest/diagnostics/` :
|
||||
|
||||
- `island-density-seed-<graine>.png` : vue de dessus et coupes centrales X/Z,
|
||||
avant/après, calculées avec les fonctions de densité réellement compilées.
|
||||
La colonne « alpha.2 » utilise ses anciennes ressources conservées ; la colonne
|
||||
de droite utilise la densité alpha.3, sur toute la hauteur Y=0..383.
|
||||
- `island-heightmap-seed-<graine>.csv` : coordonnées et hauteurs échantillonnées
|
||||
des deux versions (`-1` indique une colonne vide dans cet échantillon).
|
||||
|
||||
La vue de dessus échantillonne tous les quatre blocs en X, Y et Z. Les coupes
|
||||
échantillonnent tous les deux blocs horizontalement et chaque bloc en hauteur.
|
||||
Ces diagnostics montrent les volumes ; ils n'affichent pas les arbres, matériaux,
|
||||
fluides ou structures et ne remplacent pas un essai visuel dans le client.
|
||||
|
||||
Le chargement du serveur valide également les codecs et les références des
|
||||
registres de biomes, densités, réglages et presets. Un échec de chargement ou un
|
||||
test obligatoire en échec doit faire échouer la tâche Gradle.
|
||||
|
||||
Le framework et les hooks de test sont spécifiques à la version épinglée. Lors
|
||||
d'une mise à jour Minecraft, vérifier ces hooks avant de conclure que les tests
|
||||
exercent toujours le preset de production. La vérification explicite du générateur
|
||||
dans le premier test empêche un résultat positif sur un simple monde plat.
|
||||
|
||||
Référence du workflow : [tests automatiques Fabric](https://docs.fabricmc.net/develop/automatic-testing).
|
||||
Les signatures propres à `26.3-pre-2` ont été vérifiées dans les dépendances locales,
|
||||
car la documentation publiée vise actuellement `26.2`.
|
||||
|
||||
## Diagnostic hydrologique
|
||||
|
||||
Les essais hydrologiques demandent les chunks contenant tous les sites du plan
|
||||
et un halo d’un chunk. Ils inspectent des chunks `FULL`, avec minerais, arbres
|
||||
et plantes déjà générés, et comparent tous les blocs d’eau observés au volume
|
||||
annoncé. Leur carte montre les vrais blocs de surface ; le feuillage et les
|
||||
troncs sont masqués pour lire les berges. Il ne s’agit pas d’une capture du jeu.
|
||||
|
||||
Les chunks de l’île sont maintenus actifs pendant le test. Chaque position
|
||||
d’eau doit être dans un chunk où les blocs sont effectivement simulés. Un petit
|
||||
témoin d’eau temporaire, enfermé en hauteur dans un de ces mêmes chunks, doit
|
||||
réellement couler ; il est ensuite entièrement retiré. Les sources des sites
|
||||
reçoivent elles aussi des ticks, puis la rétention est revérifiée au tick 200.
|
||||
Attendre près de la grille éloignée du framework ne remplacerait pas ce contrôle.
|
||||
|
||||
Les fichiers `surface-hydrology-seed-<graine>-generated.json/.png` et
|
||||
`surface-hydrology-seed-<graine>-after-200-ticks.json/.png` sont écrits dans
|
||||
`mods/sanctuary/build/run/gameTest/diagnostics/`. Ils décrivent les sites et leur
|
||||
nature, leurs coordonnées, le volume d’eau réel, la canne à sucre, les raccords
|
||||
entre chunks, les berges et les bornes inspectées. Le nombre de blocs d’eau
|
||||
n’est pas une mesure de débit. Un résultat sans site `STREAM` ne valide pas
|
||||
une rivière, même s’il contient des lacs.
|
||||
|
||||
Pour itérer sur l’eau sans recalculer les trois grandes cartes de densité :
|
||||
|
||||
```sh
|
||||
./gradlew :sanctuary:runGameTest -PsanctuaryDensityMaps=false -PsanctuaryResourceSurvey=off --console=plain
|
||||
```
|
||||
|
||||
Ces options désactivent seulement les exports de densité et le relevé de
|
||||
ressources. Elles gardent les assertions de densité et tous les tests de
|
||||
comportement, y compris les cartes et contrôles hydrologiques. Le parcours
|
||||
standard conserve les exports complets de densité pour la graine 0.
|
||||
|
||||
## Vérification client facultative
|
||||
|
||||
```sh
|
||||
./gradlew :sanctuary:runClientGameTest -PsanctuaryClientTests=true --console=plain
|
||||
```
|
||||
|
||||
Ce test charge les vrais `SkyRenderer` et `FogRenderer` et vérifie l'application
|
||||
des deux mixins, puis ferme automatiquement le client sans ouvrir de sauvegarde.
|
||||
Il nécessite un environnement graphique ; `check` ne le lance pas. Ses sources
|
||||
dans `src/clientGametest/java` ne sont ajoutées au mod de test que lorsque la
|
||||
propriété est activée. Le test de transformation ne remplace pas l'essai visuel
|
||||
de l'horizon dans un nouveau monde Sanctuary.
|
||||
|
||||
Pour cet essai visuel en monde de développement créatif, comparer la même vue
|
||||
à Y=70, 62, 32, 8 puis −8. Il ne doit apparaître ni disque noir au passage de
|
||||
Y=63 ni assombrissement d'altitude au passage de Y=32. Les brouillards de l'eau,
|
||||
de la météo et des effets d'aveuglement ou d'obscurité doivent rester présents.
|
||||
|
||||
## Validation alpha.4 — 8 septembre 2026
|
||||
|
||||
Les tests serveur utilisent le preset de production et des mondes neufs. Sur
|
||||
la graine `0`, le scénario exige un véritable site `STREAM` ; les autres graines
|
||||
vérifient la continuité de chaque cours d’eau qu’elles produisent, sans exiger
|
||||
qu’une graine arbitraire soit artificiellement complétée.
|
||||
|
||||
| Graine | Spawn naturel | Eau après 200 ticks | Canne à sucre | Chunks FULL inspectés | Raccords d’eau aux frontières |
|
||||
| --- | --- | ---: | ---: | ---: | ---: |
|
||||
| `0` | `(0, 251, -4)` | 1 938 | 15 | 94 | 253 |
|
||||
| `42` | `(-4, 249, -4)` | 1 501 | 24 | 94 | 213 |
|
||||
| `8675309` | `(0, 247, 0)` | 1 551 | 12 | 90 | 190 |
|
||||
|
||||
Pour la graine `0`, le cours d’eau est à **Y=250**, de **X=−48, Z=48** à
|
||||
**X=−20, Z=−20**. Son chemin suit 18 points et relie deux petites poches d’eau.
|
||||
Deux petits lacs et quatre étangs sont également présents, en comptant la poche
|
||||
de départ du cours d’eau. La graine `42` produit elle aussi un cours d’eau, à
|
||||
Y=221, de `(−72, 24)` à `(−60, −40)` en X/Z. Ce sont des observations de ces
|
||||
graines, pas un minimum promis pour toutes les îles.
|
||||
|
||||
**10/10 tests requis réussissent sur chacune des trois graines**, avec un cours
|
||||
d’eau observé sur chacune. Les contrôles comparent chaque bloc d’eau avant et après une simulation réelle
|
||||
confirmée par un témoin d’écoulement et l’activité de tous les chunks concernés.
|
||||
Les quantités d’eau restent identiques. Les cartes des vrais blocs ont été
|
||||
inspectées : les lacs sont arrondis et les chenaux restent continus. Le tracé
|
||||
des cours d’eau comporte encore des segments géométriques ; l’appréciation du
|
||||
relief, des berges et de leur aspect dans le client reste à faire par le joueur.
|
||||
Les cartes masquent la canopée et ne remplacent pas une vue en jeu.
|
||||
|
||||
Le plan initial prend **12 060 ms** pour la graine `0` et **8 743 ms** pour `42`
|
||||
et **9 755 ms** pour `8675309`, sur le poste Apple M1 à 8 Go utilisé pendant ces
|
||||
essais. Il est calculé une seule
|
||||
fois par état aléatoire de monde, sans charger les chunks voisins. La préparation
|
||||
des 94 chunks FULL de chaque diagnostic est distincte du calcul du plan et
|
||||
ne représente pas le travail requis à chaque lancement de partie.
|
||||
|
||||
Les trois exécutions serveur réussies ont été suivies de
|
||||
`./gradlew check build assemblePack -x :sanctuary:runGameTest` : le serveur déjà
|
||||
validé séparément n’est pas relancé à l’identique. Les tests de forme et
|
||||
d’hydrologie pure, la compilation et l’assemblage du pack réussissent en 2 s.
|
||||
Le JAR ne contient aucune classe de test, et le `.mrpack` contient exactement
|
||||
ce même JAR, avec les versions Minecraft et Fabric attendues.
|
||||
|
||||
Journaux : `build/alpha4-hydrology-v2-seed0.log` et
|
||||
`build/alpha4-validation-seed42.log`, `build/alpha4-validation-seed8675309.log`
|
||||
et `build/alpha4-build.log`. Les exports correspondants sont dans
|
||||
le dossier `diagnostics` décrit plus haut. Les cartes de densité, inchangées
|
||||
par l’hydrologie, ont été exportées lors des premières exécutions de ce ticket ;
|
||||
les assertions de densité restent actives pendant chaque validation.
|
||||
|
||||
## Validation alpha.3 — 8 septembre 2026
|
||||
|
||||
Sur Java 25 / Minecraft 26.3-pre-2 / Fabric Loader 0.19.5 :
|
||||
|
||||
| Vérification | Résultat |
|
||||
| --- | --- |
|
||||
| `check build assemblePack` avec relevé complet, graine `0` | 8/8 tests requis réussis ; 14 min 49 s, dont le relevé complet |
|
||||
| Serveur neuf, graine `42` | 8/8 réussis ; 1 min 3 s |
|
||||
| Serveur neuf, graine `8675309` | 8/8 réussis ; 1 min 4 s |
|
||||
| Test client facultatif | Réussi en 23 s ; les deux injections de rendu sont appliquées |
|
||||
|
||||
La graine `0` place le spawn naturel en `(0, 251, -4)` ; la graine `42` en
|
||||
`(0, 249, 0)` et la graine `8675309` en `(0, 247, 0)`. Les tests confirment les 384 blocs du nouveau monde, les nuages
|
||||
relevés, le vide inférieur et extérieur et la conservation des anciens réglages.
|
||||
Les coupes des trois graines ont été inspectées : l'ancienne silhouette est
|
||||
relevée et le dessous devient irrégulier. Une comparaison supplémentaire de
|
||||
1 039 155 échantillons de l'ancienne géométrie refactorée donne des densités
|
||||
identiques bit à bit à l'alpha.2.
|
||||
|
||||
Le relevé complet constate **8 584 514 blocs non vides** dans les 1 444 chunks
|
||||
de l'enveloppe définie. Sa préparation prend 13 min 2,659 s, son comptage et sa
|
||||
préparation JSON 135 ms, sur un Apple M1 avec 8 Go de mémoire. Ce poste exécute
|
||||
d'autres applications et a aussi exécuté le test client pendant le relevé :
|
||||
c'est une mesure locale, pas une promesse de performance. Les quantités exactes
|
||||
et les exclusions figurent dans [la référence de ressources](expansion.md).
|
||||
|
||||
Les journaux sont `build/alpha3-validation-seed0.log`,
|
||||
`build/alpha3-validation-seed42.log`, `build/alpha3-validation-seed8675309.log`
|
||||
et `build/alpha3-client-test.log`. Le JAR alpha.3 a été inspecté : aucune classe
|
||||
de test n'est distribuée. Le `.mrpack` contient exactement le JAR vérifié.
|
||||
|
||||
Le test client valide le chargement et l'application des mixins. Il n'ouvre
|
||||
aucun monde et ne remplace donc pas l'essai visuel en vol décrit plus haut.
|
||||
|
||||
## Historique : validation alpha.2 — 8 septembre 2026
|
||||
|
||||
Commande `./gradlew check build assemblePack --console=plain` réussie en **1 min 10 s**
|
||||
sur Java **25**, Minecraft **26.3-pre-2**, Fabric Loader **0.19.5**, Fabric API
|
||||
**0.160.0+26.3** et Loom **1.17.20**, pour Sanctuary **0.1.0-alpha.2**.
|
||||
Les deux commandes supplémentaires de graines ont ensuite été exécutées
|
||||
séquentiellement, chacune sur un monde de développement neuf.
|
||||
|
||||
| Graine | Spawn naturel initial | Tests requis | Durée des tests | Durée Gradle |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `0` | `(-4, 187, -4)` | 5/5 réussis | 51,40 s, diagnostics compris | 1 min 10 s, build et pack compris |
|
||||
| `42` | `(0, 185, 0)` | 5/5 réussis | 1,843 s | 20 s |
|
||||
| `8675309` | `(0, 183, 0)` | 5/5 réussis | 1,681 s | 17 s |
|
||||
|
||||
Les cinq tests requis comprennent les quatre tests Sanctuary et le test du
|
||||
framework. Pour chacune des trois graines, le spawn possède naturellement une
|
||||
zone sèche de 3×3 avec deux blocs libres ; les douze chunks extérieurs inspectés
|
||||
au statut `FULL` sont vides. Aucun repli sur une colonne seule n'a été nécessaire.
|
||||
|
||||
- Les invariants `WorldgenSmoke`, le chargement des codecs et registres, la
|
||||
reproductibilité des densités et l'absence de matière ajoutée par la sculpture
|
||||
ont été vérifiés.
|
||||
- Les trois diagnostics PNG et CSV ont été produits avec la densité actuelle.
|
||||
Leur calcul synchrone explique la durée plus longue du test de graine `0` et
|
||||
son avertissement serveur « Can't keep up » ; ce diagnostic est absent du mod
|
||||
distribué.
|
||||
- La compilation du JAR, l'assemblage dans `build/packwiz/` et la vérification des
|
||||
versions et empreintes du pack ont réussi.
|
||||
- Le JAR `mods/sanctuary/build/libs/sanctuary-0.1.0-alpha.2.jar` ne contient aucune
|
||||
classe ni configuration de mixin GameTest.
|
||||
|
||||
Les journaux de cette validation sont conservés localement dans
|
||||
`build/alpha2-validation-seed0.log`, `build/alpha2-validation-seed42.log` et
|
||||
`build/alpha2-validation-seed8675309.log`. Les journaux courants de Minecraft sont
|
||||
dans `mods/sanctuary/build/run/gameTest/logs/` et sont remplacés au prochain test.
|
||||
Ces preuves et les diagnostics restent ignorés par Git. Les vérifications
|
||||
manuelles ci-dessous restent à effectuer ; les tests n'ont ouvert ni modifié
|
||||
aucune sauvegarde de joueur.
|
||||
|
||||
La fondation alpha.1 avait été validée le même jour sur la graine `0`, avec un
|
||||
spawn `(0, 118, 0)` reposant sur l'ancien noyau imposé. Ce résultat historique
|
||||
ne décrit plus la génération alpha.2.
|
||||
|
||||
## Vérifications manuelles restantes
|
||||
|
||||
- Créer un monde avec le preset Sanctuary dans le client et évaluer visuellement
|
||||
la côte, les trous, les surplombs, la végétation et la lecture du vide.
|
||||
- Rejoindre à plusieurs joueurs, mourir et réapparaître ; les tests headless ne
|
||||
simulent pas de connexion client ni les offsets de réapparition des joueurs.
|
||||
- Modifier le spawn administrateur, arrêter puis recharger le monde et vérifier
|
||||
sa conservation. Le test automatique couvre le premier démarrage, pas un cycle
|
||||
de sauvegarde et de redémarrage complet.
|
||||
- Explorer plusieurs seeds dans le client. Les tests compilent trois seeds et
|
||||
chaque invocation génère des chunks complets pour la seed demandée ; leur
|
||||
échantillonnage extérieur ne constitue pas une inspection exhaustive de toutes
|
||||
les coordonnées.
|
||||
- Tester les resource packs, shaders et mods communautaires après leur ajout et
|
||||
vérification de compatibilité avec la version exacte du pack.
|
||||
+317
@@ -0,0 +1,317 @@
|
||||
# Sanctuary — vision du projet
|
||||
|
||||
Ce document conserve les intentions exprimées au démarrage de Sanctuary Beta. Il décrit une **destination de conception**, pas une liste de fonctionnalités déjà livrées. Le code, les tests et les notes de version font foi pour l'état réel du mod. Les valeurs d'équilibrage ci-dessous sont des propositions initiales à éprouver en jeu.
|
||||
|
||||
La cible demandée est **Minecraft 26.3 avec Fabric**. Au démarrage du dépôt, le 8 septembre 2026, la base disponible retenue est **26.3-pre-2** ; les versions effectivement utilisées restent indiquées dans la configuration de construction. La première étape est d'initialiser proprement le projet, puis de retrouver et d'adapter les idées pertinentes du générateur de l'ancienne version 26.2.
|
||||
|
||||
## Le projet en quelques mots
|
||||
|
||||
> Sanctuary is a free expansion of Minecraft that reshapes the game around a floating world. Players begin together on Sanctuary Island, isolated in the void. By exploring, building infrastructure and producing resources, they progressively unlock new floating continents, each with its own geography. Rather than a linear campaign, the server itself becomes a world that grows through the action of its players.
|
||||
|
||||
Sanctuary transforme Minecraft en un monde flottant d'exploration, de production et de progression collective. Tous les joueurs commencent sur l'île de Sanctuary, isolée dans le vide. Leurs constructions, leurs explorations et leurs chaînes de production permettent d'ouvrir de nouveaux continents suspendus.
|
||||
|
||||
L'objectif mythologique est de progresser d'une condition très limitée, proche du hardcore, vers les possibilités du mode créatif, par la coopération. Le serveur conserve l'histoire de cette transformation : les continents ouverts, les ressources produites, les événements, les étoiles et les constellations. Battre l'Ender Dragon devient un événement parmi d'autres, et non la conclusion obligatoire d'une campagne.
|
||||
|
||||
## Principes de conception
|
||||
|
||||
- **Un monde construit par ses habitants.** Les efforts collectifs doivent avoir des effets durables et visibles sur le serveur.
|
||||
- **Des objets et des gestes dans le monde.** Favoriser panneaux, ordinateurs, cloches, bannières, télescopes et infrastructures pour accéder aux systèmes. Ajouter un menu quand il sert réellement l'interaction.
|
||||
- **Coopération et initiative individuelle.** Permettre les projets communs sans effacer les possessions, les collections, les voisinages ni les constructions personnelles.
|
||||
- **Une progression par l'expérience et la production.** L'XP sert aux capacités, aux transports et aux actions créatives ; les ressources et les métiers soutiennent l'expansion.
|
||||
- **Un monde aux ressources localisées.** Il faut partir explorer pour trouver certains biomes, cultures, structures et matériaux.
|
||||
- **Humour, surprise et souvenirs.** Les noms d'entités, les événements, les animaux rares et les objets insolites comptent autant que les systèmes économiques.
|
||||
- **Un développement par tickets.** Livrer une petite mécanique jouable et vérifiable, puis l'ajuster avec des tickets de fonctionnalités et de bugs. Ne pas construire tous les systèmes avant de pouvoir jouer.
|
||||
|
||||
## Périmètre de départ
|
||||
|
||||
Le premier incrément porte sur le socle Fabric et la génération du monde : retrouver le générateur 26.2, produire une île principale dans un vrai vide, assurer une arrivée commune et sûre, puis poser une manière reproductible de générer des continents à une distance, une direction et une taille choisies.
|
||||
|
||||
La progression, l'économie, les nouveaux monstres, les interfaces de pack et les autres dimensions restent des intentions futures. Ils ne constituent pas des dépendances du premier générateur. Le [backlog](backlog.md) précise les premiers tickets et leurs critères d'acceptation.
|
||||
|
||||
## Mod, modpack et modules autonomes
|
||||
|
||||
| Ensemble | Rôle souhaité |
|
||||
| --- | --- |
|
||||
| **Sanctuary, le mod** | Monde flottant, expansion, progression collective et systèmes propres à cet univers. |
|
||||
| **Sanctuary, le modpack** | Distribution avec packwiz, configurations, mods communautaires, resource packs et shader packs. |
|
||||
| **It's Alive !** | Mod de cuisine et d'agriculture autonome, raccordable aux biomes, événements et déblocages de Sanctuary. |
|
||||
| **Only Fun** | Mod autonome consacré aux interactions humoristiques, aux anniversaires et à des activités sociales. |
|
||||
| **Master Key** | Bibliothèque d'outils et de permissions d'administration destinée aux différents mods, afin d'administrer et de réparer sans devoir jouer en créatif. |
|
||||
|
||||
L'apparence du pack peut inclure les mentions affichées, les crédits, l'icône, l'écran de chargement et des adaptations visuelles. Les licences et attributions des composants restent attachées à leurs ayants droit ; choisir une licence pour le travail original est une décision distincte de personnaliser son habillage.
|
||||
|
||||
Les intégrations communautaires envisagées comprennent Fabric API, Sodium, Iris, Vitrail, Indium, Just Enough Items, Simple Voice Chat, Xaero's Minimap et Dynamic Torches. Golden Days est envisagé pour l'apparence du monde Alpha, et Litematica pour les plans de construction. Ce sont des pistes d'intégration : la disponibilité sur la version cible, les noms exacts, les dépendances et les conditions de distribution doivent être vérifiés au moment de chaque ticket.
|
||||
|
||||
**TerraMix** désigne ici le catalogue natif de 100 biomes d'**Another World**, retrouvé dans l'historique 26.2. Sa reprise et son extension sont une piste pour les continents ; ce nom ne désigne pas une nouvelle dépendance externe obligatoire.
|
||||
|
||||
## Monde flottant et expansion
|
||||
|
||||
### Sanctuary Island et continents
|
||||
|
||||
L'île de Sanctuary est le point de départ commun. Elle flotte dans un vide qui ne doit pas être rempli par la génération habituelle du monde. Les continents suivants sont de vastes terres suspendues, créées progressivement selon une taille et une direction maîtrisées.
|
||||
|
||||
L'île initiale doit être **tempérée et légèrement humide**, avec quelques zones
|
||||
plus sèches, notamment des plages. Une rivière et de petits étangs ou lacs de
|
||||
surface peuvent accueillir de la canne à sucre. Les grands bassins souterrains
|
||||
remplis d'eau ne sont pas retenus pour cette île. Le terrain doit laisser voir un
|
||||
dessous sculpté et dégagé, dans un monde de **384 blocs de hauteur**, avec des
|
||||
nuages relevés et sans disque noir artificiel apparaissant sous un seuil d'altitude.
|
||||
|
||||
Le premier champ de recherche des expansions associe les **huit directions**
|
||||
autour de Sanctuary à des signatures de température et d'humidité. En regardant
|
||||
la carte avec le nord en haut : **nord froid, sud chaud, ouest sec et est humide**.
|
||||
Les diagonales combinent ces tendances. La recherche porte sur des climats et
|
||||
leurs distributions, sans imposer la même forme de continent à chaque direction.
|
||||
|
||||
L'économie naturelle d'une île doit être connue **après sa génération** : nombre
|
||||
de blocs de pierre, bûches, minerais de charbon, de fer et autres ressources,
|
||||
avec une emprise et un état de référence précis. Ce relevé constate ce que la
|
||||
génération a produit ; il ne force pas des quotas de ressources et ne choisit pas
|
||||
une seed pour atteindre un stock prédéterminé. Un relevé partiel doit rester
|
||||
identifié comme tel. Le contrat de recherche et de relevé est détaillé dans
|
||||
[Expansion et ressources](expansion.md).
|
||||
|
||||
Leur géographie doit rester variée : perforations et passages de vide, océans et lacs retenus dans le terrain, rivières flottantes, montagnes, ravins et reliefs lisibles depuis les airs. Le système doit pouvoir conserver les constructions et les chunks déjà explorés lorsqu'une nouvelle expansion est ouverte.
|
||||
|
||||
L'ambition est de reprendre puis d'étendre la centaine de biomes du catalogue TerraMix d'Another World, avec des biomes originaux comme le **Black Desert**. Cette diversité sera introduite après la validation du terrain de base et de son adaptation technique.
|
||||
|
||||
### Structures et territoires
|
||||
|
||||
- **Lost Cities** : des zones traitées comme des biomes, où des bâtiments en ruine remplacent la végétation dominante. Le nom exprime ici un type de territoire souhaité, sans décider encore s'il sera produit par une intégration ou par du code Sanctuary.
|
||||
- **Structures contemporaines** : parcs, parkings et autres vestiges. Les parkings sont souterrains et peuvent s'étendre comme une infestation. Ils n'apparaissent pas dans la dimension des cavernes.
|
||||
- **Structures uniques et donjons** : d'abord sur Sanctuary Island, puis dans les continents. Ils peuvent contenir des clés de chunk et d'autres ressources rares.
|
||||
- **Secret rooms et challenge rooms** : des accès présents dans le monde mènent à des indoors aménagés, avec des équipements, des épreuves ou des gisements de fer, d'or, d'argent ou de titane.
|
||||
|
||||
### Dimensions
|
||||
|
||||
| Dimension | Intention |
|
||||
| --- | --- |
|
||||
| **Cavernes** | Monde destiné au minage, avec des biomes souterrains, sans mineshafts ni parkings. Des cités anciennes particulièrement difficiles accueillent plusieurs types de squelettes et un nécromancien illageois qui les invoque. Il détient une boule de cristal. |
|
||||
| **Alpha** | Monde inspiré de Minecraft Indev et des premières versions, avec les anciennes textures de Golden Days actives uniquement dans cette dimension. Notch y est un boss. |
|
||||
| **Backrooms** | Monde sombre à plusieurs niveaux, exploré avec une source de lumière. Les joueurs y arrivent dans un lit. Il reçoit dans des coffres les objets perdus dans le vide ou brûlés ; c'est aussi un territoire d'exploration et un lieu de quête. |
|
||||
| **Indoors** | Petits espaces associés à des objets, déclinés en classes de volume, avec une taille indicative allant de 5 × 5 × 5 à 100 × 100 × 100 blocs. Ils peuvent servir d'intérieurs, de salles secrètes ou de lieux d'événements. |
|
||||
|
||||
La géométrie des indoors reste à décider : certains peuvent être des volumes précisément délimités, d'autres des espaces fermés qui se rebouclent en trois dimensions, de type tore 3D. Leurs objets d'accès sont liés à la mailbox de leur propriétaire et doivent être récupérables s'ils sont perdus. Les règles de propriété, de partage, de transfert et de sortie sûre seront définies avant leur implémentation.
|
||||
|
||||
## Progression personnelle et collective
|
||||
|
||||
### Capacités initiales
|
||||
|
||||
Le joueur commence avec trois cœurs, trois icônes de nourriture, une capacité respiratoire réduite, une vitesse de minage très faible et l'impossibilité de courir. L'armure conserve son fonctionnement habituel.
|
||||
|
||||
L'XP et les niveaux permettent d'acheter des améliorations. L'exemple de courbe souhaitée double les coûts : **1, 2, 4, 8, 16, 32, 64 niveaux**. Les valeurs doivent être testées séparément pour chaque capacité ; il reste notamment à confirmer si chaque achat consomme des niveaux entiers ou une quantité d'XP équivalente.
|
||||
|
||||
Les capacités peuvent atteindre les valeurs usuelles de dix cœurs, dix icônes de nourriture et une respiration complète par la progression normale en XP. Le nombre d'étapes et les plafonds seront arrêtés dans les tickets concernés.
|
||||
|
||||
### Inventaire, prestige et déblocages
|
||||
|
||||
L'inventaire commence à une rangée et peut s'étendre jusqu'à six par la progression normale. La place de la barre rapide dans ce comptage reste à préciser.
|
||||
|
||||
Les **prestiges débloquent des slots de factions**. Ils ne donnent pas de capacités bonus ni de rangées d'inventaire supplémentaires. Le nombre de slots accordés par prestige et leurs règles d'utilisation seront précisés dans les tickets de factions.
|
||||
|
||||
La progression peut débloquer une table de fabrication dans l'inventaire, des capacités de minage et de construction, et l'accès à certaines fonctions de mods communautaires. Les joueurs peuvent également porter des capes et des familiers ; les spawn eggs correspondent à différents pouvoirs de familier.
|
||||
|
||||
Les recettes de Just Enough Items sont révélées par paliers liés aux advancements. Les advancements Minecraft donnent accès aux recettes Minecraft ; ceux de Sanctuary ouvrent les blocs décoratifs, équipements et systèmes correspondants. Il faudra distinguer dans chaque ticket l'affichage des recettes, leur connaissance et l'autorisation réelle de fabriquer.
|
||||
|
||||
### Construction et production
|
||||
|
||||
- **Mining** améliore la vitesse de minage et ouvre le vein mining : extraction de blocs d'une même veine ou sur un plan défini par l'orientation du joueur.
|
||||
- **Building** ouvre des outils de pose et de remplissage de surfaces, dont le vein building.
|
||||
- **Plans et préfabriqués** : conserver et copier ses constructions, puis accéder à un catalogue de prefabs. Une intégration adaptée de Litematica est envisagée. Un ancien concept de « voxelier » dans un ordinateur permettait de créer des modèles en cubes ; cette piste est considérée comme complexe et n'est pas prioritaire.
|
||||
|
||||
## Monnaies, propriétés et échanges
|
||||
|
||||
### Les trois gemmes
|
||||
|
||||
Les trois gemmes forment la palette et la symbolique triangulaire de Sanctuary.
|
||||
|
||||
| Gemme | Fonction souhaitée |
|
||||
| --- | --- |
|
||||
| **Émeraude** | Économie locale des villageois, échanges et travail dans le monde. |
|
||||
| **Rubis** | Monnaie des échanges passant par les services du serveur et ses boutiques. Nouveau minerai à extraire. |
|
||||
| **Saphir** | Réservation et sauvegarde de quantités limitées d'objets dans le catalogue. Nouveau minerai à extraire. |
|
||||
|
||||
### Mailbox, dépôts et boutiques
|
||||
|
||||
Chaque joueur a une **mailbox** et le serveur dispose de **deposit boxes** pour les apports collectifs. Les achats autorisés sont livrés dans la mailbox. Les échanges doivent pouvoir être reliés à la progression du serveur et aux ressources qu'il conserve.
|
||||
|
||||
Le shop propose des offres flash renouvelées toutes les heures. Le joueur peut débloquer jusqu'à neuf emplacements avec son XP. Le black market accueille les offres des joueurs ; le catalogue permet de réserver une quantité limitée d'un objet contre des saphirs. Des extensions et emplacements supplémentaires peuvent apparaître comme récompenses d'événements.
|
||||
|
||||
Le shop pourrait devenir une infrastructure coûteuse à construire, accessible physiquement et située en fin de progression. Sa forme exacte reste ouverte : bâtiment joueur, service du serveur ou combinaison des deux. Des casinos événementiels peuvent être installés dans de grands indoors où les joueurs se retrouvent.
|
||||
|
||||
### Bourse du navet
|
||||
|
||||
Les **navets** s'achètent **uniquement le dimanche, lors de la loterie**. Pendant la semaine, les joueurs peuvent les revendre au shop au cours variable de la **bourse du navet**. Aucun achat de navets n'est proposé les autres jours.
|
||||
|
||||
### Coffre-fort, équipes et braquage
|
||||
|
||||
Chaque joueur peut posséder un seul coffre-fort. Il contient plus de gemmes qu'un inventaire et permet un porte-monnaie utilisable en jeu. Le coffre doit pouvoir être caché et peut être percé avec une **drill en titane**.
|
||||
|
||||
Chaque joueur a une couleur unique, qui sert de couleur d'équipe. Des joueurs peuvent se fédérer en équipes temporaires, factions ou voisinages et partager les gains d'une action, y compris d'un braquage. Les slots de factions se débloquent grâce aux prestiges. Les conditions d'accès, les protections et l'équilibrage entre coopération et conflit devront être explicités dans ces tickets.
|
||||
|
||||
## Villageois et automatisation
|
||||
|
||||
Une bannière placée au-dessus d'une cloche associe un village à une faction. Faire sonner la cloche permet de mettre à jour l'appartenance des villageois concernés à l'équipe associée. Le rayon, le choix de la bannière et les conflits de cloches restent à spécifier.
|
||||
|
||||
Les villageois peuvent être payés en émeraudes pour réaliser des tâches cohérentes avec leur métier vanilla. Trois secteurs se complètent : **récolte**, **transformation** et **services**. Ils peuvent, par exemple, récolter du blé, le déposer dans une boîte ou le moudre. Les copper golems et la redstone participent au transport et aux chaînes de production.
|
||||
|
||||
Les **conveyor belts**, fabriqués notamment avec du cuir de vache, déplacent les objets sous forme de drops dans le sens de pose du bloc. Ils se combinent avec les droppers pour acheminer les ressources sur des distances importantes.
|
||||
|
||||
## Machines, équipements et redstone
|
||||
|
||||
- **Ordinateur 8 bits** : bloc programmable avec six ports d'entrée et de sortie, permettant de construire ses propres comportements de redstone. Des évolutions en contrôleur et en ordinateur d'interface donnent accès, dans le monde, aux systèmes d'expansion.
|
||||
- **Terminal et storage en titane** : stockage de fin de progression, avec jusqu'à 128 coffres connectés consultables par un terminal commun, afin de déposer et retrouver ses objets sans tri manuel constant.
|
||||
- **Chunky et clé de chunk** : bloc fabriquable maintenant un chunk actif. Son activation requiert une clé trouvable dans les donjons et structures, afin que le chargement permanent ait une valeur d'exploration et d'échange. L'orthographe des noms sera fixée à partir de l'historique.
|
||||
- **Particuleur** : émetteur de particules dont l'effet dépend de l'objet inséré et dont l'intensité dépend du signal de redstone.
|
||||
- **Caméra** : photographie le jeu et transforme les images en cartes Minecraft utilisables dans les item frames.
|
||||
- **Œil d'araignée (spider eye)** : révèle les niveaux de lumière dans le monde, pour visualiser l'intensité de l'éclairage.
|
||||
- **Disque blanc** : renommé avec une référence à une vidéo YouTube, il permet d'en jouer le contenu dans un jukebox. La forme exacte de la référence et le comportement audiovisuel restent à décider.
|
||||
- **Équipements en titane d'anomaly** : équipements impossibles à fabriquer, indestructibles, avec des enchantements exceptionnellement forts. Les machines fabriquables en titane forment une catégorie distincte.
|
||||
|
||||
### Armes et explosifs
|
||||
|
||||
L'argent, appelé aussi **silver**, est le métal des armes de précision et de certaines technologies :
|
||||
|
||||
- Le **creeper lock** consomme de la poudre à canon pour une attaque explosive de courte portée.
|
||||
- La **mitraillette** repousse les monstres à moyenne portée. Elle emploie des munitions métalliques de puissance variable ; le choix précis entre pépites et lingots reste à fixer.
|
||||
- Le **mining rifle** est une arme de longue portée utilisant des minerais.
|
||||
|
||||
Ces armes peuvent recevoir des enchantements adaptés. Les nouveaux explosifs comprennent une **Weather TNT** qui dissipe la pluie, une **Fragment TNT** qui libère plusieurs TNT, une **Randomizer TNT** qui redistribue les blocs autour d'elle, et une **Mega TNT** plus puissante.
|
||||
|
||||
Les **lucky blocks** déclenchent volontairement un événement imprévisible, parfois très favorable, parfois franchement nuisible : récompenses rares, mort, poison, sorcières, lave ou dégâts causés par des explosifs. Leur risque et leur injustice font partie du concept ; la zone et les conditions de ces effets seront fixées dans le ticket avant leur ajout à un serveur partagé.
|
||||
|
||||
## Déplacements et interactions
|
||||
|
||||
| Système | Comportement souhaité |
|
||||
| --- | --- |
|
||||
| **Waystones horizontales** | Voyager dans une même dimension vers une waystone connue, avec un coût d'XP croissant selon la distance. |
|
||||
| **Waystones verticales** | Ascenseurs : sauter pour monter, se baisser pour descendre. L'étage de destination n'a pas besoin d'avoir été découvert. |
|
||||
| **Téléporteur longue distance** | Préparation plus lente, mais grandes distances possibles, avec une arrivée approximative. |
|
||||
| **Zipline** | Corde reliant deux installations, jusqu'à une distance indicative de 512 blocs. Un clic droit lance le trajet, moins rapide qu'un minecart à pleine vitesse. |
|
||||
| **Grappling rod** | Viser un point pour s'y tirer, avec un risque réel de chute. Consomme des leads qui ne sont pas récupérés. |
|
||||
| **Bateau avec poule** | Aéronef simple obtenu en plaçant une poule dans un bateau, permettant notamment le transport de villageois. |
|
||||
| **Biplan** | Aéronef à deux places, plus rapide que le bateau volant, mais plus lent que les elytras. |
|
||||
| **Happy Ghast** | Monture volante à quatre places, avec une vitesse doublée. « Happy gust » a été prononcé dans la description ; l'identifiant exact est à vérifier. |
|
||||
| **Totem de Notch / Magic Carpet** | En tenant le totem, faire apparaître une plateforme de verre sous ses pieds pour marcher dans les airs et faciliter la construction. La taille de la plateforme, évoquée comme trois blocs, reste à fixer. |
|
||||
|
||||
Un sneak + clic droit permet de porter un mob sur sa tête, avec une limite d'un mob porté. Sur un autre joueur, le même geste permet de se placer sur sa tête. Un double sneak sans déplacement permet de s'asseoir.
|
||||
|
||||
## Faune, créatures et personnages
|
||||
|
||||
### Hostiles et fantômes
|
||||
|
||||
Une famille de zombies inspirée de Left 4 Dead comprend le **Hunter**, le **Charger**, le **Spitter**, le **Boomer** et l'**Infecté**. Le **Tank** et le **zombie géant** sont des boss futurs.
|
||||
|
||||
Le système d'insomnie et les phantoms vanilla disparaissent lorsque le monde passe au temps réel. Ils laissent place à d'autres fantômes, dont une **baleine volante, fantomatique et apaisante**, qui passe autour des joueurs et peut s'inviter dans le paysage comme un photobomb.
|
||||
|
||||
Un **Happy Creeper**, bienveillant, porte une fleur sur la tête et un visage différent. Des creepers rares ont également des apparences variées.
|
||||
|
||||
### Poules rares
|
||||
|
||||
Une poule ordinaire peut très rarement pondre un œuf en or. Les quatre variantes rares sont classées ainsi :
|
||||
|
||||
Les facteurs se cumulent d'une variété à la suivante. Si **T** est l'intervalle moyen entre deux pontes d'une poule ordinaire :
|
||||
|
||||
| Poule | Production | Fréquence de ponte | Intervalle moyen |
|
||||
| --- | --- | --- | --- |
|
||||
| Émeraude | Émeraudes | 2 fois moins souvent qu'une poule ordinaire | 2 × T |
|
||||
| Rubis | Rubis | 4 fois moins souvent que la poule émeraude | 8 × T |
|
||||
| Saphir | Saphirs | 8 fois moins souvent que la poule rubis | 64 × T |
|
||||
| Or | Œufs en or | 16 fois moins souvent que la poule saphir | 1 024 × T |
|
||||
|
||||
Ces quatre variantes **ne peuvent pas se reproduire**. Leur rareté doit préserver la valeur de la monnaie. Les joueurs les protègent avec des armures de poule inspirées des armures de loup, colorables, et peuvent les nommer. L'objectif est de créer de l'attachement et de l'attention à ces animaux.
|
||||
|
||||
### Mooblooms et noms d'entités
|
||||
|
||||
Les **Mooblooms** sont des variantes de vaches très rares associées aux fleurs, avec de petites et grandes versions. La liste initiale comprend hibiscus, narcisse, tulipes roses, orange, rouges et blanches, marguerite/oxeye daisy, tournesol, allium, houstonie/azure bluet, orchidée bleue, bleuet/cornflower, pissenlit, muguet, lilas, rose, rose bleue et coquelicot/poppy. Les libellés exacts, la variante « moobloom » générique et les équivalences linguistiques devront être alignés sur les ressources existantes.
|
||||
|
||||
La progression peut débloquer l'affichage de noms pour toutes les familles d'entités. Chaque famille a un registre humoristique : pseudos « kikoolol Xbox 360 » pour les zombies, alphabet galactique Minecraft pour les Endermen, et des noms variés pour les animaux.
|
||||
|
||||
### Végétation propre à Sanctuary
|
||||
|
||||
Nouveaux bois envisagés : **lavande, ébène, mossy et blueberry**. De nouvelles fleurs comprennent les roses et les roses bleues. Les formes finales des arbres et les noms de registre restent à définir.
|
||||
|
||||
## Temps réel, calendrier, événements et ciel
|
||||
|
||||
L'heure du jeu suit l'heure réelle : à 8 h du matin sur le serveur, il est 8 h dans le monde. L'administrateur choisit le fuseau ou le décalage horaire. Le sommeil ne sert donc plus à faire avancer la nuit, ce qui libère un rôle pour le lit comme entrée dans les Backrooms.
|
||||
|
||||
Un calendrier expose des nombres de jours simples : âge du serveur depuis sa création et nombre de jours écoulés depuis une origine historique de Minecraft. La date exacte de cette origine reste à choisir. Cette chronologie fait partie de la mythologie de Sanctuary.
|
||||
|
||||
Des panneaux d'événements permettent de proposer une activité et de s'inscrire, en lien avec les factions. Des anniversaires issus d'Only Fun peuvent y être reliés. Des événements suivent un cycle évoqué comme six jours actifs et un septième jour de repos ou de fête ; la durée et l'ancrage hebdomadaire doivent être confirmés.
|
||||
|
||||
Une **loterie du dimanche** permet de collecter des tickets pendant la semaine. Le nombre de tickets augmente les chances de récompense : lootboxes anomaly, emplacements du catalogue ou extensions du shop. Les probabilités et le financement des récompenses doivent préserver la rareté des ressources.
|
||||
|
||||
La loterie du dimanche accueille aussi l'unique occasion hebdomadaire d'acheter les navets de la [bourse du navet](#bourse-du-navet), revendables ensuite au shop pendant la semaine.
|
||||
|
||||
### Étoiles et constellations
|
||||
|
||||
Les premières nuits n'ont pas d'étoiles. Les advancements Minecraft et Sanctuary font naître progressivement des familles d'étoiles communes à tout le serveur. Avec une spyglass, le joueur peut observer une étoile, son nom et l'accomplissement qui l'a fait apparaître.
|
||||
|
||||
Il peut tracer des constellations depuis son point de vue, directement dans le ciel, sans ouvrir une interface séparée. Ajouter ou retirer un point coûte un niveau dans l'intention initiale ; il faudra préciser la différence entre point et segment lors de l'implémentation. Les constellations sont visibles par tous. Le ciel tourne plus lentement que le cycle quotidien, de sorte que les nuits évoluent au fil des jours.
|
||||
|
||||
## Quêtes, récompenses et cosmologie
|
||||
|
||||
### Panneaux et lootboxes
|
||||
|
||||
Les panneaux de quêtes utilisent les trois gemmes et leurs couleurs : vert émeraude, rouge rubis, bleu saphir, pour trois paliers de difficulté. Le nombre de quêtes réalisables est limité par heure. Le joueur voit les récompenses avant de choisir ; les quêtes donnent notamment de l'XP et des lootboxes.
|
||||
|
||||
Quatre catégories de lootboxes sont prévues : **normales**, **capes**, **spawn eggs** et **anomaly**. Les anomaly peuvent contenir les équipements en titane impossibles à fabriquer.
|
||||
|
||||
### Le cube originel et les sept boules
|
||||
|
||||
Sanctuary naît d'un **cube originel**, auquel le joueur reste secrètement lié et dont découle sa progression. Une mythologie cachée, inspirée d'une alchimie et d'une cabale minecraftiennes, relie ce cube à sept boules de cristal : six directions ou pointes et un centre. La référence visuelle évoque à la fois une figure à six pointes et les sept Dragon Balls.
|
||||
|
||||
Les objectifs déjà évoqués sont :
|
||||
|
||||
- **Boule de la fortune** : réunir une grande quantité de richesse, à calibrer pour un serveur pensé, par exemple, pour un mois de jeu.
|
||||
- **Boule du cauchemar** : accomplir dans les Backrooms une action consistant à enfermer un mob particulier.
|
||||
- **Boule de Notch** : vaincre Notch dans le monde Alpha ; cette rencontre donne aussi le totem qui active la Magic Carpet.
|
||||
- **Boule de la cité ancienne** : vaincre ou résoudre la rencontre du nécromancien dans les cavernes.
|
||||
|
||||
Les trois autres boules, les noms définitifs et la manière de les réunir restent à concevoir. L'Ender Dragon, Notch, le zombie géant, le premier burger et le premier sushi peuvent tous devenir des événements de l'histoire commune, à des échelles différentes.
|
||||
|
||||
## It's Alive ! — cuisine et agriculture
|
||||
|
||||
It's Alive ! est un mod autonome de cultures, de préparation et de transformation alimentaire. Son intégration à Sanctuary apporte des raisons d'explorer, de commercer et de produire ensemble.
|
||||
|
||||
Les cultures et ingrédients se trouvent dans des biomes appropriés à leur température et à leur humidité. Tout ne pousse pas et ne se trouve pas partout. Certaines variations de récolte suivent une logique comparable à la recherche de baies dans Cobblemon.
|
||||
|
||||
### Contenus envisagés
|
||||
|
||||
| Famille | Exemples issus de la vision initiale |
|
||||
| --- | --- |
|
||||
| Fruits et légumes | Fraise, tomate, ananas, maïs, concombre, poivron, aubergine, laitue, chou, raisin, haricot, piment, olive, citron, oignon, ail |
|
||||
| Céréales, herbes et arômes | Riz, soja, basilic, menthe, coriandre, café, vanille, thé |
|
||||
| Ingrédients transformés | Lait, tofu, beurre, crème, fromage, vinaigre, poudre — nature de cette dernière à préciser |
|
||||
| Fermentations et conserves | Vin, bière, kimchi, cornichons, yaourt, saucisson |
|
||||
| Plats et recettes | Chili, veloutés, soupes, omelettes, sautés, currys, salades, sushis, pain, sandwichs, burgers, gâteaux, préparations fourrées et pizzas |
|
||||
| Outils | Poêle, moulin, marmite, cuisinière, tonneau de macération et séchoir |
|
||||
|
||||
Le vin peut vieillir pour gagner en qualité. Le fromage et le saucisson peuvent également être affinés ; ce vieillissement n'est pas prévu pour la bière dans l'intention actuelle.
|
||||
|
||||
Des **pages culinaires** reconstituent le livre de recettes perdu de Steve. Il ne contient pas toutes les recettes : des sorcières gardent des **secret pages**, obtenues en les combattant, qui révèlent notamment certaines techniques de crème, beurre, vin et bière.
|
||||
|
||||
## Only Fun — interactions sociales et absurdes
|
||||
|
||||
Only Fun rassemble des interactions volontairement potaches : faire pipi, faire caca, vomir, fumer, cultiver du chanvre et célébrer les anniversaires de ses amis. Il peut fonctionner seul et se raccorder au calendrier, aux événements et aux factions de Sanctuary. Son contenu exact doit rester séparé du socle nécessaire pour jouer à Sanctuary.
|
||||
|
||||
## Master Key — administration
|
||||
|
||||
Master Key fournit des permissions et des outils d'administration partagés aux différents mods. L'objectif est qu'un administrateur puisse configurer le serveur, diagnostiquer et réparer une situation sans devoir activer le mode créatif pour jouer. Les actions de maintenance doivent avoir un périmètre clair, être contrôlées par le serveur et laisser des traces exploitables pour comprendre un incident.
|
||||
|
||||
## Décisions à prendre au fil des tickets
|
||||
|
||||
Les inconnues ne bloquent pas l'initialisation ni le terrain de base. Elles sont traitées au moment où elles influencent une fonctionnalité :
|
||||
|
||||
- Taille, altitude, relief, réserve de ressources et identité visuelle de Sanctuary Island.
|
||||
- Forme des continents, distances d'expansion, coûts collectifs et contrôle de leur ouverture.
|
||||
- Adaptation du catalogue TerraMix historique et compatibilité des mods communautaires avec la cible Fabric.
|
||||
- Courbe d'XP, lignes d'inventaire et distinction entre rangée et barre rapide ; conditions de passage des prestiges, nombre et fonctionnement des slots de factions débloqués.
|
||||
- Conditions de perte et de récupération des objets, durée de conservation dans les Backrooms et garantie d'unicité.
|
||||
- Nature et propriété des indoors, partage des accès et comportement à la déconnexion.
|
||||
- Règles des boutiques, du coffre-fort, du braquage et des équipes temporaires.
|
||||
- Identifiants définitifs, noms hérités, ressources réutilisables et licences associées.
|
||||
- Géométrie du ciel partagé et comportement des constellations selon la position du joueur.
|
||||
- Date d'origine du calendrier, rythme des événements et trois boules de cristal encore non décrites.
|
||||
|
||||
La prochaine décision concrète reste la même : obtenir un monde flottant stable et intéressant, puis l'éprouver en jeu avant d'ajouter les systèmes qui le feront grandir.
|
||||
@@ -0,0 +1,268 @@
|
||||
# Premier monde Sanctuary
|
||||
|
||||
Le preset `sanctuary:sanctuary` crée l'île principale à l'origine de l'Overworld,
|
||||
entourée de vide sans répétition d'îles à grande distance. C'est une première
|
||||
tranche de génération pour Minecraft **26.3-pre-2 / Fabric**, pas encore le
|
||||
système d'expansion collective.
|
||||
|
||||
## Créer un monde
|
||||
|
||||
En solo : créer un **nouveau** monde et choisir le type de monde **Sanctuary**
|
||||
dans les options de génération. Les types de monde vanilla restent disponibles.
|
||||
|
||||
Sur un nouveau serveur Fabric disposant du mod et de Fabric API :
|
||||
|
||||
```properties
|
||||
level-type=sanctuary:sanctuary
|
||||
level-name=sanctuary
|
||||
level-seed=0
|
||||
```
|
||||
|
||||
La seed est libre. Modifier `level-type` ne convertit pas un monde existant :
|
||||
utiliser un nouveau `level-name` pour tester une autre génération. Les fichiers
|
||||
de test et les sauvegardes personnelles doivent rester distincts.
|
||||
|
||||
## Ce qui est généré
|
||||
|
||||
- Le générateur reste celui de Minecraft, `minecraft:noise`.
|
||||
- La forme reprend le champ de bruit flottant actif en 26.2 : `old_blended_noise`
|
||||
avec `xz_scale=0.25`, `y_scale=0.25`, `xz_factor=80`, `y_factor=160`, `smear=4`,
|
||||
ainsi que son gradient supérieur. Il est échantillonné **64 blocs plus bas**
|
||||
pour relever l'île entière de 64 blocs. Reliefs, surplombs, trous et fragments
|
||||
dépendent de la seed Minecraft. L'ancien gradient inférieur uniforme est
|
||||
remplacé par la sculpture irrégulière décrite ci-dessous.
|
||||
- Le terrain naturel reste libre de laisser un trou à l'origine, de former des
|
||||
surplombs ou de se séparer en fragments. **Aucun noyau ellipsoïdal, cylindre ou
|
||||
plateforme de départ n'est ajouté.** Au-dessus de `Y=144`, le relief de la
|
||||
génération précédente est conservé dans la densité, simplement relevé de 64
|
||||
blocs. L’hydrologie v4 creuse ensuite localement les lits de surface.
|
||||
- Le pourtour est sculpté par retrait de matière à partir du rayon **128 blocs**.
|
||||
Le contour de référence de 256 blocs est déformé par les bruits, entre 204 et
|
||||
288 blocs ; il indique la fin de l'érosion, pas un mur de roche.
|
||||
La côte réelle apparaît plus tôt, selon la densité du terrain et la seed.
|
||||
Il reste du vide permanent au-delà de **288 blocs**, sans répétition d'îles.
|
||||
Les arbres peuvent dépasser la côte rocheuse de quelques blocs.
|
||||
- Les nouveaux mondes utilisent **384 blocs de hauteur**, de `Y=0` à `Y=383`,
|
||||
pour la dimension et le générateur. La roche disparaît avant leurs limites.
|
||||
Il n'y a ni plancher de bedrock,
|
||||
ni mer globale, ni reprise du terrain à distance. Le Nether et l'End restent
|
||||
ceux de Minecraft.
|
||||
- `sanctuary:starter_forest_hydrology` reprend végétation, minerais et animaux de la forêt
|
||||
vanilla 26.3. Ses blocs sont exclusivement vanilla. C'est un biome distinct
|
||||
pour ne pas hériter des tags de structures vanilla et de leurs apparitions
|
||||
possibles dans le vide.
|
||||
- Les carvers, géodes, donjons, lacs de lave et sources sont retirés de cette
|
||||
forêt initiale. Les minerais remplacent la roche existante et les arbres ont
|
||||
besoin de sol. Aucun mod de biomes externe n'est nécessaire.
|
||||
|
||||
L’hydrologie v4 fournit les premières eaux de surface décrites ci-dessous. La
|
||||
répartition complète des ressources de survie et la variété de biomes restent
|
||||
à développer. Les minerais vanilla conservent leurs plages d’altitude ;
|
||||
leur abondance n'est donc pas encore équilibrée pour une île flottante.
|
||||
|
||||
## Sculpture du pourtour — conservée depuis la génération v2
|
||||
|
||||
La version **0.1.0-alpha.2** retire deux contraintes de la génération v1
|
||||
(`0.1.0-alpha.1`) : le noyau central qui remplissait les creux du bruit naturel,
|
||||
et la découpe radiale qui pouvait exposer une tranche verticale à toutes les
|
||||
altitudes. Les identifiants `sanctuary:*` du preset et de la fonction restent
|
||||
stables. Il s'agit d'une évolution du terrain, pas d'une migration de sauvegarde.
|
||||
|
||||
Trois passes composent désormais la densité finale, après l'interpolation du
|
||||
champ historique :
|
||||
|
||||
1. Un bruit horizontal à grande échelle déforme le contour. Dans la bande
|
||||
extérieure, large de 128 blocs avant déformation, une érosion progressive
|
||||
(`2 × t⁴`) augmente le seuil nécessaire pour conserver de la roche. Une
|
||||
érosion supplémentaire, quadratique selon la hauteur et nulle à l'intérieur,
|
||||
fait reculer les faces hautes et basses même lorsque le bruit naturel est
|
||||
saturé dans une grande masse pleine. Elle arrondit le pourtour sans ajouter
|
||||
de volume au centre.
|
||||
2. Un bruit **3D**, échantillonné à la hauteur réelle, déplace ce contour et
|
||||
creuse des renfoncements plus larges. Le bord peut ainsi changer de position
|
||||
avec l'altitude au lieu de prolonger une ligne verticale.
|
||||
3. Un second bruit **3D**, plus fin, ajoute de petites irrégularités. Ces deux
|
||||
passes s'atténuent progressivement vers l'intérieur ; elles ne remplissent
|
||||
jamais un trou du terrain initial.
|
||||
|
||||
Les bruits `island_edge`, `island_sculpt` et `island_detail` sont tous initialisés
|
||||
avec la seed du monde. Ils utilisent respectivement les octaves de base
|
||||
`-7`, `-5` et `-3`. Les nouvelles entrées `sculpt` et `detail` du codec sont
|
||||
optionnelles pour garder les anciennes définitions JSON lisibles ; leur absence
|
||||
ne rétablit pas le noyau de la génération v1.
|
||||
|
||||
L'érosion est toujours soustraite à la densité bornée du terrain. Une densité
|
||||
nulle ou négative ne peut donc jamais devenir solide. Même avec un terrain
|
||||
d'entrée maximalement positif, elle est négative dès le rayon 280, avant la
|
||||
limite de sécurité 288. Deux transitions verticales douces protègent aussi les
|
||||
bornes de hauteur, sans découper de dalle à `Y=0` ou `Y=256`.
|
||||
|
||||
Ces formules sont toujours utilisées par les sauvegardes de la génération v2.
|
||||
La génération v3 conserve la sculpture latérale dans le repère de l'île relevée.
|
||||
|
||||
## Île relevée et dessous irrégulier — génération v3
|
||||
|
||||
La version **0.1.0-alpha.3** crée les nouveaux mondes avec les paramètres et le
|
||||
type de dimension `sanctuary:sanctuary_384` : `min_y=0`, `height=384` et
|
||||
`logical_height=384`. Le nom du preset sélectionné reste `sanctuary:sanctuary`.
|
||||
Le gain de hauteur n'agrandit pas le rayon horizontal et n'active aucun continent.
|
||||
|
||||
Le bruit naturel et les bruits de sculpture sont échantillonnés à `Y−64`.
|
||||
Le profil supérieur et les bords approuvés en alpha.2 sont ainsi relevés sans
|
||||
changer de seed ou étirer l'île. Sur les graines de régression, les sommets
|
||||
proches de `Y=184` se retrouvent vers `Y=248`, avec de l'espace libre au-dessus.
|
||||
|
||||
Sous l'île, déplacer l'ancien gradient inférieur aurait seulement déplacé son
|
||||
plan de coupe. `floating_archipelago_384` conserve donc le bruit naturel sans
|
||||
ce gradient inférieur. Une nouvelle érosion travaille sur une transition de
|
||||
**72 blocs**, dont l'altitude varie avec le bruit 3D `island_underside` et les
|
||||
deux bruits de sculpture existants. Elle laisse les lobes naturels se poursuivre
|
||||
vers le bas, puis les amincit à différentes hauteurs ; elle n'ajoute ni socle ni
|
||||
surface garantie. La transition n'affecte plus le terrain à partir de `Y=144`.
|
||||
Une marge vide protège le bas de la dimension ; le contrôle des chunks terminés
|
||||
vérifie que la tranche `Y=0..15` ne contient aucun bloc.
|
||||
|
||||
Les champs optionnels du codec `vertical_offset`, `height` et `underside` valent
|
||||
respectivement `0`, `256` et un bruit nul par défaut. Seule la nouvelle fonction
|
||||
`sanctuary:final_density_384` active l'offset `64`, la hauteur `384` et le bruit du
|
||||
dessous. Les anciennes fonctions `final_density`, `floating_archipelago` et les
|
||||
paramètres `sanctuary:sanctuary` restent inchangés : les sauvegardes alpha.2 qui
|
||||
les référencent gardent leur hauteur de génération et leur ancien terrain.
|
||||
|
||||
**Créer un nouveau monde pour tester l'île relevée.** L'actualisation du pack ne
|
||||
convertit pas les sauvegardes existantes ; aucun chunk n'est supprimé, déplacé
|
||||
ou régénéré. Il n'existe pas de migration automatique des anciens mondes vers la
|
||||
dimension de 384 blocs. Changer manuellement leurs références de générateur ou
|
||||
de dimension sort de ce contrat de compatibilité.
|
||||
|
||||
## Hydrologie de surface — génération v4
|
||||
|
||||
La version **0.1.0-alpha.4** conserve la densité, la hauteur et le ciel de
|
||||
l’alpha.3. Les nouveaux mondes sélectionnent les paramètres
|
||||
`sanctuary:sanctuary_hydrology` et le biome `sanctuary:starter_forest_hydrology`.
|
||||
Le type de dimension reste `sanctuary:sanctuary_384`. Les anciens paramètres et
|
||||
le biome `sanctuary:starter_forest` restent disponibles et inchangés : ils
|
||||
n’activent pas cette hydrologie dans les sauvegardes antérieures.
|
||||
|
||||
Un plan calculé depuis le relief et la graine recherche des emplacements de
|
||||
surface pour des étangs, des petits lacs et un premier cours d’eau calme.
|
||||
Le lit peut être légèrement creusé dans la roche existante. Avant d’accepter
|
||||
un emplacement, le plan vérifie le fond et les parois en trois dimensions ainsi
|
||||
que l’ouverture vers le ciel. Un trou profond dans la carte de hauteur ne suffit
|
||||
pas à constituer un lac. Aucune digue ni masse de roche n’est ajoutée pour
|
||||
boucher une fuite, et aucun bassin souterrain n’est rempli.
|
||||
|
||||
Les fonds alternent sable, gravier et argile ; les berges basses peuvent recevoir
|
||||
du sable. Dans ce nouveau biome, les disques de sédiments vanilla et les veines
|
||||
de gravier sont remplacés par ces dépôts de rive et de fond : une feature
|
||||
ultérieure ne doit pas transformer leur support en gravier instable. Les
|
||||
sources de magma sous-marines sont également absentes. Après les arbres et la
|
||||
végétation, de la canne à sucre peut pousser sur
|
||||
les rives qui respectent ses règles de placement vanilla. Le nombre de sites,
|
||||
leur eau et leurs ressources dépendent du terrain : le générateur ne complète
|
||||
pas une graine pour atteindre un quota économique.
|
||||
|
||||
Le plan est indépendant des chunks chargés. Chaque chunk applique uniquement
|
||||
ses propres colonnes après le terrain et avant la décoration, ce qui permet de
|
||||
raccorder un même plan d’eau aux frontières des chunks. Les anciens chunks ne
|
||||
sont jamais retouchés, et aucun travail hydrologique ne se déclenche au simple
|
||||
chargement d’une sauvegarde.
|
||||
|
||||
Cette première version vise de petites eaux retenues et des cours d’eau calmes
|
||||
sur les plateaux. Le cours d’eau est un chenal à niveau constant reliant des
|
||||
plans d’eau ; il ne simule pas encore une pente vers l’aval ou un débit. Les
|
||||
tracés peuvent passer par les diagonales, mais leur eau reste connectée de
|
||||
bloc en bloc. L’excavation est limitée à six blocs, l’eau à quatre blocs de
|
||||
profondeur, avec un fond plein sur trois blocs d’épaisseur, dont deux blocs naturels sous
|
||||
le sédiment. Les grandes rivières en pente, cascades, océans, aquifères,
|
||||
climats multiples et continents restent des incréments distincts.
|
||||
**Créer un nouveau monde Sanctuary pour tester cette hydrologie.**
|
||||
|
||||
## Apparition commune
|
||||
|
||||
Une injection limitée à la création initiale cherche du sol naturel depuis
|
||||
l'origine vers l'extérieur, dans toute l'emprise finie de l'île. Elle préfère
|
||||
une surface stable de 3 × 3 blocs, avec deux blocs libres en hauteur. En
|
||||
l'absence d'une telle surface, elle retient une colonne de sol praticable ; si
|
||||
aucun emplacement naturel sûr n'existe, la création signale la seed concernée
|
||||
plutôt que d'ajouter une plateforme. Elle ne s'applique qu'à l'Overworld
|
||||
utilisant les paramètres `sanctuary:sanctuary`, `sanctuary:sanctuary_384` ou
|
||||
`sanctuary:sanctuary_hydrology`.
|
||||
Elle enregistre le spawn partagé dans la sauvegarde et ne le réinitialise pas
|
||||
au redémarrage : les modifications administratives de `/setworldspawn` restent
|
||||
conservées. Les lits et la dispersion habituelle du spawn suivent encore les
|
||||
règles de Minecraft.
|
||||
|
||||
## Adaptation 26.2 → 26.3
|
||||
|
||||
Les références historiques sont
|
||||
`sanctuary/worldgen/density_function/base_3d_noise.json` et
|
||||
`floating_archipelago.json` dans l'ancien module Sanctuary. L'ancien masque
|
||||
annulaire n'est pas conservé : après son anneau de vide, il faisait réapparaître
|
||||
un archipel infini. Les anciennes surcharges de `minecraft:normal`, dimensions
|
||||
supplémentaires, intégrations TerraMix et injections globales de features ne
|
||||
font pas partie de ce port.
|
||||
|
||||
Minecraft 26.3 a changé l'API de génération : les fonctions de densité compilent
|
||||
maintenant un `DensitySampler`, leurs opérateurs JSON utilisent
|
||||
`left` / `right` / `input`, les gradients utilisent `from_coordinate` et les
|
||||
surfaces passent par le registre `worldgen/material_rule`. Les données de cette
|
||||
branche suivent ces formats réels ; copier directement les JSON 26.2 ne suffit
|
||||
pas. Les bruits utilisent également `base_octave`, `octave_count` et
|
||||
`amplitude_modifiers`. Le seul opérateur Java ajouté, `sanctuary:main_island`,
|
||||
compose le terrain et les bruits de sculpture, applique le repère vertical
|
||||
choisi et impose la limite extérieure après interpolation.
|
||||
|
||||
## Vérification
|
||||
|
||||
```sh
|
||||
./gradlew :sanctuary:worldgenSmoke
|
||||
./gradlew :sanctuary:runGameTest
|
||||
```
|
||||
|
||||
Le smoke test vérifie qu'aucun sol n'est ajouté, que l'intérieur conserve le champ
|
||||
naturel et que l'érosion évolue progressivement sur un profil de terrain contrôlé.
|
||||
Il vérifie aussi qu'un lobe plein recule d'au moins 30 blocs en haut et en bas
|
||||
par rapport à son milieu, pour éviter une paroi verticale simplement déplacée.
|
||||
Il couvre aussi l'influence des deux bruits de sculpture, le vide avant les
|
||||
bornes de sécurité, et le vide dans les quatre directions même avec un bruit
|
||||
entièrement positif. Les coordonnées testées incluent les limites de Minecraft
|
||||
et des entiers Java pour détecter débordements et répétitions involontaires.
|
||||
|
||||
Les GameTests chargent le preset réel sur un serveur éphémère, vérifient le
|
||||
spawn après décoration, puis inspectent des chunks complets à distance dans
|
||||
les quatre directions. Ils comparent aussi la densité compilée avec deux
|
||||
instances de la même seed et une seed différente. Cela détecte les références
|
||||
JSON invalides et les blocs que des features pourraient ajouter après le calcul
|
||||
de densité. Les injections du serveur de test sont isolées dans
|
||||
`src/gametest` et ne sont pas distribuées dans le mod.
|
||||
|
||||
Les graines de régression des générations v2 et v3 sont **0**, **42** et **8675309**.
|
||||
Le serveur de test exporte leurs cartes de hauteur et coupes de densité dans
|
||||
`mods/sanctuary/build/run/gameTest/diagnostics/`. Ces images permettent de
|
||||
comparer la vraie densité alpha.2, toujours chargée, avec la nouvelle île relevée
|
||||
dans toute la hauteur `0..383`. Les assertions vérifient la translation du relief
|
||||
supérieur, la variation des hauteurs du dessous, l'absence de plancher, les
|
||||
384 blocs de génération et la conservation des anciens paramètres 256.
|
||||
Ces images ne montrent
|
||||
pas les arbres ni les matériaux et ne remplacent pas un essai visuel en jeu.
|
||||
|
||||
## Contrat du prochain ticket : expansion
|
||||
|
||||
Avant de créer des continents, définir une donnée **persistante par monde**
|
||||
contenant pour chaque région son identifiant, centre, direction, emprise,
|
||||
seed dérivée, version de générateur et état de déblocage. L'île principale
|
||||
devient la première région de ce registre ; les nouvelles régions utilisent
|
||||
des coordonnées absolues, sans cellules périodiques.
|
||||
|
||||
L'activation doit être une action du serveur, reproductible et sauvegardée,
|
||||
conditionnée ensuite par les contributions collectives. Elle doit tenir compte
|
||||
du fait qu'un joueur peut déjà avoir chargé ou construit dans le vide : un
|
||||
chunk existant ne se régénère pas simplement parce que la liste des continents
|
||||
change. Le ticket doit choisir une réservation d'espace ou une matérialisation
|
||||
contrôlée qui protège les constructions et ne remplace jamais aveuglément des
|
||||
chunks sauvegardés.
|
||||
|
||||
La version de génération et les emprises doivent être gelées avant les premières
|
||||
sauvegardes destinées à durer. Ce prototype ne promet pas encore de migration
|
||||
automatique des mondes entre deux versions de terrain.
|
||||
@@ -0,0 +1,13 @@
|
||||
org.gradle.jvmargs=-Xmx2G
|
||||
org.gradle.parallel=true
|
||||
org.gradle.configuration-cache=false
|
||||
|
||||
# Verified 2026-09-08. 26.3 is still a pre-release; do not silently move worlds.
|
||||
minecraft_version=26.3-pre-2
|
||||
loader_version=0.19.5
|
||||
loom_version=1.17.20
|
||||
fabric_api_version=0.160.0+26.3
|
||||
|
||||
mod_version=0.1.0-alpha.4
|
||||
pack_version=0.1.0-alpha.4
|
||||
maven_group=fr.koka.sanctuary
|
||||
Vendored
BIN
Binary file not shown.
+8
@@ -0,0 +1,8 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.1-bin.zip
|
||||
distributionSha256Sum=bafc141b619ad6350fd975fc903156dd5c151998cc8b058e8c1044ab5f7b031f
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
@@ -0,0 +1,253 @@
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright © 2015 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/2d6327017519d23b96af35865dc997fcb544fb40/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||
|
||||
if [ -z "$JAVA_HOME" ] && [ -x "$APP_HOME/.jdk/current/bin/java" ] ; then
|
||||
JAVA_HOME=$APP_HOME/.jdk/current
|
||||
export JAVA_HOME
|
||||
fi
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
Vendored
+93
@@ -0,0 +1,93 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
@rem SPDX-License-Identifier: Apache-2.0
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
-632
@@ -1,632 +0,0 @@
|
||||
[
|
||||
{
|
||||
"version": "beta.001",
|
||||
"tag_name": "notes/beta.001",
|
||||
"name": "Sanctuary beta.001 — Les quatre anciennes expéditions",
|
||||
"body_file": "notes/beta.001.md",
|
||||
"source_tag_exact": false,
|
||||
"draft": false,
|
||||
"prerelease": true,
|
||||
"body_sha256": "d2faf130a76135e466a160376263915a711c20703dbdba4c1261a4d01b204adf"
|
||||
},
|
||||
{
|
||||
"version": "beta.002",
|
||||
"tag_name": "notes/beta.002",
|
||||
"name": "Sanctuary beta.002 — Biomes boréaux, corail et temples",
|
||||
"body_file": "notes/beta.002.md",
|
||||
"source_tag_exact": false,
|
||||
"draft": false,
|
||||
"prerelease": true,
|
||||
"body_sha256": "517197580174ddd46d1c6b5be20829e91ed2daa61c635b8b369ea9afad3ab4d2"
|
||||
},
|
||||
{
|
||||
"version": "beta.003",
|
||||
"tag_name": "notes/beta.003",
|
||||
"name": "Sanctuary beta.003 — Créer son habitant et commencer la progression",
|
||||
"body_file": "notes/beta.003.md",
|
||||
"source_tag_exact": false,
|
||||
"draft": false,
|
||||
"prerelease": true,
|
||||
"body_sha256": "8154048c7b2afff8a3c42e7993cc899de10716b4106fa00bc3f6f7d2827b0963"
|
||||
},
|
||||
{
|
||||
"version": "beta.004",
|
||||
"tag_name": "notes/beta.004",
|
||||
"name": "Sanctuary beta.004 — Cadre Blocodex et sprint",
|
||||
"body_file": "notes/beta.004.md",
|
||||
"source_tag_exact": false,
|
||||
"draft": false,
|
||||
"prerelease": true,
|
||||
"body_sha256": "7e587dc5519573752dd955fb4c34d9f5fae824f867b4dcbec514e5bab9d6768f"
|
||||
},
|
||||
{
|
||||
"version": "beta.005",
|
||||
"tag_name": "notes/beta.005",
|
||||
"name": "Sanctuary beta.005 — Carte native et noms de couleurs",
|
||||
"body_file": "notes/beta.005.md",
|
||||
"source_tag_exact": false,
|
||||
"draft": false,
|
||||
"prerelease": true,
|
||||
"body_sha256": "eafb297b4b2e23ab51244c817114d1c1fe55edfbea5a7eb41dadaec1b1f0a4d1"
|
||||
},
|
||||
{
|
||||
"version": "beta.006",
|
||||
"tag_name": "notes/beta.006",
|
||||
"name": "Sanctuary beta.006 — Brouillard, grille et Demeure",
|
||||
"body_file": "notes/beta.006.md",
|
||||
"source_tag_exact": false,
|
||||
"draft": false,
|
||||
"prerelease": true,
|
||||
"body_sha256": "af67ab386fae0e2ed2e0833e206cb6a4681bbbeee5cb3f77e04ec31eb2f76023"
|
||||
},
|
||||
{
|
||||
"version": "beta.007",
|
||||
"tag_name": "notes/beta.007",
|
||||
"name": "Sanctuary beta.007 — Carte immersive",
|
||||
"body_file": "notes/beta.007.md",
|
||||
"source_tag_exact": false,
|
||||
"draft": false,
|
||||
"prerelease": true,
|
||||
"body_sha256": "6ef0c1df5b317159a0dd05b9e5d4535d60cf77c7e700cc82cbc4bc63f1a673bc"
|
||||
},
|
||||
{
|
||||
"version": "beta.008",
|
||||
"tag_name": "notes/beta.008",
|
||||
"name": "Sanctuary beta.008 — Minage et vein mining",
|
||||
"body_file": "notes/beta.008.md",
|
||||
"source_tag_exact": false,
|
||||
"draft": false,
|
||||
"prerelease": true,
|
||||
"body_sha256": "5873b41ee191e709ad1b12fbbbea3b237c974e29740006923df3f295ece5ddd7"
|
||||
},
|
||||
{
|
||||
"version": "beta.009",
|
||||
"tag_name": "notes/beta.009",
|
||||
"name": "Sanctuary beta.009 — Construction et vein building",
|
||||
"body_file": "notes/beta.009.md",
|
||||
"source_tag_exact": false,
|
||||
"draft": false,
|
||||
"prerelease": true,
|
||||
"body_sha256": "1e6b325a3c5f337b711a7c6506ea8adc665c4f0ac687f9f0ef221ff8e99fe17d"
|
||||
},
|
||||
{
|
||||
"version": "beta.010",
|
||||
"tag_name": "notes/beta.010",
|
||||
"name": "Sanctuary beta.010 — Inventaire jusqu'à six rangées",
|
||||
"body_file": "notes/beta.010.md",
|
||||
"source_tag_exact": false,
|
||||
"draft": false,
|
||||
"prerelease": true,
|
||||
"body_sha256": "6bb2e7a0b6428ce0481cf565123aca6b3781e00a199dad7001302c3006a126eb"
|
||||
},
|
||||
{
|
||||
"version": "beta.011",
|
||||
"tag_name": "notes/beta.011",
|
||||
"name": "Sanctuary beta.011 — Factions, prestige et New Game+",
|
||||
"body_file": "notes/beta.011.md",
|
||||
"source_tag_exact": false,
|
||||
"draft": false,
|
||||
"prerelease": true,
|
||||
"body_sha256": "40b82f673945db70e2654181eba8cc022b2d1dec6375bf6b0d659ebfbfa2762b"
|
||||
},
|
||||
{
|
||||
"version": "beta.012",
|
||||
"tag_name": "notes/beta.012",
|
||||
"name": "Sanctuary beta.012 — Inventaire et progression lisible",
|
||||
"body_file": "notes/beta.012.md",
|
||||
"source_tag_exact": false,
|
||||
"draft": false,
|
||||
"prerelease": true,
|
||||
"body_sha256": "17eed6551ae82eef2634b1f9a7a59c19a74a542c66847aca56183e60179596cf"
|
||||
},
|
||||
{
|
||||
"version": "beta.013",
|
||||
"tag_name": "notes/beta.013",
|
||||
"name": "Sanctuary beta.013 — Progression, prestige et repères visuels",
|
||||
"body_file": "notes/beta.013.md",
|
||||
"source_tag_exact": false,
|
||||
"draft": false,
|
||||
"prerelease": true,
|
||||
"body_sha256": "92f9e99124d10c33f22e2d376d988ef9f2e2290d62c6cb32794b45583c3e4ab4"
|
||||
},
|
||||
{
|
||||
"version": "beta.014",
|
||||
"tag_name": "notes/beta.014",
|
||||
"name": "Sanctuary beta.014 — Rangement, hotbar mobile et préfixe de faction",
|
||||
"body_file": "notes/beta.014.md",
|
||||
"source_tag_exact": false,
|
||||
"draft": false,
|
||||
"prerelease": true,
|
||||
"body_sha256": "80071335c063edec5f76059d839c7904fd24bb21641f31d029cf25b88e0efdd6"
|
||||
},
|
||||
{
|
||||
"version": "beta.015",
|
||||
"tag_name": "notes/beta.015",
|
||||
"name": "Sanctuary beta.015 — Icônes de progression communes",
|
||||
"body_file": "notes/beta.015.md",
|
||||
"source_tag_exact": false,
|
||||
"draft": false,
|
||||
"prerelease": true,
|
||||
"body_sha256": "f0ea350fc5710608b445494a8f229cf7453616e27c3fe08a880a14ca087153d5"
|
||||
},
|
||||
{
|
||||
"version": "beta.016",
|
||||
"tag_name": "notes/beta.016",
|
||||
"name": "Sanctuary beta.016 — Les premiers gestes rapportent de l’XP",
|
||||
"body_file": "notes/beta.016.md",
|
||||
"source_tag_exact": false,
|
||||
"draft": false,
|
||||
"prerelease": true,
|
||||
"body_sha256": "2f049fcfa96f69b4ad77832ee21d9fa897e412a7766628479dcbb9229bf3dc15"
|
||||
},
|
||||
{
|
||||
"version": "beta.017",
|
||||
"tag_name": "notes/beta.017",
|
||||
"name": "Sanctuary beta.017 — Nage, S’allonger, Se reposer et fabrication manuelle",
|
||||
"body_file": "notes/beta.017.md",
|
||||
"source_tag_exact": false,
|
||||
"draft": false,
|
||||
"prerelease": true,
|
||||
"body_sha256": "75f45ac8203a8be496bf56f20641e5d900dcb6d656004641ab166a975b248a98"
|
||||
},
|
||||
{
|
||||
"version": "beta.018",
|
||||
"tag_name": "notes/beta.018",
|
||||
"name": "Sanctuary beta.018 — Familiers, cape et cosmétique de tête",
|
||||
"body_file": "notes/beta.018.md",
|
||||
"source_tag_exact": false,
|
||||
"draft": false,
|
||||
"prerelease": true,
|
||||
"body_sha256": "af5d7f2a9a08be017e9ea565e0ac7b7f89a5668eaca752bbef3fd83d4dcd1b83"
|
||||
},
|
||||
{
|
||||
"version": "beta.019",
|
||||
"tag_name": "notes/beta.019",
|
||||
"name": "Sanctuary beta.019 — Pouvoirs et déplacements des familiers",
|
||||
"body_file": "notes/beta.019.md",
|
||||
"source_tag_exact": false,
|
||||
"draft": false,
|
||||
"prerelease": true,
|
||||
"body_sha256": "89014f518126d23a927f385e39e0e45bac1862b753eedd5dbf6a982f2fc02ae8"
|
||||
},
|
||||
{
|
||||
"version": "beta.020",
|
||||
"tag_name": "notes/beta.020",
|
||||
"name": "Sanctuary beta.020 — Œufs singuliers, portage et plongeon",
|
||||
"body_file": "notes/beta.020.md",
|
||||
"source_tag_exact": false,
|
||||
"draft": false,
|
||||
"prerelease": true,
|
||||
"body_sha256": "a40be9d43b2e706d96b051a801445ce623889c36963f4a9a1114c3275dab1406"
|
||||
},
|
||||
{
|
||||
"version": "beta.022",
|
||||
"tag_name": "notes/beta.022",
|
||||
"name": "Sanctuary beta.022 — Création de monde plus rapide",
|
||||
"body_file": "notes/beta.022.md",
|
||||
"source_tag_exact": false,
|
||||
"draft": false,
|
||||
"prerelease": true,
|
||||
"body_sha256": "20c29abb7f12024956084f5ad5c384306a0beb57b6f49e5108dd01cad16a3e71"
|
||||
},
|
||||
{
|
||||
"version": "beta.023",
|
||||
"tag_name": "notes/beta.023",
|
||||
"name": "Sanctuary beta.023 — Cosmétiques directement sur leur case",
|
||||
"body_file": "notes/beta.023.md",
|
||||
"source_tag_exact": false,
|
||||
"draft": false,
|
||||
"prerelease": true,
|
||||
"body_sha256": "56290132ee6abb0992102e7b3bc0320298390e56a4055f7f36a49986209b356c"
|
||||
},
|
||||
{
|
||||
"version": "beta.024",
|
||||
"tag_name": "notes/beta.024",
|
||||
"name": "Sanctuary beta.024 — Recettes progressives dans le Blocodex",
|
||||
"body_file": "notes/beta.024.md",
|
||||
"source_tag_exact": false,
|
||||
"draft": false,
|
||||
"prerelease": true,
|
||||
"body_sha256": "92e5de8ae776f91a5ba5f3d0a884fea7e1f81811a83b2a712bf234c08293b4bb"
|
||||
},
|
||||
{
|
||||
"version": "beta.025",
|
||||
"tag_name": "notes/beta.025",
|
||||
"name": "Sanctuary beta.025 — Barres d’XP des compétences",
|
||||
"body_file": "notes/beta.025.md",
|
||||
"source_tag_exact": false,
|
||||
"draft": false,
|
||||
"prerelease": true,
|
||||
"body_sha256": "cbd9efc6d630a0ef2a1511c489545e9e87c8bd79b836b7616ade9ac077f0f8eb"
|
||||
},
|
||||
{
|
||||
"version": "beta.026",
|
||||
"tag_name": "notes/beta.026",
|
||||
"name": "Sanctuary beta.026 — Cache d’affichage Demeure sur la carte",
|
||||
"body_file": "notes/beta.026.md",
|
||||
"source_tag_exact": false,
|
||||
"draft": false,
|
||||
"prerelease": true,
|
||||
"body_sha256": "aee2d4e998dd2ac9dc172d79018e552c658bd66db217f55b94e5bdd6bcabd50b"
|
||||
},
|
||||
{
|
||||
"version": "beta.027",
|
||||
"tag_name": "notes/beta.027",
|
||||
"name": "Sanctuary beta.027 — Piles de joueurs",
|
||||
"body_file": "notes/beta.027.md",
|
||||
"source_tag_exact": false,
|
||||
"draft": false,
|
||||
"prerelease": true,
|
||||
"body_sha256": "9ec554eebe10b8b35955fbebd1404a273b26f8fe3693aed1b2407305cf7c7699"
|
||||
},
|
||||
{
|
||||
"version": "beta.028",
|
||||
"tag_name": "notes/beta.028",
|
||||
"name": "Sanctuary beta.028 — Plongeon jusqu’à collision",
|
||||
"body_file": "notes/beta.028.md",
|
||||
"source_tag_exact": false,
|
||||
"draft": false,
|
||||
"prerelease": true,
|
||||
"body_sha256": "af533c1dc914aeab9e967d07dc8a419a7d039e1ab78365221ac90aae69a2142d"
|
||||
},
|
||||
{
|
||||
"version": "beta.029",
|
||||
"tag_name": "notes/beta.029",
|
||||
"name": "Sanctuary beta.029 — Cri et recul des familiers",
|
||||
"body_file": "notes/beta.029.md",
|
||||
"source_tag_exact": false,
|
||||
"draft": false,
|
||||
"prerelease": true,
|
||||
"body_sha256": "bf653938f97f056493b17666cb2e5504db244cb2f114ebbd4fe581b48b20c53b"
|
||||
},
|
||||
{
|
||||
"version": "beta.030",
|
||||
"tag_name": "notes/beta.030",
|
||||
"name": "Sanctuary beta.030 — Lumières portées et objets jetés",
|
||||
"body_file": "notes/beta.030.md",
|
||||
"source_tag_exact": false,
|
||||
"draft": false,
|
||||
"prerelease": true,
|
||||
"body_sha256": "603e95e406ebf286c1b029d97e18b389f1d0135e1c92374dcdaa927b49ead93c"
|
||||
},
|
||||
{
|
||||
"version": "beta.031",
|
||||
"tag_name": "notes/beta.031",
|
||||
"name": "Sanctuary beta.031 — Apparition galactique",
|
||||
"body_file": "notes/beta.031.md",
|
||||
"source_tag_exact": false,
|
||||
"draft": false,
|
||||
"prerelease": true,
|
||||
"body_sha256": "700426014052a0c44f4a7e360912dae6c1e8c80379cfe298ba5f8c5575cd6efe"
|
||||
},
|
||||
{
|
||||
"version": "beta.032",
|
||||
"tag_name": "notes/beta.032",
|
||||
"name": "Sanctuary beta.032 — Refonte des 88 familiers",
|
||||
"body_file": "notes/beta.032.md",
|
||||
"source_tag_exact": false,
|
||||
"draft": false,
|
||||
"prerelease": true,
|
||||
"body_sha256": "b24ef127919fafdb71d5b0b79b8ed6419b786cfd8b74aed6244b3b4527e6af3f"
|
||||
},
|
||||
{
|
||||
"version": "beta.033",
|
||||
"tag_name": "notes/beta.033",
|
||||
"name": "Sanctuary beta.033 — Menus directs et profil de test rapide",
|
||||
"body_file": "notes/beta.033.md",
|
||||
"source_tag_exact": false,
|
||||
"draft": false,
|
||||
"prerelease": true,
|
||||
"body_sha256": "865724dab2959885f19f39d479b2f49218c4001a818c304c1ef200f30412ccf8"
|
||||
},
|
||||
{
|
||||
"version": "beta.034",
|
||||
"tag_name": "notes/beta.034",
|
||||
"name": "Sanctuary beta.034 — Fil de découvertes et hotbar agrandie",
|
||||
"body_file": "notes/beta.034.md",
|
||||
"source_tag_exact": false,
|
||||
"draft": false,
|
||||
"prerelease": true,
|
||||
"body_sha256": "07afd3e8dd1b82aa515bb0db789975d34b9d44e6ae00f5213b8ea29912e41367"
|
||||
},
|
||||
{
|
||||
"version": "beta.035",
|
||||
"tag_name": "notes/beta.035",
|
||||
"name": "Sanctuary beta.035 — Collections Minecraft",
|
||||
"body_file": "notes/beta.035.md",
|
||||
"source_tag_exact": false,
|
||||
"draft": false,
|
||||
"prerelease": true,
|
||||
"body_sha256": "5b705cd4160907d649f5888130432d390dbd4bc28d3fe5aabf6234feaac30cc6"
|
||||
},
|
||||
{
|
||||
"version": "beta.036",
|
||||
"tag_name": "notes/beta.036",
|
||||
"name": "Sanctuary beta.036 — Notifications et suivi choisi",
|
||||
"body_file": "notes/beta.036.md",
|
||||
"source_tag_exact": false,
|
||||
"draft": false,
|
||||
"prerelease": true,
|
||||
"body_sha256": "3e18002138675b1e7d3f80ae633a49cc9204a32c683405dc1ef2a887d1da5810"
|
||||
},
|
||||
{
|
||||
"version": "beta.037",
|
||||
"tag_name": "notes/beta.037",
|
||||
"name": "Sanctuary beta.037 — Temps réel et suivi commun",
|
||||
"body_file": "notes/beta.037.md",
|
||||
"source_tag_exact": false,
|
||||
"draft": false,
|
||||
"prerelease": true,
|
||||
"body_sha256": "a4dc686f2bea49c80a001c99b3c5e42d77bda5227223ba5e4d2a09d928915180"
|
||||
},
|
||||
{
|
||||
"version": "beta.038",
|
||||
"tag_name": "notes/beta.038",
|
||||
"name": "Sanctuary beta.038 — Nourriture, têtes-tombes et factions",
|
||||
"body_file": "notes/beta.038.md",
|
||||
"source_tag_exact": false,
|
||||
"draft": false,
|
||||
"prerelease": true,
|
||||
"body_sha256": "41d70130039d2fc5bf24fc761757a82223ed7c64bdf209371a2ce1455b2a107b"
|
||||
},
|
||||
{
|
||||
"version": "beta.039",
|
||||
"tag_name": "notes/beta.039",
|
||||
"name": "Sanctuary beta.039 — Hello World, savoir alimentaire et inventaires de mort",
|
||||
"body_file": "notes/beta.039.md",
|
||||
"source_tag_exact": false,
|
||||
"draft": false,
|
||||
"prerelease": true,
|
||||
"body_sha256": "11eeb112aef244c42253a088a407c81513bad569f1a006ea85ff8c37a87c71da"
|
||||
},
|
||||
{
|
||||
"version": "beta.040",
|
||||
"tag_name": "notes/beta.040",
|
||||
"name": "Sanctuary beta.040 — Mode opérateur, fiche Habitant et ciel partagé",
|
||||
"body_file": "notes/beta.040.md",
|
||||
"source_tag_exact": false,
|
||||
"draft": false,
|
||||
"prerelease": true,
|
||||
"body_sha256": "f4ba5e5510fef9200b5a0d1cf0935ef6bb431b334caf55d59566873cdfa068cd"
|
||||
},
|
||||
{
|
||||
"version": "beta.041",
|
||||
"tag_name": "notes/beta.041",
|
||||
"name": "Sanctuary beta.041 — Portage des bébés",
|
||||
"body_file": "notes/beta.041.md",
|
||||
"source_tag_exact": false,
|
||||
"draft": false,
|
||||
"prerelease": true,
|
||||
"body_sha256": "e9d46eb29e305c31e3826c4d65f76ca1666704c75bcef5ccbe43720e5409b8fe"
|
||||
},
|
||||
{
|
||||
"version": "beta.042",
|
||||
"tag_name": "notes/beta.042",
|
||||
"name": "Sanctuary beta.042 — Joueurs assis sur la tête",
|
||||
"body_file": "notes/beta.042.md",
|
||||
"source_tag_exact": false,
|
||||
"draft": false,
|
||||
"prerelease": true,
|
||||
"body_sha256": "bb1937a1ce6b9f559921ec0a70042eea8b5333f4377cf208559d951e6cb2ff04"
|
||||
},
|
||||
{
|
||||
"version": "beta.043",
|
||||
"tag_name": "notes/beta.043",
|
||||
"name": "Sanctuary beta.043 — Blocs portés et icône officielle",
|
||||
"body_file": "notes/beta.043.md",
|
||||
"source_tag_exact": false,
|
||||
"draft": false,
|
||||
"prerelease": true,
|
||||
"body_sha256": "335aac4865234c699ab0210ec2eba76453e5865b23ec34fba319c3e1cd29f4f9"
|
||||
},
|
||||
{
|
||||
"version": "beta.044",
|
||||
"tag_name": "notes/beta.044",
|
||||
"name": "Sanctuary beta.044 — Pouvoirs natifs des familiers",
|
||||
"body_file": "notes/beta.044.md",
|
||||
"source_tag_exact": false,
|
||||
"draft": false,
|
||||
"prerelease": true,
|
||||
"body_sha256": "4fa2312d8b24495a19f6237ddea2db6df5ff830887936900bc6673d34859fb43"
|
||||
},
|
||||
{
|
||||
"version": "beta.045",
|
||||
"tag_name": "notes/beta.045",
|
||||
"name": "Sanctuary beta.045 — Menu principal",
|
||||
"body_file": "notes/beta.045.md",
|
||||
"source_tag_exact": false,
|
||||
"draft": false,
|
||||
"prerelease": true,
|
||||
"body_sha256": "963ac6b973b2dec3a6d7874a33085c35d76ed5b6b9e3203e87b46d859ad60f3a"
|
||||
},
|
||||
{
|
||||
"version": "beta.046",
|
||||
"tag_name": "notes/beta.046",
|
||||
"name": "Sanctuary beta.046 — Constellations, vol et zoom",
|
||||
"body_file": "notes/beta.046.md",
|
||||
"source_tag_exact": false,
|
||||
"draft": false,
|
||||
"prerelease": true,
|
||||
"body_sha256": "dab6dda881a19155e56543c9a23cb2c0081f9e4bb5cd16c8ff36a3cca70ece0a"
|
||||
},
|
||||
{
|
||||
"version": "beta.047",
|
||||
"tag_name": "notes/beta.047",
|
||||
"name": "Sanctuary beta.047 — Étoiles à la longue-vue et boutons",
|
||||
"body_file": "notes/beta.047.md",
|
||||
"source_tag_exact": false,
|
||||
"draft": false,
|
||||
"prerelease": true,
|
||||
"body_sha256": "bc62848c2d43d78898ebed20beb7011e2dc006434edab6202c1a2d9b73605ecb"
|
||||
},
|
||||
{
|
||||
"version": "beta.048",
|
||||
"tag_name": "notes/beta.048",
|
||||
"name": "Sanctuary beta.048 — Bateaux collectifs et moteurs animaux",
|
||||
"body_file": "notes/beta.048.md",
|
||||
"source_tag_exact": false,
|
||||
"draft": false,
|
||||
"prerelease": true,
|
||||
"body_sha256": "67fb7645ea42cc1ba5bc57167ea241dca10f77cead976bf055acb29d596dd773"
|
||||
},
|
||||
{
|
||||
"version": "beta.049",
|
||||
"tag_name": "notes/beta.049",
|
||||
"name": "Sanctuary beta.049 — Bateaux rectangulaires et chapeaux actifs",
|
||||
"body_file": "notes/beta.049.md",
|
||||
"source_tag_exact": false,
|
||||
"draft": false,
|
||||
"prerelease": true,
|
||||
"body_sha256": "4c4a60abae6821ba0fa790abd90ff0ccf87f838b19507cf620e840c2da6b3915"
|
||||
},
|
||||
{
|
||||
"version": "beta.050",
|
||||
"tag_name": "notes/beta.050",
|
||||
"name": "Sanctuary beta.050 — Bateaux compacts et redstone portée",
|
||||
"body_file": "notes/beta.050.md",
|
||||
"source_tag_exact": false,
|
||||
"draft": false,
|
||||
"prerelease": true,
|
||||
"body_sha256": "6e5a697160c8696017ffcf946934d030dea5dc86982f70612ec2ee2bfb1e497d"
|
||||
},
|
||||
{
|
||||
"version": "beta.051",
|
||||
"tag_name": "notes/beta.051",
|
||||
"name": "Sanctuary beta.051 — Distributeur porté orienté par le regard",
|
||||
"body_file": "notes/beta.051.md",
|
||||
"source_tag_exact": false,
|
||||
"draft": false,
|
||||
"prerelease": true,
|
||||
"body_sha256": "087d760d64ebe1ff6b948080e8e47583101c28c31c6e61ed1c6cb1713cd1bfc5"
|
||||
},
|
||||
{
|
||||
"version": "beta.052",
|
||||
"tag_name": "notes/beta.052",
|
||||
"name": "Sanctuary beta.052 — Nom du familier de départ",
|
||||
"body_file": "notes/beta.052.md",
|
||||
"source_tag_exact": false,
|
||||
"draft": false,
|
||||
"prerelease": true,
|
||||
"body_sha256": "9039530f3a89ee84a36883a7ddcf631029400b698ac480355c814bf8fa83be0b"
|
||||
},
|
||||
{
|
||||
"version": "beta.053",
|
||||
"tag_name": "notes/beta.053",
|
||||
"name": "Sanctuary beta.053 — Brume progressive et saturation",
|
||||
"body_file": "notes/beta.053.md",
|
||||
"source_tag_exact": false,
|
||||
"draft": false,
|
||||
"prerelease": true,
|
||||
"body_sha256": "f8cb565c3f91c9c02a364557acb89fd38a85c90d8e7f60e867959cc66cbbe2d6"
|
||||
},
|
||||
{
|
||||
"version": "beta.054",
|
||||
"tag_name": "notes/beta.054",
|
||||
"name": "Sanctuary beta.054 — Duels, mises et niveaux des familiers",
|
||||
"body_file": "notes/beta.054.md",
|
||||
"source_tag_exact": false,
|
||||
"draft": false,
|
||||
"prerelease": true,
|
||||
"body_sha256": "d402b014be15f7bcce2e0cd3e570f06aedd0fb0bb3ce03896b86654d53f70346"
|
||||
},
|
||||
{
|
||||
"version": "beta.055",
|
||||
"tag_name": "notes/beta.055",
|
||||
"name": "Sanctuary beta.055 — Force et portage de joueurs",
|
||||
"body_file": "notes/beta.055.md",
|
||||
"source_tag_exact": false,
|
||||
"draft": false,
|
||||
"prerelease": true,
|
||||
"body_sha256": "858439b89ffce6ccd712bbe490fc795ef8dd57ea2de7a02d7ce81a6e0e7f150c"
|
||||
},
|
||||
{
|
||||
"version": "beta.056",
|
||||
"tag_name": "notes/beta.056",
|
||||
"name": "Sanctuary beta.056 — Étoiles dispersées et saturation",
|
||||
"body_file": "notes/beta.056.md",
|
||||
"source_tag_exact": false,
|
||||
"draft": false,
|
||||
"prerelease": true,
|
||||
"body_sha256": "59db3c7369bad4ac4553e99df7af9f1a7740c990be11e1c5348b0e02eb54109e"
|
||||
},
|
||||
{
|
||||
"version": "beta.057",
|
||||
"tag_name": "notes/beta.057",
|
||||
"name": "Sanctuary beta.057 — Météo quotidienne et nouvelles ambiances",
|
||||
"body_file": "notes/beta.057.md",
|
||||
"source_tag_exact": false,
|
||||
"draft": false,
|
||||
"prerelease": true,
|
||||
"body_sha256": "d9f72880eb257b40711f85163f9ea5e16f74c7fb161482e9f65d66b37fafcbd7"
|
||||
},
|
||||
{
|
||||
"version": "beta.058",
|
||||
"tag_name": "notes/beta.058",
|
||||
"name": "Sanctuary beta.058 — Altitude de la carte",
|
||||
"body_file": "notes/beta.058.md",
|
||||
"source_tag_exact": false,
|
||||
"draft": false,
|
||||
"prerelease": true,
|
||||
"body_sha256": "690d0e236dcc4bef1ba3ce888224e3650dda38076496994157908c674f84b325"
|
||||
},
|
||||
{
|
||||
"version": "beta.059",
|
||||
"tag_name": "notes/beta.059",
|
||||
"name": "Sanctuary beta.059 — Bannières et cartes au trésor",
|
||||
"body_file": "notes/beta.059.md",
|
||||
"source_tag_exact": false,
|
||||
"draft": false,
|
||||
"prerelease": true,
|
||||
"body_sha256": "367ee855927b0f58c5b21daa67611d4c07f951d05edb3b088a60994193bdc873"
|
||||
},
|
||||
{
|
||||
"version": "beta.060",
|
||||
"tag_name": "beta.060",
|
||||
"name": "Sanctuary beta.060 — Objets et blocs sur les mobs",
|
||||
"body_file": "notes/beta.060.md",
|
||||
"source_tag_exact": true,
|
||||
"draft": false,
|
||||
"prerelease": true,
|
||||
"body_sha256": "4ea89295586862e5b549e9ed386f4467b9816290380e2d26c84765eecf186ded"
|
||||
},
|
||||
{
|
||||
"version": "beta.061",
|
||||
"tag_name": "beta.061",
|
||||
"name": "Sanctuary beta.061 — Musique d’arrivée et montures familières",
|
||||
"body_file": "notes/beta.061.md",
|
||||
"source_tag_exact": true,
|
||||
"draft": false,
|
||||
"prerelease": true,
|
||||
"body_sha256": "410bc6d3b4a01133d4ebb653e7923216bd615584a148c1711d009f681d1d5616"
|
||||
},
|
||||
{
|
||||
"version": "beta.062",
|
||||
"tag_name": "notes/beta.062",
|
||||
"name": "Sanctuary beta.062 — Soleil et chapeaux des familiers",
|
||||
"body_file": "notes/beta.062.md",
|
||||
"source_tag_exact": false,
|
||||
"draft": false,
|
||||
"prerelease": true,
|
||||
"body_sha256": "a082002465fb10d1c9e133270e9ec96ade18ec087703b1e7e3cbfd57f06d0a12"
|
||||
},
|
||||
{
|
||||
"version": "beta.063",
|
||||
"tag_name": "notes/beta.063",
|
||||
"name": "Sanctuary beta.063 — Chargement et règle de jeu Sanctuary",
|
||||
"body_file": "notes/beta.063.md",
|
||||
"source_tag_exact": false,
|
||||
"draft": false,
|
||||
"prerelease": true,
|
||||
"body_sha256": "84e009e16741375fc36a01d54d33476c0f0f9559a0dc3eb146549d9326b579c7"
|
||||
},
|
||||
{
|
||||
"version": "beta.064",
|
||||
"tag_name": "notes/beta.064",
|
||||
"name": "Sanctuary beta.064 — Cache et étapes réelles de préparation",
|
||||
"body_file": "notes/beta.064.md",
|
||||
"source_tag_exact": false,
|
||||
"draft": false,
|
||||
"prerelease": true,
|
||||
"body_sha256": "40073089e63e58e1c5f1a994ab17e3dee5d4869cbb00e33c1a63d7d204c6d015"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,91 @@
|
||||
plugins {
|
||||
id 'net.fabricmc.fabric-loom'
|
||||
}
|
||||
|
||||
group = rootProject.maven_group
|
||||
version = rootProject.mod_version
|
||||
base { archivesName = 'sanctuary' }
|
||||
|
||||
def clientRenderTests = providers.gradleProperty('sanctuaryClientTests')
|
||||
.map { it.toBoolean() }.getOrElse(false)
|
||||
|
||||
fabricApi {
|
||||
configureTests {
|
||||
createSourceSet = true
|
||||
modId = 'sanctuary-gametest'
|
||||
enableGameTests = true
|
||||
// Explicit opt-in: the rendering check needs a graphics-capable client.
|
||||
enableClientGameTests = clientRenderTests
|
||||
eula = false
|
||||
}
|
||||
}
|
||||
|
||||
if (clientRenderTests) {
|
||||
sourceSets.named('gametest') {
|
||||
java.srcDir('src/clientGametest/java')
|
||||
}
|
||||
}
|
||||
|
||||
// Every integration run must exercise fresh generation, never stale saved chunks.
|
||||
tasks.register('cleanGameTestWorld', Delete) {
|
||||
delete(layout.buildDirectory.dir('run/gameTest/world'))
|
||||
}
|
||||
tasks.named('runGameTest') {
|
||||
dependsOn('cleanGameTestWorld')
|
||||
systemProperty('sanctuary.test.seed', providers.gradleProperty('sanctuaryTestSeed').getOrElse('0'))
|
||||
systemProperty('sanctuary.test.resourceSurvey', providers.gradleProperty('sanctuaryResourceSurvey').getOrElse('sample'))
|
||||
systemProperty('sanctuary.test.densityMaps', providers.gradleProperty('sanctuaryDensityMaps').getOrElse('true'))
|
||||
}
|
||||
|
||||
dependencies {
|
||||
minecraft "com.mojang:minecraft:${rootProject.minecraft_version}"
|
||||
implementation "net.fabricmc:fabric-loader:${rootProject.loader_version}"
|
||||
implementation "net.fabricmc.fabric-api:fabric-api:${rootProject.fabric_api_version}"
|
||||
}
|
||||
|
||||
java {
|
||||
toolchain.languageVersion = JavaLanguageVersion.of(25)
|
||||
withSourcesJar()
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile).configureEach {
|
||||
options.encoding = 'UTF-8'
|
||||
options.release = 25
|
||||
}
|
||||
|
||||
processResources {
|
||||
// The launcher uses 26.3-pre-2; Fabric's runtime normalizes it to 26.3-pre.2.
|
||||
def values = [version: project.version, minecraft_version: rootProject.minecraft_version,
|
||||
minecraft_dependency_version: rootProject.minecraft_version.replace('-pre-', '-pre.'),
|
||||
loader_version: rootProject.loader_version, fabric_api_version: rootProject.fabric_api_version]
|
||||
inputs.properties(values)
|
||||
filesMatching('fabric.mod.json') { expand(values) }
|
||||
}
|
||||
|
||||
tasks.named('jar') {
|
||||
from(rootProject.file('LICENSE')) { rename { 'LICENSE_sanctuary' } }
|
||||
from(rootProject.file('THIRD_PARTY_NOTICES.md'))
|
||||
}
|
||||
|
||||
tasks.register('worldgenSmoke', JavaExec) {
|
||||
group = 'verification'
|
||||
description = 'Check natural central holes, continuous erosion, density bounds and exterior void.'
|
||||
dependsOn('testClasses')
|
||||
classpath = sourceSets.test.runtimeClasspath
|
||||
mainClass = 'fr.koka.sanctuary.worldgen.WorldgenSmoke'
|
||||
}
|
||||
|
||||
tasks.named('check') { dependsOn('worldgenSmoke') }
|
||||
|
||||
tasks.register('hydrologySmoke', JavaExec) {
|
||||
group = 'verification'
|
||||
description = 'Check deterministic surface drainage, natural support and bounded excavation.'
|
||||
dependsOn('testClasses')
|
||||
classpath = sourceSets.test.runtimeClasspath
|
||||
mainClass = 'fr.koka.sanctuary.worldgen.SurfaceHydrologySmoke'
|
||||
}
|
||||
|
||||
tasks.named('check') { dependsOn('hydrologySmoke') }
|
||||
|
||||
// src/test contains a JavaExec assertion harness; engine tests use Fabric's gametest source set.
|
||||
tasks.named('test') { failOnNoDiscoveredTests = false }
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package fr.koka.sanctuary.gametest;
|
||||
|
||||
import fr.koka.sanctuary.SanctuaryMod;
|
||||
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. */
|
||||
public final class SanctuaryClientRenderTests implements FabricClientGameTest {
|
||||
@Override
|
||||
public void runTest(ClientGameTestContext context) {
|
||||
context.runOnClient(client -> {
|
||||
assertApplied(client.getClass().getClassLoader(),
|
||||
"net.minecraft.client.renderer.SkyRenderer", "sanctuary$openLowerSky");
|
||||
assertApplied(client.getClass().getClassLoader(),
|
||||
"net.minecraft.client.renderer.fog.FogRenderer", "sanctuary$openVoidFog");
|
||||
SanctuaryMod.LOGGER.info("Client render tests passed: Sanctuary sky and fog mixins applied.");
|
||||
});
|
||||
}
|
||||
|
||||
private static void assertApplied(ClassLoader loader, String target, String hook) {
|
||||
try {
|
||||
// Force transformation even when this renderer is lazily loaded on world entry.
|
||||
Class<?> transformed = Class.forName(target, true, loader);
|
||||
boolean applied = Arrays.stream(transformed.getDeclaredMethods())
|
||||
.anyMatch(method -> method.getName().contains(hook));
|
||||
if (!applied) {
|
||||
throw new AssertionError("Missing Sanctuary render injection: " + target + " / " + hook);
|
||||
}
|
||||
} catch (ClassNotFoundException failure) {
|
||||
throw new AssertionError("Minecraft render target is missing: " + target, failure);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
package fr.koka.sanctuary.gametest;
|
||||
|
||||
import fr.koka.sanctuary.SanctuaryMod;
|
||||
import fr.koka.sanctuary.worldgen.MainIslandDensity;
|
||||
import fr.koka.sanctuary.worldgen.SanctuarySpawn;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.gametest.framework.GameTestHelper;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.world.level.levelgen.RandomState;
|
||||
import net.minecraft.world.level.levelgen.densityfunction.DensityFunction;
|
||||
import net.minecraft.world.level.storage.LevelData;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.Color;
|
||||
import java.awt.Font;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
/** Test-only sampled density plots, not screenshots or a substitute for looking at generated chunks. */
|
||||
final class DensityDiagnostics {
|
||||
private static final int EXTENT = 304;
|
||||
private static final int STEP = 4;
|
||||
private static final int GRID = 2 * EXTENT / STEP + 1;
|
||||
private static final int MAP_SIZE = GRID * 4;
|
||||
private static final int VOID_COLOR = 0x132132;
|
||||
|
||||
private DensityDiagnostics() {}
|
||||
|
||||
static void verifyAndExport(GameTestHelper helper, LevelData.RespawnData spawn) throws IOException {
|
||||
var level = helper.getLevel();
|
||||
var registries = level.registryAccess();
|
||||
var settings = registries.lookupOrThrow(Registries.NOISE_SETTINGS)
|
||||
.getOrThrow(SanctuarySpawn.RAISED_SETTINGS).value();
|
||||
DensityFunction finalDensity = registries.lookupOrThrow(Registries.DENSITY_FUNCTION)
|
||||
.getOrThrow(ResourceKey.create(Registries.DENSITY_FUNCTION, SanctuaryMod.id("final_density_384"))).value();
|
||||
helper.assertTrue(finalDensity instanceof MainIslandDensity,
|
||||
"The diagnostic must inspect the actual production island density wrapper");
|
||||
MainIslandDensity island = (MainIslandDensity) finalDensity;
|
||||
DensityFunction legacy = registries.lookupOrThrow(Registries.DENSITY_FUNCTION)
|
||||
.getOrThrow(ResourceKey.create(Registries.DENSITY_FUNCTION, SanctuaryMod.id("final_density"))).value();
|
||||
var noises = registries.lookupOrThrow(Registries.NOISE);
|
||||
// The default run exports all three seeds once. Additional complete-world runs only
|
||||
// repeat the sparse assertions, avoiding redundant image generation.
|
||||
boolean export = level.getSeed() == 0L && Boolean.parseBoolean(System.getProperty("sanctuary.test.densityMaps", "true"));
|
||||
boolean foundUnforcedCentralVoid = false;
|
||||
for (long seed : new long[]{0L, 42L, 8675309L}) {
|
||||
RandomState state = RandomState.create(noises, seed, settings);
|
||||
boolean foundSolid = false;
|
||||
var bottomHeights = new java.util.TreeSet<Integer>();
|
||||
for (int x = -288; x <= 288; x += 16) {
|
||||
for (int z = -288; z <= 288; z += 16) {
|
||||
int firstSolid = -1;
|
||||
for (int y = 4; y < island.height(); y += 8) {
|
||||
float shaped = state.sampleBlockValueUncached(finalDensity, x, y, z);
|
||||
float natural = state.sampleBlockValueUncached(island.terrain(), x, y - island.verticalOffset(), z);
|
||||
helper.assertTrue(Float.isFinite(shaped), "Sculpted density must be finite");
|
||||
helper.assertTrue(shaped <= 0.0F || natural > 0.0F,
|
||||
"Sculpt must not invent solid terrain; seed " + seed + " at " + x + "," + y + "," + z);
|
||||
foundSolid |= shaped > 0.0F;
|
||||
if (shaped > 0.0F && firstSolid < 0) firstSolid = y;
|
||||
if (Math.abs(x) <= 48 && Math.abs(z) <= 48 && y >= 44 + island.verticalOffset() && y <= 108 + island.verticalOffset()) {
|
||||
foundUnforcedCentralVoid |= natural < 0.0F && shaped < 0.0F;
|
||||
}
|
||||
}
|
||||
if (firstSolid >= 0 && Math.abs(x) <= 96 && Math.abs(z) <= 96) bottomHeights.add(firstSolid);
|
||||
}
|
||||
}
|
||||
helper.assertTrue(foundSolid, "The sampled natural island must contain land for seed " + seed);
|
||||
helper.assertTrue(bottomHeights.size() >= 4 && bottomHeights.first() >= 16,
|
||||
"The central underside must vary in height and leave an empty margin below; seed " + seed);
|
||||
SanctuaryMod.LOGGER.info("Raised island seed {}: sampled central underside Y{}..{} ({} distinct heights)",
|
||||
seed, bottomHeights.first(), bottomHeights.last(), bottomHeights.size());
|
||||
verifyUpperTranslation(helper, state, island, legacy, seed);
|
||||
if (export) {
|
||||
export(state, island, legacy, seed, seed == level.getSeed() ? spawn : null);
|
||||
}
|
||||
}
|
||||
helper.assertTrue(foundUnforcedCentralVoid,
|
||||
"The regression seeds must exercise natural holes inside the former forced core");
|
||||
}
|
||||
|
||||
private static void verifyUpperTranslation(GameTestHelper helper, RandomState state,
|
||||
MainIslandDensity island, DensityFunction legacy, long seed) {
|
||||
for (int x = -256; x <= 256; x += 32) {
|
||||
for (int z = -256; z <= 256; z += 32) {
|
||||
for (int oldY = 80; oldY < 256; oldY += 8) {
|
||||
float before = state.sampleBlockValueUncached(legacy, x, oldY, z);
|
||||
float after = state.sampleBlockValueUncached(island, x, oldY + island.verticalOffset(), z);
|
||||
helper.assertTrue(Math.abs(before - after) < 0.000001F,
|
||||
"The upper silhouette must keep its seed's alpha.2 profile after translation; seed "
|
||||
+ seed + " at " + x + "," + oldY + "," + z + ": " + before + " -> " + after);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void export(RandomState state, MainIslandDensity island, DensityFunction legacy, long seed,
|
||||
LevelData.RespawnData spawn) throws IOException {
|
||||
Path directory = Path.of("diagnostics");
|
||||
Files.createDirectories(directory);
|
||||
BufferedImage picture = new BufferedImage(MAP_SIZE * 2 + 60, 715 + 2 * (island.height() + 28), BufferedImage.TYPE_INT_RGB);
|
||||
Graphics2D graphics = picture.createGraphics();
|
||||
graphics.setColor(new Color(0xF0F0EC));
|
||||
graphics.fillRect(0, 0, picture.getWidth(), picture.getHeight());
|
||||
graphics.setColor(new Color(0x14202A));
|
||||
graphics.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 18));
|
||||
graphics.drawString("Seed " + seed + " | alpha.2 (actual preserved density)", 20, 27);
|
||||
graphics.drawString("Raised production density (384 blocks)", MAP_SIZE + 40, 27);
|
||||
graphics.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 13));
|
||||
graphics.drawString("Top view: x/z -304..304, sample every 4 blocks; height sampled every 4 blocks", 20, 48);
|
||||
graphics.drawString("Dark = void; low green to high cream. Terrain only; no trees/materials.", MAP_SIZE + 40, 48);
|
||||
StringBuilder heights = new StringBuilder("x,z,alpha2_top_y,raised_top_y\n");
|
||||
int beforeColumns = 0;
|
||||
int afterColumns = 0;
|
||||
for (int gx = 0; gx < GRID; gx++) {
|
||||
int x = -EXTENT + gx * STEP;
|
||||
for (int gz = 0; gz < GRID; gz++) {
|
||||
int z = -EXTENT + gz * STEP;
|
||||
int beforeTop = -1;
|
||||
int afterTop = -1;
|
||||
// Stop as soon as both topmost samples have been found.
|
||||
for (int y = island.height() - 4; y >= 4 && (beforeTop < 0 || afterTop < 0); y -= 4) {
|
||||
if (beforeTop < 0) {
|
||||
if (state.sampleBlockValueUncached(legacy, x, y, z) > 0.0F) beforeTop = y;
|
||||
}
|
||||
if (afterTop < 0 && state.sampleBlockValueUncached(island, x, y, z) > 0.0F) afterTop = y;
|
||||
}
|
||||
beforeColumns += beforeTop >= 0 ? 1 : 0;
|
||||
afterColumns += afterTop >= 0 ? 1 : 0;
|
||||
graphics.setColor(new Color(heightColor(beforeTop)));
|
||||
graphics.fillRect(20 + gx * 4, 60 + gz * 4, 4, 4);
|
||||
graphics.setColor(new Color(heightColor(afterTop)));
|
||||
graphics.fillRect(MAP_SIZE + 40 + gx * 4, 60 + gz * 4, 4, 4);
|
||||
heights.append(x).append(',').append(z).append(',').append(beforeTop).append(',').append(afterTop).append('\n');
|
||||
}
|
||||
}
|
||||
if (spawn != null) {
|
||||
graphics.setColor(Color.MAGENTA);
|
||||
int px = MAP_SIZE + 40 + spawn.pos().getX() + EXTENT;
|
||||
int pz = 60 + spawn.pos().getZ() + EXTENT;
|
||||
graphics.drawOval(px - 4, pz - 4, 8, 8);
|
||||
graphics.drawString("Initial spawn " + spawn.pos().toShortString(), MAP_SIZE + 40, 689);
|
||||
}
|
||||
drawSections(graphics, state, island, legacy, false, 715);
|
||||
drawSections(graphics, state, island, legacy, true, 715 + island.height() + 28);
|
||||
graphics.dispose();
|
||||
Path output = directory.resolve("island-density-seed-" + seed + ".png");
|
||||
ImageIO.write(picture, "png", output.toFile());
|
||||
Files.writeString(directory.resolve("island-heightmap-seed-" + seed + ".csv"), heights);
|
||||
SanctuaryMod.LOGGER.info("Island density diagnostic seed {}: {} -> {} sampled land columns; {}",
|
||||
seed, beforeColumns, afterColumns, output.toAbsolutePath());
|
||||
}
|
||||
|
||||
private static void drawSections(Graphics2D graphics, RandomState state, MainIslandDensity island, DensityFunction legacy,
|
||||
boolean alongZ, int top) {
|
||||
graphics.setColor(new Color(0x14202A));
|
||||
graphics.drawString(alongZ ? "Section x=0: z -304..304; y 383 (top)..0 (bottom)"
|
||||
: "Section z=0: x -304..304; y 383 (top)..0 (bottom)", 20, top - 8);
|
||||
for (int horizontal = -EXTENT; horizontal < EXTENT; horizontal += 2) {
|
||||
int x = alongZ ? 0 : horizontal;
|
||||
int z = alongZ ? horizontal : 0;
|
||||
for (int y = 0; y < island.height(); y++) {
|
||||
float before = state.sampleBlockValueUncached(legacy, x, y, z);
|
||||
float after = state.sampleBlockValueUncached(island, x, y, z);
|
||||
graphics.setColor(new Color(before > 0.0F ? heightColor(y) : VOID_COLOR));
|
||||
graphics.fillRect(20 + horizontal + EXTENT, top + island.height() - 1 - y, 2, 1);
|
||||
graphics.setColor(new Color(after > 0.0F ? heightColor(y) : VOID_COLOR));
|
||||
graphics.fillRect(MAP_SIZE + 40 + horizontal + EXTENT, top + island.height() - 1 - y, 2, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int heightColor(int y) {
|
||||
if (y < 0) return VOID_COLOR;
|
||||
float height = Math.clamp(y / 280.0F, 0.0F, 1.0F);
|
||||
int red = (int) (43 + 192 * height);
|
||||
int green = (int) (91 + 139 * height);
|
||||
int blue = (int) (70 + 117 * height);
|
||||
return red << 16 | green << 8 | blue;
|
||||
}
|
||||
}
|
||||
+448
@@ -0,0 +1,448 @@
|
||||
package fr.koka.sanctuary.gametest;
|
||||
|
||||
import com.google.gson.GsonBuilder;
|
||||
import fr.koka.sanctuary.SanctuaryMod;
|
||||
import fr.koka.sanctuary.worldgen.HydrologyRuntime;
|
||||
import fr.koka.sanctuary.worldgen.SanctuarySpawn;
|
||||
import fr.koka.sanctuary.worldgen.ShoreSugarCaneFeature;
|
||||
import fr.koka.sanctuary.worldgen.SurfaceHydrology;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.gametest.framework.GameTestHelper;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.tags.BlockTags;
|
||||
import net.minecraft.tags.FluidTags;
|
||||
import net.minecraft.util.RandomSource;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
import net.minecraft.world.level.chunk.status.ChunkStatus;
|
||||
import net.minecraft.world.level.levelgen.Heightmap;
|
||||
import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator;
|
||||
import net.minecraft.world.level.levelgen.RandomState;
|
||||
import net.minecraft.world.level.levelgen.densityfunction.DensityFunction;
|
||||
import net.minecraft.world.level.material.Fluids;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.Color;
|
||||
import java.awt.Font;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/** Checks the actual decorated terrain and fluid simulation in a disposable world, far from test fixtures. */
|
||||
public final class HydrologyDiagnostics {
|
||||
private static final List<Direction> HORIZONTAL = List.of(Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST);
|
||||
|
||||
private HydrologyDiagnostics() {}
|
||||
|
||||
public static void start(GameTestHelper helper) throws IOException {
|
||||
Context context = new Context(helper);
|
||||
context.prepare();
|
||||
context.verifyNaturalShellAndFinishedWater();
|
||||
context.verifyAllObservedWaterIsPlanned();
|
||||
context.export("generated");
|
||||
helper.assertTrue(context.caneBlocks > 0,
|
||||
"Regression seed must provide at least one naturally valid shore sugar cane plant; inspect the exported site map");
|
||||
// Other synchronous generation diagnostics finish before this tick. The temporary fluid
|
||||
// witness therefore cannot be counted by the initial resource survey.
|
||||
helper.runAtTickTime(20, context::startFluidWitness);
|
||||
helper.runAtTickTime(50, context::verifyFluidWitnessAndRemoveIt);
|
||||
helper.runAtTickTime(200, () -> {
|
||||
try {
|
||||
context.verifyNaturalShellAndFinishedWater();
|
||||
context.verifyAllObservedWaterIsPlanned();
|
||||
context.export("after-200-ticks");
|
||||
SanctuaryMod.LOGGER.info("Hydrology seed {}: {} water blocks retained after real fluid ticks, {} chunk seams checked",
|
||||
context.level.getSeed(), context.expectedWater.size(), context.seams);
|
||||
helper.succeed();
|
||||
} catch (IOException exception) {
|
||||
throw new IllegalStateException("Could not write hydrology diagnostics", exception);
|
||||
} finally {
|
||||
context.cleanup();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void verifyLegacyIsolation(GameTestHelper helper) {
|
||||
ServerLevel level = helper.getLevel();
|
||||
var active = (NoiseBasedChunkGenerator) level.getChunkSource().getGenerator();
|
||||
helper.assertTrue(HydrologyRuntime.enabled(active), "The current preset must enable surface hydrology");
|
||||
helper.assertTrue(active.generatorSettings().is(SanctuarySpawn.HYDROLOGY_SETTINGS),
|
||||
"The active generator must use the new hydrology settings, not alter a legacy settings key");
|
||||
var registry = level.registryAccess().lookupOrThrow(Registries.NOISE_SETTINGS);
|
||||
for (var key : List.of(SanctuarySpawn.SETTINGS, SanctuarySpawn.RAISED_SETTINGS)) {
|
||||
var legacy = new NoiseBasedChunkGenerator(active.getBiomeSource(), registry.getOrThrow(key));
|
||||
helper.assertFalse(HydrologyRuntime.enabled(legacy), "Saved legacy generator must remain dry: " + key.identifier());
|
||||
helper.assertFalse(new ShoreSugarCaneFeature().place(level, legacy, RandomSource.create(0), BlockPos.ZERO),
|
||||
"Even an explicitly invoked shoreline feature must ignore legacy generators");
|
||||
}
|
||||
var biomes = level.registryAccess().lookupOrThrow(Registries.BIOME);
|
||||
var oldBiome = biomes.getOrThrow(ResourceKey.create(Registries.BIOME, SanctuaryMod.id("starter_forest"))).value();
|
||||
var wetBiome = biomes.getOrThrow(ResourceKey.create(Registries.BIOME, SanctuaryMod.id("starter_forest_hydrology"))).value();
|
||||
var shore = level.registryAccess().lookupOrThrow(Registries.PLACED_FEATURE).getOrThrow(
|
||||
ResourceKey.create(Registries.PLACED_FEATURE, SanctuaryMod.id("shore_sugar_cane"))).value();
|
||||
helper.assertFalse(oldBiome.getGenerationSettings().hasFeature(shore),
|
||||
"The old biome referenced by saved worlds must not acquire the new shoreline decoration");
|
||||
helper.assertTrue(wetBiome.getGenerationSettings().hasFeature(shore),
|
||||
"The new hydrology biome must include its shoreline decoration");
|
||||
helper.assertTrue(active.getBiomeSource().possibleBiomes().stream().allMatch(holder -> holder.is(
|
||||
ResourceKey.create(Registries.BIOME, SanctuaryMod.id("starter_forest_hydrology")))),
|
||||
"The current preset must select the separate hydrology biome");
|
||||
}
|
||||
|
||||
private static final class Context {
|
||||
final GameTestHelper helper;
|
||||
final ServerLevel level;
|
||||
final SurfaceHydrology.Plan plan;
|
||||
final RandomState random;
|
||||
final DensityFunction natural;
|
||||
final Map<Long, SurfaceHydrology.Cell> cells = new HashMap<>();
|
||||
final Set<BlockPos> expectedWater = new HashSet<>();
|
||||
final Map<Long, LevelChunk> chunks = new TreeMap<>();
|
||||
final Set<Long> newlyForced = new HashSet<>();
|
||||
BlockPos witness;
|
||||
boolean witnessPlaced;
|
||||
boolean allWaterChunksTicking;
|
||||
int seams;
|
||||
int caneBlocks;
|
||||
|
||||
Context(GameTestHelper helper) {
|
||||
this.helper = helper;
|
||||
level = helper.getLevel();
|
||||
var generator = (NoiseBasedChunkGenerator) level.getChunkSource().getGenerator();
|
||||
helper.assertTrue(HydrologyRuntime.enabled(generator), "Hydrology test requires the production wet preset");
|
||||
plan = HydrologyRuntime.plan(level);
|
||||
random = level.getChunkSource().randomState();
|
||||
natural = generator.generatorSettings().value().noiseRouter().finalDensity();
|
||||
}
|
||||
|
||||
void prepare() {
|
||||
helper.assertTrue(!plan.features().isEmpty(), "Regression seed must exercise real surface water");
|
||||
for (var feature : plan.features()) {
|
||||
SanctuaryMod.LOGGER.info("Hydrology planned site: seed {}, {}, Y{}, start {}, {} water columns, {} path points",
|
||||
level.getSeed(), feature.kind(), feature.waterY(), feature.path().getFirst(), feature.waterCells(), feature.path().size());
|
||||
}
|
||||
if (level.getSeed() == 0L) {
|
||||
helper.assertTrue(plan.features().stream().anyMatch(feature -> feature.kind() == SurfaceHydrology.Kind.STREAM),
|
||||
"The reference seed 0 must demonstrate a real stream, not only isolated ponds; see the logged planned sites");
|
||||
}
|
||||
Set<Long> requested = new HashSet<>();
|
||||
for (var cell : plan.cells()) {
|
||||
helper.assertTrue(cells.put(ChunkPos.pack(cell.x(), cell.z()), cell) == null,
|
||||
"A plan must own each horizontal column exactly once");
|
||||
if (cell.hasWater()) {
|
||||
for (int y = cell.bedY() + 1; y <= cell.waterY(); y++) {
|
||||
expectedWater.add(new BlockPos(cell.x(), y, cell.z()));
|
||||
}
|
||||
}
|
||||
int cx = cell.x() >> 4;
|
||||
int cz = cell.z() >> 4;
|
||||
for (int dx = -1; dx <= 1; dx++) {
|
||||
for (int dz = -1; dz <= 1; dz++) requested.add(ChunkPos.pack(cx + dx, cz + dz));
|
||||
}
|
||||
}
|
||||
helper.assertTrue(!expectedWater.isEmpty(), "At least one generated water volume must be tested");
|
||||
SanctuaryMod.LOGGER.info("Hydrology seed {}: preparing {} site and neighbour chunks for actual water inspection",
|
||||
level.getSeed(), requested.size());
|
||||
for (long packed : requested.stream().sorted().toList()) {
|
||||
ChunkPos pos = ChunkPos.unpack(packed);
|
||||
if (level.setChunkForced(pos.x(), pos.z(), true)) newlyForced.add(packed);
|
||||
var chunk = level.getChunkSource().getChunk(pos.x(), pos.z(), ChunkStatus.FULL, true);
|
||||
helper.assertTrue(chunk instanceof LevelChunk, "Hydrology inspection requires a decorated FULL chunk");
|
||||
chunks.put(packed, (LevelChunk) chunk);
|
||||
}
|
||||
// Use one contained witness in the SAME simulation chunk as actual water. It proves
|
||||
// that the island's fluids tick, rather than merely waiting beside the remote test grid.
|
||||
BlockPos first = expectedWater.stream().min(Comparator.comparingInt((BlockPos pos) -> pos.getX())
|
||||
.thenComparingInt(BlockPos::getZ).thenComparingInt(BlockPos::getY)).orElseThrow();
|
||||
witness = new BlockPos((first.getX() >> 4) * 16 + 8, level.getMaxY() - 20,
|
||||
(first.getZ() >> 4) * 16 + 8);
|
||||
for (BlockPos position : expectedWater) {
|
||||
for (Direction direction : List.of(Direction.EAST, Direction.SOUTH)) {
|
||||
BlockPos next = position.relative(direction);
|
||||
if (expectedWater.contains(next)
|
||||
&& (position.getX() >> 4 != next.getX() >> 4 || position.getZ() >> 4 != next.getZ() >> 4)) seams++;
|
||||
}
|
||||
}
|
||||
helper.assertTrue(seams > 0, "Regression seed must exercise a continuous water volume across a chunk seam");
|
||||
SanctuaryMod.LOGGER.info("Hydrology seed {}: {} features, {} water blocks, {} FULL chunks including halo, {} seam adjacencies",
|
||||
level.getSeed(), plan.features().size(), expectedWater.size(), chunks.size(), seams);
|
||||
}
|
||||
|
||||
void verifyNaturalShellAndFinishedWater() {
|
||||
for (var cell : plan.cells()) {
|
||||
if (!cell.hasWater()) continue;
|
||||
helper.assertTrue(cell.waterY() > cell.bedY() && cell.waterY() - cell.bedY() <= SurfaceHydrology.MAX_DEPTH,
|
||||
"A surface basin must keep the declared shallow water depth");
|
||||
helper.assertTrue(cell.carveTop() - cell.bedY() <= SurfaceHydrology.MAX_CARVE,
|
||||
"Hydrology must not excavate a deep artificial chamber beneath the surface");
|
||||
for (int depth = 0; depth < 3; depth++) {
|
||||
int y = cell.bedY() - depth;
|
||||
helper.assertTrue(sample(cell.x(), y, cell.z()) > 0,
|
||||
"Water must retain three blocks of natural floor, without adding a liner at " + cell.x() + "," + y + "," + cell.z());
|
||||
BlockPos floor = new BlockPos(cell.x(), y, cell.z());
|
||||
helper.assertTrue(level.getBlockState(floor).isCollisionShapeFullBlock(level, floor),
|
||||
"Generated water floor must remain solid after decoration and fluid ticks: " + floor);
|
||||
}
|
||||
for (int y = cell.bedY() + 1; y <= cell.waterY(); y++) {
|
||||
BlockPos water = new BlockPos(cell.x(), y, cell.z());
|
||||
helper.assertTrue(level.getFluidState(water).is(FluidTags.WATER) && level.getFluidState(water).isSource(),
|
||||
"The planned water volume must exist as retained source water: " + water);
|
||||
for (Direction direction : HORIZONTAL) {
|
||||
BlockPos side = water.relative(direction);
|
||||
if (expectedWater.contains(side)) continue;
|
||||
helper.assertTrue(sample(side.getX(), side.getY(), side.getZ()) > 0,
|
||||
"Every external underwater side must be supported by natural terrain: " + side);
|
||||
helper.assertTrue(level.getBlockState(side).isCollisionShapeFullBlock(level, side),
|
||||
"A decorated shore must not leave a hole in the three-dimensional water shell: " + side);
|
||||
}
|
||||
}
|
||||
// The original density must have no roof above the explicitly carved surface.
|
||||
// Leaves and branches from neighbouring trees may shade the water afterwards.
|
||||
for (int y = cell.carveTop() + 1; y <= level.getMaxY(); y++) {
|
||||
helper.assertTrue(sample(cell.x(), y, cell.z()) <= 0,
|
||||
"A surface basin must not be a flooded underground chamber: " + cell.x() + "," + y + "," + cell.z());
|
||||
}
|
||||
}
|
||||
for (var feature : plan.features()) {
|
||||
if (feature.kind() != SurfaceHydrology.Kind.STREAM) continue;
|
||||
helper.assertTrue(feature.path().size() > 1, "A stream needs a route connecting distinct surface locations");
|
||||
double travelled = 0;
|
||||
for (int i = 1; i < feature.path().size(); i++) {
|
||||
var from = feature.path().get(i - 1);
|
||||
var to = feature.path().get(i);
|
||||
int length = Math.max(Math.abs(to.x() - from.x()), Math.abs(to.z() - from.z()));
|
||||
helper.assertTrue(length > 0, "A stream route must advance between consecutive points");
|
||||
helper.assertTrue(from.x() == to.x() || from.z() == to.z()
|
||||
|| Math.abs(to.x() - from.x()) == Math.abs(to.z() - from.z()),
|
||||
"Stream control points must be joined by cardinal or diagonal steps");
|
||||
travelled += Math.hypot(to.x() - from.x(), to.z() - from.z());
|
||||
BlockPos previous = null;
|
||||
for (int step = 0; step <= length; step++) {
|
||||
BlockPos p = new BlockPos(from.x() + (to.x() - from.x()) * step / length,
|
||||
feature.waterY(), from.z() + (to.z() - from.z()) * step / length);
|
||||
helper.assertTrue(expectedWater.contains(p) && level.getFluidState(p).is(FluidTags.WATER),
|
||||
"The real stream must form an uninterrupted water route across chunk boundaries: " + p);
|
||||
var column = plan.cellAt(p.getX(), p.getZ());
|
||||
helper.assertTrue(column != null && column.waterY() == feature.waterY(),
|
||||
"This stream contract has one constant water level along its whole route");
|
||||
if (previous != null && p.getX() != previous.getX() && p.getZ() != previous.getZ()) {
|
||||
BlockPos bridgeX = new BlockPos(p.getX(), p.getY(), previous.getZ());
|
||||
BlockPos bridgeZ = new BlockPos(previous.getX(), p.getY(), p.getZ());
|
||||
helper.assertTrue(expectedWater.contains(bridgeX) && level.getFluidState(bridgeX).is(FluidTags.WATER)
|
||||
|| expectedWater.contains(bridgeZ) && level.getFluidState(bridgeZ).is(FluidTags.WATER),
|
||||
"Diagonal route steps need actual cardinal water connectivity, not only touching corners");
|
||||
}
|
||||
previous = p;
|
||||
}
|
||||
}
|
||||
var first = feature.path().getFirst();
|
||||
var last = feature.path().getLast();
|
||||
helper.assertTrue(travelled >= 24 && Math.hypot(last.x() - first.x(), last.z() - first.z()) >= 16,
|
||||
"A demonstrated stream must have at least 24 blocks of route between locations at least 16 blocks apart");
|
||||
}
|
||||
}
|
||||
|
||||
void verifyAllObservedWaterIsPlanned() {
|
||||
int[] observed = {0};
|
||||
int[] cane = {0};
|
||||
for (LevelChunk chunk : chunks.values()) {
|
||||
chunk.findBlocks(state -> state.getFluidState().is(FluidTags.WATER) || state.is(Blocks.SUGAR_CANE), (pos, state) -> {
|
||||
if (state.getFluidState().is(FluidTags.WATER)) {
|
||||
helper.assertTrue(expectedWater.contains(pos),
|
||||
"Water escaped its declared surface volume, possibly through a chunk seam or toward the void: " + pos);
|
||||
observed[0]++;
|
||||
}
|
||||
if (state.is(Blocks.SUGAR_CANE)) {
|
||||
helper.assertTrue(state.canSurvive(level, pos), "Generated shore sugar cane must survive at " + pos);
|
||||
cane[0]++;
|
||||
}
|
||||
});
|
||||
}
|
||||
helper.assertTrue(observed[0] == expectedWater.size(), "Observed water must account for every planned water block");
|
||||
caneBlocks = cane[0];
|
||||
}
|
||||
|
||||
void startFluidWitness() {
|
||||
helper.assertTrue(level.shouldTickBlocksAt(witness), "The witness and its water-site chunk must actually tick");
|
||||
for (int dx = -1; dx <= 1; dx++) {
|
||||
for (int dz = -1; dz <= 1; dz++) {
|
||||
for (int dy = -4; dy <= 0; dy++) {
|
||||
BlockPos p = witness.offset(dx, dy, dz);
|
||||
helper.assertTrue(level.getBlockState(p).isAir(), "The temporary simulation witness must use empty test space");
|
||||
}
|
||||
}
|
||||
}
|
||||
witnessPlaced = true;
|
||||
for (int dx = -1; dx <= 1; dx++) {
|
||||
for (int dz = -1; dz <= 1; dz++) {
|
||||
for (int dy = -4; dy <= 0; dy++) {
|
||||
if (dx != 0 || dz != 0 || dy == -4) level.setBlock(witness.offset(dx, dy, dz), Blocks.STONE.defaultBlockState(), 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
level.setBlock(witness, Blocks.WATER.defaultBlockState(), 3);
|
||||
helper.assertTrue(level.getFluidState(witness.below()).isEmpty(), "Witness starts with an empty cell below its source");
|
||||
level.scheduleTick(witness, Fluids.WATER, 1);
|
||||
for (BlockPos water : expectedWater) {
|
||||
helper.assertTrue(level.shouldTickBlocksAt(water), "Every actual water-site chunk must be simulated: " + water);
|
||||
level.scheduleTick(water, Fluids.WATER, 1);
|
||||
}
|
||||
allWaterChunksTicking = true;
|
||||
}
|
||||
|
||||
void verifyFluidWitnessAndRemoveIt() {
|
||||
try {
|
||||
helper.assertTrue(level.getFluidState(witness.below()).is(FluidTags.WATER),
|
||||
"Real fluid simulation must move the witness water down inside the forced island chunk");
|
||||
SanctuaryMod.LOGGER.info("Hydrology seed {}: fluid tick witness passed at {}", level.getSeed(), witness);
|
||||
} finally {
|
||||
removeWitness();
|
||||
}
|
||||
}
|
||||
|
||||
void removeWitness() {
|
||||
if (!witnessPlaced) return;
|
||||
// Empty the fluid column before opening its walls, keeping the test fixture contained.
|
||||
for (int dy = 0; dy >= -3; dy--) level.setBlock(witness.offset(0, dy, 0), Blocks.AIR.defaultBlockState(), 3);
|
||||
for (int dx = -1; dx <= 1; dx++) {
|
||||
for (int dz = -1; dz <= 1; dz++) {
|
||||
for (int dy = -4; dy <= 0; dy++) level.setBlock(witness.offset(dx, dy, dz), Blocks.AIR.defaultBlockState(), 3);
|
||||
}
|
||||
}
|
||||
witnessPlaced = false;
|
||||
}
|
||||
|
||||
void cleanup() {
|
||||
removeWitness();
|
||||
for (long packed : newlyForced) {
|
||||
ChunkPos chunk = ChunkPos.unpack(packed);
|
||||
level.setChunkForced(chunk.x(), chunk.z(), false);
|
||||
}
|
||||
}
|
||||
|
||||
float sample(int x, int y, int z) {
|
||||
return random.sampleBlockValueUncached(natural, x, y, z);
|
||||
}
|
||||
|
||||
void export(String phase) throws IOException {
|
||||
Path directory = Path.of("diagnostics");
|
||||
Files.createDirectories(directory);
|
||||
String stem = "surface-hydrology-seed-" + level.getSeed() + "-" + phase;
|
||||
Map<String, Object> report = new LinkedHashMap<>();
|
||||
report.put("seed", level.getSeed());
|
||||
report.put("phase", phase);
|
||||
report.put("actual_water_blocks", expectedWater.size());
|
||||
report.put("actual_cane_blocks", caneBlocks);
|
||||
report.put("checked_chunk_seam_adjacencies", seams);
|
||||
report.put("all_water_site_chunks_confirmed_ticking", allWaterChunksTicking);
|
||||
report.put("inspected_full_chunks", chunks.values().stream().map(chunk ->
|
||||
Map.of("x", chunk.getPos().x(), "z", chunk.getPos().z())).toList());
|
||||
report.put("features", plan.features());
|
||||
report.put("shore_diagnostics_by_feature", shoreDiagnostics());
|
||||
report.put("planned_columns", plan.cells());
|
||||
report.put("scope", "actual blocks in completed site chunks and a one-chunk halo; uninspected map cells remain blank");
|
||||
Files.writeString(directory.resolve(stem + ".json"), new GsonBuilder().setPrettyPrinting().create().toJson(report) + "\n");
|
||||
exportMap(directory.resolve(stem + ".png"), phase);
|
||||
}
|
||||
|
||||
Map<Long, Map<String, Object>> shoreDiagnostics() {
|
||||
Map<Long, Map<String, Object>> result = new LinkedHashMap<>();
|
||||
for (var feature : plan.features()) {
|
||||
int dryBanks = 0;
|
||||
int banksAtWaterLevel = 0;
|
||||
int baseCanSurvive = 0;
|
||||
int unobstructedBases = 0;
|
||||
int viableUnobstructedBases = 0;
|
||||
int actualCane = 0;
|
||||
for (var cell : plan.cells()) {
|
||||
if (cell.featureId() != feature.id() || cell.hasWater()) continue;
|
||||
dryBanks++;
|
||||
if (cell.bedY() == feature.waterY()) banksAtWaterLevel++;
|
||||
BlockPos base = new BlockPos(cell.x(), cell.bedY() + 1, cell.z());
|
||||
boolean viable = Blocks.SUGAR_CANE.defaultBlockState().canSurvive(level, base);
|
||||
boolean clear = level.getBlockState(base).isAir() || level.getBlockState(base).is(Blocks.SUGAR_CANE);
|
||||
if (viable) baseCanSurvive++;
|
||||
if (clear) unobstructedBases++;
|
||||
if (viable && clear) viableUnobstructedBases++;
|
||||
for (int height = 0; height < 4; height++) {
|
||||
if (level.getBlockState(base.above(height)).is(Blocks.SUGAR_CANE)) actualCane++;
|
||||
else break;
|
||||
}
|
||||
}
|
||||
result.put(feature.id(), Map.of("kind", feature.kind().name(), "water_y", feature.waterY(),
|
||||
"dry_bank_columns", dryBanks, "bank_surface_at_water_level", banksAtWaterLevel,
|
||||
"bases_where_cane_can_survive", baseCanSurvive, "unobstructed_bases", unobstructedBases,
|
||||
"viable_unobstructed_bases", viableUnobstructedBases,
|
||||
"actual_cane_blocks_on_planned_banks", actualCane));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void exportMap(Path output, String phase) throws IOException {
|
||||
int minX = chunks.values().stream().mapToInt(chunk -> chunk.getPos().getMinBlockX()).min().orElseThrow();
|
||||
int maxX = chunks.values().stream().mapToInt(chunk -> chunk.getPos().getMaxBlockX()).max().orElseThrow();
|
||||
int minZ = chunks.values().stream().mapToInt(chunk -> chunk.getPos().getMinBlockZ()).min().orElseThrow();
|
||||
int maxZ = chunks.values().stream().mapToInt(chunk -> chunk.getPos().getMaxBlockZ()).max().orElseThrow();
|
||||
int scale = Math.max(1, Math.min(3, 1000 / Math.max(maxX - minX + 1, maxZ - minZ + 1)));
|
||||
int width = (maxX - minX + 1) * scale;
|
||||
int height = (maxZ - minZ + 1) * scale;
|
||||
BufferedImage image = new BufferedImage(Math.max(width + 40, 920), height + 110, BufferedImage.TYPE_INT_RGB);
|
||||
Graphics2D graphics = image.createGraphics();
|
||||
graphics.setColor(new Color(0xF0F0EC));
|
||||
graphics.fillRect(0, 0, image.getWidth(), image.getHeight());
|
||||
graphics.setColor(new Color(0x17222C));
|
||||
graphics.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 17));
|
||||
graphics.drawString("Actual FULL chunks | seed " + level.getSeed() + " | " + phase, 20, 24);
|
||||
graphics.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 12));
|
||||
graphics.drawString("Water blue; sand gold; clay teal; gravel brown; cane bright green; terrain height grey. Canopy skipped.", 20, 45);
|
||||
graphics.drawString("Not a Minecraft render. X=" + minX + ".." + maxX + ", Z=" + minZ + ".." + maxZ
|
||||
+ ". Dark blue = void; dark brown = not inspected; faint lines = chunk seams.", 20, 63);
|
||||
BlockPos.MutableBlockPos position = new BlockPos.MutableBlockPos();
|
||||
for (int x = minX; x <= maxX; x++) {
|
||||
for (int z = minZ; z <= maxZ; z++) {
|
||||
LevelChunk chunk = chunks.get(ChunkPos.pack(x >> 4, z >> 4));
|
||||
int color = 0x3A3030;
|
||||
if (chunk != null) {
|
||||
color = 0x142738;
|
||||
for (int y = chunk.getHeight(Heightmap.Types.WORLD_SURFACE, x, z); y >= level.getMinY(); y--) {
|
||||
position.set(x, y, z);
|
||||
BlockState state = chunk.getBlockState(position);
|
||||
if (state.getFluidState().is(FluidTags.WATER)) { color = 0x207BD4; break; }
|
||||
if (state.is(Blocks.SUGAR_CANE)) { color = 0x7AFF38; break; }
|
||||
if (state.is(Blocks.SAND)) { color = 0xD5B668; break; }
|
||||
if (state.is(Blocks.CLAY)) { color = 0x6D9FAD; break; }
|
||||
if (state.is(Blocks.GRAVEL)) { color = 0x9B8878; break; }
|
||||
if (state.isAir() || state.is(BlockTags.LEAVES) || state.is(BlockTags.LOGS)
|
||||
|| state.getCollisionShape(level, position).isEmpty()) continue;
|
||||
int value = 40 + Math.clamp(y, 0, level.getHeight()) * 180 / level.getHeight();
|
||||
color = value << 16 | value << 8 | value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
graphics.setColor(new Color(color));
|
||||
graphics.fillRect(20 + (x - minX) * scale, 80 + (z - minZ) * scale, scale, scale);
|
||||
}
|
||||
}
|
||||
graphics.setColor(new Color(255, 255, 255, 35));
|
||||
for (int x = minX; x <= maxX; x += 16) graphics.drawLine(20 + (x - minX) * scale, 80, 20 + (x - minX) * scale, 80 + height);
|
||||
for (int z = minZ; z <= maxZ; z += 16) graphics.drawLine(20, 80 + (z - minZ) * scale, 20 + width, 80 + (z - minZ) * scale);
|
||||
graphics.dispose();
|
||||
ImageIO.write(image, "png", output.toFile());
|
||||
}
|
||||
}
|
||||
}
|
||||
+245
@@ -0,0 +1,245 @@
|
||||
package fr.koka.sanctuary.gametest;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import fr.koka.sanctuary.SanctuaryMod;
|
||||
import fr.koka.sanctuary.worldgen.IslandShape;
|
||||
import fr.koka.sanctuary.worldgen.SanctuarySpawn;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import net.minecraft.SharedConstants;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.gametest.framework.GameTestHelper;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.tags.BlockTags;
|
||||
import net.minecraft.tags.FluidTags;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.block.state.properties.Property;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
import net.minecraft.world.level.chunk.LevelChunkSection;
|
||||
import net.minecraft.world.level.chunk.status.ChunkStatus;
|
||||
import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator;
|
||||
import net.minecraft.world.level.storage.LevelData;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.StringJoiner;
|
||||
import java.util.TreeMap;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/** Exact post-generation observations in the disposable GameTest world; never edits terrain. */
|
||||
public final class ResourceSurveyDiagnostics {
|
||||
private static final Gson JSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
|
||||
private static final int DECORATION_MARGIN = 16;
|
||||
private static final int PREPARATION_HALO_CHUNKS = 1;
|
||||
|
||||
private ResourceSurveyDiagnostics() {}
|
||||
|
||||
public static void verifyAndExport(GameTestHelper helper, LevelData.RespawnData initialSpawn) throws IOException {
|
||||
String mode = System.getProperty("sanctuary.test.resourceSurvey", "sample");
|
||||
if (mode.equals("off")) return;
|
||||
if (!mode.equals("sample") && !mode.equals("island")) {
|
||||
throw new IllegalArgumentException("sanctuary.test.resourceSurvey must be sample, island or off");
|
||||
}
|
||||
ServerLevel level = helper.getLevel();
|
||||
helper.assertTrue(SanctuarySpawn.usesSanctuaryGenerator(level),
|
||||
"A resource survey must inspect the production Sanctuary generator");
|
||||
helper.assertTrue(initialSpawn != null, "Use the actual initial spawn, not the remote GameTest fixture");
|
||||
Instant started = Instant.now();
|
||||
long startedNanos = System.nanoTime();
|
||||
long initialGameTime = level.getGameTime();
|
||||
int centreX = initialSpawn.pos().getX() >> 4;
|
||||
int centreZ = initialSpawn.pos().getZ() >> 4;
|
||||
int envelopeRadius = IslandShape.TERRAIN_LIMIT + DECORATION_MARGIN;
|
||||
int minimumIslandChunk = Math.floorDiv(-envelopeRadius, 16);
|
||||
int maximumIslandChunk = Math.floorDiv(envelopeRadius - 1, 16);
|
||||
int minimumX = mode.equals("island") ? minimumIslandChunk : centreX - 1;
|
||||
int maximumX = mode.equals("island") ? maximumIslandChunk : centreX + 1;
|
||||
int minimumZ = mode.equals("island") ? minimumIslandChunk : centreZ - 1;
|
||||
int maximumZ = mode.equals("island") ? maximumIslandChunk : centreZ + 1;
|
||||
int expectedChunks = (maximumX - minimumX + 1) * (maximumZ - minimumZ + 1);
|
||||
BlockPos fixture = helper.absolutePos(BlockPos.ZERO);
|
||||
helper.assertTrue(fixture.getX() < minimumX * 16 - 1024 || fixture.getX() > (maximumX + 1) * 16 + 1024
|
||||
|| fixture.getZ() < minimumZ * 16 - 1024 || fixture.getZ() > (maximumZ + 1) * 16 + 1024,
|
||||
"The remote GameTest fixture must remain well outside the resource survey envelope");
|
||||
|
||||
// Complete the entire requested region, plus its immediate decoration neighbours, before
|
||||
// taking counts. Counting while still generating the next chunk would risk stale borders.
|
||||
SanctuaryMod.LOGGER.info("Resource survey {}: preparing {} inspected chunks plus a {}-chunk halo (seed {})",
|
||||
mode, expectedChunks, PREPARATION_HALO_CHUNKS, level.getSeed());
|
||||
int preparedChunks = 0;
|
||||
for (int x = minimumX - PREPARATION_HALO_CHUNKS; x <= maximumX + PREPARATION_HALO_CHUNKS; x++) {
|
||||
for (int z = minimumZ - PREPARATION_HALO_CHUNKS; z <= maximumZ + PREPARATION_HALO_CHUNKS; z++) {
|
||||
requireFullChunk(helper, level, x, z);
|
||||
preparedChunks++;
|
||||
}
|
||||
if (mode.equals("island") && (x - minimumX + PREPARATION_HALO_CHUNKS + 1) % 4 == 0) {
|
||||
SanctuaryMod.LOGGER.info("Resource survey preparation: {} FULL chunks ready", preparedChunks);
|
||||
}
|
||||
}
|
||||
long preparedNanos = System.nanoTime();
|
||||
Map<BlockState, Long> observedStates = new IdentityHashMap<>();
|
||||
List<Map<String, Object>> chunks = new ArrayList<>();
|
||||
long totalCells = 0;
|
||||
for (int x = minimumX; x <= maximumX; x++) {
|
||||
for (int z = minimumZ; z <= maximumZ; z++) {
|
||||
LevelChunk chunk = requireFullChunk(helper, level, x, z);
|
||||
Map<BlockState, Long> chunkStates = new IdentityHashMap<>();
|
||||
for (LevelChunkSection section : chunk.getSections()) {
|
||||
// This counts every cell in each palette, including all forms of air. Registry
|
||||
// lookups and property serialization happen once per distinct state later.
|
||||
section.getStates().count((state, count) -> chunkStates.merge(state, (long) count, Long::sum));
|
||||
}
|
||||
long chunkCells = sum(chunkStates);
|
||||
helper.assertTrue(chunkCells == 256L * level.getHeight(),
|
||||
"Every cell in the full dimension height must be counted at chunk " + x + "," + z);
|
||||
long nonAir = 0;
|
||||
for (var entry : chunkStates.entrySet()) {
|
||||
observedStates.merge(entry.getKey(), entry.getValue(), Long::sum);
|
||||
if (!entry.getKey().isAir()) nonAir += entry.getValue();
|
||||
}
|
||||
chunks.add(Map.of("x", x, "z", z, "status", "minecraft:full", "counted_cells", chunkCells,
|
||||
"non_air_blocks", nonAir, "distinct_block_states", chunkStates.size()));
|
||||
totalCells += chunkCells;
|
||||
}
|
||||
}
|
||||
Map<String, Long> blocks = new TreeMap<>();
|
||||
Map<String, Long> states = new TreeMap<>();
|
||||
long air = 0;
|
||||
for (var entry : observedStates.entrySet()) {
|
||||
String block = blockName(entry.getKey());
|
||||
blocks.merge(block, entry.getValue(), Long::sum);
|
||||
states.put(stateName(entry.getKey(), block), entry.getValue());
|
||||
if (entry.getKey().isAir()) air += entry.getValue();
|
||||
}
|
||||
long expectedCells = expectedChunks * 256L * level.getHeight();
|
||||
helper.assertTrue(chunks.size() == expectedChunks && totalCells == expectedCells,
|
||||
"Survey completeness must match its explicit requested chunk rectangle and height");
|
||||
helper.assertTrue(sum(blocks) == totalCells && sum(states) == totalCells,
|
||||
"Registry block and full-state totals must account for every inspected cell");
|
||||
var settingsHolder = ((NoiseBasedChunkGenerator) level.getChunkSource().getGenerator()).generatorSettings();
|
||||
var settings = settingsHolder.value();
|
||||
String modVersion = FabricLoader.getInstance().getModContainer("sanctuary").orElseThrow()
|
||||
.getMetadata().getVersion().getFriendlyString();
|
||||
Map<String, Object> report = new LinkedHashMap<>();
|
||||
report.put("schema_version", 1);
|
||||
report.put("purpose", "development_post_generation_resource_observation");
|
||||
report.put("mode", mode);
|
||||
report.put("seed", level.getSeed());
|
||||
report.put("minecraft_version", SharedConstants.getCurrentVersion().name());
|
||||
report.put("generator", Map.of("preset", "sanctuary:sanctuary", "noise_settings", settingsHolder.getRegisteredName(),
|
||||
"mod_version", modVersion, "version", "sanctuary@" + modVersion,
|
||||
"noise_min_y", settings.noiseSettings().minY(),
|
||||
"noise_max_y_exclusive", settings.noiseSettings().minY() + settings.noiseSettings().height()));
|
||||
report.put("dimension", "minecraft:overworld");
|
||||
report.put("initial_spawn", Map.of("x", initialSpawn.pos().getX(), "y", initialSpawn.pos().getY(),
|
||||
"z", initialSpawn.pos().getZ()));
|
||||
report.put("remote_test_fixture", Map.of("x", fixture.getX(), "y", fixture.getY(), "z", fixture.getZ(),
|
||||
"verified_outside_bounds_with_margin_blocks", 1024));
|
||||
report.put("bounds", Map.of("min_x_inclusive", minimumX * 16, "max_x_exclusive", (maximumX + 1) * 16,
|
||||
"min_z_inclusive", minimumZ * 16, "max_z_exclusive", (maximumZ + 1) * 16,
|
||||
"min_y_inclusive", level.getMinY(), "max_y_exclusive", level.getMinY() + level.getHeight(),
|
||||
"min_chunk_x_inclusive", minimumX, "max_chunk_x_inclusive", maximumX,
|
||||
"min_chunk_z_inclusive", minimumZ, "max_chunk_z_inclusive", maximumZ));
|
||||
report.put("declared_island_envelope", Map.of("terrain_radius_blocks", IslandShape.TERRAIN_LIMIT,
|
||||
"decoration_margin_blocks", DECORATION_MARGIN, "bounding_rectangle_radius_blocks", envelopeRadius));
|
||||
report.put("completeness", Map.of("requested_chunks", expectedChunks, "inspected_full_chunks", chunks.size(),
|
||||
"complete_within_requested_bounds", true, "covers_declared_island_envelope", mode.equals("island"),
|
||||
"covers_entire_world", false, "extrapolated", false, "height_coverage", "entire_dimension",
|
||||
"basis", "all palette cells in every inspected FULL chunk, not density samples"));
|
||||
report.put("preparation", Map.of("halo_chunks", PREPARATION_HALO_CHUNKS, "full_chunks_prepared", preparedChunks,
|
||||
"halo_included_in_counts", false, "world", "fresh_disposable_gametest_world",
|
||||
"personal_saves_accessed", false, "player_inventory_included", false, "remote_test_fixture_included", false));
|
||||
report.put("counts", Map.of("all_cells", totalCells, "air_cells", air, "non_air_blocks", totalCells - air,
|
||||
"by_block_registry_id", blocks, "by_complete_block_state", states));
|
||||
report.put("aggregates", aggregates(observedStates, blocks));
|
||||
report.put("aggregate_semantics", "Counts are blocks, not drops or economic value; groups overlap and must not be added together.");
|
||||
report.put("inspected_chunks", chunks);
|
||||
report.put("exclusions", List.of("entities", "item_drops", "container_contents", "player_inventories",
|
||||
"other_dimensions", "future_expansions", "blocks_outside_requested_bounds", "future_growth_and_loot"));
|
||||
report.put("observation", Map.of("started_at_utc", started.toString(), "completed_at_utc", Instant.now().toString(),
|
||||
"game_time_at_start", initialGameTime, "game_time_at_end", level.getGameTime(),
|
||||
"generation_preparation_millis", (preparedNanos - startedNanos) / 1_000_000L,
|
||||
"counting_and_serialization_preparation_millis", (System.nanoTime() - preparedNanos) / 1_000_000L,
|
||||
"scope", "snapshot after FULL generation; no resource quotas or terrain correction applied"));
|
||||
Path directory = Path.of("diagnostics");
|
||||
Files.createDirectories(directory);
|
||||
Path output = directory.resolve("resource-survey-seed-" + level.getSeed() + "-" + mode + ".json");
|
||||
Files.writeString(output, JSON.toJson(report) + "\n");
|
||||
SanctuaryMod.LOGGER.info("Resource survey {} complete: {} FULL chunks, {} non-air blocks; {}",
|
||||
mode, chunks.size(), totalCells - air, output.toAbsolutePath());
|
||||
}
|
||||
|
||||
private static LevelChunk requireFullChunk(GameTestHelper helper, ServerLevel level, int x, int z) {
|
||||
ChunkAccess chunk = level.getChunkSource().getChunk(x, z, ChunkStatus.FULL, true);
|
||||
helper.assertTrue(chunk instanceof LevelChunk, "Survey requires a FULL chunk at " + x + "," + z);
|
||||
return (LevelChunk) chunk;
|
||||
}
|
||||
|
||||
private static Map<String, Object> aggregates(Map<BlockState, Long> states, Map<String, Long> blocks) {
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
result.put("stone", explicitBlocks(blocks, "minecraft:stone"));
|
||||
result.put("base_stone_overworld", taggedStates(states, state -> state.is(BlockTags.BASE_STONE_OVERWORLD),
|
||||
"#minecraft:base_stone_overworld"));
|
||||
result.put("logs", taggedStates(states, state -> state.is(BlockTags.LOGS), "#minecraft:logs"));
|
||||
result.put("leaves", taggedStates(states, state -> state.is(BlockTags.LEAVES), "#minecraft:leaves"));
|
||||
for (String ore : List.of("coal", "iron", "copper", "gold", "redstone", "lapis", "diamond", "emerald")) {
|
||||
result.put(ore + "_ore", explicitBlocks(blocks, "minecraft:" + ore + "_ore", "minecraft:deepslate_" + ore + "_ore"));
|
||||
}
|
||||
result.put("water_blocks", explicitBlocks(blocks, "minecraft:water"));
|
||||
result.put("water_containing_blocks", taggedStates(states, state -> state.getFluidState().is(FluidTags.WATER),
|
||||
"block states containing #minecraft:water, including waterlogged blocks"));
|
||||
result.put("lava_blocks", explicitBlocks(blocks, "minecraft:lava"));
|
||||
result.put("sand", explicitBlocks(blocks, "minecraft:sand", "minecraft:red_sand"));
|
||||
result.put("gravel", explicitBlocks(blocks, "minecraft:gravel"));
|
||||
result.put("clay", explicitBlocks(blocks, "minecraft:clay"));
|
||||
result.put("sugar_cane", explicitBlocks(blocks, "minecraft:sugar_cane"));
|
||||
result.put("soil", explicitBlocks(blocks, "minecraft:grass_block", "minecraft:dirt", "minecraft:coarse_dirt",
|
||||
"minecraft:rooted_dirt", "minecraft:podzol", "minecraft:mud"));
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Map<String, Object> explicitBlocks(Map<String, Long> blocks, String... names) {
|
||||
Map<String, Long> members = new TreeMap<>();
|
||||
for (String name : names) members.put(name, blocks.getOrDefault(name, 0L));
|
||||
return Map.of("count", sum(members), "definition", "sum of the listed block registry IDs", "members", members);
|
||||
}
|
||||
|
||||
private static Map<String, Object> taggedStates(Map<BlockState, Long> states, Predicate<BlockState> predicate,
|
||||
String definition) {
|
||||
Map<String, Long> members = new TreeMap<>();
|
||||
states.forEach((state, count) -> {
|
||||
if (predicate.test(state)) members.merge(blockName(state), count, Long::sum);
|
||||
});
|
||||
return Map.of("count", sum(members), "definition", definition, "observed_members", members);
|
||||
}
|
||||
|
||||
private static String blockName(BlockState state) {
|
||||
return BuiltInRegistries.BLOCK.getKey(state.getBlock()).toString();
|
||||
}
|
||||
|
||||
private static String stateName(BlockState state, String block) {
|
||||
Map<String, String> properties = new TreeMap<>();
|
||||
for (Property<?> property : state.getProperties()) properties.put(property.getName(), propertyValue(state, property));
|
||||
if (properties.isEmpty()) return block;
|
||||
StringJoiner suffix = new StringJoiner(",", "[", "]");
|
||||
properties.forEach((name, value) -> suffix.add(name + "=" + value));
|
||||
return block + suffix;
|
||||
}
|
||||
|
||||
private static <T extends Comparable<T>> String propertyValue(BlockState state, Property<T> property) {
|
||||
return property.getName(state.getValue(property));
|
||||
}
|
||||
|
||||
private static long sum(Map<?, Long> counts) {
|
||||
return counts.values().stream().mapToLong(Long::longValue).sum();
|
||||
}
|
||||
}
|
||||
+169
@@ -0,0 +1,169 @@
|
||||
package fr.koka.sanctuary.gametest;
|
||||
|
||||
import fr.koka.sanctuary.worldgen.SanctuarySpawn;
|
||||
import fr.koka.sanctuary.worldgen.IslandShape;
|
||||
import fr.koka.sanctuary.worldgen.MainIslandDensity;
|
||||
import fr.koka.sanctuary.SanctuaryMod;
|
||||
import net.fabricmc.fabric.api.gametest.v1.GameTest;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.gametest.framework.GameTestHelper;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
import net.minecraft.world.level.chunk.LevelChunkSection;
|
||||
import net.minecraft.world.level.chunk.status.ChunkStatus;
|
||||
import net.minecraft.world.level.levelgen.NoiseGeneratorSettings;
|
||||
import net.minecraft.world.level.levelgen.RandomState;
|
||||
import net.minecraft.world.level.levelgen.densityfunction.DensityFunction;
|
||||
import net.minecraft.world.level.storage.LevelData;
|
||||
|
||||
/** Assertions use absolute world positions, away from the framework's random fixture grid. */
|
||||
public final class SanctuaryWorldGameTests {
|
||||
public static LevelData.RespawnData initialSpawn;
|
||||
|
||||
@GameTest(maxTicks = 400)
|
||||
public void generatedSurfaceWaterRemainsContained(GameTestHelper helper) throws java.io.IOException {
|
||||
HydrologyDiagnostics.start(helper);
|
||||
}
|
||||
|
||||
@GameTest(maxTicks = 200)
|
||||
public void hydrologyDoesNotActivateForSavedLegacySettings(GameTestHelper helper) {
|
||||
HydrologyDiagnostics.verifyLegacyIsolation(helper);
|
||||
helper.succeed();
|
||||
}
|
||||
|
||||
@GameTest(maxTicks = 200)
|
||||
public void generatedIslandAndInitialSpawn(GameTestHelper helper) {
|
||||
ServerLevel level = helper.getLevel();
|
||||
helper.assertTrue(SanctuarySpawn.usesSanctuaryGenerator(level),
|
||||
"GameTest must load the production Sanctuary preset, not a flat world");
|
||||
|
||||
helper.assertTrue(initialSpawn != null, "Capture the world's initial spawn before the test grid moves it");
|
||||
BlockPos spawn = initialSpawn.pos();
|
||||
helper.assertTrue(Math.hypot((double) spawn.getX(), (double) spawn.getZ()) < IslandShape.TERRAIN_LIMIT,
|
||||
"Shared spawn must lie on the finite natural island: " + spawn);
|
||||
helper.assertTrue(spawn.getY() > 1 && spawn.getY() < level.getMaxY() - 1,
|
||||
"Shared spawn must be inside the dimension height");
|
||||
var settings = level.registryAccess().lookupOrThrow(Registries.NOISE_SETTINGS)
|
||||
.getOrThrow(SanctuarySpawn.RAISED_SETTINGS).value();
|
||||
float spawnFloorDensity = level.getChunkSource().randomState().sampleBlockValueUncached(
|
||||
settings.noiseRouter().finalDensity(), spawn.getX(), spawn.getY() - 1, spawn.getZ());
|
||||
helper.assertTrue(spawnFloorDensity > 0.0F,
|
||||
"The spawn floor must already exist in natural terrain density, without adding a platform");
|
||||
// These regression seeds have a naturally flat 3x3 arrival area. Production also supports
|
||||
// a single safe column when a more irregular seed offers no sampled 3x3 floor.
|
||||
for (int dx = -1; dx <= 1; dx++) {
|
||||
for (int dz = -1; dz <= 1; dz++) {
|
||||
BlockPos feet = spawn.offset(dx, 0, dz);
|
||||
BlockPos floor = feet.below();
|
||||
helper.assertTrue(level.getBlockState(floor).isCollisionShapeFullBlock(level, floor),
|
||||
"Every cell of the 3x3 arrival area needs a full solid floor: " + floor);
|
||||
for (int height = 0; height < 2; height++) {
|
||||
BlockPos space = feet.above(height);
|
||||
helper.assertTrue(level.getBlockState(space).getCollisionShape(level, space).isEmpty()
|
||||
&& level.getFluidState(space).isEmpty(),
|
||||
"The arrival area needs two dry unobstructed blocks: " + space);
|
||||
}
|
||||
}
|
||||
}
|
||||
helper.succeed();
|
||||
}
|
||||
|
||||
@GameTest(maxTicks = 200)
|
||||
public void sculptOnlyRemovesTerrain(GameTestHelper helper) throws java.io.IOException {
|
||||
DensityDiagnostics.verifyAndExport(helper, initialSpawn);
|
||||
helper.succeed();
|
||||
}
|
||||
|
||||
@GameTest(maxTicks = 200)
|
||||
public void generationHeightAndLegacySettingsStaySeparate(GameTestHelper helper) {
|
||||
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");
|
||||
float cloudHeight = level.environmentAttributes()
|
||||
.getDimensionValue(net.minecraft.world.attribute.EnvironmentAttributes.CLOUD_HEIGHT);
|
||||
helper.assertTrue(Math.abs(cloudHeight - 352.33F) < 0.001F,
|
||||
"Sanctuary clouds must be raised to Y352.33");
|
||||
helper.assertTrue(settings.getOrThrow(SanctuarySpawn.RAISED_SETTINGS).value().noiseSettings().height() == 384,
|
||||
"Noise generation must use all 384 blocks, not just a taller dimension");
|
||||
helper.assertTrue(settings.getOrThrow(SanctuarySpawn.SETTINGS).value().noiseSettings().height() == 256,
|
||||
"The noise settings referenced by saved alpha.2 worlds must retain their old height");
|
||||
var legacy = registries.lookupOrThrow(Registries.DENSITY_FUNCTION).getOrThrow(
|
||||
ResourceKey.create(Registries.DENSITY_FUNCTION, SanctuaryMod.id("final_density"))).value();
|
||||
helper.assertTrue(legacy instanceof MainIslandDensity old && old.verticalOffset() == 0 && old.height() == 256,
|
||||
"The legacy density codec must remain in its unshifted alpha.2 mode");
|
||||
helper.succeed();
|
||||
}
|
||||
|
||||
@GameTest(maxTicks = 200)
|
||||
public void completedLowerMarginIsVoid(GameTestHelper helper) {
|
||||
for (int[] coordinate : new int[][]{{0, 0}, {-8, 0}, {8, 0}, {0, -8}, {0, 8}}) {
|
||||
ChunkAccess chunk = helper.getLevel().getChunkSource().getChunk(
|
||||
coordinate[0], coordinate[1], ChunkStatus.FULL, true);
|
||||
helper.assertTrue(chunk != null && chunk.getSection(0).hasOnlyAir(),
|
||||
"The fully generated island must leave Y0..15 empty, without a floor cut at the dimension bound");
|
||||
}
|
||||
helper.succeed();
|
||||
}
|
||||
|
||||
@GameTest(maxTicks = 200)
|
||||
public void generatedResourceSurvey(GameTestHelper helper) throws java.io.IOException {
|
||||
ResourceSurveyDiagnostics.verifyAndExport(helper, initialSpawn);
|
||||
helper.succeed();
|
||||
}
|
||||
|
||||
@GameTest(maxTicks = 200)
|
||||
public void completedExteriorChunksAreVoid(GameTestHelper helper) {
|
||||
ServerLevel level = helper.getLevel();
|
||||
// First row is just beyond the declared terrain/decorations envelope. The last
|
||||
// samples catch the legacy bug where the infinite archipelago returned farther out.
|
||||
int[][] chunks = {
|
||||
{20, 0}, {-21, 0}, {0, 20}, {0, -21},
|
||||
{32, 0}, {-33, 0}, {0, 32}, {0, -33},
|
||||
{256, 0}, {-257, 0}, {0, 256}, {0, -257}
|
||||
};
|
||||
for (int[] coordinate : chunks) {
|
||||
ChunkAccess chunk = level.getChunkSource().getChunk(coordinate[0], coordinate[1], ChunkStatus.FULL, true);
|
||||
helper.assertTrue(chunk instanceof LevelChunk, "The test must inspect a fully generated chunk");
|
||||
for (LevelChunkSection section : chunk.getSections()) {
|
||||
helper.assertTrue(section.hasOnlyAir(),
|
||||
"Finished exterior chunk contains blocks after world generation: " + chunk.getPos());
|
||||
}
|
||||
}
|
||||
helper.succeed();
|
||||
}
|
||||
|
||||
@GameTest(maxTicks = 200)
|
||||
public void compiledWorldDensityUsesTheSeed(GameTestHelper helper) {
|
||||
ServerLevel level = helper.getLevel();
|
||||
var registries = level.registryAccess();
|
||||
NoiseGeneratorSettings settings = registries.lookupOrThrow(Registries.NOISE_SETTINGS)
|
||||
.getOrThrow(SanctuarySpawn.RAISED_SETTINGS).value();
|
||||
var noises = registries.lookupOrThrow(Registries.NOISE);
|
||||
RandomState first = RandomState.create(noises, 0L, settings);
|
||||
RandomState replay = RandomState.create(noises, 0L, settings);
|
||||
RandomState otherSeed = RandomState.create(noises, 8675309L, settings);
|
||||
DensityFunction density = settings.noiseRouter().finalDensity();
|
||||
boolean observedSeedVariation = false;
|
||||
for (int x : new int[]{112, 160, 208}) {
|
||||
for (int z : new int[]{-96, -48, 0, 48, 96}) {
|
||||
for (int y : new int[]{96, 128, 160, 192, 224}) {
|
||||
float value = first.sampleBlockValueUncached(density, x, y, z);
|
||||
float repeated = replay.sampleBlockValueUncached(density, x, y, z);
|
||||
float changed = otherSeed.sampleBlockValueUncached(density, x, y, z);
|
||||
helper.assertTrue(Float.isFinite(value) && Float.isFinite(changed),
|
||||
"Compiled world density must remain finite");
|
||||
helper.assertTrue(Float.floatToIntBits(value) == Float.floatToIntBits(repeated),
|
||||
"The actual datapack density must replay identically for the same seed");
|
||||
observedSeedVariation |= Float.floatToIntBits(value) != Float.floatToIntBits(changed);
|
||||
}
|
||||
}
|
||||
}
|
||||
helper.assertTrue(observedSeedVariation, "Different world seeds must change the production density");
|
||||
helper.succeed();
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
package fr.koka.sanctuary.gametest.mixin;
|
||||
|
||||
import fr.koka.sanctuary.SanctuaryMod;
|
||||
import fr.koka.sanctuary.gametest.SanctuaryWorldGameTests;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.gametest.framework.GameTestServer;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.levelgen.presets.WorldPreset;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.ModifyArg;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
/** Test-only: run the real Sanctuary datapack instead of the framework's flat fixture. */
|
||||
@Mixin(GameTestServer.class)
|
||||
public abstract class SanctuaryGameTestServerMixin {
|
||||
@ModifyArg(method = "<clinit>", at = @At(value = "INVOKE", target =
|
||||
"Lnet/minecraft/world/level/levelgen/WorldOptions;<init>(JZZ)V"), index = 0)
|
||||
private static long sanctuary$testSeed(long original) {
|
||||
return Long.getLong("sanctuary.test.seed", 0L);
|
||||
}
|
||||
|
||||
@ModifyArg(method = "<clinit>", at = @At(value = "INVOKE", target =
|
||||
"Lnet/minecraft/world/level/levelgen/WorldOptions;<init>(JZZ)V"), index = 1)
|
||||
private static boolean sanctuary$enableStructureGeneration(boolean original) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Redirect(method = "lambda$create$1", at = @At(value = "FIELD", target =
|
||||
"Lnet/minecraft/world/level/levelgen/presets/WorldPresets;FLAT_ALL_DIMENSIONS:Lnet/minecraft/resources/ResourceKey;"))
|
||||
private static ResourceKey<WorldPreset> sanctuary$selectActualPreset() {
|
||||
return ResourceKey.create(Registries.WORLD_PRESET, SanctuaryMod.id("sanctuary"));
|
||||
}
|
||||
|
||||
@Inject(method = "startTests", at = @At("HEAD"))
|
||||
private void sanctuary$captureInitialSpawn(ServerLevel level, CallbackInfo ci) {
|
||||
// The framework moves its own spawn to a random test grid after this point.
|
||||
SanctuaryWorldGameTests.initialSpawn = level.getRespawnData();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "sanctuary-gametest",
|
||||
"version": "1.0.0",
|
||||
"name": "Sanctuary generation tests",
|
||||
"environment": "*",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"entrypoints": {
|
||||
"fabric-gametest": ["fr.koka.sanctuary.gametest.SanctuaryWorldGameTests"],
|
||||
"fabric-client-gametest": ["fr.koka.sanctuary.gametest.SanctuaryClientRenderTests"]
|
||||
},
|
||||
"mixins": ["sanctuary-gametest.mixins.json"],
|
||||
"depends": {
|
||||
"sanctuary": "*",
|
||||
"fabric-gametest-api-v1": "*"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"required": true,
|
||||
"package": "fr.koka.sanctuary.gametest.mixin",
|
||||
"compatibilityLevel": "JAVA_25",
|
||||
"mixins": ["SanctuaryGameTestServerMixin"],
|
||||
"injectors": {"defaultRequire": 1}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package fr.koka.sanctuary;
|
||||
|
||||
import fr.koka.sanctuary.worldgen.MainIslandDensity;
|
||||
import fr.koka.sanctuary.worldgen.ShoreSugarCaneFeature;
|
||||
import net.fabricmc.api.ModInitializer;
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public final class SanctuaryMod implements ModInitializer {
|
||||
public static final String ID = "sanctuary";
|
||||
public static final Logger LOGGER = LoggerFactory.getLogger(ID);
|
||||
|
||||
public static Identifier id(String path) {
|
||||
return Identifier.fromNamespaceAndPath(ID, path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInitialize() {
|
||||
Registry.register(BuiltInRegistries.DENSITY_FUNCTION_TYPE, id("main_island"), MainIslandDensity.CODEC);
|
||||
Registry.register(BuiltInRegistries.FEATURE_TYPE, id("shore_sugar_cane"), ShoreSugarCaneFeature.CODEC);
|
||||
LOGGER.info("Sanctuary initialized: the Sanctuary world preset is available.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package fr.koka.sanctuary.client;
|
||||
|
||||
import fr.koka.sanctuary.SanctuaryMod;
|
||||
import net.minecraft.client.multiplayer.ClientLevel;
|
||||
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 SanctuarySky() {
|
||||
}
|
||||
|
||||
public static boolean isFloatingWorld(ClientLevel level) {
|
||||
return level.dimensionTypeRegistration().is(FLOATING_WORLD);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package fr.koka.sanctuary.mixin;
|
||||
|
||||
import fr.koka.sanctuary.worldgen.SanctuarySpawn;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.progress.LevelLoadListener;
|
||||
import net.minecraft.world.level.storage.ServerLevelData;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(MinecraftServer.class)
|
||||
public abstract class InitialSpawnMixin {
|
||||
@Inject(method = "setInitialSpawn", at = @At("RETURN"))
|
||||
private static void sanctuary$initialSpawn(ServerLevel level, ServerLevelData data, boolean bonusChest,
|
||||
boolean debugWorld, LevelLoadListener listener, CallbackInfo ci) {
|
||||
SanctuarySpawn.initialize(level);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package fr.koka.sanctuary.mixin;
|
||||
|
||||
import fr.koka.sanctuary.worldgen.HydrologyRuntime;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.server.level.WorldGenRegion;
|
||||
import net.minecraft.world.level.StructureManager;
|
||||
import net.minecraft.world.level.biome.Biome;
|
||||
import net.minecraft.world.level.biome.BiomeManager;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator;
|
||||
import net.minecraft.world.level.levelgen.RandomState;
|
||||
import net.minecraft.world.level.levelgen.blending.Blender;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
/** The terrain future completes before decoration starts; never reads or writes another chunk. */
|
||||
@Mixin(NoiseBasedChunkGenerator.class)
|
||||
public abstract class SurfaceHydrologyMixin {
|
||||
@Inject(method = "buildTerrain", at = @At("RETURN"), cancellable = true)
|
||||
private void sanctuary$surfaceWater(ChunkAccess chunk, Blender blender, RandomState randomState,
|
||||
StructureManager structures, BiomeManager biomeManager, WorldGenRegion region,
|
||||
Set<Holder<Biome>> biomes, CallbackInfoReturnable<CompletableFuture<ChunkAccess>> callback) {
|
||||
var generator = (NoiseBasedChunkGenerator) (Object) this;
|
||||
if (HydrologyRuntime.enabled(generator)) {
|
||||
callback.setReturnValue(callback.getReturnValue().thenApply(generated -> {
|
||||
HydrologyRuntime.apply(generator, randomState, generated);
|
||||
return generated;
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package fr.koka.sanctuary.mixin.client;
|
||||
|
||||
import fr.koka.sanctuary.client.SanctuarySky;
|
||||
import net.minecraft.client.Camera;
|
||||
import net.minecraft.client.multiplayer.ClientLevel;
|
||||
import net.minecraft.client.renderer.fog.FogRenderer;
|
||||
import net.minecraft.util.Mth;
|
||||
import org.joml.Vector4f;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
@Mixin(FogRenderer.class)
|
||||
public abstract class SanctuaryFogMixin {
|
||||
// Only the altitude-derived darkness is removed. Vanilla still applies
|
||||
// blindness/darkness effects, weather, fluids and the normal distance fog.
|
||||
@Redirect(method = "computeFogColor", at = @At(value = "INVOKE",
|
||||
target = "Lnet/minecraft/util/Mth;clamp(FFF)F", ordinal = 0))
|
||||
private float sanctuary$openVoidFog(float value, float minimum, float maximum,
|
||||
Camera camera, float partialTicks, ClientLevel level,
|
||||
int renderDistance, float darkenWorldAmount, Vector4f destination) {
|
||||
return SanctuarySky.isFloatingWorld(level) ? 0.0F : Mth.clamp(value, minimum, maximum);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package fr.koka.sanctuary.mixin.client;
|
||||
|
||||
import fr.koka.sanctuary.client.SanctuarySky;
|
||||
import net.minecraft.client.multiplayer.ClientLevel;
|
||||
import net.minecraft.client.renderer.SkyRenderer;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
@Mixin(SkyRenderer.class)
|
||||
public abstract class SanctuarySkyMixin {
|
||||
// Vanilla draws a black lower sky below Y=63 even when there is no terrain.
|
||||
@Inject(method = "shouldRenderDarkDisc", at = @At("HEAD"), cancellable = true)
|
||||
private void sanctuary$openLowerSky(float partialTicks, ClientLevel level,
|
||||
CallbackInfoReturnable<Boolean> callback) {
|
||||
if (SanctuarySky.isFloatingWorld(level)) {
|
||||
callback.setReturnValue(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package fr.koka.sanctuary.worldgen;
|
||||
|
||||
import fr.koka.sanctuary.SanctuaryMod;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Map;
|
||||
import java.util.WeakHashMap;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.levelgen.Heightmap;
|
||||
import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator;
|
||||
import net.minecraft.world.level.levelgen.RandomState;
|
||||
|
||||
/** One immutable plan per world random state. The value retains neither that state nor its sampler. */
|
||||
public final class HydrologyRuntime {
|
||||
private static final Map<RandomState, SurfaceHydrology.Plan> PLANS =
|
||||
Collections.synchronizedMap(new WeakHashMap<>());
|
||||
|
||||
private HydrologyRuntime() {}
|
||||
|
||||
public static boolean enabled(NoiseBasedChunkGenerator generator) {
|
||||
return generator.generatorSettings().is(SanctuarySpawn.HYDROLOGY_SETTINGS);
|
||||
}
|
||||
|
||||
public static SurfaceHydrology.Plan plan(ServerLevel level) {
|
||||
return plan((NoiseBasedChunkGenerator) level.getChunkSource().getGenerator(),
|
||||
level.getChunkSource().randomState());
|
||||
}
|
||||
|
||||
public static SurfaceHydrology.Plan plan(NoiseBasedChunkGenerator generator, RandomState randomState) {
|
||||
if (!enabled(generator)) throw new IllegalArgumentException("Hydrology is not enabled for this generator");
|
||||
return PLANS.computeIfAbsent(randomState, state -> {
|
||||
long start = System.nanoTime();
|
||||
var density = generator.generatorSettings().value().noiseRouter().finalDensity();
|
||||
var plan = SurfaceHydrology.create(state.seed(),
|
||||
(x, y, z) -> state.sampleBlockValueUncached(density, x, y, z));
|
||||
SanctuaryMod.LOGGER.info("Sanctuary surface hydrology: seed {}, {} features, {} columns, {} ms",
|
||||
state.seed(), plan.features().size(), plan.cells().size(), (System.nanoTime() - start) / 1_000_000);
|
||||
return plan;
|
||||
});
|
||||
}
|
||||
|
||||
/** Executed during fresh terrain generation only, before ores, trees and shoreline plants. */
|
||||
public static void apply(NoiseBasedChunkGenerator generator, RandomState randomState, ChunkAccess chunk) {
|
||||
if (!enabled(generator)) return;
|
||||
var chunkPos = chunk.getPos();
|
||||
if (Math.abs((long) chunkPos.x() * 16) > IslandShape.TERRAIN_LIMIT + 16L
|
||||
|| Math.abs((long) chunkPos.z() * 16) > IslandShape.TERRAIN_LIMIT + 16L) return;
|
||||
var cells = plan(generator, randomState).cellsInChunk(chunkPos.x(), chunkPos.z());
|
||||
if (cells.isEmpty()) return;
|
||||
BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos();
|
||||
// Validate this chunk's entire foundation before the first write. The planner may only
|
||||
// replace existing terrain; a mismatch must fail rather than leave a partially applied bed.
|
||||
for (var cell : cells) {
|
||||
for (int y = cell.bedY() - 2; y <= cell.bedY(); y++) {
|
||||
pos.set(cell.x(), y, cell.z());
|
||||
if (!chunk.getBlockState(pos).isCollisionShapeFullBlock(chunk, pos)) {
|
||||
throw new IllegalStateException("Hydrology bed differs from natural terrain at " + pos
|
||||
+ " for seed " + randomState.seed());
|
||||
}
|
||||
}
|
||||
}
|
||||
for (var cell : cells) {
|
||||
pos.set(cell.x(), cell.bedY(), cell.z());
|
||||
BlockState material = switch (cell.material()) {
|
||||
case SAND -> Blocks.SAND.defaultBlockState();
|
||||
case GRAVEL -> Blocks.GRAVEL.defaultBlockState();
|
||||
case CLAY -> Blocks.CLAY.defaultBlockState();
|
||||
};
|
||||
chunk.setBlockState(pos, material, 0);
|
||||
if (!cell.hasWater()) continue;
|
||||
for (int y = cell.bedY() + 1; y <= Math.max(cell.waterY(), cell.carveTop()); y++) {
|
||||
pos.set(cell.x(), y, cell.z());
|
||||
chunk.setBlockState(pos, y <= cell.waterY()
|
||||
? Blocks.WATER.defaultBlockState() : Blocks.AIR.defaultBlockState(), 0);
|
||||
if (y <= cell.waterY()) chunk.markPosForPostProcessing(pos);
|
||||
}
|
||||
}
|
||||
// buildTerrain's incoming proto status may not yet maintain the WG heightmaps. Decoration
|
||||
// must see the new bed and waterline so trees are placed on dry shores, not below the lake.
|
||||
Heightmap.primeHeightmaps(chunk, EnumSet.of(Heightmap.Types.WORLD_SURFACE_WG,
|
||||
Heightmap.Types.OCEAN_FLOOR_WG));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package fr.koka.sanctuary.worldgen;
|
||||
|
||||
/** Subtractive sculpting of the legacy seeded field. Nothing here creates terrain or a spawn platform. */
|
||||
public final class IslandShape {
|
||||
public static final int RADIUS = 256;
|
||||
public static final int EDGE_WARP = 32;
|
||||
public static final int TERRAIN_LIMIT = RADIUS + EDGE_WARP;
|
||||
public static final int INNER_RADIUS = 128;
|
||||
public static final int RAISED_HEIGHT = 384;
|
||||
public static final int VERTICAL_OFFSET = 64;
|
||||
|
||||
private IslandShape() {}
|
||||
|
||||
public static float density(int x, int y, int z, float terrain, float distortion) {
|
||||
return density(x, y, z, terrain, distortion, 0, 0);
|
||||
}
|
||||
|
||||
public static float density(int x, int y, int z, float terrain, float distortion, float sculpt, float detail) {
|
||||
double distance = Math.hypot((double) x, (double) z);
|
||||
// Emergency bounds only: the sculpt has already made the field negative before these planes.
|
||||
if (distance >= TERRAIN_LIMIT || y <= 0 || y >= 256) {
|
||||
return -1.0F;
|
||||
}
|
||||
|
||||
double erosion = rimErosion(distance, y, distortion, sculpt, detail);
|
||||
erosion += 2.0 * smoothstep((32.0 - y) / 32.0);
|
||||
erosion += 2.0 * smoothstep((y - 224.0) / 32.0);
|
||||
return bounded(terrain, erosion);
|
||||
}
|
||||
|
||||
/** 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) {
|
||||
double distance = Math.hypot((double) x, (double) z);
|
||||
if (distance >= TERRAIN_LIMIT || y <= 0 || y >= height) return -1.0F;
|
||||
double localY = (double) y - offset;
|
||||
double erosion = rimErosion(distance, localY, distortion, sculpt, detail);
|
||||
|
||||
// The former uniform lower gradient is absent from the new natural field. Let its seeded
|
||||
// lobes continue downwards, then sculpt them over a broad, spatially varying transition.
|
||||
// The extra margin below the translated island must not merely reveal the same flat cut.
|
||||
double bottom = -24.0 + Math.clamp(underside, -1.0F, 1.0F) * 16.0
|
||||
+ Math.clamp(sculpt, -1.0F, 1.0F) * 8.0 + Math.clamp(detail, -1.0F, 1.0F) * 4.0;
|
||||
double lower = Math.clamp((bottom + 72.0 - localY) / 72.0, 0.0, 1.0);
|
||||
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((24.0 - y) / 24.0);
|
||||
erosion += 2.0 * smoothstep((y - (height - 24.0)) / 24.0);
|
||||
return bounded(terrain, erosion);
|
||||
}
|
||||
|
||||
private static double rimErosion(double distance, double y, float distortion, float sculpt, float detail) {
|
||||
double coast = Math.clamp(distortion, -1.0F, 1.0F);
|
||||
double coarse = Math.clamp(sculpt, -1.0F, 1.0F);
|
||||
double fine = Math.clamp(detail, -1.0F, 1.0F);
|
||||
|
||||
// Pass 1: a broad outline bends through the island's height with two truly 3D noises.
|
||||
// Keep the central field untouched even when a deeply recessed rim approaches it.
|
||||
double rimEnd = Math.min(TERRAIN_LIMIT, RADIUS + coast * 16.0 + coarse * 28.0 + fine * 8.0);
|
||||
double rim = Math.clamp((distance - INNER_RADIUS) / (rimEnd - INNER_RADIUS), 0.0, 1.0);
|
||||
// Raising the density threshold gradually shrinks each natural lobe from all sides. Unlike
|
||||
// min(terrain, distance-to-edge), this never shears every altitude at one vertical contour.
|
||||
double erosion = 2.0 * rim * rim * rim * rim;
|
||||
|
||||
// A saturated natural lobe has almost no density gradient through its middle. Merely
|
||||
// raising its threshold can still leave a tall near-vertical wall. Retreat its upper and
|
||||
// lower rim faces as well, with a curved profile that disappears entirely at the interior.
|
||||
// This is an outer-rim cut, never an ellipsoid combined with (or added to) the natural field.
|
||||
double vertical = (y - (104.0 + coarse * 20.0)) / 88.0;
|
||||
erosion += 8.0 * rim * rim * vertical * vertical;
|
||||
|
||||
// Passes 2 and 3: broad recesses and smaller surface erosion, fading into the untouched
|
||||
// interior. Both subtract material; holes stay empty and detached fragments are never joined.
|
||||
double sculptWeight = smoothstep(rim);
|
||||
erosion += sculptWeight * (0.10 * (coarse + 1.0) * 0.5 + 0.04 * (fine + 1.0) * 0.5);
|
||||
|
||||
return erosion;
|
||||
}
|
||||
|
||||
private static float bounded(float terrain, double erosion) {
|
||||
return (float) Math.clamp(Math.clamp(terrain, -1.0F, 1.0F) - erosion, -1.0, 1.0);
|
||||
}
|
||||
|
||||
private static double smoothstep(double value) {
|
||||
double t = Math.clamp(value, 0.0, 1.0);
|
||||
return t * t * (3.0 - 2.0 * t);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package fr.koka.sanctuary.worldgen;
|
||||
|
||||
import com.mojang.serialization.Codec;
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder;
|
||||
import net.minecraft.util.Interval;
|
||||
import net.minecraft.world.level.levelgen.densityfunction.DensityBuffer;
|
||||
import net.minecraft.world.level.levelgen.densityfunction.DensityFunction;
|
||||
import net.minecraft.world.level.levelgen.densityfunction.DensityFunctions;
|
||||
import net.minecraft.world.level.levelgen.densityfunction.DensitySampler;
|
||||
import net.minecraft.world.level.levelgen.densityfunction.DensityVolume;
|
||||
import net.minecraft.world.level.levelgen.densityfunction.DfRewriteRule;
|
||||
import net.minecraft.world.level.levelgen.densityfunction.SamplerContext;
|
||||
|
||||
/** Compiled density API. Legacy defaults preserve the generator saved by alpha.2 worlds. */
|
||||
public record MainIslandDensity(DensityFunction terrain, DensityFunction distortion,
|
||||
DensityFunction sculpt, DensityFunction detail,
|
||||
int verticalOffset, int height, DensityFunction underside) implements DensityFunction {
|
||||
public static final MapCodec<MainIslandDensity> CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group(
|
||||
DensityFunction.CODEC.fieldOf("terrain").forGetter(MainIslandDensity::terrain),
|
||||
DensityFunction.CODEC.fieldOf("distortion").forGetter(MainIslandDensity::distortion),
|
||||
DensityFunction.CODEC.optionalFieldOf("sculpt", DensityFunctions.zero()).forGetter(MainIslandDensity::sculpt),
|
||||
DensityFunction.CODEC.optionalFieldOf("detail", DensityFunctions.zero()).forGetter(MainIslandDensity::detail),
|
||||
Codec.intRange(0, 128).optionalFieldOf("vertical_offset", 0).forGetter(MainIslandDensity::verticalOffset),
|
||||
Codec.intRange(256, 384).optionalFieldOf("height", 256).forGetter(MainIslandDensity::height),
|
||||
DensityFunction.CODEC.optionalFieldOf("underside", DensityFunctions.zero()).forGetter(MainIslandDensity::underside)
|
||||
).apply(instance, MainIslandDensity::new));
|
||||
|
||||
public MainIslandDensity(DensityFunction terrain, DensityFunction distortion) {
|
||||
this(terrain, distortion, DensityFunctions.zero(), DensityFunctions.zero());
|
||||
}
|
||||
|
||||
public MainIslandDensity(DensityFunction terrain, DensityFunction distortion,
|
||||
DensityFunction sculpt, DensityFunction detail) {
|
||||
this(terrain, distortion, sculpt, detail, 0, 256, DensityFunctions.zero());
|
||||
}
|
||||
|
||||
@Override
|
||||
public DensitySampler compileSampler(CompileContext context) {
|
||||
DensitySampler terrainSampler = terrain.compileSampler(context);
|
||||
DensitySampler distortionSampler = distortion.compileSampler(context);
|
||||
DensitySampler sculptSampler = sculpt.compileSampler(context);
|
||||
DensitySampler detailSampler = detail.compileSampler(context);
|
||||
DensitySampler undersideSampler = underside.compileSampler(context);
|
||||
return new DensitySampler() {
|
||||
@Override
|
||||
public float sampleValue(SamplerContext samplerContext, int x, int y, int z) {
|
||||
if (Math.hypot((double) x, (double) z) >= IslandShape.TERRAIN_LIMIT || y <= 0 || y >= height) {
|
||||
return -1.0F;
|
||||
}
|
||||
int sampleY = y - verticalOffset;
|
||||
float natural = terrainSampler.sampleValue(samplerContext, x, sampleY, z);
|
||||
float coast = distortionSampler.sampleValue(samplerContext, x, 0, z);
|
||||
float coarse = sculptSampler.sampleValue(samplerContext, x, sampleY, z);
|
||||
float fine = detailSampler.sampleValue(samplerContext, x, sampleY, z);
|
||||
if (verticalOffset == 0 && height == 256) {
|
||||
return IslandShape.density(x, y, z, natural, coast, coarse, fine);
|
||||
}
|
||||
return IslandShape.raisedDensity(x, y, z, natural, coast, coarse, fine,
|
||||
undersideSampler.sampleValue(samplerContext, x, sampleY, z), verticalOffset, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sampleVolume(SamplerContext context, DensityBuffer buffer, DensityVolume volume) {
|
||||
DensitySampler.sampleVolumeNaive(context, buffer, volume, this);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public DensityFunction rewriteChildren(DfRewriteRule rule) {
|
||||
return new MainIslandDensity(rule.rewrite(terrain), rule.rewrite(distortion),
|
||||
rule.rewrite(sculpt), rule.rewrite(detail), verticalOffset, height, rule.rewrite(underside));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Interval range() {
|
||||
return Interval.of(-1.0F, 1.0F);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int domainAxes() {
|
||||
return ALL_AXES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapCodec<MainIslandDensity> codec() {
|
||||
return CODEC;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package fr.koka.sanctuary.worldgen;
|
||||
|
||||
import fr.koka.sanctuary.SanctuaryMod;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.tags.BlockTags;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.levelgen.Heightmap;
|
||||
import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator;
|
||||
import net.minecraft.world.level.levelgen.NoiseGeneratorSettings;
|
||||
import net.minecraft.world.level.storage.LevelData;
|
||||
|
||||
public final class SanctuarySpawn {
|
||||
public static final ResourceKey<NoiseGeneratorSettings> SETTINGS =
|
||||
ResourceKey.create(Registries.NOISE_SETTINGS, SanctuaryMod.id("sanctuary"));
|
||||
public static final ResourceKey<NoiseGeneratorSettings> RAISED_SETTINGS =
|
||||
ResourceKey.create(Registries.NOISE_SETTINGS, SanctuaryMod.id("sanctuary_384"));
|
||||
public static final ResourceKey<NoiseGeneratorSettings> HYDROLOGY_SETTINGS =
|
||||
ResourceKey.create(Registries.NOISE_SETTINGS, SanctuaryMod.id("sanctuary_hydrology"));
|
||||
|
||||
private SanctuarySpawn() {}
|
||||
|
||||
public static boolean usesSanctuaryGenerator(ServerLevel level) {
|
||||
return level.dimension() == Level.OVERWORLD
|
||||
&& level.getChunkSource().getGenerator() instanceof NoiseBasedChunkGenerator generator
|
||||
&& (generator.generatorSettings().is(SETTINGS) || generator.generatorSettings().is(RAISED_SETTINGS)
|
||||
|| generator.generatorSettings().is(HYDROLOGY_SETTINGS));
|
||||
}
|
||||
|
||||
/** Called only during first creation; saved spawns and /setworldspawn survive subsequent loads. */
|
||||
public static void initialize(ServerLevel level) {
|
||||
if (!usesSanctuaryGenerator(level)) {
|
||||
return;
|
||||
}
|
||||
NoiseBasedChunkGenerator generator = (NoiseBasedChunkGenerator) level.getChunkSource().getGenerator();
|
||||
BlockPos fallback = null;
|
||||
// Search the finite island from its centre outwards. Sampling base terrain first avoids
|
||||
// generating/decorating full chunks for the holes that can now cross the origin.
|
||||
for (int radius = 0; radius <= IslandShape.TERRAIN_LIMIT; radius += 4) {
|
||||
for (int x = -radius; x <= radius; x += 4) {
|
||||
for (int z = -radius; z <= radius; z += 4) {
|
||||
if (Math.max(Math.abs(x), Math.abs(z)) != radius) continue;
|
||||
if (Math.hypot((double) x, (double) z) >= IslandShape.TERRAIN_LIMIT) continue;
|
||||
if (generator.getBaseHeight(x, z, Heightmap.Types.MOTION_BLOCKING_NO_LEAVES,
|
||||
level, level.getChunkSource().randomState()) <= generator.getMinY()) continue;
|
||||
// Level.getHeight returns minY for an unloaded chunk; request full terrain first.
|
||||
level.getChunk(x >> 4, z >> 4);
|
||||
int top = level.getHeight(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, x, z);
|
||||
for (int y = top; y > level.getMinY(); y--) {
|
||||
BlockPos pos = new BlockPos(x, y, z);
|
||||
if (!isSafe(level, pos, 0)) continue;
|
||||
if (fallback == null) fallback = pos;
|
||||
if (isSafe(level, pos, 1)) {
|
||||
setSpawn(level, pos, "3x3 natural ground");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (fallback != null) {
|
||||
setSpawn(level, fallback, "single-column natural ground; no flat 3x3 area found");
|
||||
return;
|
||||
}
|
||||
throw new IllegalStateException("Sanctuary found no natural safe ground within "
|
||||
+ IslandShape.TERRAIN_LIMIT + " blocks for seed " + level.getSeed()
|
||||
+ "; no artificial platform was created");
|
||||
}
|
||||
|
||||
private static void setSpawn(ServerLevel level, BlockPos pos, String area) {
|
||||
level.setRespawnData(LevelData.RespawnData.of(Level.OVERWORLD, pos, 0.0F, 0.0F));
|
||||
SanctuaryMod.LOGGER.info("Sanctuary shared spawn: {} ({}, seed {})", pos, area, level.getSeed());
|
||||
}
|
||||
|
||||
private static boolean isSafe(ServerLevel level, BlockPos pos, int radius) {
|
||||
for (int dx = -radius; dx <= radius; dx++) {
|
||||
for (int dz = -radius; dz <= radius; dz++) {
|
||||
BlockPos feet = pos.offset(dx, 0, dz);
|
||||
BlockState floor = level.getBlockState(feet.below());
|
||||
// In 26.3 the dirt tag no longer includes grass_block.
|
||||
if (!(floor.is(Blocks.GRASS_BLOCK) || floor.is(BlockTags.DIRT) || floor.is(BlockTags.BASE_STONE_OVERWORLD))
|
||||
|| !floor.isCollisionShapeFullBlock(level, feet.below())
|
||||
|| !level.getFluidState(feet).isEmpty()
|
||||
|| !level.getFluidState(feet.above()).isEmpty()
|
||||
|| !level.getBlockState(feet).getCollisionShape(level, feet).isEmpty()
|
||||
|| !level.getBlockState(feet.above()).getCollisionShape(level, feet.above()).isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package fr.koka.sanctuary.worldgen;
|
||||
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.util.RandomSource;
|
||||
import net.minecraft.world.level.WorldGenLevel;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.chunk.ChunkGenerator;
|
||||
import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator;
|
||||
import net.minecraft.world.level.levelgen.feature.Feature;
|
||||
|
||||
/** A few plants on existing suitable shores, after the forest has been decorated. */
|
||||
public final class ShoreSugarCaneFeature implements Feature {
|
||||
public static final MapCodec<ShoreSugarCaneFeature> CODEC = MapCodec.unit(ShoreSugarCaneFeature::new);
|
||||
|
||||
@Override
|
||||
public MapCodec<ShoreSugarCaneFeature> codec() {
|
||||
return CODEC;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean place(WorldGenLevel level, ChunkGenerator generator, RandomSource random, BlockPos origin) {
|
||||
if (!(generator instanceof NoiseBasedChunkGenerator noise) || !HydrologyRuntime.enabled(noise)) return false;
|
||||
var plan = HydrologyRuntime.plan(noise, level.getLevel().getChunkSource().randomState());
|
||||
var cane = Blocks.SUGAR_CANE.defaultBlockState();
|
||||
boolean placed = false;
|
||||
for (var cell : plan.cellsInChunk(origin.getX() >> 4, origin.getZ() >> 4)) {
|
||||
if (cell.hasWater() || random.nextInt(7) != 0) continue;
|
||||
BlockPos base = new BlockPos(cell.x(), cell.bedY() + 1, cell.z());
|
||||
if (!level.getBlockState(base).isAir() || !cane.canSurvive(level, base)) continue;
|
||||
int height = 1 + random.nextInt(3);
|
||||
for (int y = 0; y < height; y++) {
|
||||
BlockPos pos = base.above(y);
|
||||
if (!level.getBlockState(pos).isAir() || !cane.canSurvive(level, pos)) break;
|
||||
setBlock(level, pos, cane);
|
||||
placed = true;
|
||||
}
|
||||
}
|
||||
return placed;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,465 @@
|
||||
package fr.koka.sanctuary.worldgen;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.PriorityQueue;
|
||||
import java.util.Set;
|
||||
|
||||
/** A seed-only surface-water plan. No chunks, random generation order or Minecraft objects are retained. */
|
||||
public final class SurfaceHydrology {
|
||||
public static final int RADIUS = 192;
|
||||
public static final int MAX_CARVE = 6;
|
||||
public static final int MAX_DEPTH = 4;
|
||||
private static final int GRID = 8;
|
||||
private static final int[][] CARDINALS = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
|
||||
private static final int[][] NEIGHBORS = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}, {1, 1}, {1, -1}, {-1, 1}, {-1, -1}};
|
||||
|
||||
private SurfaceHydrology() {}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface Sampler { float sample(int x, int y, int z); }
|
||||
|
||||
public enum Kind { POND, LAKE, STREAM }
|
||||
public enum ShoreMaterial { SAND, GRAVEL, CLAY }
|
||||
public record Point(int x, int z) {}
|
||||
public record Bounds(int minX, int minZ, int maxX, int maxZ) {}
|
||||
|
||||
/** bedY is an existing solid block. carveTop includes waterY when a natural hollow is filled. */
|
||||
public record Cell(int x, int z, int waterY, int bedY, int carveTop,
|
||||
ShoreMaterial material, long featureId) {
|
||||
public boolean hasWater() { return waterY >= 0; }
|
||||
}
|
||||
|
||||
public record Feature(long id, Kind kind, int waterY, List<Point> path, int waterCells, Bounds bounds) {
|
||||
public Feature { path = List.copyOf(path); }
|
||||
}
|
||||
|
||||
public static final class Plan {
|
||||
private final Map<Long, Cell> cells;
|
||||
private final Map<Long, List<Cell>> chunks;
|
||||
private final List<Feature> features;
|
||||
private final int sampledColumns;
|
||||
private final long densitySamples;
|
||||
|
||||
private Plan(Map<Long, Cell> source, List<Feature> features, int columns, long samples) {
|
||||
List<Cell> sorted = source.values().stream()
|
||||
.sorted(Comparator.comparingInt(Cell::x).thenComparingInt(Cell::z)).toList();
|
||||
Map<Long, Cell> ordered = new LinkedHashMap<>();
|
||||
Map<Long, List<Cell>> byChunk = new HashMap<>();
|
||||
for (Cell cell : sorted) {
|
||||
ordered.put(key(cell.x(), cell.z()), cell);
|
||||
byChunk.computeIfAbsent(key(cell.x() >> 4, cell.z() >> 4), ignored -> new ArrayList<>()).add(cell);
|
||||
}
|
||||
byChunk.replaceAll((ignored, values) -> List.copyOf(values));
|
||||
cells = Collections.unmodifiableMap(ordered);
|
||||
chunks = Map.copyOf(byChunk);
|
||||
this.features = List.copyOf(features);
|
||||
sampledColumns = columns;
|
||||
densitySamples = samples;
|
||||
}
|
||||
|
||||
public Cell cellAt(int x, int z) { return cells.get(key(x, z)); }
|
||||
public Collection<Cell> cells() { return cells.values(); }
|
||||
public List<Cell> cellsInChunk(int x, int z) { return chunks.getOrDefault(key(x, z), List.of()); }
|
||||
public List<Feature> features() { return features; }
|
||||
public int sampledColumns() { return sampledColumns; }
|
||||
public long densitySamples() { return densitySamples; }
|
||||
}
|
||||
|
||||
public static Plan create(long seed, Sampler sampler) {
|
||||
return new Planner(seed, sampler).build();
|
||||
}
|
||||
|
||||
private record Column(int top, int solidBottom) {}
|
||||
private record Candidate(int x, int z, double score) {}
|
||||
private record Node(int x, int z, double cost) {}
|
||||
private static final Comparator<Node> NODE_ORDER = Comparator.comparingDouble(Node::cost)
|
||||
.thenComparingInt(Node::x).thenComparingInt(Node::z);
|
||||
|
||||
private static final class Planner {
|
||||
private final long seed;
|
||||
private final Sampler sampler;
|
||||
private final Map<Long, Column> columns = new HashMap<>();
|
||||
private final Map<Long, Integer> coarse = new HashMap<>();
|
||||
private final Map<Long, Cell> cells = new HashMap<>();
|
||||
private final List<Feature> features = new ArrayList<>();
|
||||
private long samples;
|
||||
|
||||
Planner(long seed, Sampler sampler) { this.seed = seed; this.sampler = sampler; }
|
||||
|
||||
Plan build() {
|
||||
List<Candidate> candidates = candidates();
|
||||
int attempts = 0;
|
||||
for (Candidate candidate : candidates) {
|
||||
if (features.size() >= 5 || attempts++ >= 96) break;
|
||||
Point center = new Point(candidate.x(), candidate.z());
|
||||
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;
|
||||
int target = kind == Kind.LAKE ? 150 + (int) (random(seed, center.x(), center.z()) * 100)
|
||||
: 40 + (int) (random(seed + 1, center.x(), center.z()) * 45);
|
||||
// Prefer an existing low spot; a shallow cut is the fallback on level terrain.
|
||||
for (int level : new int[]{column.top() + 1, column.top() - 1, column.top() - 2}) {
|
||||
Set<Long> footprint = growBasin(center, level, target);
|
||||
if (footprint == null) continue;
|
||||
long id = featureId(features.size());
|
||||
Map<Long, Cell> wet = waterCells(footprint, level, id);
|
||||
if (wet == null || !contained(wet)) continue;
|
||||
addFeature(id, kind, level, List.of(center), wet);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// One quiet stream connects a basin to a nearby surface refuge at the same water level.
|
||||
// Its route follows low excavation cost through the relief; it has no falling outlets.
|
||||
boolean stream = false;
|
||||
for (Feature source : List.copyOf(features)) if (addStream(source)) { stream = true; break; }
|
||||
if (!stream) addPlateauStream(candidates);
|
||||
addBanks();
|
||||
return new Plan(cells, features, columns.size(), samples);
|
||||
}
|
||||
|
||||
private List<Candidate> candidates() {
|
||||
for (int x = -RADIUS; x <= RADIUS; x += GRID) {
|
||||
for (int z = -RADIUS; z <= RADIUS; z += GRID) {
|
||||
if (inside(x, z, RADIUS)) coarse.put(key(x, z), coarseTop(x, z));
|
||||
}
|
||||
}
|
||||
List<Candidate> result = new ArrayList<>();
|
||||
for (int x = -RADIUS + GRID; x < RADIUS; x += GRID) {
|
||||
for (int z = -RADIUS + GRID; z < RADIUS; z += GRID) {
|
||||
int height = coarse.getOrDefault(key(x, z), -1);
|
||||
if (height < 64 || !inside(x, z, RADIUS - 24)) continue;
|
||||
double average = 0;
|
||||
boolean safe = true;
|
||||
for (int[] d : CARDINALS) {
|
||||
int neighbor = coarse.getOrDefault(key(x + d[0] * GRID, z + d[1] * GRID), -1);
|
||||
if (neighbor < 64 || Math.abs(neighbor - height) > 16) safe = false;
|
||||
average += neighbor * 0.25;
|
||||
}
|
||||
if (safe) result.add(new Candidate(x, z,
|
||||
(average - height) * 2.0 + random(seed + 2, x, z) * 3.0));
|
||||
}
|
||||
}
|
||||
result.sort(Comparator.comparingDouble(Candidate::score).reversed()
|
||||
.thenComparingInt(Candidate::x).thenComparingInt(Candidate::z));
|
||||
return result;
|
||||
}
|
||||
|
||||
private int coarseTop(int x, int z) {
|
||||
for (int y = 383; y >= 32; y -= 4) if (solid(x, y, z)) return y;
|
||||
return -1;
|
||||
}
|
||||
|
||||
private Column column(int x, int z) {
|
||||
long key = key(x, z);
|
||||
Column old = columns.get(key);
|
||||
if (old != null) return old;
|
||||
int top = -1;
|
||||
// Exact scan, including everything above the planned water: a cave roof cannot be
|
||||
// mistaken for a surface. Only candidates and their nearby shore need this refinement.
|
||||
for (int y = 383; y >= 24; y--) {
|
||||
if (solid(x, y, z)) { top = y; break; }
|
||||
}
|
||||
int bottom = top;
|
||||
if (top >= 0) {
|
||||
while (bottom > Math.max(16, top - 16) && solid(x, bottom - 1, z)) bottom--;
|
||||
}
|
||||
Column created = new Column(top, bottom);
|
||||
columns.put(key, created);
|
||||
return created;
|
||||
}
|
||||
|
||||
private boolean eligible(int x, int z, int level) {
|
||||
if (level < 32 || level >= 383 || !inside(x, z, RADIUS - 2)) return false;
|
||||
Cell old = cells.get(key(x, z));
|
||||
if (old != null && old.hasWater() && old.waterY() != level) return false;
|
||||
Column column = column(x, z);
|
||||
return column.top() >= level - 3 && column.top() < level + MAX_CARVE
|
||||
&& column.solidBottom() <= Math.min(column.top(), level - 1) - 2;
|
||||
}
|
||||
|
||||
private Set<Long> growBasin(Point center, int level, int target) {
|
||||
if (!eligible(center.x(), center.z(), level)) return null;
|
||||
Set<Long> wet = new HashSet<>();
|
||||
Map<Long, Double> costs = new HashMap<>();
|
||||
PriorityQueue<Node> queue = new PriorityQueue<>(NODE_ORDER);
|
||||
queue.add(new Node(center.x(), center.z(), 0));
|
||||
costs.put(key(center.x(), center.z()), 0.0);
|
||||
while (!queue.isEmpty() && wet.size() < target) {
|
||||
Node current = queue.remove();
|
||||
if (current.cost() > costs.getOrDefault(key(current.x(), current.z()), Double.POSITIVE_INFINITY)) continue;
|
||||
if (!eligible(current.x(), current.z(), level)) continue;
|
||||
wet.add(key(current.x(), current.z()));
|
||||
for (int[] d : NEIGHBORS) {
|
||||
int x = current.x() + d[0], z = current.z() + d[1];
|
||||
if (Math.hypot(x - center.x(), z - center.z()) > 24 || wet.contains(key(x, z))) continue;
|
||||
if (!eligible(x, z, level)) continue;
|
||||
double step = Math.hypot(d[0], d[1]);
|
||||
double cost = current.cost() + step * (1 + Math.max(0, column(x, z).top() - level) * 0.8
|
||||
+ terrainNoise(x, z) * 2.0);
|
||||
if (cost >= costs.getOrDefault(key(x, z), Double.POSITIVE_INFINITY)) continue;
|
||||
costs.put(key(x, z), cost);
|
||||
queue.add(new Node(x, z, cost));
|
||||
}
|
||||
}
|
||||
wet = connectedTo(wet, key(center.x(), center.z()));
|
||||
if (wet.size() < Math.min(target, 30)) return null;
|
||||
// Filling a hollow requires every lower connected outlet to be included. Abort rather
|
||||
// than place a dam if this natural basin runs to a cliff, cave or an oversized catchment.
|
||||
List<Long> work = new ArrayList<>(wet);
|
||||
for (int i = 0; i < work.size(); i++) {
|
||||
Point point = point(work.get(i));
|
||||
for (int[] d : CARDINALS) {
|
||||
int x = point.x() + d[0], z = point.z() + d[1];
|
||||
long neighbor = key(x, z);
|
||||
if (wet.contains(neighbor) || column(x, z).top() >= level) continue;
|
||||
if (wet.size() >= 420 || Math.hypot(x - center.x(), z - center.z()) > 28
|
||||
|| !eligible(x, z, level)) return null;
|
||||
wet.add(neighbor);
|
||||
work.add(neighbor);
|
||||
}
|
||||
}
|
||||
return wet;
|
||||
}
|
||||
|
||||
private Map<Long, Cell> waterCells(Set<Long> footprint, int level, long id) {
|
||||
Map<Long, Cell> result = new HashMap<>();
|
||||
for (long key : footprint) {
|
||||
Point p = point(key);
|
||||
if (!eligible(p.x(), p.z(), level)) return null;
|
||||
boolean edge = false;
|
||||
for (int[] d : CARDINALS) edge |= !footprint.contains(key(p.x() + d[0], p.z() + d[1]));
|
||||
Column column = column(p.x(), p.z());
|
||||
int depth = Math.max(level - column.top() + 1, edge ? 1 : 2 + (terrainNoise(p.x(), p.z()) > 0.55 ? 1 : 0));
|
||||
depth = Math.min(depth, MAX_CARVE - Math.max(0, column.top() - level));
|
||||
if (depth < 1 || depth > MAX_DEPTH) return null;
|
||||
int bed = level - depth;
|
||||
if (column.top() < bed || column.solidBottom() > bed - 2) return null;
|
||||
ShoreMaterial material = edge ? ShoreMaterial.SAND
|
||||
: terrainNoise(p.x() + 73, p.z() - 19) > 0.65 ? ShoreMaterial.CLAY : ShoreMaterial.GRAVEL;
|
||||
result.put(key, new Cell(p.x(), p.z(), level, bed, Math.max(level, column.top()), material, id));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean contained(Map<Long, Cell> wet) {
|
||||
for (Cell cell : wet.values()) {
|
||||
for (int y = cell.bedY() - 2; y <= cell.bedY(); y++) {
|
||||
if (!solid(cell.x(), y, cell.z())) return false;
|
||||
}
|
||||
for (int[] d : CARDINALS) {
|
||||
int x = cell.x() + d[0], z = cell.z() + d[1];
|
||||
Cell neighbor = wet.get(key(x, z));
|
||||
if (neighbor == null) neighbor = cells.get(key(x, z));
|
||||
for (int y = cell.bedY() + 1; y <= cell.waterY(); y++) {
|
||||
if (neighbor != null && neighbor.hasWater()) {
|
||||
if (neighbor.waterY() != cell.waterY()) return false;
|
||||
if (y > neighbor.bedY()) continue;
|
||||
}
|
||||
if (!solid(x, y, z)) return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void addFeature(long id, Kind kind, int level, List<Point> path, Map<Long, Cell> wet) {
|
||||
int minX = Integer.MAX_VALUE, minZ = Integer.MAX_VALUE, maxX = Integer.MIN_VALUE, maxZ = Integer.MIN_VALUE;
|
||||
int count = 0;
|
||||
for (Cell cell : wet.values()) {
|
||||
Cell old = cells.get(key(cell.x(), cell.z()));
|
||||
if (old == null || !old.hasWater()) { cells.put(key(cell.x(), cell.z()), cell); count++; }
|
||||
minX = Math.min(minX, cell.x()); minZ = Math.min(minZ, cell.z());
|
||||
maxX = Math.max(maxX, cell.x()); maxZ = Math.max(maxZ, cell.z());
|
||||
}
|
||||
features.add(new Feature(id, kind, level, path, count, new Bounds(minX, minZ, maxX, maxZ)));
|
||||
}
|
||||
|
||||
private boolean addStream(Feature source) {
|
||||
Point start = source.path().getFirst();
|
||||
int level = source.waterY();
|
||||
Map<Long, Double> costs = new HashMap<>();
|
||||
Map<Long, Long> parent = new HashMap<>();
|
||||
PriorityQueue<Node> queue = new PriorityQueue<>(NODE_ORDER);
|
||||
List<Node> refuges = new ArrayList<>();
|
||||
long startKey = key(start.x(), start.z());
|
||||
costs.put(startKey, 0.0);
|
||||
queue.add(new Node(start.x(), start.z(), 0));
|
||||
int visited = 0;
|
||||
while (!queue.isEmpty() && visited++ < 1200) {
|
||||
Node node = queue.remove();
|
||||
long at = key(node.x(), node.z());
|
||||
if (node.cost() > costs.getOrDefault(at, Double.POSITIVE_INFINITY)) continue;
|
||||
double distance = Math.hypot(node.x() - start.x(), node.z() - start.z());
|
||||
if (distance >= 44 && distance <= 76 && column(node.x(), node.z()).top() <= level + 3) refuges.add(node);
|
||||
for (int[] d : NEIGHBORS) {
|
||||
int x = node.x() + d[0] * 4, z = node.z() + d[1] * 4;
|
||||
if (Math.hypot(x - start.x(), z - start.z()) > 88 || !eligible(x, z, level)) continue;
|
||||
long next = key(x, z);
|
||||
double cost = node.cost() + Math.hypot(d[0], d[1])
|
||||
* (4 + Math.abs(column(x, z).top() - level) * 1.4 + terrainNoise(x, z) * 4);
|
||||
if (cost >= costs.getOrDefault(next, Double.POSITIVE_INFINITY)) continue;
|
||||
costs.put(next, cost); parent.put(next, at); queue.add(new Node(x, z, cost));
|
||||
}
|
||||
}
|
||||
refuges.sort(Comparator.comparingDouble((Node n) -> n.cost() / Math.hypot(n.x() - start.x(), n.z() - start.z()))
|
||||
.thenComparingInt(Node::x).thenComparingInt(Node::z));
|
||||
int attempts = 0;
|
||||
for (Node refuge : refuges) {
|
||||
if (attempts++ >= 24) break;
|
||||
List<Point> path = new ArrayList<>();
|
||||
long cursor = key(refuge.x(), refuge.z());
|
||||
while (cursor != startKey) { path.add(point(cursor)); cursor = parent.get(cursor); }
|
||||
path.add(start); Collections.reverse(path);
|
||||
Set<Long> corridor = new HashSet<>();
|
||||
for (int i = 1; i < path.size(); i++) {
|
||||
Point a = path.get(i - 1), b = path.get(i);
|
||||
int length = Math.max(Math.abs(b.x() - a.x()), Math.abs(b.z() - a.z()));
|
||||
for (int step = 0; step <= length; step++) {
|
||||
int x = a.x() + (b.x() - a.x()) * step / length;
|
||||
int z = a.z() + (b.z() - a.z()) * step / length;
|
||||
int width = terrainNoise(x + 17, z + 29) > 0.65 ? 2 : 1;
|
||||
for (int dx = -width; dx <= width; dx++) for (int dz = -width; dz <= width; dz++) {
|
||||
if (dx * dx + dz * dz <= width * width + 1) corridor.add(key(x + dx, z + dz));
|
||||
}
|
||||
}
|
||||
}
|
||||
Set<Long> refugePool = growBasin(new Point(refuge.x(), refuge.z()), level, 36);
|
||||
if (refugePool == null) continue;
|
||||
corridor.addAll(refugePool);
|
||||
if (!closeStreamOutlets(corridor, level)) continue;
|
||||
long id = featureId(features.size());
|
||||
Map<Long, Cell> wet = waterCells(corridor, level, id);
|
||||
if (wet == null || !contained(wet)) continue;
|
||||
addFeature(id, Kind.STREAM, level, path, wet);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Low lake floors may be trapped below every nearby plateau. Try a small natural surface
|
||||
* refuge as a source too, retaining it only if a fully contained stream can actually leave it. */
|
||||
private boolean addPlateauStream(List<Candidate> candidates) {
|
||||
List<Candidate> plateaus = candidates.stream().filter(candidate -> flatness(candidate.x(), candidate.z()) <= 6)
|
||||
.sorted(Comparator.comparingDouble((Candidate c) -> flatness(c.x(), c.z())
|
||||
- random(seed + 91, c.x(), c.z()) * 2).thenComparingInt(Candidate::x).thenComparingInt(Candidate::z)).toList();
|
||||
int attempts = 0;
|
||||
for (Candidate candidate : plateaus) {
|
||||
if (attempts++ >= 20) break;
|
||||
Point center = new Point(candidate.x(), candidate.z());
|
||||
if (nearWater(center.x(), center.z(), 24)) continue;
|
||||
Column column = column(center.x(), center.z());
|
||||
if (column.top() < 64 || column.top() - column.solidBottom() < 7) continue;
|
||||
for (int level : new int[]{column.top() - 1, column.top() - 2}) {
|
||||
Set<Long> footprint = growBasin(center, level, 40);
|
||||
if (footprint == null) continue;
|
||||
long id = featureId(features.size());
|
||||
Map<Long, Cell> wet = waterCells(footprint, level, id);
|
||||
if (wet == null || !contained(wet)) continue;
|
||||
addFeature(id, Kind.POND, level, List.of(center), wet);
|
||||
if (addStream(features.getLast())) return true;
|
||||
features.removeLast();
|
||||
cells.values().removeIf(cell -> cell.featureId() == id);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private int flatness(int x, int z) {
|
||||
int low = coarse.getOrDefault(key(x, z), -1000), high = low;
|
||||
for (int[] d : CARDINALS) {
|
||||
int value = coarse.getOrDefault(key(x + d[0] * GRID, z + d[1] * GRID), -1000);
|
||||
low = Math.min(low, value); high = Math.max(high, value);
|
||||
}
|
||||
return high - low;
|
||||
}
|
||||
|
||||
/** Include small connected low spots beside a channel; reject large/deep/open outlets.
|
||||
* This adds water to a verified natural hollow, never rock to close a leak. */
|
||||
private boolean closeStreamOutlets(Set<Long> corridor, int level) {
|
||||
int maximum = Math.min(900, corridor.size() + 180);
|
||||
List<Long> work = new ArrayList<>(corridor);
|
||||
for (int i = 0; i < work.size(); i++) {
|
||||
Point point = point(work.get(i));
|
||||
for (int[] d : CARDINALS) {
|
||||
int x = point.x() + d[0], z = point.z() + d[1];
|
||||
long next = key(x, z);
|
||||
Cell old = cells.get(next);
|
||||
if (corridor.contains(next) || old != null && old.hasWater() && old.waterY() == level
|
||||
|| column(x, z).top() >= level) continue;
|
||||
if (corridor.size() >= maximum || !eligible(x, z, level)) return false;
|
||||
corridor.add(next); work.add(next);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static Set<Long> connectedTo(Set<Long> footprint, long start) {
|
||||
Set<Long> connected = new HashSet<>();
|
||||
List<Long> work = new ArrayList<>();
|
||||
connected.add(start); work.add(start);
|
||||
for (int i = 0; i < work.size(); i++) {
|
||||
Point point = point(work.get(i));
|
||||
for (int[] d : CARDINALS) {
|
||||
long next = key(point.x() + d[0], point.z() + d[1]);
|
||||
if (footprint.contains(next) && connected.add(next)) work.add(next);
|
||||
}
|
||||
}
|
||||
return connected;
|
||||
}
|
||||
|
||||
private void addBanks() {
|
||||
List<Cell> water = cells.values().stream().filter(Cell::hasWater)
|
||||
.sorted(Comparator.comparingInt(Cell::x).thenComparingInt(Cell::z)).toList();
|
||||
for (Cell cell : water) {
|
||||
for (int[] d : CARDINALS) {
|
||||
int x = cell.x() + d[0], z = cell.z() + d[1];
|
||||
if (cells.containsKey(key(x, z))) continue;
|
||||
Column column = column(x, z);
|
||||
if (column.top() < cell.waterY() || column.top() > cell.waterY() + 2
|
||||
|| column.solidBottom() > column.top() - 2) continue;
|
||||
cells.put(key(x, z), new Cell(x, z, -1, column.top(), column.top(),
|
||||
ShoreMaterial.SAND, cell.featureId()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean nearWater(int x, int z, int distance) {
|
||||
for (Feature feature : features) {
|
||||
Point point = feature.path().getFirst();
|
||||
if (Math.hypot(x - point.x(), z - point.z()) < distance) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean solid(int x, int y, int z) { samples++; return sampler.sample(x, y, z) > 0; }
|
||||
private long featureId(int index) { return mix(seed ^ (0x51A7E7L + index * 0x9E3779B97F4A7C15L)); }
|
||||
|
||||
private double terrainNoise(int x, int z) {
|
||||
int gx = Math.floorDiv(x, 8), gz = Math.floorDiv(z, 8);
|
||||
double tx = smooth(Math.floorMod(x, 8) / 8.0), tz = smooth(Math.floorMod(z, 8) / 8.0);
|
||||
double a = random(seed + 31, gx, gz) * (1 - tx) + random(seed + 31, gx + 1, gz) * tx;
|
||||
double b = random(seed + 31, gx, gz + 1) * (1 - tx) + random(seed + 31, gx + 1, gz + 1) * tx;
|
||||
return a * (1 - tz) + b * tz;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean inside(int x, int z, int radius) { return Math.hypot((double) x, z) < radius; }
|
||||
private static long key(int x, int z) { return ((long) x << 32) | (z & 0xffffffffL); }
|
||||
private static Point point(long key) { return new Point((int) (key >> 32), (int) key); }
|
||||
private static double smooth(double t) { return t * t * (3 - 2 * t); }
|
||||
private static double random(long seed, int x, int z) { return (mix(seed ^ key(x, z)) >>> 11) * 0x1.0p-53; }
|
||||
private static long mix(long value) {
|
||||
value = (value ^ (value >>> 30)) * 0xbf58476d1ce4e5b9L;
|
||||
value = (value ^ (value >>> 27)) * 0x94d049bb133111ebL;
|
||||
return value ^ (value >>> 31);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"generator.sanctuary.sanctuary": "Sanctuary",
|
||||
"biome.sanctuary.starter_forest": "Sanctuary Forest",
|
||||
"biome.sanctuary.starter_forest_hydrology": "Sanctuary Forest and Shores"
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"generator.sanctuary.sanctuary": "Sanctuary",
|
||||
"biome.sanctuary.starter_forest": "Forêt de Sanctuary",
|
||||
"biome.sanctuary.starter_forest_hydrology": "Forêt et rives de Sanctuary"
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"replace": false,
|
||||
"values": [
|
||||
"sanctuary:sanctuary"
|
||||
]
|
||||
}
|
||||
@@ -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": 384,
|
||||
"infiniburn": "#minecraft:infiniburn_overworld",
|
||||
"logical_height": 384,
|
||||
"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"
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
{
|
||||
"attributes": {
|
||||
"minecraft:audio/background_music": {
|
||||
"default": {
|
||||
"max_delay": 24000,
|
||||
"min_delay": 12000,
|
||||
"sound": "minecraft:music.overworld.forest"
|
||||
}
|
||||
},
|
||||
"minecraft:gameplay/natural_mob_spawns": {
|
||||
"argument": {
|
||||
"spawn_costs": {},
|
||||
"spawns_by_category": {
|
||||
"ambient": [
|
||||
{
|
||||
"type": "minecraft:bat",
|
||||
"count": 8,
|
||||
"weight": 10
|
||||
}
|
||||
],
|
||||
"creature": [
|
||||
{
|
||||
"type": "minecraft:sheep",
|
||||
"count": 4,
|
||||
"weight": 12
|
||||
},
|
||||
{
|
||||
"type": "minecraft:pig",
|
||||
"count": 4,
|
||||
"weight": 10
|
||||
},
|
||||
{
|
||||
"type": "minecraft:chicken",
|
||||
"count": 4,
|
||||
"weight": 10
|
||||
},
|
||||
{
|
||||
"type": "minecraft:cow",
|
||||
"count": 4,
|
||||
"weight": 8
|
||||
},
|
||||
{
|
||||
"type": "minecraft:wolf",
|
||||
"count": 4,
|
||||
"weight": 5
|
||||
}
|
||||
],
|
||||
"monster": [
|
||||
{
|
||||
"type": "minecraft:spider",
|
||||
"count": 4,
|
||||
"weight": 100
|
||||
},
|
||||
{
|
||||
"type": "minecraft:zombie",
|
||||
"count": 4,
|
||||
"weight": 95
|
||||
},
|
||||
{
|
||||
"type": "minecraft:zombie_villager",
|
||||
"count": 1,
|
||||
"weight": 5
|
||||
},
|
||||
{
|
||||
"type": "minecraft:skeleton",
|
||||
"count": 4,
|
||||
"weight": 100
|
||||
},
|
||||
{
|
||||
"type": "minecraft:creeper",
|
||||
"count": 4,
|
||||
"weight": 100
|
||||
},
|
||||
{
|
||||
"type": "minecraft:slime",
|
||||
"count": 4,
|
||||
"weight": 100
|
||||
},
|
||||
{
|
||||
"type": "minecraft:enderman",
|
||||
"count": {
|
||||
"type": "minecraft:uniform",
|
||||
"max_inclusive": 4,
|
||||
"min_inclusive": 1
|
||||
},
|
||||
"weight": 10
|
||||
},
|
||||
{
|
||||
"type": "minecraft:witch",
|
||||
"count": 1,
|
||||
"weight": 5
|
||||
}
|
||||
],
|
||||
"underground_water_creature": [
|
||||
{
|
||||
"type": "minecraft:glow_squid",
|
||||
"count": {
|
||||
"type": "minecraft:uniform",
|
||||
"max_inclusive": 6,
|
||||
"min_inclusive": 4
|
||||
},
|
||||
"weight": 10
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"modifier": "overlay"
|
||||
},
|
||||
"minecraft:visual/sky_color": "#79a6ff"
|
||||
},
|
||||
"carvers": [],
|
||||
"downfall": 0.8,
|
||||
"effects": {
|
||||
"water_color": "#3f76e4"
|
||||
},
|
||||
"features": [
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[
|
||||
"minecraft:ore_dirt",
|
||||
"minecraft:ore_gravel",
|
||||
"minecraft:ore_granite_upper",
|
||||
"minecraft:ore_granite_lower",
|
||||
"minecraft:ore_diorite_upper",
|
||||
"minecraft:ore_diorite_lower",
|
||||
"minecraft:ore_andesite_upper",
|
||||
"minecraft:ore_andesite_lower",
|
||||
"minecraft:ore_tuff",
|
||||
"minecraft:ore_coal_upper",
|
||||
"minecraft:ore_coal_lower",
|
||||
"minecraft:ore_iron_upper",
|
||||
"minecraft:ore_iron_middle",
|
||||
"minecraft:ore_iron_small",
|
||||
"minecraft:ore_gold",
|
||||
"minecraft:ore_gold_lower",
|
||||
"minecraft:ore_redstone",
|
||||
"minecraft:ore_redstone_lower",
|
||||
"minecraft:ore_diamond",
|
||||
"minecraft:ore_diamond_medium",
|
||||
"minecraft:ore_diamond_large",
|
||||
"minecraft:ore_diamond_buried",
|
||||
"minecraft:ore_lapis",
|
||||
"minecraft:ore_lapis_buried",
|
||||
"minecraft:ore_copper",
|
||||
"minecraft:underwater_magma",
|
||||
"minecraft:disk_sand",
|
||||
"minecraft:disk_clay",
|
||||
"minecraft:disk_gravel"
|
||||
],
|
||||
[],
|
||||
[],
|
||||
[
|
||||
"minecraft:glow_lichen",
|
||||
"minecraft:forest_flowers",
|
||||
"minecraft:trees_birch_and_oak_leaf_litter",
|
||||
"minecraft:patch_bush",
|
||||
"minecraft:flower_default",
|
||||
"minecraft:patch_grass_forest",
|
||||
"minecraft:brown_mushroom_normal",
|
||||
"minecraft:red_mushroom_normal",
|
||||
"minecraft:patch_pumpkin",
|
||||
"minecraft:patch_sugar_cane",
|
||||
"minecraft:patch_firefly_bush_near_water"
|
||||
],
|
||||
[
|
||||
"minecraft:freeze_top_layer"
|
||||
]
|
||||
],
|
||||
"has_precipitation": true,
|
||||
"temperature": 0.7
|
||||
}
|
||||
+171
@@ -0,0 +1,171 @@
|
||||
{
|
||||
"attributes": {
|
||||
"minecraft:audio/background_music": {
|
||||
"default": {
|
||||
"max_delay": 24000,
|
||||
"min_delay": 12000,
|
||||
"sound": "minecraft:music.overworld.forest"
|
||||
}
|
||||
},
|
||||
"minecraft:gameplay/natural_mob_spawns": {
|
||||
"argument": {
|
||||
"spawn_costs": {},
|
||||
"spawns_by_category": {
|
||||
"ambient": [
|
||||
{
|
||||
"type": "minecraft:bat",
|
||||
"count": 8,
|
||||
"weight": 10
|
||||
}
|
||||
],
|
||||
"creature": [
|
||||
{
|
||||
"type": "minecraft:sheep",
|
||||
"count": 4,
|
||||
"weight": 12
|
||||
},
|
||||
{
|
||||
"type": "minecraft:pig",
|
||||
"count": 4,
|
||||
"weight": 10
|
||||
},
|
||||
{
|
||||
"type": "minecraft:chicken",
|
||||
"count": 4,
|
||||
"weight": 10
|
||||
},
|
||||
{
|
||||
"type": "minecraft:cow",
|
||||
"count": 4,
|
||||
"weight": 8
|
||||
},
|
||||
{
|
||||
"type": "minecraft:wolf",
|
||||
"count": 4,
|
||||
"weight": 5
|
||||
}
|
||||
],
|
||||
"monster": [
|
||||
{
|
||||
"type": "minecraft:spider",
|
||||
"count": 4,
|
||||
"weight": 100
|
||||
},
|
||||
{
|
||||
"type": "minecraft:zombie",
|
||||
"count": 4,
|
||||
"weight": 95
|
||||
},
|
||||
{
|
||||
"type": "minecraft:zombie_villager",
|
||||
"count": 1,
|
||||
"weight": 5
|
||||
},
|
||||
{
|
||||
"type": "minecraft:skeleton",
|
||||
"count": 4,
|
||||
"weight": 100
|
||||
},
|
||||
{
|
||||
"type": "minecraft:creeper",
|
||||
"count": 4,
|
||||
"weight": 100
|
||||
},
|
||||
{
|
||||
"type": "minecraft:slime",
|
||||
"count": 4,
|
||||
"weight": 100
|
||||
},
|
||||
{
|
||||
"type": "minecraft:enderman",
|
||||
"count": {
|
||||
"type": "minecraft:uniform",
|
||||
"max_inclusive": 4,
|
||||
"min_inclusive": 1
|
||||
},
|
||||
"weight": 10
|
||||
},
|
||||
{
|
||||
"type": "minecraft:witch",
|
||||
"count": 1,
|
||||
"weight": 5
|
||||
}
|
||||
],
|
||||
"underground_water_creature": [
|
||||
{
|
||||
"type": "minecraft:glow_squid",
|
||||
"count": {
|
||||
"type": "minecraft:uniform",
|
||||
"max_inclusive": 6,
|
||||
"min_inclusive": 4
|
||||
},
|
||||
"weight": 10
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"modifier": "overlay"
|
||||
},
|
||||
"minecraft:visual/sky_color": "#79a6ff"
|
||||
},
|
||||
"carvers": [],
|
||||
"downfall": 0.8,
|
||||
"effects": {
|
||||
"water_color": "#3f76e4"
|
||||
},
|
||||
"features": [
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[
|
||||
"minecraft:ore_dirt",
|
||||
"minecraft:ore_granite_upper",
|
||||
"minecraft:ore_granite_lower",
|
||||
"minecraft:ore_diorite_upper",
|
||||
"minecraft:ore_diorite_lower",
|
||||
"minecraft:ore_andesite_upper",
|
||||
"minecraft:ore_andesite_lower",
|
||||
"minecraft:ore_tuff",
|
||||
"minecraft:ore_coal_upper",
|
||||
"minecraft:ore_coal_lower",
|
||||
"minecraft:ore_iron_upper",
|
||||
"minecraft:ore_iron_middle",
|
||||
"minecraft:ore_iron_small",
|
||||
"minecraft:ore_gold",
|
||||
"minecraft:ore_gold_lower",
|
||||
"minecraft:ore_redstone",
|
||||
"minecraft:ore_redstone_lower",
|
||||
"minecraft:ore_diamond",
|
||||
"minecraft:ore_diamond_medium",
|
||||
"minecraft:ore_diamond_large",
|
||||
"minecraft:ore_diamond_buried",
|
||||
"minecraft:ore_lapis",
|
||||
"minecraft:ore_lapis_buried",
|
||||
"minecraft:ore_copper"
|
||||
],
|
||||
[],
|
||||
[],
|
||||
[
|
||||
"minecraft:glow_lichen",
|
||||
"minecraft:forest_flowers",
|
||||
"minecraft:trees_birch_and_oak_leaf_litter",
|
||||
"minecraft:patch_bush",
|
||||
"minecraft:flower_default",
|
||||
"minecraft:patch_grass_forest",
|
||||
"minecraft:brown_mushroom_normal",
|
||||
"minecraft:red_mushroom_normal",
|
||||
"minecraft:patch_pumpkin",
|
||||
"minecraft:patch_sugar_cane",
|
||||
"minecraft:patch_firefly_bush_near_water",
|
||||
"sanctuary:shore_sugar_cane"
|
||||
],
|
||||
[
|
||||
"minecraft:freeze_top_layer"
|
||||
]
|
||||
],
|
||||
"has_precipitation": true,
|
||||
"temperature": 0.7
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"type": "minecraft:old_blended_noise",
|
||||
"smear_scale_multiplier": 4.0,
|
||||
"xz_factor": 80.0,
|
||||
"xz_scale": 0.25,
|
||||
"y_factor": 160.0,
|
||||
"y_scale": 0.25
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"type": "sanctuary:main_island",
|
||||
"terrain": {
|
||||
"type": "minecraft:squeeze",
|
||||
"input": {
|
||||
"type": "minecraft:interpolated",
|
||||
"cell_size_xz": 8,
|
||||
"cell_size_y": 4,
|
||||
"input": {
|
||||
"type": "minecraft:mul",
|
||||
"left": 0.64,
|
||||
"right": "sanctuary:floating_archipelago"
|
||||
}
|
||||
}
|
||||
},
|
||||
"distortion": {
|
||||
"type": "minecraft:noise",
|
||||
"noise": "sanctuary:island_edge",
|
||||
"xz_scale": 1.0,
|
||||
"y_scale": 0.0
|
||||
},
|
||||
"sculpt": {
|
||||
"type": "minecraft:noise",
|
||||
"noise": "sanctuary:island_sculpt",
|
||||
"xz_scale": 1.0,
|
||||
"y_scale": 1.0
|
||||
},
|
||||
"detail": {
|
||||
"type": "minecraft:noise",
|
||||
"noise": "sanctuary:island_detail",
|
||||
"xz_scale": 1.0,
|
||||
"y_scale": 1.0
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"type": "sanctuary:main_island",
|
||||
"terrain": {
|
||||
"type": "minecraft:squeeze",
|
||||
"input": {
|
||||
"type": "minecraft:interpolated",
|
||||
"cell_size_xz": 8,
|
||||
"cell_size_y": 4,
|
||||
"input": {
|
||||
"type": "minecraft:mul",
|
||||
"left": 0.64,
|
||||
"right": "sanctuary:floating_archipelago_384"
|
||||
}
|
||||
}
|
||||
},
|
||||
"distortion": {
|
||||
"type": "minecraft:noise",
|
||||
"noise": "sanctuary:island_edge",
|
||||
"xz_scale": 1.0,
|
||||
"y_scale": 0.0
|
||||
},
|
||||
"sculpt": {
|
||||
"type": "minecraft:noise",
|
||||
"noise": "sanctuary:island_sculpt",
|
||||
"xz_scale": 1.0,
|
||||
"y_scale": 1.0
|
||||
},
|
||||
"detail": {
|
||||
"type": "minecraft:noise",
|
||||
"noise": "sanctuary:island_detail",
|
||||
"xz_scale": 1.0,
|
||||
"y_scale": 1.0
|
||||
},
|
||||
"vertical_offset": 64,
|
||||
"height": 384,
|
||||
"underside": {
|
||||
"type": "minecraft:noise",
|
||||
"noise": "sanctuary:island_underside",
|
||||
"xz_scale": 1.0,
|
||||
"y_scale": 1.0
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"type": "minecraft:lerp",
|
||||
"alpha": {
|
||||
"type": "minecraft:gradient",
|
||||
"axis": "y",
|
||||
"from_coordinate": 4,
|
||||
"to_coordinate": 32,
|
||||
"from_value": 0.0,
|
||||
"to_value": 1.0
|
||||
},
|
||||
"first": -0.234375,
|
||||
"second": {
|
||||
"type": "minecraft:lerp",
|
||||
"alpha": {
|
||||
"type": "minecraft:gradient",
|
||||
"axis": "y",
|
||||
"from_coordinate": 184,
|
||||
"to_coordinate": 440,
|
||||
"from_value": 1.0,
|
||||
"to_value": 0.0
|
||||
},
|
||||
"first": -23.4375,
|
||||
"second": "sanctuary:base_3d_noise"
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"type": "minecraft:lerp",
|
||||
"alpha": {
|
||||
"type": "minecraft:gradient",
|
||||
"axis": "y",
|
||||
"from_coordinate": 184,
|
||||
"to_coordinate": 440,
|
||||
"from_value": 1.0,
|
||||
"to_value": 0.0
|
||||
},
|
||||
"first": -23.4375,
|
||||
"second": "sanctuary:base_3d_noise"
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"type": "minecraft:sequence",
|
||||
"sequence": [
|
||||
{
|
||||
"type": "minecraft:condition",
|
||||
"if_true": "minecraft:on_floor",
|
||||
"then_run": {
|
||||
"type": "minecraft:block",
|
||||
"result_state": "minecraft:grass_block"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "minecraft:condition",
|
||||
"if_true": "minecraft:under_floor",
|
||||
"then_run": {
|
||||
"type": "minecraft:block",
|
||||
"result_state": "minecraft:dirt"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"base_octave": -3,
|
||||
"octave_count": 2,
|
||||
"amplitude_modifiers": [1.0, 0.5]
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"base_octave": -7,
|
||||
"octave_count": 3,
|
||||
"amplitude_modifiers": [
|
||||
1.0,
|
||||
0.5,
|
||||
0.25
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"base_octave": -5,
|
||||
"octave_count": 3,
|
||||
"amplitude_modifiers": [1.0, 0.5, 0.25]
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"base_octave": -6,
|
||||
"octave_count": 3,
|
||||
"amplitude_modifiers": [
|
||||
1.0,
|
||||
0.5,
|
||||
0.25
|
||||
]
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"default_block": "minecraft:stone",
|
||||
"default_fluid": "minecraft:air",
|
||||
"disable_mob_generation": false,
|
||||
"legacy_random_source": true,
|
||||
"material_rule": "sanctuary:starter_island",
|
||||
"noise": {
|
||||
"height": 256,
|
||||
"min_y": 0
|
||||
},
|
||||
"noise_router": {
|
||||
"chunk_surface_level": 0.0,
|
||||
"continents": 0.0,
|
||||
"depth": 0.0,
|
||||
"erosion": 0.0,
|
||||
"final_density": "sanctuary:final_density",
|
||||
"ridges": 0.0,
|
||||
"temperature": 0.0,
|
||||
"vegetation": 0.0
|
||||
},
|
||||
"sea_level": -64,
|
||||
"spawn_target": []
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"default_block": "minecraft:stone",
|
||||
"default_fluid": "minecraft:air",
|
||||
"disable_mob_generation": false,
|
||||
"legacy_random_source": true,
|
||||
"material_rule": "sanctuary:starter_island",
|
||||
"noise": {
|
||||
"height": 384,
|
||||
"min_y": 0
|
||||
},
|
||||
"noise_router": {
|
||||
"chunk_surface_level": 0.0,
|
||||
"continents": 0.0,
|
||||
"depth": 0.0,
|
||||
"erosion": 0.0,
|
||||
"final_density": "sanctuary:final_density_384",
|
||||
"ridges": 0.0,
|
||||
"temperature": 0.0,
|
||||
"vegetation": 0.0
|
||||
},
|
||||
"sea_level": -64,
|
||||
"spawn_target": []
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"default_block": "minecraft:stone",
|
||||
"default_fluid": "minecraft:air",
|
||||
"disable_mob_generation": false,
|
||||
"legacy_random_source": true,
|
||||
"material_rule": "sanctuary:starter_island",
|
||||
"noise": {
|
||||
"height": 384,
|
||||
"min_y": 0
|
||||
},
|
||||
"noise_router": {
|
||||
"chunk_surface_level": 0.0,
|
||||
"continents": 0.0,
|
||||
"depth": 0.0,
|
||||
"erosion": 0.0,
|
||||
"final_density": "sanctuary:final_density_384",
|
||||
"ridges": 0.0,
|
||||
"temperature": 0.0,
|
||||
"vegetation": 0.0
|
||||
},
|
||||
"sea_level": -64,
|
||||
"spawn_target": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"feature": {
|
||||
"type": "sanctuary:shore_sugar_cane"
|
||||
},
|
||||
"placement": []
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"dimensions": {
|
||||
"minecraft:overworld": {
|
||||
"type": "sanctuary:sanctuary_384",
|
||||
"generator": {
|
||||
"type": "minecraft:noise",
|
||||
"biome_source": {
|
||||
"type": "minecraft:fixed",
|
||||
"biome": "sanctuary:starter_forest_hydrology"
|
||||
},
|
||||
"settings": "sanctuary:sanctuary_hydrology"
|
||||
}
|
||||
},
|
||||
"minecraft:the_end": {
|
||||
"type": "minecraft:the_end",
|
||||
"generator": {
|
||||
"type": "minecraft:noise",
|
||||
"biome_source": {
|
||||
"type": "minecraft:the_end"
|
||||
},
|
||||
"settings": "minecraft:end"
|
||||
}
|
||||
},
|
||||
"minecraft:the_nether": {
|
||||
"type": "minecraft:the_nether",
|
||||
"generator": {
|
||||
"type": "minecraft:noise",
|
||||
"biome_source": {
|
||||
"type": "minecraft:multi_noise",
|
||||
"preset": "minecraft:nether"
|
||||
},
|
||||
"settings": "minecraft:nether"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "sanctuary",
|
||||
"version": "${version}",
|
||||
"name": "Sanctuary",
|
||||
"description": "A floating world that grows through the actions of its players.",
|
||||
"authors": [
|
||||
"Koka"
|
||||
],
|
||||
"contact": {
|
||||
"sources": "https://git.botsu.net/koka/sanctuary-beta",
|
||||
"issues": "https://git.botsu.net/koka/sanctuary-beta/issues"
|
||||
},
|
||||
"license": "GPL-3.0-or-later",
|
||||
"environment": "*",
|
||||
"entrypoints": {
|
||||
"main": [
|
||||
"fr.koka.sanctuary.SanctuaryMod"
|
||||
]
|
||||
},
|
||||
"mixins": [
|
||||
"sanctuary.mixins.json"
|
||||
],
|
||||
"depends": {
|
||||
"fabricloader": ">=${loader_version}",
|
||||
"minecraft": "${minecraft_dependency_version}",
|
||||
"java": ">=25",
|
||||
"fabric-api": ">=${fabric_api_version}"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"required": true,
|
||||
"package": "fr.koka.sanctuary.mixin",
|
||||
"compatibilityLevel": "JAVA_25",
|
||||
"mixins": [
|
||||
"InitialSpawnMixin",
|
||||
"SurfaceHydrologyMixin"
|
||||
],
|
||||
"client": [
|
||||
"client.SanctuarySkyMixin",
|
||||
"client.SanctuaryFogMixin"
|
||||
],
|
||||
"injectors": {
|
||||
"defaultRequire": 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package fr.koka.sanctuary.worldgen;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/** Determinism and containment checks independent of Minecraft, using controlled floating terrain. */
|
||||
public final class SurfaceHydrologySmoke {
|
||||
private static final int[][] CARDINALS = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
|
||||
|
||||
public static void main(String[] args) {
|
||||
SurfaceHydrology.Sampler terrain = (x, y, z) -> {
|
||||
int top = 200 + (int) Math.round(2 * Math.sin(x / 24.0) + 2 * Math.cos(z / 31.0));
|
||||
return Math.hypot(x, z) < 220 && y <= top && y >= 145 ? 0.45F : -0.45F;
|
||||
};
|
||||
var first = SurfaceHydrology.create(0, terrain);
|
||||
var replay = SurfaceHydrology.create(0, terrain);
|
||||
var changed = SurfaceHydrology.create(42, terrain);
|
||||
require(!first.features().isEmpty(), "The safe rolling surface should contain surface water");
|
||||
require(List.copyOf(first.cells()).equals(List.copyOf(replay.cells())) && first.features().equals(replay.features()),
|
||||
"The same seed and density must replay exactly");
|
||||
require(!List.copyOf(first.cells()).equals(List.copyOf(changed.cells())), "The world seed must influence water placement");
|
||||
verify(first, terrain);
|
||||
verify(changed, terrain);
|
||||
SurfaceHydrology.Sampler pierced = (x, y, z) ->
|
||||
Math.floorMod(x, 37) < 2 && Math.floorMod(z, 41) < 2 ? -0.45F : terrain.sample(x, y, z);
|
||||
var aroundShafts = SurfaceHydrology.create(0, pierced);
|
||||
require(!aroundShafts.features().isEmpty(), "Safe areas around natural shafts should remain usable");
|
||||
verify(aroundShafts, pierced);
|
||||
require(SurfaceHydrology.create(0, (x, y, z) -> -1).cells().isEmpty(), "An empty world must not acquire a guaranteed basin");
|
||||
var roof = SurfaceHydrology.create(0, (x, y, z) ->
|
||||
Math.hypot(x, z) < 220 && (y == 240 || y >= 145 && y <= 200) ? 0.45F : -0.45F);
|
||||
require(roof.cells().isEmpty(), "Do not create underground lakes below a thin higher roof");
|
||||
var slab = SurfaceHydrology.create(0, (x, y, z) ->
|
||||
Math.hypot(x, z) < 220 && y >= 199 && y <= 200 ? 0.45F : -0.45F);
|
||||
require(slab.cells().isEmpty(), "A thin floating slab cannot safely support water");
|
||||
var flat = SurfaceHydrology.create(0, (x, y, z) ->
|
||||
Math.hypot(x, z) < 220 && y >= 145 && y <= 200 ? 0.45F : -0.45F);
|
||||
require(flat.features().stream().anyMatch(feature -> feature.kind() == SurfaceHydrology.Kind.STREAM),
|
||||
"The controlled safe plateau must exercise the stream planner");
|
||||
System.out.println("SurfaceHydrologySmoke: deterministic lakes/stream, sealed floors/walls, chunk order, no roof/void/thin-slab water passed.");
|
||||
}
|
||||
|
||||
private static void verify(SurfaceHydrology.Plan plan, SurfaceHydrology.Sampler terrain) {
|
||||
Map<String, SurfaceHydrology.Cell> byChunks = new HashMap<>();
|
||||
// Reverse chunk traversal must reconstruct the same cells without generating neighbours.
|
||||
for (int cx = 12; cx >= -12; cx--) for (int cz = 12; cz >= -12; cz--) {
|
||||
var chunk = plan.cellsInChunk(cx, cz);
|
||||
SurfaceHydrology.Cell previous = null;
|
||||
for (var cell : chunk) {
|
||||
require(cell.x() >> 4 == cx && cell.z() >> 4 == cz, "A chunk slice writes outside its chunk");
|
||||
require(previous == null || previous.x() < cell.x() || previous.x() == cell.x() && previous.z() < cell.z(),
|
||||
"Cell order must be stable for seeded shore vegetation");
|
||||
previous = cell;
|
||||
require(byChunks.put(cell.x() + "," + cell.z(), cell) == null, "A cell belongs to multiple chunk slices");
|
||||
}
|
||||
}
|
||||
require(byChunks.size() == plan.cells().size(), "Chunk slices omitted planned cells");
|
||||
for (var cell : plan.cells()) {
|
||||
require(cell.equals(byChunks.get(cell.x() + "," + cell.z())), "Chunk traversal changed the plan");
|
||||
for (int y = cell.bedY() - 2; y <= cell.bedY(); y++) {
|
||||
require(terrain.sample(cell.x(), y, cell.z()) > 0, "Bed material requires three existing solid blocks");
|
||||
}
|
||||
if (!cell.hasWater()) continue;
|
||||
require(cell.waterY() - cell.bedY() >= 1 && cell.waterY() - cell.bedY() <= 4, "Invalid shallow-water depth");
|
||||
require(cell.carveTop() - cell.bedY() <= SurfaceHydrology.MAX_CARVE, "Excavation exceeds six blocks");
|
||||
for (int y = cell.carveTop() + 1; y < 384; y++) {
|
||||
require(terrain.sample(cell.x(), y, cell.z()) <= 0, "Water placed below a natural roof");
|
||||
}
|
||||
for (int[] d : CARDINALS) {
|
||||
int x = cell.x() + d[0], z = cell.z() + d[1];
|
||||
var neighbor = plan.cellAt(x, z);
|
||||
for (int y = cell.bedY() + 1; y <= cell.waterY(); y++) {
|
||||
boolean plannedWater = neighbor != null && neighbor.hasWater()
|
||||
&& y > neighbor.bedY() && y <= neighbor.waterY();
|
||||
boolean carvedAir = neighbor != null && y > neighbor.bedY() && y <= neighbor.carveTop()
|
||||
&& !plannedWater;
|
||||
require(plannedWater || !carvedAir && terrain.sample(x, y, z) > 0,
|
||||
"A water cell has an open horizontal outlet, including excavated banks");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void require(boolean condition, String message) {
|
||||
if (!condition) throw new AssertionError(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
package fr.koka.sanctuary.worldgen;
|
||||
|
||||
/** Run with :sanctuary:worldgenSmoke. No Minecraft client or test library is required. */
|
||||
public final class WorldgenSmoke {
|
||||
private static final float[] NOISE_EXTREMES = {-1, 0, 1};
|
||||
|
||||
public static void main(String[] args) {
|
||||
noAddedTerrain();
|
||||
untouchedInterior();
|
||||
gradualErosion();
|
||||
finiteVoid();
|
||||
densityBounds();
|
||||
raisedIsland();
|
||||
System.out.println("WorldgenSmoke: legacy shape, raised silhouette, irregular underside, no artificial material and finite void passed.");
|
||||
}
|
||||
|
||||
private static void raisedIsland() {
|
||||
for (int x = -288; x <= 288; x += 16) {
|
||||
for (int z = -288; z <= 288; z += 16) {
|
||||
for (int y = 0; y < IslandShape.RAISED_HEIGHT; y += 8) {
|
||||
for (float noise : NOISE_EXTREMES) {
|
||||
for (float natural : new float[]{-0.75F, 0, 0.45F, 1.0F}) {
|
||||
float value = raised(x, y, z, natural, noise, -noise, noise, noise);
|
||||
require(Float.isFinite(value) && value <= natural, "raised sculpt added material");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Preserve the approved silhouette above the underside's working band, shifted by 64.
|
||||
for (int x = -272; x <= 272; x += 16) {
|
||||
for (int y = 80; y < 256; y += 4) {
|
||||
for (float noise : NOISE_EXTREMES) {
|
||||
float old = IslandShape.density(x, y, 32, 0.45F, noise, -noise, noise);
|
||||
float raised = raised(x, y + IslandShape.VERTICAL_OFFSET, 32, 0.45F, noise, -noise, noise, noise);
|
||||
require(Float.floatToIntBits(old) == Float.floatToIntBits(raised),
|
||||
"raising the island changed its approved upper silhouette");
|
||||
}
|
||||
}
|
||||
}
|
||||
int deep = firstRaisedSolid(-1.0F);
|
||||
int high = firstRaisedSolid(1.0F);
|
||||
require(deep > 24 && high - deep >= 24, "underside ends on a uniform horizontal plane");
|
||||
require(deep < IslandShape.VERTICAL_OFFSET, "underside only moved the former lower cutoff upwards");
|
||||
for (float noise : NOISE_EXTREMES) {
|
||||
require(raised(0, 8, 0, Float.MAX_VALUE, noise, noise, noise, noise) < 0, "raised terrain reaches world bottom");
|
||||
require(raised(0, 376, 0, Float.MAX_VALUE, noise, noise, noise, noise) < 0, "raised terrain reaches world top");
|
||||
require(raised(280, 168, 0, Float.MAX_VALUE, noise, noise, noise, noise) < 0, "raised terrain reaches radial guard");
|
||||
}
|
||||
}
|
||||
|
||||
private static int firstRaisedSolid(float underside) {
|
||||
for (int y = 1; y < IslandShape.RAISED_HEIGHT; y++) {
|
||||
if (raised(0, y, 0, 0.45F, 0, 0, 0, underside) > 0) return y;
|
||||
}
|
||||
throw new AssertionError("the controlled raised lobe has no terrain");
|
||||
}
|
||||
|
||||
private static float raised(int x, int y, int z, float terrain, float coast, float coarse, float fine, float underside) {
|
||||
return IslandShape.raisedDensity(x, y, z, terrain, coast, coarse, fine, underside,
|
||||
IslandShape.VERTICAL_OFFSET, IslandShape.RAISED_HEIGHT);
|
||||
}
|
||||
|
||||
private static void noAddedTerrain() {
|
||||
// In particular, a hollow origin must stay hollow: spawn search cannot create land.
|
||||
for (int x = -288; x <= 288; x += 8) {
|
||||
for (int z = -288; z <= 288; z += 8) {
|
||||
for (int y = 0; y <= 256; y += 16) {
|
||||
for (float noise : NOISE_EXTREMES) {
|
||||
for (float terrain : new float[]{-1.0F, -0.001F, 0.0F, 0.15F, 0.75F}) {
|
||||
float shaped = IslandShape.density(x, y, z, terrain, noise, -noise, noise);
|
||||
require(shaped <= terrain, "sculpt added material or filled a natural hole");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void untouchedInterior() {
|
||||
// The least favourable rim starts at radius 128; between the original vertical fades,
|
||||
// the entire interior keeps precisely the original seeded field, including its cavities.
|
||||
for (int x = -128; x <= 128; x += 8) {
|
||||
for (int z = -128; z <= 128; z += 8) {
|
||||
if (Math.hypot(x, z) > 128) continue;
|
||||
for (int y = 32; y <= 224; y += 16) {
|
||||
for (float terrain : new float[]{-0.7F, -0.01F, 0, 0.01F, 0.7F}) {
|
||||
for (float noise : NOISE_EXTREMES) {
|
||||
require(IslandShape.density(x, y, z, terrain, noise, noise, noise) == terrain,
|
||||
"sculpt changed the interior field");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void gradualErosion() {
|
||||
// A smooth test lobe must thin continuously over the rim, not survive intact until a wall.
|
||||
float previous = IslandShape.density(128, 96, 0, 0.45F, 0, 0, 0);
|
||||
int erodedPositiveSamples = 0;
|
||||
for (int radius = 129; radius < IslandShape.TERRAIN_LIMIT; radius++) {
|
||||
float current = IslandShape.density(radius, 96, 0, 0.45F, 0, 0, 0);
|
||||
require(current <= previous, "uniform rim profile became non-monotonic");
|
||||
require(previous - current < 0.085F, "one-block density jump in the rim");
|
||||
if (current > 0 && current < 0.40F) erodedPositiveSamples++;
|
||||
previous = current;
|
||||
}
|
||||
require(erodedPositiveSamples >= 24, "sculpt band is too narrow to round the rim");
|
||||
// Saturated lobes must have genuinely rounded side profiles, not just a uniformly
|
||||
// displaced vertical wall. The face retreats by several chunks above and below its belly.
|
||||
int middleRim = lastSolidRadius(104);
|
||||
require(middleRim - lastSolidRadius(180) >= 30, "upper rim remains a tall vertical wall");
|
||||
require(middleRim - lastSolidRadius(40) >= 30, "lower rim remains a tall vertical wall");
|
||||
require(IslandShape.density(208, 96, 0, 0.45F, 0, -1, 0)
|
||||
!= IslandShape.density(208, 96, 0, 0.45F, 0, 1, 0), "coarse 3D field has no effect");
|
||||
require(IslandShape.density(208, 96, 0, 0.45F, 0, 0, -1)
|
||||
!= IslandShape.density(208, 96, 0, 0.45F, 0, 0, 1), "fine 3D field has no effect");
|
||||
|
||||
// Even maximal positive input becomes empty before any emergency cutoff. This prevents
|
||||
// the hard bound itself from exposing a cylindrical wall or a flat top/bottom rock cap.
|
||||
for (float coast : NOISE_EXTREMES) {
|
||||
for (float coarse : NOISE_EXTREMES) {
|
||||
for (float fine : NOISE_EXTREMES) {
|
||||
require(IslandShape.density(280, 96, 0, Float.MAX_VALUE, coast, coarse, fine) < 0,
|
||||
"terrain reaches the radial safety cutoff");
|
||||
require(IslandShape.density(0, 8, 0, Float.MAX_VALUE, coast, coarse, fine) < 0,
|
||||
"terrain reaches the bottom safety cutoff");
|
||||
require(IslandShape.density(0, 248, 0, Float.MAX_VALUE, coast, coarse, fine) < 0,
|
||||
"terrain reaches the top safety cutoff");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int lastSolidRadius(int y) {
|
||||
int last = 0;
|
||||
for (int radius = 0; radius < IslandShape.TERRAIN_LIMIT; radius++) {
|
||||
if (IslandShape.density(radius, y, 0, 0.45F, 0, 0, 0) > 0) last = radius;
|
||||
}
|
||||
return last;
|
||||
}
|
||||
|
||||
private static void finiteVoid() {
|
||||
int[] distances = {288, 320, 512, 1024, 4096, 10000, 1000000, 29999984, Integer.MAX_VALUE};
|
||||
for (int distance : distances) {
|
||||
for (int y = -64; y <= 320; y += 4) {
|
||||
for (int sign : new int[]{-1, 1}) {
|
||||
require(IslandShape.density(sign * distance, y, 0, 1000, 1000, 1000, 1000) < 0,
|
||||
"terrain outside X limit");
|
||||
require(IslandShape.density(0, y, sign * distance, 1000, 1000, 1000, 1000) < 0,
|
||||
"terrain outside Z limit");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void densityBounds() {
|
||||
for (int coordinate : new int[]{Integer.MIN_VALUE, -29999984, -256, 0, 256, 29999984, Integer.MAX_VALUE}) {
|
||||
for (int y : new int[]{Integer.MIN_VALUE, 0, 96, 256, Integer.MAX_VALUE}) {
|
||||
float value = IslandShape.density(coordinate, y, coordinate, Float.MAX_VALUE, -Float.MAX_VALUE,
|
||||
Float.MAX_VALUE, -Float.MAX_VALUE);
|
||||
require(value >= -1 && value <= 1 && Float.isFinite(value), "invalid density at extreme coordinates");
|
||||
}
|
||||
}
|
||||
for (int x = -320; x <= 320; x += 7) {
|
||||
for (int z = -320; z <= 320; z += 7) {
|
||||
float first = IslandShape.density(x, 100, z, 0.35F, 0.25F, -0.4F, 0.3F);
|
||||
require(first == IslandShape.density(-z, 100, x, 0.35F, 0.25F, -0.4F, 0.3F),
|
||||
"unseeded geometry biases a direction");
|
||||
require(first >= -1 && first <= 1 && Float.isFinite(first), "invalid density range");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void require(boolean condition, String message) {
|
||||
if (!condition) throw new AssertionError(message);
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
**Note de livraison rétrospective** — le tag `notes/beta.001` archive ces documents. Les archives automatiques « Code source » de ce tag contiennent des notes, pas les sources historiques du mod.
|
||||
|
||||
## Nouveautés
|
||||
|
||||
- Adopte `beta.xxx`, à partir de `001`, pour le mod, le pack et les
|
||||
futurs tags ; une livraison consomme un numéro, y compris pour un correctif.
|
||||
- Vérifie le format et l’alignement mod/pack lors du contrôle packwiz.
|
||||
- Clôt le chantier worldgen sur le socle alpha.30.7 à la demande du créateur ;
|
||||
ouvre le chantier des anciennes expéditions avant la progression en jeu.
|
||||
- Les nouveaux mondes ouvrent quatre îles de 512 blocs : nord glacial/Peaks,
|
||||
est tropical/Volcan, sud aride/Canyon et ouest humide/Océan, avec noms FR/EN.
|
||||
- Cherche la première position admissible sur chaque axe, sans l’écart et le
|
||||
décalage aléatoire alpha ; conserve les emprises écrites et les bateaux.
|
||||
- Enregistre les cinq régions dans une transaction initiale, journal bêta
|
||||
distinct, sans migration automatique des anciennes sauvegardes.
|
||||
- Le relief initial et les codecs alpha restent conservés. La livraison initiale était locale ; le canal packwiz conservait l’alpha.30.7.
|
||||
|
||||
Voir [les expéditions et leurs vérifications](https://git.botsu.net/koka/sanctuary-beta/src/commit/c8b0684460e9bdba15d5658a6987b266fcbe7139/docs/expeditions-beta001.md) et
|
||||
[la convention de versionnement](https://git.botsu.net/koka/sanctuary-beta/src/commit/c8b0684460e9bdba15d5658a6987b266fcbe7139/docs/versioning.md).
|
||||
|
||||
## Détails et vérifications
|
||||
|
||||
[Versionnement Sanctuary](https://git.botsu.net/koka/sanctuary-beta/src/commit/c8b0684460e9bdba15d5658a6987b266fcbe7139/docs/versioning.md) · [beta.001 — les quatre anciennes expéditions](https://git.botsu.net/koka/sanctuary-beta/src/commit/c8b0684460e9bdba15d5658a6987b266fcbe7139/docs/expeditions-beta001.md)
|
||||
|
||||
**Environnement :** Minecraft Java **26.3-pre-2**, Fabric Loader **0.19.5**, Fabric API **0.160.0+26.3**, Java **25**. Les packs de cette livraison sont conservés localement ; ce post publie ses notes et ne met pas à jour le canal packwiz.
|
||||
@@ -1,19 +0,0 @@
|
||||
**Note de livraison rétrospective** — le tag `notes/beta.002` archive ces documents. Les archives automatiques « Code source » de ce tag contiennent des notes, pas les sources historiques du mod.
|
||||
|
||||
## Nouveautés
|
||||
|
||||
- Nord boréal/Peaks, avec les trois biomes de glace de l’ancien profil glacial
|
||||
ajoutés à la palette boréale de cette révision.
|
||||
- Bassin ouest en `warm_ocean` pour la décoration native de corail.
|
||||
- Réserve une pyramide du désert au sud et un temple de la jungle à l’est,
|
||||
avec des emplacements déterministes variables et des pièces vanilla.
|
||||
- Isole cette génération dans les nouveaux mondes de révision 2 ; les mondes
|
||||
beta.001 et alpha ne sont ni convertis ni régénérés.
|
||||
- Version du mod et du pack `beta.002`, MRpack `Sanctuary-beta.002.mrpack`.
|
||||
Vérifications : [notes techniques](https://git.botsu.net/koka/sanctuary-beta/src/commit/c8b0684460e9bdba15d5658a6987b266fcbe7139/docs/expeditions-beta002.md).
|
||||
|
||||
## Détails et vérifications
|
||||
|
||||
[beta.002 — climats et temples des expéditions](https://git.botsu.net/koka/sanctuary-beta/src/commit/c8b0684460e9bdba15d5658a6987b266fcbe7139/docs/expeditions-beta002.md)
|
||||
|
||||
**Environnement :** Minecraft Java **26.3-pre-2**, Fabric Loader **0.19.5**, Fabric API **0.160.0+26.3**, Java **25**. Les packs de cette livraison sont conservés localement ; ce post publie ses notes et ne met pas à jour le canal packwiz.
|
||||
@@ -1,25 +0,0 @@
|
||||
**Note de livraison rétrospective** — le tag `notes/beta.003` archive ces documents. Les archives automatiques « Code source » de ce tag contiennent des notes, pas les sources historiques du mod.
|
||||
|
||||
## Nouveautés
|
||||
|
||||
- HELLO_WORLD avant le spawn : skin, pseudo, bio, palette RGB déterministe
|
||||
extensible et accueil `helloworld {pseudo}` unique.
|
||||
- Blocodex commun depuis B et Échap : fiche, progression, histoire, blocs.
|
||||
- Compétences à gauche (six entrées, dont Minage, Construction et Inventaire
|
||||
réservées au prochain ticket), aptitudes en liste défilante à droite.
|
||||
- Couleurs dans des boutons Minecraft classiques : glyphes SGA teintés,
|
||||
hexadécimal standard dans l'infobulle.
|
||||
- Départ 3/3/3, capacités achetables avec les niveaux et conservées à la mort.
|
||||
- Première aptitude Noms : familles de 26.2, configuration serveur par type
|
||||
de mob et rechargement opérateur, priorité aux étiquettes des joueurs.
|
||||
- Position assise disponible dès le début, sans XP ; sièges non sauvegardés.
|
||||
- 70 % des points d'XP disponibles laissés en orbes à la mort, configurable ;
|
||||
respect de `keepInventory`.
|
||||
- Données additives ; adoption explicite des anciens mondes, aucun changement
|
||||
du terrain ou des expansions. Notes : [notes techniques](https://git.botsu.net/koka/sanctuary-beta/src/commit/c8b0684460e9bdba15d5658a6987b266fcbe7139/docs/progression-beta003-release.md).
|
||||
|
||||
## Détails et vérifications
|
||||
|
||||
[beta.003 — premier habitant et première aptitude](https://git.botsu.net/koka/sanctuary-beta/src/commit/c8b0684460e9bdba15d5658a6987b266fcbe7139/docs/progression-beta003-release.md)
|
||||
|
||||
**Environnement :** Minecraft Java **26.3-pre-2**, Fabric Loader **0.19.5**, Fabric API **0.160.0+26.3**, Java **25**. Les packs de cette livraison sont conservés localement ; ce post publie ses notes et ne met pas à jour le canal packwiz.
|
||||
@@ -1,21 +0,0 @@
|
||||
**Note de livraison rétrospective** — le tag `notes/beta.004` archive ces documents. Les archives automatiques « Code source » de ce tag contiennent des notes, pas les sources historiques du mod.
|
||||
|
||||
## Nouveautés
|
||||
|
||||
- Cadre Blocodex natif : titre et onglets Discovery, Progression, Story,
|
||||
Inhabitant en haut ; Done / Terminé centré dans le pied de page.
|
||||
- Contenu adapté à la taille du GUI, cadre partagé avec le catalogue de
|
||||
découvertes, biographie et histoire défilantes avec texte complet.
|
||||
- Six icônes 9×9 fournies par le créateur, conservées sans transformation
|
||||
et affichées à gauche de chaque compétence.
|
||||
- Rétablit le seuil vanilla de sprint : plus de 6 points de faim. Le premier
|
||||
rang de Faim permet de dépasser ce seuil après avoir mangé. Contrôle côté
|
||||
serveur, arrêt à 6 points, exceptions créative et montures conservées.
|
||||
- Compatible avec les profils beta.003, sans migration des sauvegardes ni
|
||||
changement de terrain. Voir [notes techniques](https://git.botsu.net/koka/sanctuary-beta/src/commit/c8b0684460e9bdba15d5658a6987b266fcbe7139/docs/blocodex-beta004.md).
|
||||
|
||||
## Détails et vérifications
|
||||
|
||||
[UI-01 — cadre natif du Blocodex et seuil de sprint](https://git.botsu.net/koka/sanctuary-beta/src/commit/c8b0684460e9bdba15d5658a6987b266fcbe7139/docs/blocodex-beta004.md)
|
||||
|
||||
**Environnement :** Minecraft Java **26.3-pre-2**, Fabric Loader **0.19.5**, Fabric API **0.160.0+26.3**, Java **25**. Les packs de cette livraison sont conservés localement ; ce post publie ses notes et ne met pas à jour le canal packwiz.
|
||||
@@ -1,21 +0,0 @@
|
||||
**Note de livraison rétrospective** — le tag `notes/beta.005` archive ces documents. Les archives automatiques « Code source » de ce tag contiennent des notes, pas les sources historiques du mod.
|
||||
|
||||
## Nouveautés
|
||||
|
||||
- Carte native de surface dans Blocodex → Monde : glisser, zoomer, recentrer,
|
||||
orientation et couleur du joueur. Aucun code ou asset Xaero repris.
|
||||
- Aptitude Grande carte, coût serveur configurable (4 niveaux par défaut) ;
|
||||
exploration personnelle enregistrée avant l’achat et conservée après la mort.
|
||||
- Inspection opérateur explicite des chunks chargés, séparée des découvertes
|
||||
de l’habitant. Pas de génération déclenchée par la consultation.
|
||||
- Noms de couleurs FR/EN associés aux teintes, à la place du SGA ; boutons
|
||||
Minecraft au texte coloré, sans changer les couleurs déjà attribuées.
|
||||
- Retrait des boutons S’asseoir des aptitudes et de la fiche ; geste inné conservé.
|
||||
- Lecture des progressions/configurations schéma 1, ajout de l’aptitude au
|
||||
schéma 2 et fichiers de relevés séparés. Contrat : [notes techniques](https://git.botsu.net/koka/sanctuary-beta/src/commit/c8b0684460e9bdba15d5658a6987b266fcbe7139/docs/atlas-beta005.md).
|
||||
|
||||
## Détails et vérifications
|
||||
|
||||
[MAP-01 — carte Sanctuary native, beta.005](https://git.botsu.net/koka/sanctuary-beta/src/commit/c8b0684460e9bdba15d5658a6987b266fcbe7139/docs/atlas-beta005.md)
|
||||
|
||||
**Environnement :** Minecraft Java **26.3-pre-2**, Fabric Loader **0.19.5**, Fabric API **0.160.0+26.3**, Java **25**. Les packs de cette livraison sont conservés localement ; ce post publie ses notes et ne met pas à jour le canal packwiz.
|
||||
@@ -1,22 +0,0 @@
|
||||
**Note de livraison rétrospective** — le tag `notes/beta.006` archive ces documents. Les archives automatiques « Code source » de ce tag contiennent des notes, pas les sources historiques du mod.
|
||||
|
||||
## Nouveautés
|
||||
|
||||
- Corrige le bouton gauche (SDL / API Minecraft 26.3), les glissements successifs,
|
||||
le relâchement hors carte et la navigation pendant les rafraîchissements.
|
||||
- Brouillard cartographique circulaire, bordure tramée et fond parchemin ;
|
||||
seuls les pixels observés sont transmis. Les anciens chunks restent connus.
|
||||
- Grille de chunks et surcouche Demeure activables ; survol des zones connues
|
||||
avec état, influence principale et couleur de l’habitant.
|
||||
- Premier port autonome de Demeure : activité, placements réussis, destruction,
|
||||
récoltes, sommeil, mort et présence du vivant. Jours UTC réels, budgets
|
||||
persistants et décroissance sans résurrection des anciens scores.
|
||||
- Données Demeure séparées ; Atlas schéma 1 lu puis sauvegardé en schéma 2.
|
||||
Pas d’import historique, de modification du terrain, de claim ou de protection.
|
||||
Voir [notes techniques](https://git.botsu.net/koka/sanctuary-beta/src/commit/c8b0684460e9bdba15d5658a6987b266fcbe7139/docs/demeure-beta006.md) et `mods/demeure/PROVENANCE.md`.
|
||||
|
||||
## Détails et vérifications
|
||||
|
||||
[Ticket beta.006 — Atlas exploré et premières empreintes Demeure](https://git.botsu.net/koka/sanctuary-beta/src/commit/c8b0684460e9bdba15d5658a6987b266fcbe7139/docs/demeure-beta006.md)
|
||||
|
||||
**Environnement :** Minecraft Java **26.3-pre-2**, Fabric Loader **0.19.5**, Fabric API **0.160.0+26.3**, Java **25**. Les packs de cette livraison sont conservés localement ; ce post publie ses notes et ne met pas à jour le canal packwiz.
|
||||
@@ -1,19 +0,0 @@
|
||||
**Note de livraison rétrospective** — le tag `notes/beta.007` archive ces documents. Les archives automatiques « Code source » de ce tag contiennent des notes, pas les sources historiques du mod.
|
||||
|
||||
## Nouveautés
|
||||
|
||||
- Carte plein écran sous les commandes, carré central clair et abords sombres.
|
||||
Le vide observé et les zones inconnues sont transparents ; leurs états restent
|
||||
distincts dans les relevés personnels existants.
|
||||
- Boutons natifs superposés, coordonnées et zoom en bas à gauche. Grille,
|
||||
empreintes Demeure et marqueur du joueur suivent le terrain sur tout l'écran.
|
||||
- Glisser-déplacer et molette dans les marges ; priorité des boutons, y compris
|
||||
désactivés. Zoom minimal adapté à la taille du GUI. Libellés FR/EN du zoom.
|
||||
- Aucun changement de protocole, de format de sauvegarde ou de génération.
|
||||
Voir [notes techniques](https://git.botsu.net/koka/sanctuary-beta/src/commit/c8b0684460e9bdba15d5658a6987b266fcbe7139/docs/atlas-immersive-beta007.md) pour la validation et l'artefact.
|
||||
|
||||
## Détails et vérifications
|
||||
|
||||
[MAP-03 — Carte immersive — beta.007](https://git.botsu.net/koka/sanctuary-beta/src/commit/c8b0684460e9bdba15d5658a6987b266fcbe7139/docs/atlas-immersive-beta007.md)
|
||||
|
||||
**Environnement :** Minecraft Java **26.3-pre-2**, Fabric Loader **0.19.5**, Fabric API **0.160.0+26.3**, Java **25**. Les packs de cette livraison sont conservés localement ; ce post publie ses notes et ne met pas à jour le canal packwiz.
|
||||
@@ -1,20 +0,0 @@
|
||||
**Note de livraison rétrospective** — le tag `notes/beta.008` archive ces documents. Les archives automatiques « Code source » de ce tag contiennent des notes, pas les sources historiques du mod.
|
||||
|
||||
## Nouveautés
|
||||
|
||||
- Compétence Minage achetable, sept rangs et vitesse native de 100 à 200 %.
|
||||
Plafonds de veine : 1 / 4 / 8 / 16 / 32 / 64 / 128 / 256 blocs.
|
||||
- R configurable pour l'aperçu, R + clic gauche maintenus pour miner,
|
||||
R + molette pour limiter la sélection. Contour des blocs et compteur en jeu.
|
||||
- Durée calculée sur le serveur, interruption et protections par bloc,
|
||||
destruction native pour outils, loot, XP, faim, enchantements et statistiques.
|
||||
Un seul effet sonore de casse par groupe ; la rupture d'outil arrête le reste.
|
||||
- Progression schéma 3 : ajout de Minage à zéro lors de la lecture des anciens
|
||||
profils, achats et dates conservés, rangs persistants après mort et reprise.
|
||||
Configuration existante conservée. Voir [notes techniques](https://git.botsu.net/koka/sanctuary-beta/src/commit/c8b0684460e9bdba15d5658a6987b266fcbe7139/docs/mining-beta008.md).
|
||||
|
||||
## Détails et vérifications
|
||||
|
||||
[PROG-02A — Minage et vein mining — beta.008](https://git.botsu.net/koka/sanctuary-beta/src/commit/c8b0684460e9bdba15d5658a6987b266fcbe7139/docs/mining-beta008.md)
|
||||
|
||||
**Environnement :** Minecraft Java **26.3-pre-2**, Fabric Loader **0.19.5**, Fabric API **0.160.0+26.3**, Java **25**. Les packs de cette livraison sont conservés localement ; ce post publie ses notes et ne met pas à jour le canal packwiz.
|
||||
@@ -1,20 +0,0 @@
|
||||
**Note de livraison rétrospective** — le tag `notes/beta.009` archive ces documents. Les archives automatiques « Code source » de ce tag contiennent des notes, pas les sources historiques du mod.
|
||||
|
||||
## Nouveautés
|
||||
|
||||
- Construction devient une compétence achetable : sept rangs, surfaces de
|
||||
3×3 à 15×15, coûts identiques aux autres compétences et rangs conservés à la mort.
|
||||
- Aperçu vert, R + clic droit maintenus, R + molette pour le diamètre. La touche
|
||||
configurable est partagée avec Minage ; une surface reste figée pendant le geste.
|
||||
- Placement Minecraft côté serveur, stock réellement consommé, surfaces orientées,
|
||||
dalles et escaliers natifs, refus de collision/protection et interruptions.
|
||||
Les contenants de support ne sont pas ouverts. Les objets à pose multiple
|
||||
(portes, lits, plantes doubles), échafaudages et nénuphars restent en pose individuelle.
|
||||
- Migration stricte de progression vers le schéma 4 avec `building: 0`, sans
|
||||
modifier les achats existants. [Contrat et vérifications](https://git.botsu.net/koka/sanctuary-beta/src/commit/c8b0684460e9bdba15d5658a6987b266fcbe7139/docs/building-beta009.md).
|
||||
|
||||
## Détails et vérifications
|
||||
|
||||
[PROG-02B — Construction et vein building — beta.009](https://git.botsu.net/koka/sanctuary-beta/src/commit/c8b0684460e9bdba15d5658a6987b266fcbe7139/docs/building-beta009.md)
|
||||
|
||||
**Environnement :** Minecraft Java **26.3-pre-2**, Fabric Loader **0.19.5**, Fabric API **0.160.0+26.3**, Java **25**. Les packs de cette livraison sont conservés localement ; ce post publie ses notes et ne met pas à jour le canal packwiz.
|
||||
@@ -1,20 +0,0 @@
|
||||
**Note de livraison rétrospective** — le tag `notes/beta.010` archive ces documents. Les archives automatiques « Code source » de ce tag contiennent des notes, pas les sources historiques du mod.
|
||||
|
||||
## Nouveautés
|
||||
|
||||
- Compétence Inventaire : cinq achats, de neuf à 54 cases, barre rapide incluse.
|
||||
- Migration progression 4 → 5 avec rang Inventaire nul et historique préservé.
|
||||
- Conservation des anciennes cases remplies en retrait uniquement ; les 18
|
||||
nouvelles cases sont enregistrées avec le joueur et suivent `keepInventory`.
|
||||
- Portage des six fonds personnels et des fonds de conteneurs 26.2, adaptation
|
||||
aux menus natifs, défilement des rangées sur les petits écrans et accès à
|
||||
l'inventaire personnel depuis le catalogue créatif.
|
||||
- Collecte, déplacements rapides, glisser-déposer et livre de recettes suivent
|
||||
les capacités serveur. Les indices des machines et de l'équipement restent
|
||||
inchangés. Voir [notes techniques](https://git.botsu.net/koka/sanctuary-beta/src/commit/c8b0684460e9bdba15d5658a6987b266fcbe7139/docs/inventory-beta010.md) pour les preuves et limites.
|
||||
|
||||
## Détails et vérifications
|
||||
|
||||
[beta.010 — Inventaire, contrat préalable de migration](https://git.botsu.net/koka/sanctuary-beta/src/commit/c8b0684460e9bdba15d5658a6987b266fcbe7139/docs/inventory-beta010.md)
|
||||
|
||||
**Environnement :** Minecraft Java **26.3-pre-2**, Fabric Loader **0.19.5**, Fabric API **0.160.0+26.3**, Java **25**. Les packs de cette livraison sont conservés localement ; ce post publie ses notes et ne met pas à jour le canal packwiz.
|
||||
@@ -1,21 +0,0 @@
|
||||
**Note de livraison rétrospective** — le tag `notes/beta.011` archive ces documents. Les archives automatiques « Code source » de ce tag contiennent des notes, pas les sources historiques du mod.
|
||||
|
||||
## Nouveautés
|
||||
|
||||
- Bilan des six compétences et confirmation du New Game+ dans le Blocodex.
|
||||
- Un prestige par cycle terminé ; charges personnelles et prix de fondation /
|
||||
agrandissement configurables, enregistrés une seule fois côté serveur.
|
||||
- Factions persistantes, invitations de sept jours, capacité, responsable et
|
||||
membres, transmission, exclusion, départ et dissolution sans remboursement.
|
||||
- Migration additive des achats vers le schéma 6 ; aptitudes, objets et données
|
||||
communautaires conservés, six rangs remis à zéro, XP configurable.
|
||||
- Journal atomique commun aux cycles et aux dépenses de faction, historique
|
||||
daté, archivage des achats et reprise du passage après interruption.
|
||||
- Interfaces natives FR/EN défilantes ; aucun claim ni droit opérateur accordé.
|
||||
Voir [contrat et vérifications](https://git.botsu.net/koka/sanctuary-beta/src/commit/c8b0684460e9bdba15d5658a6987b266fcbe7139/docs/cycle-factions-beta011.md).
|
||||
|
||||
## Détails et vérifications
|
||||
|
||||
[CYCLE-01 · PROG-03 · FACTION-01 · NGP-01 — beta.011](https://git.botsu.net/koka/sanctuary-beta/src/commit/c8b0684460e9bdba15d5658a6987b266fcbe7139/docs/cycle-factions-beta011.md)
|
||||
|
||||
**Environnement :** Minecraft Java **26.3-pre-2**, Fabric Loader **0.19.5**, Fabric API **0.160.0+26.3**, Java **25**. Les packs de cette livraison sont conservés localement ; ce post publie ses notes et ne met pas à jour le canal packwiz.
|
||||
@@ -1,25 +0,0 @@
|
||||
**Note de livraison rétrospective** — le tag `notes/beta.012` archive ces documents. Les archives automatiques « Code source » de ce tag contiennent des notes, pas les sources historiques du mod.
|
||||
|
||||
## Nouveautés
|
||||
|
||||
- Fond des coffres, machines et zones de clic alignés avec les cases à chaque GUI.
|
||||
- Stockage dans l'ordre naturel ; barre rapide en bas et rotation Tab / Maj+Tab,
|
||||
configurable, validée par le serveur et limitée aux rangées débloquées.
|
||||
- Maj-clic fusionne les piles de toutes les rangées avant d'occuper une case vide.
|
||||
Les rangées supplémentaires suivent les règles natives de chaque machine ;
|
||||
un coffre plein ne provoque plus de déplacement interne au joueur.
|
||||
- Six jauges de compétence avec valeur/plafond, + selon l'XP disponible et −
|
||||
opérateur avec remboursement du coût réellement payé pour le rang retiré.
|
||||
- Progression au schéma 7 : audit des retraits et révision monotone, reprise des
|
||||
schémas précédents, objets conservés en récupération après réduction de capacité.
|
||||
Contrat préalable : [notes techniques](https://git.botsu.net/koka/sanctuary-beta/src/commit/c8b0684460e9bdba15d5658a6987b266fcbe7139/docs/inventory-flow-beta012.md).
|
||||
- Nouvelle capacité réseau ; client et serveur beta.012 doivent être associés.
|
||||
`check build assemblePack` et 49 tests natifs passent ; parcours graphique
|
||||
vérifié avec coffres remplis, touches Tab/E et remboursement opérateur.
|
||||
MRpack local et reçus de vérification consignés dans le ticket.
|
||||
|
||||
## Détails et vérifications
|
||||
|
||||
[beta.012 — transferts, barres rapides et progression](https://git.botsu.net/koka/sanctuary-beta/src/commit/c8b0684460e9bdba15d5658a6987b266fcbe7139/docs/inventory-flow-beta012.md)
|
||||
|
||||
**Environnement :** Minecraft Java **26.3-pre-2**, Fabric Loader **0.19.5**, Fabric API **0.160.0+26.3**, Java **25**. Les packs de cette livraison sont conservés localement ; ce post publie ses notes et ne met pas à jour le canal packwiz.
|
||||
@@ -1,30 +0,0 @@
|
||||
**Note de livraison rétrospective** — le tag `notes/beta.013` archive ces documents. Les archives automatiques « Code source » de ce tag contiennent des notes, pas les sources historiques du mod.
|
||||
|
||||
## Nouveautés
|
||||
|
||||
- Icône, nom, jauge ASCII, valeur/plafond et boutons réunis sur une ligne par
|
||||
compétence, avec colonnes adaptées au GUI et noms défilants si nécessaire.
|
||||
- Courbe par défaut 1, 2, 4, 8, 16, 32, 48 niveaux. Configuration au schéma 3 :
|
||||
reprise de l'ancienne courbe par défaut, conservation des prix personnalisés.
|
||||
Les anciens achats gardent leur montant de remboursement exact.
|
||||
- La page Factions explique les charges gagnées au New Game+ et le recrutement
|
||||
sans prestige ; un bouton mène au bilan des six compétences dans Cycle.
|
||||
- Prestige en chiffres romains après le nom sur la fiche Habitant, sans renommer
|
||||
l'identité. Les 45 cases de récupération restent grisées après un passage 6 → 1,
|
||||
y compris les cases vides des rangées supplémentaires et les vues de coffre.
|
||||
- La carte actualise les relevés sans effacer son image à chaque flux réseau ;
|
||||
les déplacements réutilisent les zones reçues, dans un cache temporaire borné.
|
||||
- Sprite de locator natif sur la carte ; couleurs d'habitants attribuées aux
|
||||
waypoints côté serveur. Les marqueurs se superposent à la barre d’XP,
|
||||
dont le fond et le remplissage restent visibles. Les règles de visibilité restent natives.
|
||||
- Molette selon la zone survolée : case de hotbar, rangée d’inventaire ou
|
||||
échanges de villageois ; rotations confirmées par le serveur.
|
||||
- `check build assemblePack`, 50 tests natifs serveur et parcours graphique
|
||||
FR/EN validés. MRpack local vérifié, avec ses ressources et versions internes.
|
||||
- [Contrat et vérifications](https://git.botsu.net/koka/sanctuary-beta/src/commit/c8b0684460e9bdba15d5658a6987b266fcbe7139/docs/progression-inline-beta013.md).
|
||||
|
||||
## Détails et vérifications
|
||||
|
||||
[beta.013 — compétences sur une ligne et prix du prestige](https://git.botsu.net/koka/sanctuary-beta/src/commit/c8b0684460e9bdba15d5658a6987b266fcbe7139/docs/progression-inline-beta013.md)
|
||||
|
||||
**Environnement :** Minecraft Java **26.3-pre-2**, Fabric Loader **0.19.5**, Fabric API **0.160.0+26.3**, Java **25**. Les packs de cette livraison sont conservés localement ; ce post publie ses notes et ne met pas à jour le canal packwiz.
|
||||
@@ -1,30 +0,0 @@
|
||||
**Note de livraison rétrospective** — le tag `notes/beta.014` archive ces documents. Les archives automatiques « Code source » de ce tag contiennent des notes, pas les sources historiques du mod.
|
||||
|
||||
## Nouveautés
|
||||
|
||||
- Aptitude Rangement dans le Blocodex, 4 niveaux par défaut et prix
|
||||
configurable.
|
||||
- Clic molette configurable : piles regroupées et stockage débloqué trié ; la
|
||||
hotbar ne participe que lorsqu'elle est visée. Les composants d'objets restent
|
||||
distincts, les cases de récupération et le conteneur ouvert sont préservés.
|
||||
- Tri validé et appliqué côté serveur, sans objets fournis par le client.
|
||||
- Tab/Maj+Tab et la molette déplacent le cadre de la hotbar parmi les rangées,
|
||||
en gardant chaque objet dans sa case visible. Le tri exclut la rangée active
|
||||
sauf si elle est visée. La sélection est sauvegardée avec les objets ; revenir
|
||||
à une rangée après New Game+ conserve les autres objets en récupération.
|
||||
- Nom de faction en préfixe du pseudo, coloré, avec prestige romain conservé :
|
||||
fiche Habitant, noms affichés, chat et liste Tab. Mise à jour après adhésion,
|
||||
départ ou dissolution, y compris quand la fiche est déjà ouverte.
|
||||
- Descriptions FR/EN des aptitudes sans rappel individuel de leur conservation
|
||||
après New Game+ ; la règle générale reste dans le bilan du cycle.
|
||||
- Progression 7 → 8 et configuration 3 → 4 selon le
|
||||
[contrat préalable](https://git.botsu.net/koka/sanctuary-beta/src/commit/c8b0684460e9bdba15d5658a6987b266fcbe7139/docs/inventory-sorting-beta014.md) ; nouvelle capacité
|
||||
réseau de rangement, mise à jour du client et du serveur ensemble.
|
||||
- `check build assemblePack`, 56 tests natifs serveur et parcours client FR/EN
|
||||
validés. Export local beta.014 vérifié ; les MRpacks beta.003–013 sont inchangés.
|
||||
|
||||
## Détails et vérifications
|
||||
|
||||
[beta.014 — rangement, hotbar mobile et préfixe de faction](https://git.botsu.net/koka/sanctuary-beta/src/commit/c8b0684460e9bdba15d5658a6987b266fcbe7139/docs/inventory-sorting-beta014.md)
|
||||
|
||||
**Environnement :** Minecraft Java **26.3-pre-2**, Fabric Loader **0.19.5**, Fabric API **0.160.0+26.3**, Java **25**. Les packs de cette livraison sont conservés localement ; ce post publie ses notes et ne met pas à jour le canal packwiz.
|
||||
@@ -1,17 +0,0 @@
|
||||
**Note de livraison rétrospective** — le tag `notes/beta.015` archive ces documents. Les archives automatiques « Code source » de ce tag contiennent des notes, pas les sources historiques du mod.
|
||||
|
||||
## Nouveautés
|
||||
|
||||
- Remplacement des six icônes de compétences par les nouveaux PNG 9 × 9 du
|
||||
créateur, sans retouche ni changement des chemins utilisés par le Blocodex.
|
||||
- Copies identiques préparées dans les sources du resource pack Sanctuary.
|
||||
Correspondances `food → hunger` et `breath → breathing` documentées dans
|
||||
[le ticket](https://git.botsu.net/koka/sanctuary-beta/src/commit/c8b0684460e9bdba15d5658a6987b266fcbe7139/docs/progression-textures-beta015.md).
|
||||
- Build, six tests serveur de progression et export local validés. Bytecode
|
||||
identique à beta.014 ; sprites du HUD de respiration et de faim vanilla conservés.
|
||||
|
||||
## Détails et vérifications
|
||||
|
||||
[beta.015 — icônes de progression communes](https://git.botsu.net/koka/sanctuary-beta/src/commit/c8b0684460e9bdba15d5658a6987b266fcbe7139/docs/progression-textures-beta015.md)
|
||||
|
||||
**Environnement :** Minecraft Java **26.3-pre-2**, Fabric Loader **0.19.5**, Fabric API **0.160.0+26.3**, Java **25**. Les packs de cette livraison sont conservés localement ; ce post publie ses notes et ne met pas à jour le canal packwiz.
|
||||
@@ -1,20 +0,0 @@
|
||||
**Note de livraison rétrospective** — le tag `notes/beta.016` archive ces documents. Les archives automatiques « Code source » de ce tag contiennent des notes, pas les sources historiques du mod.
|
||||
|
||||
## Nouveautés
|
||||
|
||||
- Cinq actions du tutoriel natif donnent chacune 8 points d’XP par défaut :
|
||||
déplacement/vue, arbre, première bûche minée, inventaire et planches fabriquées.
|
||||
- Récompenses personnelles uniques, message FR/EN et montant configurable
|
||||
dans `config/sanctuary/tutorial.json` ; créatif et spectateur exclus.
|
||||
- Attachement `sanctuary:tutorial` conservé avec les XP dans les données joueur,
|
||||
après la mort et le New Game+. Les anciens habitants peuvent réaliser les
|
||||
gestes une fois ; aucun paiement des statistiques historiques.
|
||||
- [Contrat de migration additive](https://git.botsu.net/koka/sanctuary-beta/src/commit/c8b0684460e9bdba15d5658a6987b266fcbe7139/docs/tutorial-xp-beta016.md) et capacité
|
||||
réseau beta.016 commune au client et au serveur. Icônes beta.015 conservées.
|
||||
- 57 tests serveur, contrôles purs et parcours client natif validés ; MRpack local vérifié.
|
||||
|
||||
## Détails et vérifications
|
||||
|
||||
[beta.016 — XP des premiers pas](https://git.botsu.net/koka/sanctuary-beta/src/commit/c8b0684460e9bdba15d5658a6987b266fcbe7139/docs/tutorial-xp-beta016.md)
|
||||
|
||||
**Environnement :** Minecraft Java **26.3-pre-2**, Fabric Loader **0.19.5**, Fabric API **0.160.0+26.3**, Java **25**. Les packs de cette livraison sont conservés localement ; ce post publie ses notes et ne met pas à jour le canal packwiz.
|
||||
@@ -1,26 +0,0 @@
|
||||
**Note de livraison rétrospective** — le tag `notes/beta.017` archive ces documents. Les archives automatiques « Code source » de ce tag contiennent des notes, pas les sources historiques du mod.
|
||||
|
||||
## Nouveautés
|
||||
|
||||
- Aptitudes Nage, S’allonger et Se reposer : 4 niveaux chacune, prix configurables.
|
||||
- Nage rapide verrouillée avant achat ; déplacement lent et remontée conservés.
|
||||
- W bascule la posture basse, Maj + W le repos ; touche configurable, V laissé libre.
|
||||
- Repos immobile : un demi-cœur toutes les 10 secondes pour une demi-barre de faim ;
|
||||
mouvement et dégâts interrompent la séance, sans changer l’heure.
|
||||
- Progression 8 → 9 et configuration 4 → 5 selon le [contrat](https://git.botsu.net/koka/sanctuary-beta/src/commit/c8b0684460e9bdba15d5658a6987b266fcbe7139/docs/swimming-recipebook-beta017.md) ;
|
||||
achats antérieurs conservés et nouvelles aptitudes persistantes après prestige.
|
||||
- Bouton, panneau, fantômes et invitation à consulter le livre de recettes natif retirés ;
|
||||
fabrication, recettes serveur, statistiques et advancements conservés.
|
||||
- Prix du prochain rang à côté de chaque jauge de compétence, à la place du ratio ;
|
||||
`Max.` lorsque le dernier rang est atteint.
|
||||
- Son vanilla de ramassage d'XP pour chaque récompense des Premiers pas, une seule
|
||||
fois par étape ; le défaut W tient compte de la disposition AZERTY/QWERTY.
|
||||
- JEI lié aux advancements reste à intégrer. XP du tutoriel et textures beta.016 incluses.
|
||||
- `check build assemblePack`, 62 tests serveur puis 15 tests finaux ciblés et parcours
|
||||
client natif validés ; MRpack beta.017 local vérifié, exports antérieurs inchangés.
|
||||
|
||||
## Détails et vérifications
|
||||
|
||||
[beta.017 — aptitudes Nage/S’allonger/Repos et retrait du livre de recettes](https://git.botsu.net/koka/sanctuary-beta/src/commit/c8b0684460e9bdba15d5658a6987b266fcbe7139/docs/swimming-recipebook-beta017.md)
|
||||
|
||||
**Environnement :** Minecraft Java **26.3-pre-2**, Fabric Loader **0.19.5**, Fabric API **0.160.0+26.3**, Java **25**. Les packs de cette livraison sont conservés localement ; ce post publie ses notes et ne met pas à jour le canal packwiz.
|
||||
@@ -1,26 +0,0 @@
|
||||
**Note de livraison rétrospective** — le tag `notes/beta.018` archive ces documents. Les archives automatiques « Code source » de ce tag contiennent des notes, pas les sources historiques du mod.
|
||||
|
||||
## Nouveautés
|
||||
|
||||
- Trois cases d’équipement personnel, icônes 16 × 16 du créateur dans le mod et le resource pack.
|
||||
- Titre Inventaire sans suffixe de rangées ; Maj-clic pour équiper/retirer œufs et capes.
|
||||
- Ordre visuel des emplacements : cosmétique, cape, œuf, main secondaire ; cosmétique équipé manuellement.
|
||||
- Maj + clic gauche maintenus transfèrent successivement les cases parcourues, y compris lors d’un mouvement rapide, selon les règles du conteneur.
|
||||
- Tab change la rangée de hotbar en jeu comme dans l’inventaire ; Maj inverse le sens. Touche configurable, cases et colonne sélectionnée conservées.
|
||||
- Minage et construction groupés verrouillés au premier clic : tourner la caméra ne change plus la sélection, ne réinitialise plus la charge et n’interrompt plus les poses.
|
||||
- Taille et orientation des poses figées pendant le maintien ; nouveau groupe après relâchement. Portée, droits, outils et matériaux toujours contrôlés côté serveur.
|
||||
- Familier temporaire avec navigation, promenade, retour sûr, invincibilité et absence de collisions d’entités.
|
||||
- Caresse par clic droit du propriétaire : particules de son œuf et cœurs, sans pouvoirs passifs/actifs.
|
||||
- Cape Zéro de test avec rendu et physique de cape natifs ; visuel provisoire attribué dans les notices.
|
||||
- Tout objet/bloc accepté sur la tête à titre cosmétique ; états de blocs et piston réagissant aux dégâts en rendu uniquement.
|
||||
- Stockage joueur additif `sanctuary:accessories` schéma 1, contrat de migration documenté ; mort native, conservation au prestige.
|
||||
- S’allonger gratuit avec W, retiré des aptitudes achetables ; Se reposer reste achetable et utilise la pose de sommeil des lits.
|
||||
- Preuves d’achat historiques de S’allonger conservées ; récupération vie/faim et temps inchangés.
|
||||
- Client et serveur beta.018 ensemble ; nouveau contrat réseau pour les cases d’équipement.
|
||||
- `check build assemblePack`, contrôles purs, 67 tests serveur et parcours client natif validés ; MRpack local vérifié, exports antérieurs inchangés.
|
||||
|
||||
## Détails et vérifications
|
||||
|
||||
[beta.018 — cases de familier, cape et cosmétique de tête](https://git.botsu.net/koka/sanctuary-beta/src/commit/c8b0684460e9bdba15d5658a6987b266fcbe7139/docs/companions-cosmetics-beta018.md)
|
||||
|
||||
**Environnement :** Minecraft Java **26.3-pre-2**, Fabric Loader **0.19.5**, Fabric API **0.160.0+26.3**, Java **25**. Les packs de cette livraison sont conservés localement ; ce post publie ses notes et ne met pas à jour le canal packwiz.
|
||||
@@ -1,18 +0,0 @@
|
||||
**Note de livraison rétrospective** — le tag `notes/beta.019` archive ces documents. Les archives automatiques « Code source » de ce tag contiennent des notes, pas les sources historiques du mod.
|
||||
|
||||
## Nouveautés
|
||||
|
||||
- 88 passifs et 88 actifs avec cinq raretés, Lien actif à quatre niveaux et touche G configurable.
|
||||
- État indépendant persistant, recharge commune, récupération des bénéficiaires, consentement révocable et panneau FR/EN.
|
||||
- Dégâts plafonnés, ressources réelles, échanges natifs, cycles de production attribués et actions interrompues au changement de lien.
|
||||
- Vol orbital du Dragon, vol des Withers/Ghasts, nage, déplacements terrestres et sauts adaptés ; invincibilité et absence de collision d’entités conservées.
|
||||
- Éclairage des modèles à la position réelle du familier ; animations natives pilotées sans IA hostile.
|
||||
- Fondu de proximité des familiers volants entre 0,6 et 3,5 blocs, corps et yeux compris ; monstres du monde inchangés.
|
||||
- Rangs I–III, Anomaly et obtention des œufs reportés ; équilibrage à poursuivre.
|
||||
- Contrôles purs, 91 tests serveur, parcours client et export local vérifiés. [Détails](https://git.botsu.net/koka/sanctuary-beta/src/commit/c8b0684460e9bdba15d5658a6987b266fcbe7139/docs/companion-powers-beta019.md).
|
||||
|
||||
## Détails et vérifications
|
||||
|
||||
[beta.019 — pouvoirs des 88 familiers](https://git.botsu.net/koka/sanctuary-beta/src/commit/c8b0684460e9bdba15d5658a6987b266fcbe7139/docs/companion-powers-beta019.md)
|
||||
|
||||
**Environnement :** Minecraft Java **26.3-pre-2**, Fabric Loader **0.19.5**, Fabric API **0.160.0+26.3**, Java **25**. Les packs de cette livraison sont conservés localement ; ce post publie ses notes et ne met pas à jour le canal packwiz.
|
||||
@@ -1,21 +0,0 @@
|
||||
**Note de livraison rétrospective** — le tag `notes/beta.020` archive ces documents. Les archives automatiques « Code source » de ce tag contiennent des notes, pas les sources historiques du mod.
|
||||
|
||||
## Nouveautés
|
||||
|
||||
- Portage natif par Maj + clic droit : animaux sur la tête, joueur sur joueur, ralentissement du porteur et protection mutuelle contre les coups.
|
||||
- Dépose avec la main vide, synchronisation du porteur, fin du portage à la déconnexion/mort/dimension et sauvegarde indépendante des animaux.
|
||||
- Gestes de portage séparés du raccourci double-Maj pour s’asseoir.
|
||||
- Familiers bébés lorsque possible, caresses avec cœurs seulement, coup visuel sans dégâts, nom conservé dans l’œuf via enclume/étiquette.
|
||||
- Taille aléatoire enregistrée dans chaque œuf au premier équipement, de ×0,12 à ×6 ; tailles extrêmes rares, données conservées aux transferts et sauvegardes, rendu et noms adaptés.
|
||||
- Cinq couleurs de rareté et noms cryptiques du passif/actif ; Étude des familiers à quatre niveaux pour révéler les détails dans les infobulles FR/EN.
|
||||
- Réglages et carnet déplacés dans Habitant, aides alliées acceptées par défaut, choix historiques conservés ; utilisation du pouvoir par G configurable.
|
||||
- Plongeon gratuit après un saut en sprint, avec la touche S’allonger, une impulsion par saut et rendu horizontal immédiat.
|
||||
- Détections derrière les parois et téléportations à travers les murs vers un emplacement libre, dans leur portée.
|
||||
- Freinage de chute : distance accumulée remise à zéro pendant le ralentissement, dégâts natifs pour une nouvelle chute non protégée.
|
||||
- Contrôles purs, 104 tests serveur, parcours clavier/souris et rendu des 88 modèles à trois tailles vérifiés ; export beta.020 local immuable, PNG et beta.019 conservés.
|
||||
|
||||
## Détails et vérifications
|
||||
|
||||
[beta.020 — interactions, lecture des œufs et plongeon](https://git.botsu.net/koka/sanctuary-beta/src/commit/c8b0684460e9bdba15d5658a6987b266fcbe7139/docs/familiar-interactions-beta020.md)
|
||||
|
||||
**Environnement :** Minecraft Java **26.3-pre-2**, Fabric Loader **0.19.5**, Fabric API **0.160.0+26.3**, Java **25**. Les packs de cette livraison sont conservés localement ; ce post publie ses notes et ne met pas à jour le canal packwiz.
|
||||
@@ -1,15 +0,0 @@
|
||||
**Note de livraison rétrospective** — le tag `notes/beta.022` archive ces documents. Les archives automatiques « Code source » de ce tag contiennent des notes, pas les sources historiques du mod.
|
||||
|
||||
## Nouveautés
|
||||
|
||||
- Évite les bruits dont l’influence est exactement nulle et les calculs du champ
|
||||
de base au-dessus de son extinction certaine, sans retirer les corps flottants.
|
||||
- Même génération, mêmes identifiants et formats de sauvegarde. Comparaisons
|
||||
bit à bit et mesures décrites dans [STARTUP-01](https://git.botsu.net/koka/sanctuary-beta/src/commit/c8b0684460e9bdba15d5658a6987b266fcbe7139/docs/initial-loading.md).
|
||||
- Livraison initialement locale sur `codex/initial-loading`, avec le pack conservé pour les essais.
|
||||
|
||||
## Détails et vérifications
|
||||
|
||||
[STARTUP-01 — raccourcir la création de monde](https://git.botsu.net/koka/sanctuary-beta/src/commit/c8b0684460e9bdba15d5658a6987b266fcbe7139/docs/initial-loading.md)
|
||||
|
||||
**Environnement :** Minecraft Java **26.3-pre-2**, Fabric Loader **0.19.5**, Fabric API **0.160.0+26.3**, Java **25**. Les packs de cette livraison sont conservés localement ; ce post publie ses notes et ne met pas à jour le canal packwiz.
|
||||
@@ -1,20 +0,0 @@
|
||||
**Note de livraison rétrospective** — le tag `notes/beta.023` archive ces documents. Les archives automatiques « Code source » de ce tag contiennent des notes, pas les sources historiques du mod.
|
||||
|
||||
## Nouveautés
|
||||
|
||||
- Intègre le correctif de chargement beta.022, sans modifier les valeurs du terrain.
|
||||
- Retrait du grand bouton Cosmetic ; clic droit sur la case avec l’objet au curseur, clic gauche et Maj-clic conservés.
|
||||
- Bougie/gâteau/statue manipulables depuis l’inventaire ; coûts et restitution au curseur contrôlés côté serveur.
|
||||
- Infobulles FR/EN pour les huit cases d’équipement, vides ou occupées.
|
||||
- Panneaux écrits par autrui, motifs de bannières, lits/portes complets, minecarts et blocs enveloppant la tête.
|
||||
- TNT amorcée par les dégâts, mèche native et explosion réelle qui continue après retrait du cosmétique.
|
||||
- [Contrat, stockage et vérifications](https://git.botsu.net/koka/sanctuary-beta/src/commit/c8b0684460e9bdba15d5658a6987b266fcbe7139/docs/head-cosmetics-beta021.md).
|
||||
- 117 tests serveur, parcours client et 588 288 comparaisons de densité réussis ; MRpack local vérifié.
|
||||
|
||||
Le chantier commencé sous le numéro **beta.021** a été finalisé dans cette livraison, après le correctif beta.022. Beta.021 n’a pas eu de livraison autonome.
|
||||
|
||||
## Détails et vérifications
|
||||
|
||||
[beta.023 — cosmétiques de tête interactifs](https://git.botsu.net/koka/sanctuary-beta/src/commit/c8b0684460e9bdba15d5658a6987b266fcbe7139/docs/head-cosmetics-beta021.md)
|
||||
|
||||
**Environnement :** Minecraft Java **26.3-pre-2**, Fabric Loader **0.19.5**, Fabric API **0.160.0+26.3**, Java **25**. Les packs de cette livraison sont conservés localement ; ce post publie ses notes et ne met pas à jour le canal packwiz.
|
||||
@@ -1,20 +0,0 @@
|
||||
**Note de livraison rétrospective** — le tag `notes/beta.024` archive ces documents. Les archives automatiques « Code source » de ce tag contiennent des notes, pas les sources historiques du mod.
|
||||
|
||||
## Nouveautés
|
||||
|
||||
- Port source MIT de JEI 26.2 vers Minecraft 26.3-pre-2 ; fork et dépendances embarqués avec leurs notices.
|
||||
- Catalogue à 4 niveaux, découvertes conservées avant achat, filtrage personnel des objets et recettes.
|
||||
- Récompenses natives d’advancements et collections du bois/de l’alchimie configurables par datapack.
|
||||
- Fabrication manuelle libre ; plans consultables avant la découverte de leur poste de travail.
|
||||
- Vue opérateur explicite, révocation des droits et retrait des recettes cachées jusque dans les favoris.
|
||||
- Transfert JEI depuis les six rangées débloquées ; cases de récupération exclues.
|
||||
- Carnet indépendant conservé au prestige, à la mort et au redémarrage, FR/EN.
|
||||
- Conserve le retrait du bouton Cosmetic et l’optimisation du terrain, vérifiée bit à bit.
|
||||
- 123 tests serveur et parcours client natif réussis ; export MRpack local vérifié.
|
||||
- [Contrat et limites](https://git.botsu.net/koka/sanctuary-beta/src/commit/c8b0684460e9bdba15d5658a6987b266fcbe7139/docs/recipes-jei-beta024.md).
|
||||
|
||||
## Détails et vérifications
|
||||
|
||||
[Recettes JEI — collections et Blocodex](https://git.botsu.net/koka/sanctuary-beta/src/commit/c8b0684460e9bdba15d5658a6987b266fcbe7139/docs/recipes-jei-beta024.md) · [Collections Minecraft — advancements, blocs, objets et recettes](https://git.botsu.net/koka/sanctuary-beta/src/commit/c8b0684460e9bdba15d5658a6987b266fcbe7139/docs/collections-minecraft.md)
|
||||
|
||||
**Environnement :** Minecraft Java **26.3-pre-2**, Fabric Loader **0.19.5**, Fabric API **0.160.0+26.3**, Java **25**. Les packs de cette livraison sont conservés localement ; ce post publie ses notes et ne met pas à jour le canal packwiz.
|
||||
@@ -1,16 +0,0 @@
|
||||
**Note de livraison rétrospective** — le tag `notes/beta.025` archive ces documents. Les archives automatiques « Code source » de ce tag contiennent des notes, pas les sources historiques du mod.
|
||||
|
||||
## Nouveautés
|
||||
|
||||
- Remplace les jauges ASCII et la colonne Max/prix par les sprites de la barre d’expérience native.
|
||||
- Prix du prochain palier centré, nombre vert et contour noir ; remplissage initial visible et barre complète au plafond.
|
||||
- Largeur adaptée au GUI, icônes et boutons conservés, explications au survol FR/EN.
|
||||
- Prix, remboursements opérateur et six rangées d’inventaire inchangés.
|
||||
- 24 tests serveur, parcours client FR/EN et export MRpack local vérifiés.
|
||||
- [Contrat et vérifications](https://git.botsu.net/koka/sanctuary-beta/src/commit/c8b0684460e9bdba15d5658a6987b266fcbe7139/docs/skill-xp-bars-beta025.md).
|
||||
|
||||
## Détails et vérifications
|
||||
|
||||
[beta.025 — barres d’expérience des compétences](https://git.botsu.net/koka/sanctuary-beta/src/commit/c8b0684460e9bdba15d5658a6987b266fcbe7139/docs/skill-xp-bars-beta025.md)
|
||||
|
||||
**Environnement :** Minecraft Java **26.3-pre-2**, Fabric Loader **0.19.5**, Fabric API **0.160.0+26.3**, Java **25**. Les packs de cette livraison sont conservés localement ; ce post publie ses notes et ne met pas à jour le canal packwiz.
|
||||
@@ -1,17 +0,0 @@
|
||||
**Note de livraison rétrospective** — le tag `notes/beta.026` archive ces documents. Les archives automatiques « Code source » de ce tag contiennent des notes, pas les sources historiques du mod.
|
||||
|
||||
## Nouveautés
|
||||
|
||||
- Remplace les rectangles par bloc et par image par une couche de texture alignée sur le terrain.
|
||||
- Repeint seulement les empreintes ou pixels connus modifiés ; réutilise les lots réseau identiques.
|
||||
- Préserve les contours, couleurs, pixels inconnus et transparence du vide ; retire les empreintes absentes du lot suivant.
|
||||
- Reprojection lors du changement de fenêtre, nettoyage des données à la révocation et libération de la texture à la fermeture.
|
||||
- Demeure activé par défaut ; règles serveur, génération et sauvegardes inchangées.
|
||||
- Build et 9 tests serveur réussis ; test graphique compilé, exécution bloquée par la session Mac verrouillée.
|
||||
- [Diagnostic et vérifications](https://git.botsu.net/koka/sanctuary-beta/src/commit/c8b0684460e9bdba15d5658a6987b266fcbe7139/docs/demeure-map-performance-beta026.md).
|
||||
|
||||
## Détails et vérifications
|
||||
|
||||
[beta.026 — affichage Demeure sans recalcul par image](https://git.botsu.net/koka/sanctuary-beta/src/commit/c8b0684460e9bdba15d5658a6987b266fcbe7139/docs/demeure-map-performance-beta026.md)
|
||||
|
||||
**Environnement :** Minecraft Java **26.3-pre-2**, Fabric Loader **0.19.5**, Fabric API **0.160.0+26.3**, Java **25**. Les packs de cette livraison sont conservés localement ; ce post publie ses notes et ne met pas à jour le canal packwiz.
|
||||
@@ -1,16 +0,0 @@
|
||||
**Note de livraison rétrospective** — le tag `notes/beta.027` archive ces documents. Les archives automatiques « Code source » de ce tag contiennent des notes, pas les sources historiques du mod.
|
||||
|
||||
## Nouveautés
|
||||
|
||||
- Permet de monter sur un joueur déjà porté et de déplacer une pile sur un nouveau porteur.
|
||||
- Compte tous les passagers au-dessus pour le ralentissement : facteur 0,75 par passager, plancher 0,25.
|
||||
- Refuse les coups entre tous les membres, les cycles et les plafonds trop bas pour le groupe.
|
||||
- Dépose du groupe supérieur et nettoyage des liens à la mort ou au déchargement.
|
||||
- Build, 27 tests serveur et MRpack local vérifiés ; essai visuel en attente.
|
||||
- [Contrat et vérifications](https://git.botsu.net/koka/sanctuary-beta/src/commit/c8b0684460e9bdba15d5658a6987b266fcbe7139/docs/player-stacks-beta027.md).
|
||||
|
||||
## Détails et vérifications
|
||||
|
||||
[beta.027 — piles de joueurs](https://git.botsu.net/koka/sanctuary-beta/src/commit/c8b0684460e9bdba15d5658a6987b266fcbe7139/docs/player-stacks-beta027.md)
|
||||
|
||||
**Environnement :** Minecraft Java **26.3-pre-2**, Fabric Loader **0.19.5**, Fabric API **0.160.0+26.3**, Java **25**. Les packs de cette livraison sont conservés localement ; ce post publie ses notes et ne met pas à jour le canal packwiz.
|
||||
@@ -1,16 +0,0 @@
|
||||
**Note de livraison rétrospective** — le tag `notes/beta.028` archive ces documents. Les archives automatiques « Code source » de ce tag contiennent des notes, pas les sources historiques du mod.
|
||||
|
||||
## Nouveautés
|
||||
|
||||
- Retire la fin automatique du plongeon après 30 ticks.
|
||||
- Termine la posture sur le sol, un mur, un plafond ou l’entrée dans un fluide.
|
||||
- Ignore les nouvelles demandes de posture pendant le plongeon, côté client et serveur.
|
||||
- Conserve une impulsion par saut, les dégâts de chute et les interruptions de sécurité.
|
||||
- Build, 32 tests serveur et MRpack local vérifiés ; essai visuel à confirmer.
|
||||
- [Contrat et vérifications](https://git.botsu.net/koka/sanctuary-beta/src/commit/c8b0684460e9bdba15d5658a6987b266fcbe7139/docs/dive-until-collision-beta028.md).
|
||||
|
||||
## Détails et vérifications
|
||||
|
||||
[beta.028 — plongeon jusqu’à collision](https://git.botsu.net/koka/sanctuary-beta/src/commit/c8b0684460e9bdba15d5658a6987b266fcbe7139/docs/dive-until-collision-beta028.md)
|
||||
|
||||
**Environnement :** Minecraft Java **26.3-pre-2**, Fabric Loader **0.19.5**, Fabric API **0.160.0+26.3**, Java **25**. Les packs de cette livraison sont conservés localement ; ce post publie ses notes et ne met pas à jour le canal packwiz.
|
||||
@@ -1,15 +0,0 @@
|
||||
**Note de livraison rétrospective** — le tag `notes/beta.029` archive ces documents. Les archives automatiques « Code source » de ce tag contiennent des notes, pas les sources historiques du mod.
|
||||
|
||||
## Nouveautés
|
||||
|
||||
- Ajoute le cri natif de l’espèce, la voix bébé et les variantes terrestres/aquatiques.
|
||||
- Ajoute un recul physique de 0,4 et une courte pause des déplacements autonomes.
|
||||
- Conserve le délai entre réactions, les collisions, le suivi, l’invulnérabilité et les protections du portage.
|
||||
- Build, 38 tests serveur et MRpack local vérifiés ; écoute et essai visuel à confirmer.
|
||||
- [Contrat et vérifications](https://git.botsu.net/koka/sanctuary-beta/src/commit/c8b0684460e9bdba15d5658a6987b266fcbe7139/docs/familiar-hit-feedback-beta029.md).
|
||||
|
||||
## Détails et vérifications
|
||||
|
||||
[beta.029 — réaction des familiers aux coups](https://git.botsu.net/koka/sanctuary-beta/src/commit/c8b0684460e9bdba15d5658a6987b266fcbe7139/docs/familiar-hit-feedback-beta029.md)
|
||||
|
||||
**Environnement :** Minecraft Java **26.3-pre-2**, Fabric Loader **0.19.5**, Fabric API **0.160.0+26.3**, Java **25**. Les packs de cette livraison sont conservés localement ; ce post publie ses notes et ne met pas à jour le canal packwiz.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user