Compare commits

..
Author SHA1 Message Date
koka c8e28d878a Publish Sanctuary beta.144 (83c377acc1) 2026-09-18 18:43:11 +02:00
koka bafff5e9fc Publish Sanctuary beta.143 (97ff2b54cb) 2026-09-18 12:42:14 +02:00
koka d661260e2b Publish Sanctuary beta.142 (ea151dd5df) 2026-09-18 05:06:17 +02:00
koka 7e6c84400f Publish Sanctuary beta.141 (93ed5574a4) 2026-09-18 04:11:22 +02:00
koka 2c360a0759 Publish Sanctuary beta.140 (6e1b3b7108) 2026-09-18 03:13:58 +02:00
koka 9f10167b6a Publish Sanctuary beta.139 (dc33ffa546) 2026-09-18 02:25:59 +02:00
koka c049423de1 Publish Sanctuary beta.138 (daf36fdeac) 2026-09-18 01:24:25 +02:00
koka 68cb39a5ed Publish Sanctuary beta.137 (2d7072b8a8) 2026-09-18 01:01:05 +02:00
koka dd5b545a7c Publish Sanctuary beta.136 (71296a9cfb) 2026-09-18 00:27:25 +02:00
koka 5b0df577fc Publish Sanctuary beta.135 (7fada570ef) 2026-09-17 23:46:41 +02:00
koka 35ec0f6e14 Publish Sanctuary beta.134 (fc4a0cb68e) 2026-09-17 22:54:53 +02:00
koka 9ceb5a5353 Publish Sanctuary beta.133 (216d3f75b5) 2026-09-17 22:18:28 +02:00
koka 9edaaedc02 Publish Sanctuary beta.132 (7e21781a9a) 2026-09-17 21:57:10 +02:00
koka efb18c274d Publish Sanctuary beta.131 (078d3e6833) 2026-09-17 21:32:35 +02:00
koka 68e06353bf Publish Sanctuary beta.130 (534dfec4f8) 2026-09-17 21:04:46 +02:00
koka 2eaa0130a4 Publish Sanctuary beta.129 (35103a7ea8) 2026-09-17 19:11:26 +02:00
koka eba01726ee Publish Sanctuary beta.128 (a4e96c0d99) 2026-09-17 18:33:20 +02:00
koka 7fd24a43e0 Publish Sanctuary beta.127 (00be1eb0d0) 2026-09-17 17:51:31 +02:00
koka 31749b95d2 Publish Sanctuary beta.126 (99860af312) 2026-09-17 17:10:34 +02:00
koka 47eb833911 Publish Sanctuary beta.125 (4ec5b0d028) 2026-09-17 16:35:59 +02:00
koka 67ebcc7d07 Publish Sanctuary beta.124 (00179e30ef) 2026-09-17 16:01:12 +02:00
koka f988d01b90 Publish Sanctuary beta.123 (4552cc347b) 2026-09-17 15:28:22 +02:00
koka 3d340bea00 Publish Sanctuary beta.122 (edc7fc3dfb) 2026-09-17 15:02:25 +02:00
koka e3e6572da9 Publish Sanctuary beta.121 (d9b3dd6bed) 2026-09-17 14:09:34 +02:00
koka e8060764a4 Publish Sanctuary beta.120 (248dff47b7) 2026-09-17 13:43:15 +02:00
koka 5e823af780 Publish Sanctuary beta.119 (3fd64e1f2a) 2026-09-17 13:15:05 +02:00
koka e6bab433be Publish Sanctuary beta.118 (0a81b3c147) 2026-09-17 13:03:39 +02:00
koka a5b75a70f8 Publish Sanctuary beta.117 (0f1efdec83) 2026-09-17 12:18:25 +02:00
koka c741870160 Publish Sanctuary beta.116 (e3ca9fcd63) 2026-09-17 07:49:15 +02:00
koka c26f3bb594 Publish Sanctuary beta.114 (21845bd736) 2026-09-17 06:57:47 +02:00
koka e4c16d517b Publish Sanctuary beta.113 (6166d0aa9e) 2026-09-17 06:38:17 +02:00
koka 573c5b0d84 Publish Sanctuary beta.112 (1eaa976ecf) 2026-09-17 03:59:24 +02:00
koka 87c9f81761 Publish Sanctuary beta.111 (289697becf) 2026-09-17 03:43:46 +02:00
koka 828353a6e2 Publish Sanctuary beta.110 (468743bfe7) 2026-09-17 03:13:06 +02:00
koka 82a6169286 Publish Sanctuary 0.1.0-alpha.30.7 (9a65e96061) 2026-09-12 20:32:20 +02:00
koka 393cc87d4d Publish Sanctuary 0.1.0-alpha.30.7 (9a65e96061) 2026-09-12 20:31:15 +02:00
koka b4a3da5c0a Publish Sanctuary 0.1.0-alpha.30.6 (fbd870a4e3) 2026-09-12 19:05:51 +02:00
koka bdaf594f61 Publish Sanctuary 0.1.0-alpha.30.5 (7f81bf35a0) 2026-09-12 16:59:20 +02:00
koka 664b0c6876 Publish Sanctuary 0.1.0-alpha.30.4 (2dc70fe905) 2026-09-12 15:56:45 +02:00
koka a68fe0df2f Publish Sanctuary 0.1.0-alpha.30.3 (076c9a6c16) 2026-09-12 13:31:22 +02:00
koka b9eac60a25 Publish Sanctuary 0.1.0-alpha.30.2 (c5381b939a) 2026-09-12 12:37:23 +02:00
koka 3d1680f9f2 Publish Sanctuary 0.1.0-alpha.30.1 (cccb89150e) 2026-09-12 11:47:17 +02:00
koka 77bc330da6 Publish Sanctuary 0.1.0-alpha.30 (bf327b75d4) 2026-09-12 10:37:07 +02:00
koka 6ec1c10c71 Publish Sanctuary 0.1.0-alpha.28 (79d3185cba) 2026-09-12 01:36:21 +02:00
koka 9803b2279a Publish Sanctuary 0.1.0-alpha.27 (392ade0b7e) 2026-09-12 00:36:29 +02:00
koka bc12e91ea7 Publish Sanctuary 0.1.0-alpha.25 (df51e17198) 2026-09-11 23:52:52 +02:00
koka bd9b4c4571 Publish Sanctuary 0.1.0-alpha.24 (27d7cba93f) 2026-09-11 18:30:00 +02:00
koka 001917854a Publish Sanctuary 0.1.0-alpha.22 (d6e0c144d0) 2026-09-10 11:52:41 +02:00
koka 8002fce039 Publish Sanctuary 0.1.0-alpha.21 (bb63fab388) 2026-09-10 10:04:22 +02:00
koka 4e0967d553 Publish Sanctuary 0.1.0-alpha.20 (0edac29dd8) 2026-09-10 01:18:43 +02:00
koka ea883b73c8 Publish Sanctuary 0.1.0-alpha.19 (c23e72b8e6) 2026-09-09 22:45:37 +02:00
koka 6e0c2979e2 Publish Sanctuary 0.1.0-alpha.17 (0969ed52da) 2026-09-09 19:14:21 +02:00
koka 927c8626a3 Publish Sanctuary 0.1.0-alpha.16 (98fc13cf0a) 2026-09-09 18:12:35 +02:00
koka 6cf54ee91f Publish Sanctuary 0.1.0-alpha.15 (dd9c795695) 2026-09-09 16:40:39 +02:00
koka dabf57ef67 Publish Sanctuary 0.1.0-alpha.14 (1c5c2c2c7e) 2026-09-09 14:00:33 +02:00
koka 7dcb33a09c Publish Sanctuary 0.1.0-alpha.13.1 (b19e93dd21) 2026-09-09 12:37:44 +02:00
koka 8080853125 Publish Sanctuary 0.1.0-alpha.13 (78b63be8e1) 2026-09-09 11:13:34 +02:00
koka 35adaa5174 Publish Sanctuary 0.1.0-alpha.12.1 (332d60be96) 2026-09-09 08:55:38 +02:00
koka 5f389ad288 Publish Sanctuary 0.1.0-alpha.12 (cdfbba0089) 2026-09-09 08:31:48 +02:00
koka 2164964d20 Publish Sanctuary 0.1.0-alpha.11 (f30a350240) 2026-09-09 06:18:07 +02:00
koka 75cb57f195 Publish Sanctuary 0.1.0-alpha.10 (ef5d1eaad9) 2026-09-09 04:14:41 +02:00
koka 1139ee6796 Publish Sanctuary 0.1.0-alpha.9 (40b655517e) 2026-09-09 02:18:52 +02:00
koka 2df9abd1ee Publish Sanctuary 0.1.0-alpha.8 (80e78e0421) 2026-09-09 00:29:55 +02:00
koka e330c70475 Publish Sanctuary 0.1.0-alpha.7 (d50d47e149) 2026-09-08 23:12:22 +02:00
koka 61064265a7 Publish Sanctuary 0.1.0-alpha.6 (f1e48fa969) 2026-09-08 19:17:01 +02:00
koka e646efb6a0 Publish Sanctuary 0.1.0-alpha.5 (f621f921a1) 2026-09-08 16:10:32 +02:00
koka 6e0ab04829 Publish Sanctuary 0.1.0-alpha.4 (6e94c1c539) 2026-09-08 14:25:14 +02:00
koka 4e73ebd790 Publish Sanctuary 0.1.0-alpha.3 (40168e2054) 2026-09-08 13:40:06 +02:00
koka 0500a00fb3 Publish Sanctuary 0.1.0-alpha.2 (ddc43d716b) 2026-09-08 12:50:44 +02:00
66 changed files with 39 additions and 4294 deletions
-15
View File
@@ -1,15 +0,0 @@
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
-9
View File
@@ -1,9 +0,0 @@
* 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
-41
View File
@@ -1,41 +0,0 @@
---
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.
-26
View File
@@ -1,26 +0,0 @@
---
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.
-17
View File
@@ -1,17 +0,0 @@
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
View File
@@ -1,17 +0,0 @@
.DS_Store
.gradle/
**/build/
**/run/
**/out/
.idea/
*.iml
*.jar
!gradle/wrapper/gradle-wrapper.jar
*.mrpack
logs/
crash-reports/
saves/
screenshots/
.env
.env.*
!.env.example
-19
View File
@@ -1,19 +0,0 @@
# 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.
-39
View File
@@ -1,39 +0,0 @@
# Changelog
## 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.
-48
View File
@@ -1,48 +0,0 @@
# 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.
-676
View File
@@ -1,676 +0,0 @@
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>.
-146
View File
@@ -1,146 +0,0 @@
# Sanctuary
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.
> 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.
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.
## Premier incrément
- 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.
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 lacs et rivières, les ruines et
les autres fonctionnalités restent à construire par tickets.
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 |
| --- | --- |
| 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.2 |
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.113.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.2.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.
-25
View File
@@ -1,25 +0,0 @@
# 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.
-20
View File
@@ -1,20 +0,0 @@
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')
}
-153
View File
@@ -1,153 +0,0 @@
# 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.
- 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é.
- 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é.
### 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.
-154
View File
@@ -1,154 +0,0 @@
# 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=0383, 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.104106 | Introduction des massifs, lacs, ravins et hauteur de dimension 384. |
| Alpha.108111 | Expériences de relief extrême, puis réduction des plateaux et pentes. |
| Alpha.116120 | Protection centrale, passage à l'île procédurale et réduction du halo. |
| Alpha.143148 | 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.
-120
View File
@@ -1,120 +0,0 @@
# 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.2.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.2/sanctuary-0.1.0-alpha.2.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`. Le ZIP préparé le fait déjà.
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 nouvelle sculpture de terrain de l'alpha.2 se teste dans un **nouveau monde**
avec le preset **Sanctuary**. 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).
-163
View File
@@ -1,163 +0,0 @@
# 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.
## 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 quatre 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. Les trous dans l'ancien noyau restent vides,
et chaque graine conserve du terrain dans l'échantillon de l'île.
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.1 » reconstitue l'ancienne formule du noyau et du bord en
utilisant les bruits actuels ; elle ne reproduit pas exactement l'ancien bruit
de bord. La colonne de droite est la densité actuelle.
- `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`.
## 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.
-296
View File
@@ -1,296 +0,0 @@
# 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.
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.
-182
View File
@@ -1,182 +0,0 @@
# 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 ses deux gradients verticaux. Reliefs, surplombs, trous et fragments
dépendent donc de la seed Minecraft.
- 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é.** Le centre conserve exactement le champ
historique dans un rayon de 128 blocs, entre `Y=32` et `Y=224`.
- 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.
- La roche disparaît progressivement avant `Y=0` et `Y=256` ; 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` 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.
Ce premier prototype ne fournit pas encore d'eau : hydrologie, répartition
complète des ressources de survie et variété de biomes constituent le prochain
ticket de terrain. Les minerais vanilla conservent leurs plages d'altitude ;
leur abondance n'est donc pas encore équilibrée pour une île flottante.
## Sculpture du pourtour — 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`.
**Tester cette version dans un nouveau monde.** Mettre à jour le pack ne remodèle
pas les chunks enregistrés ; aucun chunk n'est supprimé ou régénéré par le mod.
Continuer à explorer une ancienne sauvegarde avec ce générateur peut juxtaposer
les deux versions du terrain. Aucun contrat de migration n'est livré.
## 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`.
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 trois champs de sculpture 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 de la génération v2 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
contrôler le profil des bords et les anciens volumes forcés ; elles 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.
-13
View File
@@ -1,13 +0,0 @@
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.2
pack_version=0.1.0-alpha.2
maven_group=fr.koka.sanctuary
Binary file not shown.
-8
View File
@@ -1,8 +0,0 @@
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
Vendored
-253
View File
@@ -1,253 +0,0 @@
#!/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
View File
@@ -1,93 +0,0 @@
@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
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

+15
View File
@@ -0,0 +1,15 @@
hash-format = "sha256"
[[files]]
file = "icon.png"
hash = "33adb8f689d55b7bf8d0824b02942ea3823f128b27fab458713ab9582061a1a0"
[[files]]
file = "mods/fabric-api.pw.toml"
hash = "e3a8b560330802ec556f34a7de7a73bc712202359a657a36b39e6f85598bf8b8"
metafile = true
[[files]]
file = "mods/sanctuary.pw.toml"
hash = "657c15ae04b657da6f4591da01044330d92f3e60c6f3ade35b434bd581cd1b6f"
metafile = true
+13
View File
@@ -0,0 +1,13 @@
name = "Fabric API"
filename = "fabric-api-0.160.5+26.3.jar"
side = "both"
[download]
url = "https://cdn.modrinth.com/data/P7dR8mSH/versions/oCYG2H4L/fabric-api-0.160.5%2B26.3.jar"
hash-format = "sha512"
hash = "494378477dd9571f49c27cc1eaee3f78f140b9d8bd860c757f453bee198fd5b8a9eee444069c595b0fb6f8cb6bc86d51ccddbcec8d782fd46ef2fb1d9ac22f16"
[update]
[update.modrinth]
mod-id = "P7dR8mSH"
version = "oCYG2H4L"
+8
View File
@@ -0,0 +1,8 @@
name = "Sanctuary"
filename = "sanctuary-beta.144.jar"
side = "both"
[download]
url = "https://git.botsu.net/attachments/8cff53e2-2383-40f9-b309-e81320b818ab"
hash-format = "sha256"
hash = "1ddbe941f3262da8a1497ea5df4a992e260b80ae95b341623dd68f8f9729ae88"
-69
View File
@@ -1,69 +0,0 @@
plugins {
id 'net.fabricmc.fabric-loom'
}
group = rootProject.maven_group
version = rootProject.mod_version
base { archivesName = 'sanctuary' }
fabricApi {
configureTests {
createSourceSet = true
modId = 'sanctuary-gametest'
enableGameTests = true
enableClientGameTests = false
eula = false
}
}
// 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'))
}
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') }
// src/test contains a JavaExec assertion harness; engine tests use Fabric's gametest source set.
tasks.named('test') { failOnNoDiscoveredTests = false }
@@ -1,172 +0,0 @@
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.SETTINGS).value();
DensityFunction finalDensity = registries.lookupOrThrow(Registries.DENSITY_FUNCTION)
.getOrThrow(ResourceKey.create(Registries.DENSITY_FUNCTION, SanctuaryMod.id("final_density"))).value();
helper.assertTrue(finalDensity instanceof MainIslandDensity,
"The diagnostic must inspect the actual production island density wrapper");
MainIslandDensity island = (MainIslandDensity) finalDensity;
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 foundUnforcedCentralVoid = false;
for (long seed : new long[]{0L, 42L, 8675309L}) {
RandomState state = RandomState.create(noises, seed, settings);
boolean foundSolid = false;
for (int x = -288; x <= 288; x += 16) {
for (int z = -288; z <= 288; z += 16) {
for (int y = 4; y < 256; y += 8) {
float shaped = state.sampleBlockValueUncached(finalDensity, x, y, z);
float natural = state.sampleBlockValueUncached(island.terrain(), x, y, 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 (Math.abs(x) <= 48 && Math.abs(z) <= 48 && y >= 44 && y <= 108) {
foundUnforcedCentralVoid |= natural < 0.0F && shaped < 0.0F;
}
}
}
}
helper.assertTrue(foundSolid, "The sampled natural island must contain land for seed " + seed);
if (export) {
export(state, island, seed, seed == level.getSeed() ? spawn : null);
}
}
helper.assertTrue(foundUnforcedCentralVoid,
"The regression seeds must exercise natural holes inside the former forced core");
}
private static void export(RandomState state, MainIslandDensity island, long seed,
LevelData.RespawnData spawn) throws IOException {
Path directory = Path.of("diagnostics");
Files.createDirectories(directory);
BufferedImage picture = new BufferedImage(MAP_SIZE * 2 + 60, 1260, 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.1 (reconstructed density)", 20, 27);
graphics.drawString("Sculpted production density", 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,alpha1_top_y,sculpted_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;
float distortion = state.sampleBlockValueUncached(island.distortion(), x, 0, z);
// Stop as soon as both topmost samples have been found.
for (int y = 252; y >= 4 && (beforeTop < 0 || afterTop < 0); y -= 4) {
if (beforeTop < 0) {
float natural = state.sampleBlockValueUncached(island.terrain(), x, y, z);
if (alphaOne(x, y, z, natural, distortion) > 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, false, 715);
drawSections(graphics, state, island, true, 998);
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,
boolean alongZ, int top) {
graphics.setColor(new Color(0x14202A));
graphics.drawString(alongZ ? "Section x=0: z -304..304; y 255 (top)..0 (bottom)"
: "Section z=0: x -304..304; y 255 (top)..0 (bottom)", 20, top - 8);
for (int horizontal = -EXTENT; horizontal < EXTENT; horizontal += 2) {
int x = alongZ ? 0 : horizontal;
int z = alongZ ? horizontal : 0;
float distortion = state.sampleBlockValueUncached(island.distortion(), x, 0, z);
for (int y = 0; y < 256; y++) {
float natural = state.sampleBlockValueUncached(island.terrain(), x, y, z);
float before = alphaOne(x, y, z, natural, distortion);
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 + 255 - y, 2, 1);
graphics.setColor(new Color(after > 0.0F ? heightColor(y) : VOID_COLOR));
graphics.fillRect(MAP_SIZE + 40 + horizontal + EXTENT, top + 255 - y, 2, 1);
}
}
}
/** Exact former envelope/core formula, applied to the same seed's unchanged natural field. */
private static float alphaOne(int x, int y, int z, float natural, float distortion) {
double distance = Math.hypot((double) x, (double) z);
if (distance >= 288 || y <= 0 || y >= 256) return -1.0F;
double edge = (256.0 + Math.clamp(distortion, -1.0F, 1.0F) * 32.0 - distance) / 48.0;
double core = 1.0 - distance * distance / (96.0 * 96.0)
- (y - 76) * (double) (y - 76) / (42.0 * 42.0);
return (float) Math.clamp(Math.min(Math.max(natural, core), edge), -1.0, 1.0);
}
private static int heightColor(int y) {
if (y < 0) return VOID_COLOR;
float height = Math.clamp(y / 200.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;
}
}
@@ -1,116 +0,0 @@
package fr.koka.sanctuary.gametest;
import fr.koka.sanctuary.worldgen.SanctuarySpawn;
import fr.koka.sanctuary.worldgen.IslandShape;
import net.fabricmc.fabric.api.gametest.v1.GameTest;
import net.minecraft.core.BlockPos;
import net.minecraft.core.registries.Registries;
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 = 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.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 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.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[]{64, 96, 128, 160}) {
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();
}
}
@@ -1,43 +0,0 @@
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();
}
}
@@ -1,16 +0,0 @@
{
"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"]
},
"mixins": ["sanctuary-gametest.mixins.json"],
"depends": {
"sanctuary": "*",
"fabric-gametest-api-v1": "*"
}
}
@@ -1,7 +0,0 @@
{
"required": true,
"package": "fr.koka.sanctuary.gametest.mixin",
"compatibilityLevel": "JAVA_25",
"mixins": ["SanctuaryGameTestServerMixin"],
"injectors": {"defaultRequire": 1}
}
@@ -1,24 +0,0 @@
package fr.koka.sanctuary;
import fr.koka.sanctuary.worldgen.MainIslandDensity;
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);
LOGGER.info("Sanctuary initialized: the Sanctuary world preset is available.");
}
}
@@ -1,20 +0,0 @@
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);
}
}
@@ -1,59 +0,0 @@
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;
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 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);
// Fade out before the vertical safety bounds too. The original vertical gradients remain
// responsible for the natural profile; these guards must not cut a horizontal rock slab.
erosion += 2.0 * smoothstep((32.0 - y) / 32.0);
erosion += 2.0 * smoothstep((y - 224.0) / 32.0);
double value = Math.clamp(terrain, -1.0F, 1.0F) - erosion;
return (float) Math.clamp(value, -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);
}
}
@@ -1,74 +0,0 @@
package fr.koka.sanctuary.worldgen;
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;
/** 26.3's compiled density API; all four child functions receive Minecraft's world seed. */
public record MainIslandDensity(DensityFunction terrain, DensityFunction distortion,
DensityFunction sculpt, DensityFunction detail) 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)
).apply(instance, MainIslandDensity::new));
public MainIslandDensity(DensityFunction terrain, DensityFunction distortion) {
this(terrain, distortion, DensityFunctions.zero(), 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);
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 >= 256) {
return -1.0F;
}
return IslandShape.density(x, y, z,
terrainSampler.sampleValue(samplerContext, x, y, z),
distortionSampler.sampleValue(samplerContext, x, 0, z),
sculptSampler.sampleValue(samplerContext, x, y, z),
detailSampler.sampleValue(samplerContext, x, y, z));
}
@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));
}
@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;
}
}
@@ -1,92 +0,0 @@
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"));
private SanctuarySpawn() {}
public static boolean usesSanctuaryGenerator(ServerLevel level) {
return level.dimension() == Level.OVERWORLD
&& level.getChunkSource().getGenerator() instanceof NoiseBasedChunkGenerator generator
&& generator.generatorSettings().is(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;
}
}
@@ -1,4 +0,0 @@
{
"generator.sanctuary.sanctuary": "Sanctuary",
"biome.sanctuary.starter_forest": "Sanctuary Forest"
}
@@ -1,4 +0,0 @@
{
"generator.sanctuary.sanctuary": "Sanctuary",
"biome.sanctuary.starter_forest": "For\u00eat de Sanctuary"
}
@@ -1,6 +0,0 @@
{
"replace": false,
"values": [
"sanctuary:sanctuary"
]
}
@@ -1,175 +0,0 @@
{
"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
}
@@ -1,8 +0,0 @@
{
"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
}
@@ -1,34 +0,0 @@
{
"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
}
}
@@ -1,25 +0,0 @@
{
"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"
}
}
@@ -1,21 +0,0 @@
{
"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"
}
}
]
}
@@ -1,5 +0,0 @@
{
"base_octave": -3,
"octave_count": 2,
"amplitude_modifiers": [1.0, 0.5]
}
@@ -1,9 +0,0 @@
{
"base_octave": -7,
"octave_count": 3,
"amplitude_modifiers": [
1.0,
0.5,
0.25
]
}
@@ -1,5 +0,0 @@
{
"base_octave": -5,
"octave_count": 3,
"amplitude_modifiers": [1.0, 0.5, 0.25]
}
@@ -1,23 +0,0 @@
{
"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": []
}
@@ -1,36 +0,0 @@
{
"dimensions": {
"minecraft:overworld": {
"type": "minecraft:overworld",
"generator": {
"type": "minecraft:noise",
"biome_source": {
"type": "minecraft:fixed",
"biome": "sanctuary:starter_forest"
},
"settings": "sanctuary:sanctuary"
}
},
"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"
}
}
}
}
@@ -1,30 +0,0 @@
{
"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}"
}
}
@@ -1,11 +0,0 @@
{
"required": true,
"package": "fr.koka.sanctuary.mixin",
"compatibilityLevel": "JAVA_25",
"mixins": [
"InitialSpawnMixin"
],
"injectors": {
"defaultRequire": 1
}
}
@@ -1,131 +0,0 @@
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();
System.out.println("WorldgenSmoke: no artificial core, untouched interior, gradual 3D erosion, finite void and density bounds passed.");
}
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);
}
}
+3 -3
View File
@@ -1,13 +1,13 @@
name = "Sanctuary"
author = "KOKA99CAB"
version = "0.1.0-alpha.2"
version = "beta.144"
pack-format = "packwiz:1.1.0"
[index]
file = "index.toml"
hash-format = "sha256"
hash = "b590dd2bc0267a4e551b559d0987179d64e3b90ccb73552d694db153e9e1b1f4"
hash = "d4af92d1da96432e3940a9d0f74459aa6ab278be009571d5ee35be460d54ec1a"
[versions]
fabric = "0.19.5"
minecraft = "26.3-pre-2"
minecraft = "26.3"
-9
View File
@@ -1,9 +0,0 @@
.DS_Store
README.md
*.mrpack
logs/
crash-reports/
saves/
screenshots/
options.txt
servers.dat
-71
View File
@@ -1,71 +0,0 @@
# Pack Sanctuary et Prism Launcher
Ce dossier contient le manifeste packwiz source et Fabric API, avec une version
exacte de Minecraft et du chargeur Fabric. Les autres mods communautaires sont
des intégrations futures à vérifier par ticket.
Depuis la racine :
```sh
./gradlew check build assemblePack
```
Le dossier `build/packwiz/` contient le pack complet de développement : manifeste,
index, dépendances distantes et JAR Sanctuary construit localement. Les sources
dans ce dossier `packwiz/` ne contiennent jamais de JAR Sanctuary généré.
## Une instance Prism qui se met à jour
Le canal de distribution de Sanctuary Beta est :
```text
https://git.botsu.net/koka/sanctuary-beta/raw/branch/packwiz/pack.toml
```
Le [guide de distribution](../docs/packwiz.md) décrit sa publication et le passage
d'une instance existante à packwiz. Le fichier `packwiz/pack.toml` de la branche
source ne suffit pas pour jouer : il manque la référence du mod compilé, ajoutée
au moment de la livraison.
Pour préparer une instance importable dans Prism :
```sh
python3 scripts/pack.py prism https://git.botsu.net/koka/sanctuary-beta/raw/branch/packwiz/pack.toml
```
Importer `build/Sanctuary-Prism-auto-update.zip` dans Prism, via **Ajouter une
instance → Importer**. Conserver ensuite cette même instance : packwiz vérifie
les mises à jour avant chaque lancement. Java 25 doit être disponible dans Prism.
La génération du ZIP ne publie pas le pack et ne modifie aucune instance locale.
## Pack local et export ponctuel
Pour le servir à un installateur packwiz depuis cette machine :
```sh
cd build/packwiz
packwiz serve
```
Pour produire un fichier importable par un lanceur compatible Modrinth :
```sh
cd build/packwiz # depuis la racine du dépôt
packwiz modrinth export --output ../Sanctuary-0.1.0-alpha.2.mrpack
```
L'export contient le JAR Sanctuary local et la référence vérifiée de Fabric API.
Il ne publie rien sur Modrinth.
Utiliser l'URL HTTP affichée de `pack.toml` dans packwiz-installer. Le serveur
local doit rester actif pendant l'installation. Ce n'est pas une publication
hébergée permanente. Une installation manuelle reste possible : copier le JAR
Sanctuary construit et Fabric API dans `mods/` d'une nouvelle instance
Minecraft 26.3-pre-2 / Fabric 0.19.5 / Java 25.
Pour modifier les dépendances, utiliser packwiz ici puis `packwiz refresh`.
La cible Minecraft, Fabric et Fabric API doit rester synchronisée avec
`gradle.properties`. Le build vérifie versions et hashes de l'index.
Les resource packs et shaders en préparation restent dans leurs dossiers de
sources tant que leur ticket de distribution n'est pas terminé.
-6
View File
@@ -1,6 +0,0 @@
hash-format = "sha256"
[[files]]
file = "mods/fabric-api.pw.toml"
hash = "583fbe8be73d0f2802623bb4467a7056070d09fe2f412c39fc601b9dcf7a6fd5"
metafile = true
-13
View File
@@ -1,13 +0,0 @@
name = "Fabric API"
filename = "fabric-api-0.160.0+26.3.jar"
side = "both"
[download]
url = "https://cdn.modrinth.com/data/P7dR8mSH/versions/o9uChmGq/fabric-api-0.160.0%2B26.3.jar"
hash-format = "sha512"
hash = "0f9fb41ec3e480e7680e1640f90fb82a9298f5848c049ded3be47605615c3117fc17fc7acab59ec145dd4653422b742df5c5f137608d243d9aa4bf2d9ba2427d"
[update]
[update.modrinth]
mod-id = "P7dR8mSH"
version = "o9uChmGq"
-9
View File
@@ -1,9 +0,0 @@
# Resource packs
Sources des resource packs propres à Sanctuary. Le nom de ce dossier existant
est conservé ; le dossier installé dans Minecraft s'appelle `resourcepacks/`.
Le visuel `helloworld/assets/minecraft_title.png` est présent à l'initialisation.
Il reste une source de travail, pas encore un resource pack installable.
Les mentions, crédits, icône, écran de chargement et activation conditionnelle
de Golden Days dans la dimension Alpha seront traités par tickets distincts.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

-202
View File
@@ -1,202 +0,0 @@
#!/usr/bin/env python3
"""Validate the source pack or stage an installable packwiz directory (stdlib only)."""
import hashlib
import json
import os
from pathlib import Path
import re
import shutil
import sys
from urllib.parse import urlsplit
from urllib.request import urlopen
import zipfile
try:
import tomllib
except ImportError:
# macOS ships Python 3.9; use an installed modern Python without pip packages.
for name in ('python3.14', 'python3.13', 'python3.12', 'python3.11'):
candidate = shutil.which(name)
if candidate and Path(candidate).resolve() != Path(sys.executable).resolve():
os.execv(candidate, [candidate, *sys.argv])
raise SystemExit('Python 3.11+ is required for pack verification (tomllib).')
ROOT = Path(__file__).resolve().parents[1]
SOURCE = ROOT / 'packwiz'
BOOTSTRAP_URL = ('https://github.com/packwiz/packwiz-installer-bootstrap/releases/'
'download/v0.0.3/packwiz-installer-bootstrap.jar')
BOOTSTRAP_SHA256 = 'a8fbb24dc604278e97f4688e82d3d91a318b98efc08d5dbfcbcbcab6443d116c'
def read_toml(path):
return tomllib.loads(path.read_text(encoding='utf-8'))
def sha256(path):
return hashlib.sha256(path.read_bytes()).hexdigest()
def properties():
return dict(line.split('=', 1) for line in
(ROOT / 'gradle.properties').read_text().splitlines()
if line.strip() and not line.lstrip().startswith('#'))
def require(condition, message):
if not condition:
raise SystemExit(message)
def indexed_path(base, relative):
path = (base / relative).resolve()
require(path.is_relative_to(base.resolve()), 'Index path escapes pack: ' + relative)
require(path.is_file(), 'Missing indexed file: ' + str(path))
return path
def check_index(base):
pack = read_toml(base / 'pack.toml')
index_path = indexed_path(base, pack['index']['file'])
require(pack['index']['hash-format'] == 'sha256', 'Pack index must use sha256')
require(sha256(index_path) == pack['index']['hash'], 'Stale pack hash; run packwiz refresh')
index = read_toml(index_path)
require(index['hash-format'] == 'sha256', 'Index files must use sha256')
entries = index.get('files', [])
require(len({entry['file'] for entry in entries}) == len(entries), 'Duplicate index entry')
for entry in entries:
path = indexed_path(base, entry['file'])
require(sha256(path) == entry['hash'], 'Stale index hash: ' + entry['file'])
if entry.get('metafile'):
metadata = read_toml(path)
download = metadata['download']
require(download['url'].startswith('https://'), 'Dependency URL must use HTTPS')
algorithm = download['hash-format']
require(algorithm in ('sha256', 'sha512'), 'Dependency needs SHA-256/512')
length = 64 if algorithm == 'sha256' else 128
require(re.fullmatch('[0-9a-f]{' + str(length) + '}', download['hash']),
'Invalid dependency download hash')
return pack, entries
def check():
values = properties()
pack, entries = check_index(SOURCE)
require(pack['versions']['minecraft'] == values['minecraft_version'], 'Minecraft version drift')
require(pack['versions']['fabric'] == values['loader_version'], 'Fabric Loader version drift')
require(pack['version'] == values['pack_version'], 'Pack version drift')
api = read_toml(SOURCE / 'mods/fabric-api.pw.toml')
require(api['filename'] == 'fabric-api-' + values['fabric_api_version'] + '.jar',
'Fabric API version drift')
require(any(e['file'] == 'mods/fabric-api.pw.toml' and e.get('metafile') for e in entries),
'Fabric API is missing from the index')
print('Pack versions and index hashes verified.')
return values, entries
def https_url(value):
parsed = urlsplit(value)
require(parsed.scheme == 'https' and parsed.hostname and not parsed.username
and not parsed.password and not parsed.fragment
and not re.search(r'[\s"\'\\]', value), 'Expected a public HTTPS URL')
return value
def assemble(jar_url=None):
values, entries = check()
jar = ROOT / 'mods/sanctuary/build/libs' / ('sanctuary-' + values['mod_version'] + '.jar')
require(jar.is_file(), 'Build Sanctuary before assembling the pack: ' + str(jar))
destination = ROOT / ('build/packwiz-release' if jar_url else 'build/packwiz')
# Only this generated staging directory is replaced; never touch a game instance.
if destination.exists():
shutil.rmtree(destination)
destination.mkdir(parents=True)
staged_entries = []
for entry in entries:
source = indexed_path(SOURCE, entry['file'])
target = destination / entry['file']
target.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(source, target)
staged_entries.append(entry)
target = destination / 'mods' / ('sanctuary.pw.toml' if jar_url else jar.name)
target.parent.mkdir(parents=True, exist_ok=True)
if jar_url:
# Keep this metadata path stable so packwiz replaces the previous JAR.
target.write_text('\n'.join([
'name = "Sanctuary"', 'filename = ' + json.dumps(jar.name),
'side = "both"', '', '[download]',
'url = ' + json.dumps(https_url(jar_url)), 'hash-format = "sha256"',
'hash = ' + json.dumps(sha256(jar)), '',
]), encoding='utf-8')
staged_entries.append({'file': 'mods/sanctuary.pw.toml',
'hash': sha256(target), 'metafile': True})
else:
shutil.copy2(jar, target)
staged_entries.append({'file': 'mods/' + jar.name, 'hash': sha256(target)})
lines = ['hash-format = "sha256"', '']
for entry in sorted(staged_entries, key=lambda e: e['file']):
lines.append('[[files]]')
for key, value in entry.items():
lines.append(key + ' = ' + json.dumps(value))
lines.append('')
index_path = destination / 'index.toml'
index_path.write_text('\n'.join(lines), encoding='utf-8')
manifest = (SOURCE / 'pack.toml').read_text(encoding='utf-8')
manifest, count = re.subn(r'(?m)^hash = "[a-f0-9]+"$',
'hash = "' + sha256(index_path) + '"', manifest)
require(count == 1, 'Expected a single pack index hash')
(destination / 'pack.toml').write_text(manifest, encoding='utf-8')
check_index(destination)
print('Pack staged at ' + str(destination))
def prism(pack_url):
"""Create an importable launcher template; never modify an installed instance."""
https_url(pack_url)
values, _ = check()
with urlopen(BOOTSTRAP_URL, timeout=60) as response:
bootstrap = response.read()
require(hashlib.sha256(bootstrap).hexdigest() == BOOTSTRAP_SHA256,
'Unexpected packwiz bootstrap SHA-256')
command = ('"$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" '
+ pack_url)
# Prism instance.cfg uses Qt INI escaping for quotation marks.
config = '\n'.join([
'[General]', 'ConfigVersion=1.3', 'InstanceType=OneSix',
'name=Sanctuary Beta', 'iconKey=default', 'AutomaticJava=true',
'OverrideJavaLocation=false', 'IgnoreJavaCompatibility=false',
'OverrideMemory=true', 'MinMemAlloc=512', 'MaxMemAlloc=4096',
'ManagedPack=false', 'OverrideCommands=true', 'LogPrePostOutput=true',
'PreLaunchCommand=' + command.replace('"', '\\"'),
'notes=Sanctuary Beta : mise a jour packwiz a chaque lancement.', '',
])
components = {
'formatVersion': 1,
'components': [
{'uid': 'net.minecraft', 'version': values['minecraft_version'], 'important': True},
{'uid': 'net.fabricmc.fabric-loader', 'version': values['loader_version']},
],
}
destination = ROOT / 'build/Sanctuary-Prism-auto-update.zip'
destination.parent.mkdir(parents=True, exist_ok=True)
with zipfile.ZipFile(destination, 'w', zipfile.ZIP_DEFLATED) as archive:
archive.writestr('instance.cfg', config)
archive.writestr('mmc-pack.json', json.dumps(components, indent=2) + '\n')
archive.writestr('minecraft/packwiz-installer-bootstrap.jar', bootstrap)
print('Prism auto-update template: ' + str(destination))
print('Pre-launch command: ' + command)
if __name__ == '__main__':
require((len(sys.argv) == 2 and sys.argv[1] in ('check', 'assemble'))
or (len(sys.argv) == 3 and sys.argv[1] in ('release', 'prism')),
'Usage: python3 scripts/pack.py check|assemble|release <jar-https-url>|prism <pack-https-url>')
if sys.argv[1] == 'release':
assemble(https_url(sys.argv[2]))
elif sys.argv[1] == 'prism':
prism(sys.argv[2])
elif sys.argv[1] == 'assemble':
assemble()
else:
check()
-213
View File
@@ -1,213 +0,0 @@
#!/usr/bin/env python3
"""Publish a verified build to Gitea and advance the metadata-only packwiz branch.
This command publishes externally. Run check/build/assemblePack, commit, and push
the source branch first. Git's configured credential helper supplies credentials
in memory; no token is accepted on the command line or written to disk.
"""
import argparse
import base64
import hashlib
import json
from pathlib import Path
import shutil
import subprocess
import sys
import tempfile
from urllib.error import HTTPError, URLError
from urllib.parse import quote, urlsplit
from urllib.request import HTTPRedirectHandler, Request, build_opener, urlopen
import uuid
import zipfile
sys.dont_write_bytecode = True
import pack
ROOT = pack.ROOT
CHANNEL = 'packwiz'
def git(*args, cwd=ROOT, input_text=None):
result = subprocess.run(['git', *args], cwd=cwd, input=input_text,
text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
pack.require(result.returncode == 0, 'Git operation failed: ' + ' '.join(args[:2]))
return result.stdout.strip()
class NoRedirect(HTTPRedirectHandler):
# Never forward an Authorization header to a redirected URL.
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
class Gitea:
def __init__(self, origin):
parsed = urlsplit(pack.https_url(origin))
pack.require(not parsed.query, 'Git remote URL must not contain query parameters')
path = parsed.path.removesuffix('.git').strip('/')
pack.require(len(path.split('/')) == 2, 'Expected an owner/repository Git URL')
self.repo_url = f'https://{parsed.netloc}/{path}'
self.api_url = f'https://{parsed.netloc}/api/v1/repos/{path}'
result = subprocess.run(['git', 'credential', 'fill'], cwd=ROOT,
input='url=' + origin + '\n\n', text=True,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
pack.require(result.returncode == 0, 'Git credential helper did not supply credentials')
credentials = dict(line.split('=', 1) for line in result.stdout.splitlines() if '=' in line)
pack.require(credentials.get('username') and credentials.get('password'),
'Git credential helper needs a username and password/token')
token = credentials['username'] + ':' + credentials['password']
self.authorization = 'Basic ' + base64.b64encode(token.encode()).decode()
self.opener = build_opener(NoRedirect)
def request(self, endpoint, method='GET', value=None, missing_ok=False,
content_type='application/json'):
data = value if isinstance(value, bytes) else (
json.dumps(value).encode() if value is not None else None)
request = Request(self.api_url + endpoint, data=data, method=method,
headers={'Authorization': self.authorization,
'Accept': 'application/json', 'Content-Type': content_type})
try:
with self.opener.open(request, timeout=60) as response:
body = response.read()
return json.loads(body) if body else None
except HTTPError as error:
if missing_ok and error.code == 404:
return None
# Do not include request/response headers or credential-helper output.
raise SystemExit(f'Gitea {method} {endpoint.split("?")[0]} failed: HTTP {error.code}')
except URLError:
raise SystemExit('Gitea connection failed; no distribution channel was advanced.')
def upload(self, release, path):
filename = path.name
pack.require(not any(c in filename for c in '\r\n"'), 'Invalid asset filename')
existing = [asset for asset in release['assets'] if asset['name'] == filename]
pack.require(len(existing) <= 1, 'Duplicate release asset: ' + filename)
if existing:
asset = existing[0]
else:
boundary = 'sanctuary-' + uuid.uuid4().hex
payload = (f'--{boundary}\r\nContent-Disposition: form-data; '
f'name="attachment"; filename="{filename}"\r\n'
'Content-Type: application/octet-stream\r\n\r\n').encode()
payload += path.read_bytes() + f'\r\n--{boundary}--\r\n'.encode()
asset = self.request(f'/releases/{release["id"]}/assets?name={quote(filename)}',
'POST', payload,
content_type='multipart/form-data; boundary=' + boundary)
release['assets'].append(asset)
asset_url = pack.https_url(asset['browser_download_url'])
# Verify anonymous access: players must not need publishing credentials.
with urlopen(asset_url, timeout=60) as response:
digest = hashlib.sha256(response.read()).hexdigest()
pack.require(digest == pack.sha256(path),
'Immutable release asset conflicts with local build: ' + filename)
return asset_url
def remote_tag_commit(tag):
refs = git('ls-remote', 'origin', 'refs/tags/' + tag, 'refs/tags/' + tag + '^{}')
found = dict(reversed(line.split('\t', 1)) for line in refs.splitlines())
return found.get('refs/tags/' + tag + '^{}') or found.get('refs/tags/' + tag)
def publish_channel(origin, version, source_head):
staged = ROOT / 'build/packwiz-release'
pack.check_index(staged)
files = [path for path in staged.rglob('*') if path.is_file()]
pack.require(all(path.suffix == '.toml' for path in files),
'Distribution branch must contain only generated TOML metadata')
remote = git('ls-remote', '--heads', 'origin', 'refs/heads/' + CHANNEL)
with tempfile.TemporaryDirectory(prefix='sanctuary-pack-channel-') as temp:
directory = Path(temp)
git('init', '--quiet', cwd=directory)
git('remote', 'add', 'origin', origin, cwd=directory)
# A source checkout may use Git's inferred identity without user.* config.
# Attribute generated metadata to the verified source commit's author.
git('config', 'user.name', git('show', '-s', '--format=%an', source_head), cwd=directory)
git('config', 'user.email', git('show', '-s', '--format=%ae', source_head), cwd=directory)
if remote:
git('fetch', '--quiet', 'origin', CHANNEL, cwd=directory)
git('checkout', '--quiet', '-b', CHANNEL, 'FETCH_HEAD', cwd=directory)
tracked = git('ls-files', cwd=directory).splitlines()
pack.require(all(name.endswith('.toml') for name in tracked),
'Existing distribution branch contains unexpected files')
for name in tracked:
(directory / name).unlink()
else:
git('checkout', '--quiet', '--orphan', CHANNEL, cwd=directory)
for path in files:
target = directory / path.relative_to(staged)
target.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(path, target)
git('add', '--all', cwd=directory)
if git('status', '--porcelain', cwd=directory):
git('commit', '--quiet', '-m',
f'Publish Sanctuary {version} ({source_head[:12]})', cwd=directory)
# No force: reject a concurrent publication instead of overwriting it.
git('push', '--quiet', 'origin', 'HEAD:refs/heads/' + CHANNEL, cwd=directory)
else:
print('Distribution metadata already matches this release.')
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--notes-file', type=Path, help='UTF-8 release notes; otherwise a short build reference')
parser.add_argument('--asset', action='append', type=Path, default=[],
help='Additional public release asset, e.g. the generated .mrpack (repeatable)')
args = parser.parse_args()
pack.require(not git('status', '--porcelain'), 'Commit all source changes before publishing')
branch = git('symbolic-ref', '--quiet', '--short', 'HEAD')
pack.require(branch != CHANNEL, 'Publish from a source branch, not the distribution branch')
head = git('rev-parse', 'HEAD')
remote = git('ls-remote', '--heads', 'origin', 'refs/heads/' + branch)
pack.require(remote and remote.split()[0] == head, 'Push the current source branch before publishing')
values, _ = pack.check()
version = values['pack_version']
jar = ROOT / 'mods/sanctuary/build/libs' / ('sanctuary-' + values['mod_version'] + '.jar')
pack.require(jar.is_file(), 'Run ./gradlew check build assemblePack first')
with zipfile.ZipFile(jar) as archive:
mod = json.loads(archive.read('fabric.mod.json'))
pack.require(mod['id'] == 'sanctuary' and mod['version'] == values['mod_version'],
'Built JAR does not match the Sanctuary source version')
assets = [jar] + [path.resolve() for path in args.asset]
pack.require(all(path.is_file() for path in assets), 'A requested release asset is missing')
pack.require(len({path.name for path in assets}) == len(assets), 'Release asset names must be unique')
notes = args.notes_file.read_text(encoding='utf-8') if args.notes_file else (
f'Sanctuary {version}\n\nMinecraft {values["minecraft_version"]}; '
f'Fabric {values["loader_version"]}.\n\nSource: {head}.\n')
origin = git('remote', 'get-url', 'origin')
gitea = Gitea(origin)
tag = 'v' + version
existing_commit = remote_tag_commit(tag)
pack.require(existing_commit is None or existing_commit == head,
'Release tag already belongs to another source commit; increment the version')
release = gitea.request('/releases/tags/' + quote(tag), missing_ok=True)
if release is None:
release = gitea.request('/releases', 'POST', {
'tag_name': tag, 'target_commitish': head, 'name': 'Sanctuary ' + version,
'body': notes, 'draft': False, 'prerelease': True,
})
pack.require(not release.get('draft'), 'The existing release is a draft; publish it explicitly first')
pack.require(remote_tag_commit(tag) == head, 'Release tag must resolve to the verified source commit')
jar_url = gitea.upload(release, jar)
for asset in assets[1:]:
gitea.upload(release, asset)
pack.assemble(jar_url)
publish_channel(origin, version, head)
stable_url = gitea.repo_url + '/raw/branch/' + CHANNEL + '/pack.toml'
with urlopen(stable_url, timeout=60) as response:
remote_manifest = response.read()
pack.require(remote_manifest == (ROOT / 'build/packwiz-release/pack.toml').read_bytes(),
'Published manifest differs from this build; inspect the distribution channel')
for path in (ROOT / 'build/packwiz-release').rglob('*.toml'):
relative = path.relative_to(ROOT / 'build/packwiz-release').as_posix()
with urlopen(stable_url.removesuffix('pack.toml') + quote(relative), timeout=60) as response:
published = response.read()
pack.require(published == path.read_bytes(), 'Published metadata differs: ' + relative)
print('Published Sanctuary ' + version)
print('Release: ' + gitea.repo_url + '/releases/tag/' + tag)
print('Packwiz: ' + stable_url)
if __name__ == '__main__':
main()
-11
View File
@@ -1,11 +0,0 @@
pluginManagement {
repositories {
maven { url = 'https://maven.fabricmc.net/' }
mavenCentral()
gradlePluginPortal()
}
}
rootProject.name = 'sanctuary-beta'
include('sanctuary')
project(':sanctuary').projectDir = file('mods/sanctuary')
-5
View File
@@ -1,5 +0,0 @@
# Shader packs
Emplacement des sources et réglages propres à Sanctuary. Aucun shader n'est
encore distribué. Les shaders externes seront ajoutés au manifeste packwiz
avec version, URL, hash, licence et compatibilité vérifiée pour le jeu et Iris.