Compare commits

...
Author SHA1 Message Date
koka 6c76df7d9f Document beta.203 backup and single-mod consolidation audit
Build Sanctuary / build (push) Waiting to run
2026-10-04 23:01:17 +02:00
koka 454d9a5c08 Add fractured northern relief and giant taiga for beta.203
Build Sanctuary / build (push) Canceled after 0s
2026-10-04 13:13:17 +02:00
koka ba1b228e76 Refine northern ecology and snowy igloo placement for beta.202 2026-10-04 12:42:16 +02:00
koka 1d60a3eedc feat: compact northern glacier and use vanilla ice spikes in beta.201 2026-10-04 12:05:38 +02:00
koka 16d81d892f feat: deliver beta.200 North expansion and igloo village 2026-10-04 05:52:20 +02:00
koka 7497d9182d beta.199: connect runtime expansions and synchronize the stellar week 2026-10-01 21:38:44 +02:00
koka 6b97d57bde Fix seed-dependent central gallery creation in beta.198 2026-10-01 20:02:28 +02:00
koka 7c14696020 beta.197: repair island arrival, dungeon details and seed-dependent gallery creation 2026-10-01 19:37:37 +02:00
koka 39ff7e95d5 beta.196: share the current island lab in three sizes and profile loading 2026-10-01 18:40:01 +02:00
koka 918143aaa6 beta.195: hide legacy lab presets and open the seed zero visit 2026-10-01 18:02:21 +02:00
koka 9da3f7f13b beta.194: remove Friends from the title menu and pause terrain work 2026-10-01 01:42:49 +02:00
koka 7179b5d4cb beta.193: protect worldgen water and add native multiseed photo comparisons 2026-10-01 01:29:17 +02:00
koka 78f047d3dc beta.192: sculpt local summit crowns and rocky mountain faces 2026-09-30 22:34:43 +02:00
koka c7f32c9416 beta.191: restore pre-terrace relief and original surface geology 2026-09-30 21:47:10 +02:00
koka 97ea94e482 beta.190: restore organic relief, widen spillways and stock ruin chests 2026-09-30 21:05:30 +02:00
koka 51e7e2590e beta.189: fit refuges to terrain and finish shores, terraces and cave ruins 2026-09-30 20:31:06 +02:00
koka 658aaf8732 beta.188: add living lake beds, sulfur details and enchanted iron loot 2026-09-30 09:04:25 +02:00
koka c53b39e71c Integrate beta.187 natural sulfur caves, surface lakes and oriented anchors 2026-09-30 08:41:38 +02:00
koka f40382ad04 Build beta.186 ornamental rosettes and eight terrain-bound anchors 2026-09-30 08:09:11 +02:00
koka a067c7e66b Build beta.185 fine stem and integrated palace roof portal 2026-09-30 07:33:49 +02:00
koka 6cfa4c07bf Build beta.184 natural sky palace and fill retained terrain cavities 2026-09-30 03:46:00 +02:00
koka 1ca01a47b7 Confirm beta.183 solo entry and spectator visit 2026-09-30 03:12:37 +02:00
koka a46946bd4c Add beta.183 island discoveries and adaptive ISS atlas 2026-09-30 03:10:10 +02:00
koka 65e7127b1d Add beta.182 natural lower gallery and eight-axis glass rosette 2026-09-30 02:23:35 +02:00
koka 5f32455f79 Add beta.181 aerial origin landmark in an isolated lab preset 2026-09-30 02:07:09 +02:00
koka 459420503e Add beta.180 connected cave basins, cherry landmarks and mine dungeon 2026-09-30 00:29:23 +02:00
koka a82fc559b1 Record successful beta.179 full validation 2026-09-30 00:10:13 +02:00
koka 5a74ba57ed Add beta.179 living cave districts and underground complexes 2026-09-29 23:16:34 +02:00
koka db6194c3c0 Add beta.178 rocky ecology laboratory and solo checks 2026-09-29 23:00:10 +02:00
koka 9613322c6b beta.177: tie lab ecology to island relief 2026-09-29 22:26:42 +02:00
koka 96433d3306 feat: add beta.176 ecology laboratory 2026-09-29 20:33:41 +02:00
koka ba4cf19233 wip: preserve beta.175 sky laboratory and visit findings 2026-09-29 17:10:16 +02:00
koka 968c8d7a43 Add isolated fast Sanctuary terrain lab for beta.174 2026-09-29 15:58:14 +02:00
koka b9727dfa41 Reframe Storyquest around independent geography and playable routes 2026-09-29 15:27:40 +02:00
koka 63e4dab140 Add seeded origin palace and eight material anchors for beta.173 2026-09-28 12:40:08 +02:00
koka 9f993989b0 Define terrain-adapted origin palace and vertical Storyquest landmarks 2026-09-28 02:06:31 +02:00
koka 24c3315f56 Frame beta.173 Storyquest rework from beta.172 2026-09-27 14:51:15 +02:00
koka 55c745a46d Allow beta.172 horde cards at runtime 2026-09-26 21:07:55 +02:00
koka 43ac3c7c5f Add beta.171 dimensional horde families 2026-09-26 20:23:32 +02:00
koka 6e2336db9d Add beta.170 native discoverable horde maps 2026-09-26 18:57:34 +02:00
koka 3faea2c9af Add beta.169 accelerating mixed horde invasions 2026-09-26 02:47:54 +02:00
koka 8ab5a45bd3 Add beta.168 consumable horde maps with spontaneous combat and mob loot 2026-09-26 00:34:40 +02:00
koka fff288f27e Allow local horde lab joins and record main integration 2026-09-25 20:24:06 +02:00
koka 0315fe2b05 Add beta.167 cooperative horde arena laboratory prototype 2026-09-25 20:23:05 +02:00
koka c2dfa9e171 Record adventure card families and round arena lab concept 2026-09-25 19:31:13 +02:00
koka b49b3e2549 Record cooperative quests and familiar XP incubation concept 2026-09-25 19:03:30 +02:00
koka e382a65aae Document community economy and adventure direction for beta.167 2026-09-25 18:14:35 +02:00
koka 7979f33028 Document beta.144–166 release backfill and integrated Chris PR
Build Sanctuary / build (push) Canceled after 0s
2026-09-24 20:01:06 +02:00
koka 460c4706d3 beta.166: integrate resumable web registration and prepare fresh intro lab
Build Sanctuary / build (push) Canceled after 0s
2026-09-24 17:41:29 +02:00
koka ad960ef9bc Merge Chris access-code registration onto beta.165 2026-09-24 17:17:26 +02:00
koka 835e6116e6 Review Chris access-code PR integration and recovery gap 2026-09-24 17:10:51 +02:00
koka aadff6fd5e Refresh legacy GameTests against current gameplay contracts 2026-09-24 17:10:51 +02:00
koka 705fe9c1a0 Audit all 23 known beta.165 GameTest failures 2026-09-24 16:56:30 +02:00
koka dbb9e3428d Lay out Gazette article and conversation for beta.165
Build Sanctuary / build (push) Canceled after 0s
2026-09-24 16:34:38 +02:00
koka 9a429c40a1 Align pause calendar and server bulletin for beta.164
Build Sanctuary / build (push) Canceled after 0s
2026-09-24 13:56:27 +02:00
koka 905b3bcfa1 Reshape pause navigation and frame for beta.163
Build Sanctuary / build (push) Canceled after 0s
2026-09-24 13:36:56 +02:00
koka 0c55a37562 Deliver beta.162 notice author scene and side conversation
Build Sanctuary / build (push) Canceled after 0s
2026-09-24 00:38:09 +02:00
koka 8deba7cfb4 Deliver beta.161 playtest fixes and shared server metrics
Build Sanctuary / build (push) Canceled after 0s
2026-09-24 00:20:06 +02:00
koka c1d2278674 beta.160: opt-in shaders and lightweight local duo test lab
Build Sanctuary / build (push) Canceled after 0s
2026-09-23 22:27:49 +02:00
ChrisM-PekandCursor 8de569bbf1 Add access-code gate to Hello World creation (beta.144).
Build Sanctuary / build (push) Canceled after 0s
Build Sanctuary / build (pull_request) Canceled after 0s
Require a site-issued SANC code when the server has app URL and API token configured, with replay-safe ledger binding and FR/EN UI including galactic code display.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-22 03:03:48 +02:00
519 changed files with 43951 additions and 243 deletions
+69
View File
@@ -1,5 +1,74 @@
# beta.172 — cartes de horde utilisables en jeu
- Suppression de la restriction au cercle laboratoire : une carte peut ouvrir
une invasion à la position du joueur dans tout monde chargé.
- Activation autorisée en Survie et en Créatif, avec consommation serveur et
synchronisation immédiate de l'inventaire dans les deux modes.
- Les joueurs créatifs restent participants aux invasions de carte ; le socle
historique conserve ses anciennes règles d'inscription.
- Les apparitions cherchent un sol sûr jusqu'à six blocs au-dessus ou en dessous
du point prévu, sans écrire de bloc ni charger de chunk supplémentaire.
- [Contrat et vérifications](docs/horde-runtime-beta172.md).
# beta.171 — familles de hordes dimensionnelles
- Chaque carte possède une famille majoritaire stable : zombies, squelettes,
creepers, arthropodes, pillards, cauchemars, infernaux, piglins, flétris ou End.
- La dimension de découverte favorise ses familles et espèces ; des créatures
locales aléatoires et de rares intrus interdimensionnels cassent la régularité.
- Catalogue porté à 34 monstres terrestres ou volants, du zombie au blaze, au
ghast et au shulker ; les boss et créatures strictement aquatiques sont exclus.
- Illustration divisée : file exacte des apparitions en haut, toutes les piles
de butin garanties en bas, avec chevauchement lorsque nécessaire.
- Le vieux socle rejoint la carte native dans le langage de particules et
n'émet plus de messages Horde dans le chat ou la barre d'action.
- [Contrat et vérifications](docs/horde-families-beta171.md).
# beta.170 — carte de horde native et langage visuel
- Nouvel objet `sanctuary:horde_trial_card` fondé sur la carte native 26.3,
visible dans l'onglet créatif Outils et utilitaires.
- Chaque exemplaire vierge tenu en main découvre une identité unique et stable ;
les copies d'une carte découverte gardent son identité.
- Révélation, activation, progression, refus et fin d'invasion passent par des
connexions et glyphes de particules, sans message Sanctuary dans le chat.
- Compatibilité conservée avec les cartes remplies beta.168/beta.169.
- [Contrat et vérifications](docs/horde-map-native-beta170.md).
# beta.169 — invasion continue des cartes de horde
- Les cartes sont inconnues avant leur prise en main et se révèlent durablement.
- Remplace les trois vagues par 12, 18 ou 27 arrivées individuelles qui accélèrent.
- Mélange jusqu’à dix espèces selon la difficulté ; dessin et butins suivent le roster réel.
- Butins généreux propres aux monstres, rubis et saphirs inclus ; anciennes cartes préservées.
- Runes SGA natives, combat spontané élargi et interruptions diagnostiquées.
- [Contrat et vérifications](docs/horde-invasion-beta169.md).
# beta.168 — cartes de horde (prototype local)
- Trois cartes natives illustrées et consommables ; invocation immédiate sans socle.
- Participation spontanée et butins physiques sur les zombies, ramassage libre.
- Nouveau labo isolé beta.168 ; cercle beta.167 conservé pour les futurs donjons.
- [Contrat et vérifications](docs/horde-cartes-beta168.md).
# Changelog
## beta.167 — Carte de horde et socle d'épreuve, prototype local
- Arène circulaire dans un nouveau laboratoire, scène préservée aux redémarrages.
- Carte réutilisable, inscriptions de 1 à 4 joueurs, préparation et lancement
explicites, trois vagues de zombies et résultat commun.
- Nettoyage des monstres à l'interruption et reprise du labo au repos ; aucune
récompense économique, aucun changement de monde Sanctuary existant.
- [Contrat et vérifications](docs/horde-lab-beta167.md).
## beta.144 — Code d’accès à la création
- Hello World demande le code `SANC-XXXX-XXXX` quand le serveur a l’URL et le jeton du site.
- Le client ne contacte pas le site. Le serveur vérifie, puis consomme le code au moment de créer l’habitant.
- Le lien Discord est enregistré à part. Une création interrompue après `redeemed` peut se terminer sans nouveau code si le compte est encore connu.
- [Contrat](docs/access-code-beta144.md).
## beta.110 — Atelier d’argile inclus
- Une seule livraison réunit l’atelier d’argile, les statuaires et l’import GLB de beta.106 avec les œufs et la neige saisonnière de beta.107 à beta.109.
+387 -14
View File
@@ -1,37 +1,410 @@
# Sanctuary
## beta.159 — recherche communautaire et galerie adaptable (pack local)
## beta.203 — fractures et taïga géante au Nord
[Dernière passe du Nord](docs/north-fracture-beta203.md) : variations de relief
et ouvertures par bruit 3D, lisières de podzol irrégulières, épicéas pouvant
atteindre 64 blocs. La neige ajoutée après végétation est retirée ; les sommets
mêlent roche, neige et plusieurs glaces. Bassins protégés, cinq igloos conservés,
aucun nouveau village. Profils neufs seulement, Nord 1024.
## beta.202 — taïga et neige étagée au Nord
[Écologie nordique](docs/north-ecology-beta202.md) : relief et lacs 201 conservés,
vallées ouvertes et taïga sur herbe/podzol, grands épicéas et pics de glace natifs.
La neige varie avec l'altitude et le climat ; la glace des grottes reste derrière
une enveloppe rocheuse. Le village cherche une plaine entièrement enneigée.
Profils neufs uniquement, expansion de 1024 blocs.
## beta.201 — Nord compact et pics de glace vanilla
[Glacier nordique](docs/north-glacier-beta201.md) : diamètre ramené à 1024,
glace visible, deux lacs, bosquets d'épicéas et pics natifs `ice_spike` / `ice_patch`.
Le village de cinq igloos et son laboratoire sont conservés et rapprochés du lac.
Profils neufs uniquement ; les paysages froids des diagonales restent à concevoir.
## beta.200 — premier Nord glacial
[Expansion nordique](docs/north-expansion-beta200.md) : territoire de 2048 blocs,
crêtes enneigées, taïga, cavernes de glace, lacs gelés et village de cinq igloos,
dont un laboratoire souterrain. Placement dans la direction de l’ancre ; arrivée
et relais préparés, puis génération pendant l’exploration. Nouveaux mondes uniquement.
Export local : [MRpack de test](build/Sanctuary-Test-beta.200.mrpack).
Compilation, contrôles ciblés et activation/reprise du Nord vérifiés ; la suite
historique complète reste non validée, avec le détail dans le ticket.
## beta.199 — relais d’expansion en cours de partie
[Affinage du moteur existant](docs/runtime-expansions-beta199.md) : recherche
automatique proche sur 360°, régions minces, relais de continuation, reprise
après fermeture et annonce SGA à la fin. Nouveaux mondes de laboratoire
Small/Medium/Large ; étoiles synchronisées sur sept journées Minecraft.
## beta.198 — correction du plantage de création Large
[Admission de la galerie centrale](docs/gallery-generation-beta198.md) :
correction du défaut reproduit avec la graine Windows fournie, en conservant
le portail au centre et les anciens profils. Nouveaux mondes uniquement.
## beta.197 — arrivée et détails du laboratoire
[Corrections du laboratoire](docs/worldgen-fixes-beta197.md) : départ sur
l’île, cascades ouvertes vers l’extérieur, sol minier sans diagonales répétées
et toit de cabane continu. Nouveaux profils Small/Medium/Large ; enquête séparée
sur l’arrêt signalé chez un ami à la création Large.
## beta.196 — laboratoire à partager
[Pack de test Small, Medium et Large](docs/shared-lab-beta196.md) : une entrée
Sanctuary, taille personnalisable, dernier terrain du labo et atlas adapté.
Diagnostic du chargement et enregistrement JFR optionnel ; anciens mondes conservés.
## beta.195 — choix de génération simplifiés
[Sanctuary et les types vanilla](docs/worldgen-options-beta195.md) restent dans
la création de monde ; les variantes de labo sont masquées, leurs sauvegardes
restent lisibles. Nouvelle visite demandée sur le profil beta.193, graine 0,
avec le menu principal sans Amis. Le terrain est conservé pour cette comparaison.
## beta.194 — menu principal sans Amis
[Reprise des interfaces](docs/main-menu-beta194.md) : Solo et Multijoueur sont suivis
directement d’Options et Quitter, sans bouton Amis ni ligne vide. Le
[fil rouge](docs/storyquest-fil-rouge.md) reprend les interfaces et le Storyquest ;
les retouches de génération et les séries de captures sont suspendues.
## beta.193 — raccordements et comparaison visuelle
[Laboratoire sur plusieurs graines](docs/worldgen-continuity-beta193.md) : réservations
communes pour les bassins, ancres aériennes posées sur le terrain réel et
entailles locales du massif. Le nouveau profil `coherent` ajoute des contrôles
de collision entre aménagements et des visites photo Vulkan reproductibles.
Les anciens profils et sauvegardes restent séparés.
## beta.192 — couronnes et faces du massif
[Variante de relief érodé](docs/eroded-massif-beta192.md) : sommets localement
plus plats, faces rocheuses plus raides et entailles entre les reliefs.
Profil neuf `eroded`, graine 42 ; couverture végétale selon la pente réelle.
Les cavités profondes et les aménagements existants restent conservés.
## beta.191 — relief d’avant les plateaux
[Retour à la base beta.188](docs/restore-relief-beta191.md) : relief et géologie
antérieurs aux plateaux, passes de roche 189/190 retirées. Les corrections
indépendantes des berges, ancres, ruines et du portail restent présentes.
Profil neuf `original`, graine 42 ; génération, réouverture, 265 GameTests et
assemblages validés.
## beta.190 — retour au relief naturel
[Affinage de Sanctuary Island](docs/natural-refinement-beta190.md) : fin des
terrasses systématiques, épaules locales et affleurements sans cases de quatre
blocs. Déversoir proche de cinq blocs de large, coffres de fer enchanté dans les
quatre ruines. Profil `refined`, nouveau solo graine 42, vue 32 chunks.
Les reliefs très étagés passent dans la réserve des expansions futures.
## beta.189 — berges, reliefs et portail inférieur
[Finition de la génération](docs/terrain-finish-beta189.md) : refuges de surface
adaptés à la roche, sédiments selon profondeur et pente, berges sableuses,
plantations d’arbres protégées, reliefs étagés et affleurements. Portail inférieur
éclairé, cascade locale et quatre ruines sans excavation. Profil `finish`, nouvelle
sauvegarde, graine 42. 265 GameTests, génération, réouverture et assemblages réussis.
## beta.188 — fonds d’étangs et soufre vivant
[Laboratoire de finition](docs/living-details-beta188.md) : refuges adaptés
au milieu et à leur couleur, fonds mêlant argile, boue, sable et gravier,
végétation aquatique, pics et geysers natifs. Wagonnets avec une ou deux
pièces en fer enchanté. Profil `details`, monde neuf, graine 42.
265 GameTests, contrôles natifs et assemblages réussis ; solo Vulkan ouvert
à 32 chunks. Les anciennes sauvegardes restent inchangées.
## beta.187 — cavités de soufre et bassins de surface
[Nouveau laboratoire](docs/natural-caves-beta187.md) : soufre intégré aux
cavités existantes, trois bassins ouverts au ciel et huit ancres orientées
vers leur expansion, avec approche depuis le centre. Profil `natural`,
graine 42, nouvelle sauvegarde. Génération et réouverture vérifiées,
265 GameTests et assemblages réussis ; solo Vulkan ouvert à 32 chunks.
## beta.186 — rosaces et huit refuges d'ancres
[Nouveau laboratoire](docs/hidden-anchors-beta186.md) : balcon supérieur sans
barrières, liserés incrustés dans les deux dômes et huit ancres adaptées au terrain
de leur secteur. Dépôts distincts reliés aux gemmes du Bugrock. Profil `anchors`,
graine 42, nouvelle sauvegarde. Génération et réouverture vérifiées, 265 GameTests
et assemblages réussis ; solo Vulkan ouvert à 32 chunks.
## beta.185 — tige fine et portail de toiture
[Variante de laboratoire](docs/fine-stem-beta185.md) : ligne de calcite d’un bloc,
sans escalier ni paliers, fleur conservée, portail horizontal incrusté au sommet
et jour fermé sous les vitres du palais. Profil `stem`, monde neuf, graine 42.
265 GameTests, contrôles natifs et assemblages réussis ; solo Vulkan ouvert
à 32 chunks, départ dans le palais supérieur.
## beta.184 — ascension naturelle
[Nouveau laboratoire](docs/natural-ascent-beta184.md) : rosace et Bugrock à Y=640,
tige parcourable, palais vitré vers Y=1280 avec atlas au sol et portail horizontal.
La couronne se découvre en montant. Les bassins supérieurs dessinés sont remplacés
par le remplissage de creux existants. Profil `ascent`, nouveau monde uniquement.
265 GameTests, contrôles natifs et assemblages réussis ; solo Vulkan ouvert à
32 chunks, départ sur la rosace.
## beta.183 — lieux et ressources de l’île
[Nouveau laboratoire](docs/island-discovery-beta183.md) : portail horizontal,
secteurs de gemmes, soufre repérable, bassin supérieur, ISS et rosace aux huit
couleurs du Bugrock. ISS avec cartes horizontales, salle et mosaïque adaptées
au rayon du terrain. Profil `discovery`, monde neuf ; contrôles natifs,
265 GameTests et assemblages réussis. Solo préparé à 32 chunks.
## beta.182 — galerie inférieure et rosace
[Nouvelle scène de laboratoire](docs/origin-gallery-beta182.md) : galerie
naturelle sous le centre, trois salles à plusieurs niveaux, emplacement
d’obsidienne ; cloche vitrée au-dessus du Bug Rock et huit axes dégagés.
Nouveau profil `gallery`, monde neuf, contrôles natifs, 265 GameTests et
assemblages réussis. Solo à 32 chunks visité par le créateur, qui valide sa palette et sa rosace.
## beta.181 — origine à Y=320
[Premier repère central](docs/origin-landmark-beta181.md) : petite rose des vents
ouverte sur un fragment rocheux, Bug Rock en (0,320,0), lumineux et monochrome.
Nouveau profil de labo `origin`, fondé sur l’île beta.180 ; contrôle natif réussi
et solo Vulkan ouvert à 32 chunks. Suite générale et assemblages réussis.
Les salles d’ancres, secteurs de gemmes, soufre, portail et ISS sont cadrés
comme suites à construire.
## beta.180 — bassins et donjon minier
[Variante de laboratoire](docs/adventure-ecology-beta180.md) : grands bassins
avec débordements, trois cerisiers sur l’île, récifs cherry et automne.
Donjon ramifié avec spawners et wagonnets à butin (diamants, émeraudes,
rubis et saphirs), éclairage réduit et cornichons hors de l’eau retirés.
Solo Vulkan ouvert à 32 chunks, contrôles ciblés, validation générale et
assemblages réussis.
## beta.179 — grottes vivantes
[Essai de laboratoire](docs/living-caves-beta179.md) : quelques cerisiers aux
sommets, fin des bordures rocheuses du plateau, poches lush en terrasses,
marais à lucioles, chênes noirs et mycélium violet. Première version de
complexes miniers et d’une cabane de sorcière souterraine. Monde neuf ;
contrôle natif réussi et solo Vulkan ouvert à 32 chunks. Suite générale
de validation et assemblages réussis.
## beta.178 — vallées bornées et grottes rocheuses
Le [nouvel essai solo](docs/rocky-ecology-beta178.md) limite l’automne aux
vallées Y=200–232 et expose la roche sur les flancs profonds. Les grottes
gardent des mares et de petites plaques de mousse. Nouveau monde de labo,
graine 42, vue à 32 chunks et commandes activées ; contrôle ciblé réussi,
suite générale et assemblages réussis.
## beta.177 — les biomes suivent le relief
Le [nouvel essai de laboratoire](docs/relief-ecology-beta177.md) réunit
forêts et plaines fleuries sur le plateau, automne dans les creux extérieurs,
cerisiers sur un sommet local et végétation humide sous roche. La mangrove
et la neige sont retirées ; des gisements de pierre interrompent les strates.
Solo Vulkan à 32 chunks, relevés natifs et réouverture vérifiés ; 265/265
GameTests, `check build` et assemblages réussis. Détails dans la fiche.
## beta.176 — strates, biomes et mares du labo
Le [nouveau laboratoire écologique](docs/island-ecology-beta176.md) conserve
le relief beta.175 et ajoute des strates ondulées, de grandes régions
automnales et de cerisiers, des récifs végétalisés et des mares locales.
Ses biomes excluent les structures natives, notamment les mineshafts.
Trois graines et une réouverture vérifiées ; visite solo Vulkan à 32 chunks.
Une réserve sur la suite générale des familiers est détaillée dans la fiche.
Aucune modification des mondes existants.
## beta.175 — récifs aériens et minerais du labo
Le [nouveau profil de laboratoire](docs/sky-fragments-beta175.md) remplace les
anciennes masses flottantes par des récifs rares en bruit 3D, au-dessus de
l’île principale conservée. Y=512–639 reste réservé à l’ISS. Cuivre et charbon
abondants, peu de fer, lapis et améthyste, diamant enfoui dans la deepslate ;
aucun or ni redstone dans cette répartition. Relevés natifs plutôt que vues
en jeu ; contrat, mesures et limites dans la fiche du lot.
## beta.174 — laboratoire de relief Sanctuary
Le [labo worldgen](docs/worldgen-lab-beta174.md) ajoute un preset distinct
pour travailler le relief de l'île sans les calculs de plans d'eau et de
structures. La génération normale est conservée. Profil rapide et référence
complète utilisent des mondes de développement séparés. Les résultats de
validation et les limites sont consignés dans la fiche du lot.
## Refonte de Sanctuary Island — direction du 29 septembre
Le palais souterrain, ses accès et les raccordements au palais sont abandonnés
dans la conception. Le [fil rouge de la refonte](docs/storyquest-fil-rouge.md)
situe le socle, propose des plans géographiques indépendants et organise la
suite en parcours jouables qui font avancer plusieurs systèmes ensemble.
La priorité suivante est le labo worldgen rapide ci-dessus, puis un parcours
arrivée, halte extérieure, ancre et conséquence visible. L'esthétique reste
à comparer sur ce parcours.
## beta.173 — prototype du palais, conservé comme essai technique
Un palais octogonal par seed, huit ancres avec des matériaux distincts et le bloc
originel en bedrock : éteint, allumé en noir et blanc, puis une gemme colorée par
ancre activée. [Prototype, laboratoire et vérifications](docs/palais-prototype-beta173.md).
Le prototype reste disponible dans son laboratoire ; son intégration sous
Sanctuary Island sort du plan depuis le changement de direction du 29 septembre.
Le [cadrage Storyquest](docs/storyquest-beta173.md) et l'[inventaire du socle
beta.172](docs/storyquest-socle-beta172.md) conservent les autres chantiers de
refonte. Les priorités courantes sont dans le fil rouge ci-dessus.
Branche `codex/storyquest-beta173`.
## beta.172 — cartes utilisables en jeu
Une carte révélée ouvre désormais sa brèche à la position du joueur dans
n'importe quel monde, en Survie comme en Créatif. L'exemplaire est consommé
côté serveur dans les deux modes ; le terrain n'est pas modifié et les arrivées
cherchent un sol valide autour du point d'invocation.
[Contrat et vérifications](docs/horde-runtime-beta172.md).
## beta.171 — hordes dominantes et chaos dimensionnel
Chaque carte choisit une famille dominante — zombies, squelettes, creepers,
arthropodes, pillards, cauchemars, infernaux, piglins, flétris ou créatures de
l'End — puis y glisse des surprises. La dimension de découverte favorise très
fortement ses propres monstres, sans interdire de rares intrus. La carte est
séparée horizontalement : ordre exact des arrivées en haut, totalité du butin
généreux en bas. [Contrat et vérifications](docs/horde-families-beta171.md).
## beta.170 — carte de horde native et langage de particules
La carte de horde est désormais un véritable objet-carte Sanctuary, disponible
dans Outils et utilitaires. Chaque exemplaire vierge découvre en main une
identité stable et différente ; difficulté, dessin, monstres et butins restent
liés à cette carte. Révélation, refus, brèche, progression et résultat sont
communiqués uniquement par des connexions de particules, sans message Sanctuary
dans le chat. [Contrat et vérifications](docs/horde-map-native-beta170.md).
## beta.169 — cartes de horde à invasion continue
Une carte reste inconnue jusqu’à sa prise en main, puis révèle sa difficulté, ses
12, 18 ou 27 monstres variés et leurs butins. Le clic droit ouvre une invasion
sans vagues : les ennemis jaillissent un par un, de plus en plus vite. Récompenses
généreuses propres aux espèces, rubis/saphirs et runes SGA natives. Les cartes
beta.168 existantes restent compatibles.
[Contrat et vérifications](docs/horde-invasion-beta169.md).
## beta.168 — trois cartes de horde consommables
Labo sans socle, apparition immédiate, combat spontané et butins sur les monstres.
Trois cartes illustrées tenues comme des cartes Minecraft, trois difficultés.
[Essayer le laboratoire et connaître ses limites](docs/horde-cartes-beta168.md).
## beta.167 — prototype local de horde coopérative
Une arène ronde de laboratoire, une carte réutilisable et un socle interactif
pour réunir un groupe et lancer trois vagues de zombies. Inscription et départ
explicites, résultat commun, sans récompense économique dans ce prototype.
[Contrat, lancement et vérifications](docs/horde-lab-beta167.md).
Le laboratoire utilise un nouveau monde ; aucune mise à jour de Prism ni du
canal public n'est effectuée par ce chantier.
[Dernière release : beta.166](https://git.botsu.net/koka/sanctuary-beta/releases/tag/beta.166) ·
[Historique des publications beta.144–166](docs/publications-beta144-166.md).
Les releases sont distinctes du canal packwiz, qui reste inchangé lors de ce rattrapage.
La [direction de travail du 25 septembre](docs/ecosysteme-communaute-economie.md)
pose les liens entre tableau, Gazette, statistiques en BDD, État, navets,
machines de loterie, bounties et expansions. C'est un cadrage de conception,
avec décisions et propositions séparées, sans nouvelle livraison de jeu.
## beta.166 — inscription depuis le site
Accueil avec code récupéré sur le web et reprise après coupure.
[Contrat, migration et vérifications](docs/inscription-web-beta166.md).
## Vérification du socle beta.165
Les 23 échecs historiques ont été corrigés dans les scénarios de test :
252/252 GameTests passent, ainsi que `check build`. Code de jeu inchangé.
[Actualisation et preuves](docs/actualisation-tests-beta165.md).
## beta.165 — article Gazette et conversation
Grande photo et texte dans un même conteneur à gauche, auteur dessous, réponses
à droite ou sous l’article sur petit écran.
[Résultat et vérifications](docs/gazette-scene-beta165.md) ·
[Audit des 23 échecs connus](docs/audit-echecs-beta165.md).
## beta.164 — calendrier et intendance
Date et heure réunies à gauche ; titre serveur aligné sur la carte et les panneaux.
[Résultat et vérifications](docs/pause-calendar-beta164.md).
## beta.163 — menu pause encadré
Calendrier, intendance et horloge en en-tête ; deux groupes de navigation,
séparateurs natifs et reprise centrée dans le footer.
[Résultat et vérifications](docs/pause-frame-beta163.md).
## beta.162 — tableau en scène et conversation
Portrait de l’auteur tourné vers la souris, bulle aux couleurs de l’annonce,
demande à gauche et réponses à droite. Retour/Actualiser restent en haut ;
la conversation passe dessous sur les GUI étroits.
[Résultat et vérifications](docs/notice-scene-beta162.md).
## beta.161 — correctifs de séance et site vivant
Liste des joueurs sur U, suivi des demandes expliqué, intendance compacte et
compteurs réels partagés avec le site. Site public en lecture seule, Gazette
en accordéon avec aperçus compacts.
[Contrat et vérifications](docs/session-fixes-beta161.md).
## beta.160 — laboratoire de test en duo
Shader désactivé sur une configuration neuve, serveur local léger, personnages
de test optionnels et séances de captures horodatées.
[Mode d’emploi et vérifications](docs/duo-lab-beta160.md) ·
[Récap Discord 130–160](docs/discord-beta130-160.md).
## beta.159 — recherche communautaire et galerie adaptable
Recherche par joueur, titre ou texte, historique Serveur en lecture seule,
année dans les dates et galerie adaptée au GUI avec grand aperçu.
Repères « ! » et infobulles sur la carte pour les demandes suivies géolocalisées.
[Résultat et vérifications](docs/community-search-beta159.md).
## beta.158 — cartes communautaires et demandes suivies (pack local)
## beta.158 — cartes communautaires et demandes suivies
Grilles à deux colonnes, articles photo/texte, annonces colorées avec visages,
coffres descriptifs et suivi persistant fichier/MariaDB. Sélecteur photo compact.
[Contrat et vérifications](docs/community-cards-beta158.md).
## beta.157 — photo obligatoire dans la Gazette (pack local)
## beta.157 — photo obligatoire dans la Gazette
Galerie de captures défilante, aperçu et assignation à un article. Publication
avec photo obligatoire, stockage fichier ou MariaDB partagé avec le site.
[Contrat de migration et vérifications](docs/gazette-photos-beta157.md).
## beta.156 — reprendre la partie sous la colonne communautaire (pack local)
## beta.156 — reprendre la partie sous la colonne communautaire
Le bouton Reprendre la partie est aligné sous la Gazette et le tableau
d’affichage, en bas à droite. [Vérifications](docs/pause-resume-beta156.md).
## beta.155 — nouvelle structure du menu pause (pack local)
## beta.155 — nouvelle structure du menu pause
Navigation Sanctuary à gauche, carte centrale, Gazette et demandes défilantes
à droite. Date, intendance et heure en en-tête ; options et sortie en bas de la colonne
gauche, reprise de la partie en bas au centre.
[Contrat et vérifications](docs/pause-redesign-beta155.md).
## beta.154 — Gazette et tableau communautaire (pack local)
## beta.154 — Gazette et tableau communautaire
Panneaux gauche/droite du menu pause, intendance, publication, réponses,
modification et modération. Stockage fichier par défaut ; MariaDB optionnelle
@@ -39,39 +412,39 @@ commune avec le site. Shaders beta.151 conservés.
[Contrat et configuration](docs/community-contract-v1.md) ·
[Vérifications et limites](docs/community-beta154.md).
## beta.151 — Nether, reflets des blocs et SSR (pack local)
## beta.151 — Nether, reflets des blocs et SSR
Synchronisation de l’inventaire au changement de dimension, retour de l’intensité
spéculaire des blocs solides à son niveau antérieur et bois mat avec relief.
SSR activé par défaut à 20 %, choix sauvegardés conservés.
[Contrat et vérifications](docs/nether-pbr-beta151.md).
## beta.150 — Terre mate et reflets du coucher de soleil (pack local)
## beta.150 — Terre mate et reflets du coucher de soleil
Terre mate avec relief conservé. Le soleil et son reflet se colorent ensemble
en suivant la couleur et la transition du coucher de soleil natif de Minecraft.
Les accents lumineux suivent les normales animées des vaguelettes et leur
orientation vers le soleil et la caméra. [Contrat et vérifications](docs/matte-dirt-beta150.md).
## beta.149 — Distances PBR / SSR et feuillage (pack local)
## beta.149 — Distances PBR / SSR et feuillage
Deux distances séparées, 64 blocs par défaut chacune, jusqu’à 256 blocs.
Option renommée « SSR » et suppression du voile spéculaire sur le feuillage.
[Contrat et vérifications](docs/independent-distances-beta149.md).
## beta.148 — PBR 50 % et soleil diffus (pack local)
## beta.148 — PBR 50 % et soleil diffus
PBR à 50 %, reflet solaire plus doux avec fusion par texel préservant
la texture de l’eau. SSR à 20 %, portée commune jusqu’à 256 blocs.
[Contrat et vérifications](docs/pbr-soft-sun-beta148.md).
## beta.147 — PBR proche, lune et portée 256 (pack local)
## beta.147 — PBR proche, lune et portée 256
Correction du scintillement à très courte distance, reflet lunaire adouci
et distance commune PBR/SSR jusqu’à 256 blocs.
[Contrat et vérifications](docs/pbr-close-range-beta147.md).
## beta.146 — Reflets et transitions de l’eau (pack local)
## beta.146 — Reflets et transitions de l’eau
Correction du masquage des personnages en F5 et sélection stricte des surfaces PBR.
PBR à 80 % et SSR à 20 % par défaut, distance commune PBR/SSR
@@ -2407,7 +2780,7 @@ Depuis beta.090, la cible est **Minecraft 26.3 finale**, publiée le 15 septembr
| Fabric API | 0.160.5+26.3 |
| Fabric Loom | 1.17.20 |
| Gradle Wrapper | 9.5.1, distribution vérifiée par SHA-256 |
| Sanctuary / pack | beta.151 |
| Sanctuary / pack | beta.166 |
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
@@ -2436,7 +2809,7 @@ décrits dans [Validation](docs/testing.md).
Résultats :
- `mods/sanctuary/build/libs/sanctuary-beta.151.jar` : mod à installer avec
- `mods/sanctuary/build/libs/sanctuary-beta.166.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).
+61
View File
@@ -0,0 +1,61 @@
# beta.144 — Code d’accès à la création
Document historique de la PR de Chris. Le contrat de reprise ci-dessous est
remplacé par [beta.166](inscription-web-beta166.md), notamment pour les codes
consommés et les déconnexions.
Le site accepte la candidature, whitelist le pseudo et envoie un code `SANC-XXXX-XXXX`.
Hello World demande ce code avant de créer le personnage. Le client ne parle pas
au site. Seul le serveur appelle l’API, avec le jeton `SANCTUARY_API_TOKEN`.
Sans ces réglages, Hello World reste celui de beta.143 : biographie, couleur et
familier, sans code. Dès que l’URL et le jeton sont présents, un nouveau
personnage exige un code reconnu. Un habitant déjà enregistré entre sans écran.
## Réglage serveur
Variables d’environnement, ou fichier `config/sanctuary/access.json` si une
variable manque :
```json
{"appUrl":"https://exemple.sanctuary","token":"..."}
```
`appUrl` est l’origine du site, sans barre finale. Le serveur appelle
`POST {appUrl}/api/v1/access-codes/verify` puis `redeem`. Le jeton n’est pas
écrit dans les logs, le client ou le pack. Une URL ou un jeton illisible ferme
la création : aucun personnage n’est inventé hors ligne.
## Parcours
1. La whitelist laisse passer le pseudo. L’écran s’ouvre tant que l’UUID n’a pas
d’habitant.
2. Le joueur saisit le code. L’affichage du champ et de l’exemple utilise
l’alphabet galactique standard (`minecraft:alt`, la police de la table
d’enchantement). La valeur envoyée reste le texte tapé. Un format complet
déclenche `verify`, pas chaque frappe. « Code reconnu » n’écrit rien.
3. Confirmer envoie le code de la session, la biographie, la couleur et le
familier. Le pseudo envoyé au site est celui de la session.
4. `redeem` répond `redeemed` avec `discord_id` : le lien est enregistré, puis
l’habitant est créé. Les connexions suivantes sautent l’écran.
5. Erreur, site injoignable ou jeton refusé : le joueur reste sur Hello World.
Une déconnexion avant `redeemed` ne consomme pas le code. Si le site a répondu
`redeemed` et que l’écriture de l’habitant est interrompue, le lien Discord
reste dans `data/sanctuary-access.json` (`pending`, schéma 1, graine du monde).
La confirmation suivante termine le personnage sans rappeler `redeem`.
Si le code est déjà consommé et que le site ne renvoie plus `discord_id`, le
joueur reste bloqué avec un message de reprise. Le site doit alors renvoyer
`minecraft_username` et `discord_id` pour le même pseudo. Le mod accepte cette
réponse, que `valid` soit vrai ou faux, et finit la création.
Le registre des habitants ne change pas. Le Discord est un fichier à part.
Un fichier illisible est conservé et refuse l’accueil.
## Limites
Le gel dans le monde n’est pas ajouté : Hello World reste avant l’entrée, comme
aujourd’hui. Inventaire, commandes et dimensions ne sont pas accessibles tant
que l’habitant n’existe pas. La whitelist RCON reste celle du site. Un pseudo
ajouté à la main, sans code, voit l’écran sans pouvoir le valider.
+55
View File
@@ -0,0 +1,55 @@
# Actualisation des tests — socle beta.165
Suite à l’audit des 23 échecs. Changements limités aux GameTests et à leur
préparation ; aucun changement des règles, des données de production ou de
format de monde. Pas de nouvelle version binaire : le laboratoire reste beta.165.
## Scénarios corrigés
- Placement : joueur à portée réelle, sans occuper la cellule visée. Les compteurs,
événements, empreintes et les huit diamants de la tombe restent vérifiés.
- Inventaires : données d’ouverture explicites pour l’atelier d’argile et le
multibloc, avec maintien de la boucle sur toutes les entrées du registre.
- Familiers : achat de l’accès puis commande serveur de mode travail ; le helper
vérifie aussi que le mode combat n’accorde pas les anciens passifs. Les bonus,
consommation de ressources et annulations restent vérifiés.
- Ancienne invulnérabilité : test des coups amicaux sans dégâts, du K.-O. sur
dégât létal et de la conservation de l’œuf. Le poisson terrestre utilise son
profil actuel et garde sa vérification de transition aquatique.
- Permissions : accès public à l’introduction, restrictions opérateur sur names
et community admin. Catalogue recompte les 67 documents, 2042 recettes,
1780 items, 1374 blocs et 1937 identifiants distincts. Hauteur actuelle 640.
- Dragon : cycle natif tickNonPassenger (commonTick + tick), compteur d’entité
contrôlé, déplacement/altitude/collision conservés.
- Dalle/coffre : visée à portée du dessus réel, sans collision du joueur avec la
surface à bâtir. Les assertions de fusion, collisions et contenu restent.
- Hydrologie : les ouvertures OUTLET doivent être des coupes sans source ni
sédiment, appartenant à un déversement terminal déclaré. Les autres cellules
conservent les contrôles de support naturel.
## Diagnostics indépendants
- [Construction](audit-construction-beta165.md)
- [Dragon](audit-dragon-beta165.md)
- [Berges](audit-berges-beta165.md)
## Validation
Premier rejeu ciblé : **81/81 tests obligatoires passent** en 1 min 45 s.
Journal : `build/test-refresh-focused.log`.
Groupes : inventory, inventoryflow, companions, refonte, accessories, graves,
collections, progression, demeure. Ce rejeu valide notamment les nouvelles
assertions atteintes et le déplacement du dragon. Construction et hydrologie
sont réservées au rejeu complet suivant.
`./gradlew check build --continue -PsanctuaryQuickTests=true` : **BUILD SUCCESSFUL**
en 7 min 11 s. **252/252 GameTests obligatoires réussis**, zéro échec, puis
les autres contrôles de `check` terminent avec succès.
Journal : `build/test-refresh-full.log`.
Les quatre cas analysés par les agents passent dans ce rejeu, sans modification
production. Le patch supplémentaire de parois OUTLET proposé à titre conditionnel
n’a pas été appliqué : aucune assertion correspondante n’a échoué.
Ce résultat porte sur la suite configurée ci-dessus ; aucune stabilité statistique
sur plusieurs graines, plateformes ou répétitions n’est revendiquée.
+46
View File
@@ -0,0 +1,46 @@
# WG-ECO-180 — grands bassins et donjon minier
Suite du retour beta.179 : relief et écologie générale validés ; les petites
mares répétées, l’absence de cerisiers et les mines rectilignes sont à corriger.
Branche `codex/cave-dungeon-beta180`, nouveau preset de labo
`sanctuary_test:adventure_ecology_v1`, profil `adventure`, graine 42.
Aucune migration : uniquement un nouveau solo, vue 32, commandes activées.
Cibles : grands bassins irréguliers réunis par débordements ; arrêt des petites
mares et des cornichons hors de l’eau ; trois cerisiers sur l’île principale,
un récif cherry et un récif automnal (autres récifs nus, relief conservé).
Un donjon ramifié avec salles, boucles, dénivelés, spawners et wagonnets à butin,
plus un éclairage ponctuel. La cabane et les habitats souterrains sont conservés.
Implémentation : deux systèmes de grands bassins sur des sols de cavités,
berges irrégulières préservant les reliefs émergents, fonds suivant la roche et
bassins inférieurs quatre blocs plus bas. Les anciennes petites mares sont
exclues de ce preset. Les trois cerisiers sont réservés et placés avec la
fonction native ; les récifs 0 et 1 deviennent cherry et automne, les autres
restent nus. La géométrie, les minerais et l’espace ISS sont conservés.
Donjon : 13 salles, embranchements et boucles, cinq spawners natifs
(squelettes et araignées des cavernes pour cette graine), quatre wagonnets-coffres.
Butin différé natif, contenant diamants, émeraudes, `sanctuary:ruby` et
`sanctuary:sapphire`, plus des provisions. Pas d’emblème : clarification du
créateur, il parlait bien des gemmes dans le butin. Une lanterne tous les
quatre portiques environ, éclairage sur tonneau dans les caches.
Contrôle `solo180c`, graine 42 : trois vrais troncs de cerisiers vérifiés,
deux surfaces d’eau connectées de 1 232 et 1 221 blocs, passages praticables,
spawners et tirage des quatre gemmes vérifiés. La réouverture avec fluides
actifs confirme les deux chutes d’eau ; chargements forcés retirés avant arrêt.
Dernière correction ensuite : support des lanternes des caches. Contrôle
final `solo180d` réussi, quatre wagonnets avec leur table de butin conservée
en sauvegarde. Suite `check build assemblePack assembleTestPack` réussie
(11 min 26 s, journal local `build/adventure180-check-build.log`).
Points de visite : cerisiers près de (38,301,101), grand bassin vers
(-192,198,32), donjon vers (120,145,48), récifs thématiques aux mêmes
emplacements que les récifs précédents. Nouveau solo `visite180/adventure/42`,
commandes activées, vue 32, simulation 12, créatif et difficulté normale.
Les premières proportions restent à juger en jeu ; aucune ancienne sauvegarde
ni distribution personnelle modifiée.
Ouverture confirmée le 30 septembre à 00:28 : Vulkan sur Apple M1,
KokaLab connecté, distance serveur 32 et simulation 12.
+137
View File
@@ -0,0 +1,137 @@
# Audit du support naturel des berges — beta.165
## Verdict
L’échec initial est une **attente de test antérieure aux ouvertures de berges
alpha.30**, avec une confiance très forte. La cellule signalée n’est pas une
terrasse sèche avec sédiments : c’est la deuxième ouverture `Kind.OUTLET` du
plan régional. Son absence de fondation est intentionnelle. Aucune modification
de génération n’est recommandée pour faire passer cette assertion.
Ce diagnostic ne démontre pas que le reste du test passe : celui-ci échoue dès
la préparation, avant sa vérification des blocs après décoration et ticks.
Aucun monde, chunk, fichier de production ou test partagé n’a été modifié pour
cet audit. Aucun serveur ni JVM supplémentaire n’a été lancé.
## Preuves et chaîne de traitement
1. `build/beta165-check-build.log:962` signale, au tick 0 :
`Cell[x=-141, z=-86, waterY=-1, bedY=238, carveTop=241, material=STONE,
featureId=15307446929, sedimentDepth=0]`.
2. `PopulationHydrologyRuntime.regionPlan` construit un plan classique admis,
puis applique `BankOutlets30.openBanks` pour les réglages `unified_5/10/20`.
La densité vient du même `finalDensity` et du même `RandomState` que le
diagnostic, via un mémo de signe. Les coordonnées locales sont translatées
avec l’origine régionale. Dans la région `(0,0)`, cette translation est nulle.
3. `BankOutlets30.java:30–45` cherche le vide à 1–5 blocs d’un bassin et crée
des cellules sèches de profondeur de sédiments zéro, classées `OUTLET`.
Ces cellules n’ajoutent ni roche ni eau : elles décrivent la coupe de la berge.
4. Recalcul indépendant des opérations entières 64 bits en Python, sans moteur
Minecraft : pour graine 0 et région `(0,0)`, la graine régionale non signée vaut
`12661893618221475390`; la base des identifiants de sorties vaut
`15307446928`. L’identifiant en échec vaut exactement base + 1, donc la
deuxième sortie (index 1). Ce calcul renforce l’identification par
`sedimentDepth=0`, au lieu de la supposer depuis le nom du test.
5. `PopulationHydrologyRuntime.apply` exclut explicitement `plan.isOutletCell`
de la validation des deux couches de support. Sa boucle de sédiments ne fait
aucune écriture avec profondeur zéro ; la boucle de coupe supprime les blocs
entre `bedY+1` et `carveTop`. L’eau se propage ensuite par les ticks vanilla.
6. `PopulationDiagnostics.checkOwnership:333–344` applique au contraire la
densité positive à **toutes** les cellules, donc impose ici une fondation à
Y237 et Y238. C’est précisément la contrainte absente du contrat des sorties.
7. Le contrat publié dans `docs/generation-alpha30.md`, section Hydrologie,
autorise une chute dans le vide et précise l’absence de fondation ou de
colonne d’eau artificielle. `River30Smoke.java:23–24` vérifie déjà profondeur
zéro et absence de nouvelle source. Ce smoke a passé dans le journal beta.165
(lignes 453–454).
La documentation Java de `PopulationHydrology.Cell:53` décrit encore seulement
les cellules sédimentaires : elle mérite une clarification future pour le cas
OUTLET, mais ne prévaut pas sur le contrat alpha.30 ni son implémentation dédiée.
## Adaptation ciblée proposée
Le patch préparé dans `build/audit-berges.patch` ne change que
`PopulationDiagnostics.checkOwnership`. Il conserve les assertions de région
et d’appartenance de toutes les cellules. Pour une cellule **explicitement
classée OUTLET**, il exige :
- aucune source d’eau, aucun sédiment, un intervalle de coupe positif ;
- un déversement terminal déclaré avec le même identifiant.
Les autres cellules conservent intégralement l’assertion de densité naturelle des
sédiments et des deux couches de support. Ne pas remplacer ce contrôle par une
exception générale pour toutes les cellules sèches ou de profondeur zéro.
Le patch reste isolé et non appliqué pour intégration par l’agent principal.
## Reproduction et validation minimales
Le défaut de contrat se reproduit sans monde avec le témoin existant
`River30Smoke` : la côte synthétique est solide à x≤4 ; la brèche atteint x=5,
qui est du vide. Exiger un support positif sous cette cellule ferait échouer
une ouverture intentionnelle que le smoke valide.
Pour confirmer le témoin réel, rejouer le GameTest
`UnifiedWorldGameTests.retainedHydrologySurvivesNativeDecoration` après application
ciblée, dans le **monde jetable de tests**, graine 0, population 10, diamètre 724.
Conserver les vérifications de blocs FULL, de sédiments, de coquille et de ticks.
La commande et l’ordonnancement seront assurés par l’agent principal pour ne pas
saturer les 8 Go du Mac. Aucun passage de ce test n’est revendiqué ici.
## Points à surveiller après la première correction
- `verifyFinishedShell:460` impose sédiments 3–5 à ses cellules sélectionnées.
La sélection normale porte sur lac/étang/terrasse et exclut les OUTLET ; son
fallback prend la première feature. Si une ouverture est sélectionnée à
l’avenir, elle nécessitera son propre contrôle de coupe, sans support imposé.
- Le contrôle de paroi humide reconnaît `isSpillOpening`, qui ne contient que
la position terminale. Une ouverture de trois blocs de large peut également
créer une paroi ouverte avant ce point. Si cette assertion échoue ensuite,
vérifier le voxel contre une cellule OUTLET déclarée et son intervalle exact
de coupe ; ne pas désactiver le contrôle de toutes les parois.
- Le test complet n’a pas encore atteint ses contrôles finaux avec ce patch.
Les éventuels nouveaux échecs doivent être diagnostiqués séparément.
- Les plafonds historiques à Y384 restent présents dans certaines inspections
naturelles alors que la dimension atteint Y640. Ils ne causent pas l’échec
ici à Y237/238, et ne sont pas modifiés dans ce patch ciblé.
## Fichiers examinés
- `mods/sanctuary/src/main/java/fr/koka/sanctuary/worldgen/BankOutlets30.java`
- `mods/sanctuary/src/main/java/fr/koka/sanctuary/worldgen/PopulationHydrology.java`
- `mods/sanctuary/src/main/java/fr/koka/sanctuary/worldgen/PopulationHydrologyRuntime.java`
- `mods/sanctuary/src/main/java/fr/koka/sanctuary/worldgen/PopulationIslandDensity.java`
- `mods/sanctuary/src/main/java/fr/koka/sanctuary/worldgen/IslandCapacity.java`
- `mods/sanctuary/src/gametest/java/fr/koka/sanctuary/gametest/PopulationDiagnostics.java`
- `mods/sanctuary/src/gametest/java/fr/koka/sanctuary/gametest/River30WorldGameTests.java`
- `mods/sanctuary/src/test/java/fr/koka/sanctuary/worldgen/River30Smoke.java`
## Complément : contrôle précis de la paroi ouverte
Un second patch non appliqué, `build/audit-berges-shell.patch`, est disponible
**uniquement si l’exécution atteint un échec de paroi correspondant à une
ouverture réelle**. Il ajoute un cas au contrôle des parois de
`verifyFinishedShell`, après ses exceptions existantes pour l’eau planifiée et
le voxel terminal du spill.
Le voxel doit appartenir au plan de sa propre position X/Z, à une cellule
explicitement `OUTLET`, sans eau planifiée ni sédiments, associée par son
identifiant à un spill terminal. Son Y doit être **strictement supérieur à
bedY et inférieur ou égal à carveTop**. Le bloc final doit alors être de l’air
ou de l’eau vanilla ; un bloc solide ou de la lave fait toujours échouer le test.
Les couches sous la coupe, les voisins latéraux hors emprise, les autres types
de cellules et les voxels au-dessus de la coupe conservent leur contrôle normal.
Le patch parcourt `cellsAt` plutôt que de se fier seulement à `cellAt(x,y,z)` :
ce dernier inclut aussi les couches de support dans sa sélection, ce qui aurait
créé une exception trop large. Les assertions sur les sédiments sélectionnés
restent inchangées. La suite en cours doit décider si ce patch est nécessaire ;
aucun résultat d’exécution n’est anticipé.
## Résultat de l’intégration
Le patch proposé a ensuite été intégré aux tests par l’agent principal.
Le rejeu complet `build/test-refresh-full.log` termine avec 252/252 GameTests
réussis et `check build` réussi. Aucun code de production ni monde existant modifié.
Voir [la validation consolidée](actualisation-tests-beta165.md).
+98
View File
@@ -0,0 +1,98 @@
# Audit ciblé — construction groupée sur dalle et coffre
Audit du 24 septembre 2026, sources beta.165. Aucun changement de production,
aucun monde lancé ou modifié, aucune nouvelle exécution de GameTest dans cet audit.
## Conclusion
Les deux refus initiaux ont une explication commune de **fixture hors portée**, avec
une confiance forte : le rayon de visée s’arrête à 3,5 blocs avant la surface réelle.
Ils ne démontrent ni une fusion de dalle cassée, ni une altération du contenu du coffre.
Les assertions suivantes restent à rejouer ; elles ne sont pas déclarées réussies.
Le contrat beta.009 utilisait la portée native, tandis que le contrat actuel
[beta.081](build-reach-beta081.md) fixe le rang 1 à 3,5 blocs. Le même point de vue
atteint encore un cube plein, mais pas les surfaces plus basses.
## Chaîne de refus
- `Building009GameTests.java:107–113` : plan 3×3 de dalles basses ; joueur au rang 1 ;
`aim` place ses pieds en `(0,5 ; 1 ; −2,5)` par rapport à l’origine, puis vise
`(0,5 ; 0,5 ; 0,5)`.
- `Building009GameTests.java:165–170` : coffre central et pierre autour ; même position,
mais `aim` conserve la cible d’un cube plein `(0,5 ; 1 ; 0,5)`.
- `BuildReach.java:13–21` et `BuildingLimits.java:6` : portée 3,5 ; le contrôle préalable
de proximité utilise l’AABB du **bloc entier**, pas la forme de la dalle/coffre.
- `BuildSelection.java:40–44` : `player.pick(3.5, 1, false)` doit réellement toucher
le bloc d’origine. Un MISS ou un autre bloc donne `null`.
- `BuildingSession.java:26–30` : ce `null` empêche de créer la session, avant tout
placement, fusion ou usage du coffre.
- Le journal existant `build/beta165-check-build.log:1105–1138` confirme les refus
à `start`, lignes 113 et 170 des tests, au tick 0.
## Géométrie vérifiée
Les constantes natives ont été inspectées par `javap -c -p` dans le JAR local exact
Minecraft 26.3 : `Avatar` définit les yeux debout à 1,62 ; `SlabBlock` utilise
`column(16,0,8)` pour la dalle basse ; `ChestBlock` utilise `column(14,0,14)` pour
le coffre simple. Traces dans `build/audit-construction-avatar-bytecode.txt` et
`build/audit-construction-native-bytecode.txt`. Aucun serveur/JVM Minecraft lancé.
Les yeux sont donc en **E=(0,5 ; 2,62 ; −2,5)**. Calculs Python indépendants du jeu :
| Surface et visée actuelle | Premier impact géométrique prévu | Distance yeux-impact |
|---|---|---:|
| Cube plein témoin | `(0,5 ; 1 ; 0,5)` | 3,409457 |
| Dalle basse, visée corrigée sur son dessus | `(0,5 ; 0,5 ; 0,5)` | **3,673472** |
| Coffre, visée restée au dessus d’un cube plein | `(0,5 ; 0,875 ; 0,731481)` | **3,672533** |
Le coffre occupe horizontalement `[1/16 ; 15/16]`, donc ce point est bien dans son
dessus. Les cubes de pierre voisins ne coupent pas ce rayon avant lui : à `y=1`,
le rayon est déjà au centre de la cellule d’origine. Les autres dalles basses ne
coupent pas davantage le rayon avant `y=0,5`. Le précontrôle AABB entier passe,
avec une distance minimale de 2,978993 : il ne garantit pas que le rayon atteigne
la forme réelle.
L’écart d’environ 0,173 bloc dépasse largement les arrondis float de la direction
et de la hauteur des yeux. Ces calculs expliquent le refus dans les deux fixtures ;
une trace native reste utile pour confirmer explicitement le MISS en exécution.
## Correction recommandée, limitée aux tests
Patch proposé, **non appliqué** : `build/audit-construction.patch`.
Le helper dédié `aimInsetTop` rapproche les pieds à `z=−1,5`, conserve `y=1` et
vise le centre du dessus réel (`y=0,5` pour la dalle, `14/16` pour le coffre).
Les distances deviennent respectivement **2,914515** et **2,654247** blocs.
Le joueur reste hors du plan 3×3 (son bord proche est vers `z=−1,2`, le plan
commence à `z=−1`) afin de ne pas exclure une pose par sa propre collision.
Une assertion explicite vérifie que le rayon atteint la face UP avant le démarrage.
Toutes les assertions métier existantes restent : neuf consommations/fusion,
dalles de plafond, exclusion de la vache, trois diamants conservés, menu fermé,
refus des objets à placement particulier. Aucun changement de portée de production,
aucune suppression de test, aucun passage en créatif pour masquer le problème.
## Protocole ciblé restant
1. Relire/appliquer le patch de tests sur une branche de correction coordonnée.
2. Rejouer la famille `building` dans un monde GameTest jetable, avec le filtre
du dépôt `-PsanctuaryFocusedTests=building`, après libération de la mémoire du
laboratoire. Ne pas exécuter en parallèle plusieurs serveurs sur le Mac 8 Go.
3. Si un refus subsiste, tracer yeux, portée, `player.pick(...)`, bloc/face/distance,
`BuildSelection.aimed`, puis nombre de cibles : ne pas augmenter arbitrairement
la portée ou neutraliser une validation.
4. Confirmer les assertions suivantes : une fois la première barrière levée, des
défauts secondaires peuvent devenir visibles. Garder un scénario séparé de refus
hors portée à la limite, sans le confondre avec fusion ou conservation d’objets.
Risque du patch faible et limité à la géométrie de test. Risque de modifier la
production maintenant inutilement élevé : cela modifierait le contrat de portée
pour contourner un scénario préparé avec l’ancien contrat.
## Résultat de l’intégration
Le patch proposé a ensuite été intégré aux tests par l’agent principal.
Le rejeu complet `build/test-refresh-full.log` termine avec 252/252 GameTests
réussis et `check build` réussi. Aucun code de production ni monde existant modifié.
Voir [la validation consolidée](actualisation-tests-beta165.md).
+79
View File
@@ -0,0 +1,79 @@
# Audit dragon — beta.165
## Verdict
L’échec de `Companion019GameTests.creatureProfilesActuallyMove` est expliqué par
une simulation incomplète du cycle natif de l’entité. Il ne démontre pas un
blocage du dragon en jeu. Aucune correction de locomotion ne se justifie avant
le rejeu du scénario avec son horloge d’entité effective.
## Chaîne causale confirmée
1. [Le test](../mods/sanctuary/src/gametest/java/fr/koka/sanctuary/gametest/Companion019GameTests.java)
appelle `pet.tick()` 120 fois immédiatement, au tick GameTest 0. Le journal
`build/beta165-check-build.log` confirme l’assertion de déplacement échouée au tick 0.
2. Dans Minecraft **26.3**, `ServerLevel.tickNonPassenger(Entity)` appelle
**`Entity.commonTick()` puis `Entity.tick()`**. L’incrément `tickCount++` se trouve
dans `commonTick`, avec la mise à jour des anciennes positions et du délai
d’invulnérabilité ; il ne se trouve pas dans `Entity.tick()` ou `baseTick()`.
Vérification directe du bytecode du JAR natif local par `javap -c -p` :
`build/audit-dragon-serverlevel.txt` (méthode `tickNonPassenger`),
`build/audit-dragon-entity.txt` (méthode `commonTick`).
3. Au premier tick, [FamiliarEntity.configureMotion](../mods/sanctuary/src/main/java/fr/koka/sanctuary/cosmetics/FamiliarEntity.java)
choisit `FlyingPathNavigation`, `FlyingMoveControl`, désactive la gravité et
appelle `stopRoaming()`.
4. [FamiliarRoam.reset](../mods/sanctuary/src/main/java/fr/koka/sanctuary/familiar/FamiliarRoam.java)
fixe `nextChoice = pet.tickCount + 20`. L’errance lit **pet.tickCount**, pas
`level.getGameTime()`. Les 120 appels directs laissent donc `now = 0` et
`nextChoice = 20`. La condition `if(now < nextChoice) return` interdit toute
première destination.
5. Le familier apparaît déjà près du joueur, à environ +2 blocs en Y grâce à
`teleportNearOwner`. Il n’atteint pas le seuil de retour vers le propriétaire
qui pourrait contourner cette attente. Aucun déplacement vers une destination
n’est donc engagé dans ce scénario.
L’hypothèse initiale d’une simple dépendance au temps du monde était trop vague :
la cause directe est l’absence de **commonTick**, et donc le compteur d’entité figé.
## Correction de test recommandée
Pour ce test synchrone de locomotion, remplacer les appels de simulation par
`h.getLevel().tickNonPassenger(pet)` ; cela reproduit le préambule natif complet,
contrairement à un simple `pet.tickCount++`. Conserver les assertions sur le profil,
le déplacement supérieur à un bloc, l’altitude et l’absence de collision.
Ajouter une assertion du nombre de ticks réellement avancés afin d’éviter une
régression de la fixture. Rejouer les vérifications Ghast et Wither, masquées
jusqu’ici par l’échec dragon.
Le correctif proposé est disponible dans `build/audit-dragon.patch`, sans avoir
été appliqué aux sources par cet audit. Cette boucle reste synchrone : le temps
du monde, les hooks serveur et le propriétaire ne progressent pas. Elle vérifie
le cycle de locomotion de l’entité, pas une séance complète de suivi en jeu.
Un scénario supplémentaire de suivi en ticks réels est utile si ce premier rejeu
échoue : propriétaire déplacé sans dépasser le seuil de téléportation, personnage
et graine d’errance fixés, trajet effectivement parcouru contrôlé. Il doit vivre
dans un monde de test jetable, jamais dans la sauvegarde du laboratoire.
## Aléa et limites
Le tempérament provient du UUID du lien, créé aléatoirement. Les rayons d’errance
sont 2,5 / 4 / 5 blocs et les pauses 100–139 / 25–64 / 50–89 ticks. Ces pauses
sont intentionnelles : ne pas imposer un mouvement à chaque tick. Pour une
reproduction déterministe, fixer la graine du générateur de l’entité et le
tempérament dans les données du test, ou tester explicitement les trois.
Les destinations nécessitent des chunks chargés, une autorisation de mouvement,
un volume libre et un chemin accessible. Aucun de ces refus n’a été démontré
ici : l’ancien scénario s’arrêtait **avant** leur évaluation.
Audit statique et lecture du bytecode terminés. À ce stade, aucun serveur de test
supplémentaire lancé, aucune source de jeu modifiée, aucune sauvegarde touchée.
Le passage effectif du test corrigé reste à confirmer dans l’exécution coordonnée.
## Résultat de l’intégration
Le patch proposé a ensuite été intégré aux tests par l’agent principal.
Le rejeu complet `build/test-refresh-full.log` termine avec 252/252 GameTests
réussis et `check build` réussi. Aucun code de production ni monde existant modifié.
Voir [la validation consolidée](actualisation-tests-beta165.md).
+100
View File
@@ -0,0 +1,100 @@
# Audit des 23 échecs GameTest — socle beta.165
Audit initial du 24 septembre 2026, code `dbb9e34` (tag beta.165).
Suite : [actualisation des tests et validation](actualisation-tests-beta165.md).
## Portée et méthode
Lecture des 23 messages du dernier journal `build/beta165-check-build.log`, des
méthodes de test et des chemins de production concernés. La liste a été comparée
à beta.164 : mêmes 23 identifiants. Recompte local des JSON de collections.
Aucun test désactivé, aucune correction de code, aucune modification du monde,
aucun nouveau lancement Minecraft pendant cet audit. Le laboratoire reste disponible.
Il s’agit d’un diagnostic statique étayé par l’exécution précédente, pas d’une
preuve que les tests passeront après adaptation. Un échec à la première assertion
masque potentiellement des problèmes dans la suite de la même méthode.
## Conclusion
**19 échecs ont une explication étayée dans le scénario de test ou un ancien
contrat ; 4 nécessitent encore une reproduction ciblée.** Ce ne sont donc pas
23 bugs joueurs démontrés. Inversement, la stabilité de la liste ne prouve pas
l’absence de bugs. Les objectifs des tests restent majoritairement pertinents.
| Famille | Nombre | Lecture principale |
| --- | ---: | --- |
| Placement simple / empreinte / tombe | 5 | Positions de test hors portée |
| Menus d’inventaire | 3 | Constructeur incompatible avec les menus étendus |
| Bonus de familiers | 6 | Mode travail absent des fixtures |
| Invulnérabilité et poisson familier | 2 | Anciennes règles remplacées par le système de combat |
| Collections, permissions, hauteur du monde | 3 | Attentes historiques à remettre à jour |
| Construction dalle/coffre | 2 | Raycast et portée à instrumenter |
| Déplacement du dragon | 1 | Ticks réels et navigation à reproduire |
| Hydrologie | 1 | Contrat de support naturel à examiner |
## Inventaire exhaustif
| Test | Verdict | Preuve et limite | Suite pertinente |
| --- | --- | --- | --- |
| [BlockKnowledgeGameTests.placementCountsConfirmedStatesAndRejectsFailure](/Users/koka/Documents/sanctuary/sanctuary-community-beta154/mods/sanctuary/src/gametest/java/fr/koka/sanctuary/gametest/BlockKnowledgeGameTests.java:44) | Test mal positionné — forte confiance | Joueur en (5,5 ; 3 ; 5,5), support en (1 ; 1 ; 1) : la distance horizontale seule dépasse 5,65 blocs. Le mixin de placement applique la portée réelle (vanilla ou progression), avant les assertions métier. | Rapprocher le joueur et viser la face réelle ; conserver les assertions de compteurs, événements et empreinte. Ajouter/conserver un cas hors portée refusé. |
| [BlockKnowledgeGameTests.listenersReceiveUpdatedProgressionAndStockChanges](/Users/koka/Documents/sanctuary/sanctuary-community-beta154/mods/sanctuary/src/gametest/java/fr/koka/sanctuary/gametest/BlockKnowledgeGameTests.java:220) | Test mal positionné — forte confiance | Joueur en (5,5 ; 3 ; 5,5), support en (1 ; 1 ; 1) : la distance horizontale seule dépasse 5,65 blocs. Le mixin de placement applique la portée réelle (vanilla ou progression), avant les assertions métier. | Rapprocher le joueur et viser la face réelle ; conserver les assertions de compteurs, événements et empreinte. Ajouter/conserver un cas hors portée refusé. |
| [MaterialActivityGameTests.nativeActionsProduceDatedDeltasWhileObservationsProduceNone](/Users/koka/Documents/sanctuary/sanctuary-community-beta154/mods/sanctuary/src/gametest/java/fr/koka/sanctuary/gametest/MaterialActivityGameTests.java:39) | Test mal positionné — forte confiance | Joueur en (5,5 ; 3 ; 5,5), support en (1 ; 1 ; 1) : la distance horizontale seule dépasse 5,65 blocs. Le mixin de placement applique la portée réelle (vanilla ou progression), avant les assertions métier. | Rapprocher le joueur et viser la face réelle ; conserver les assertions de compteurs, événements et empreinte. Ajouter/conserver un cas hors portée refusé. |
| [Demeure006GameTests.confirmedGesturesAndPersonalAtlasBoundary](/Users/koka/Documents/sanctuary/sanctuary-community-beta154/mods/sanctuary/src/gametest/java/fr/koka/sanctuary/gametest/Demeure006GameTests.java:20) | Test mal positionné — forte confiance | Joueur en (5,5 ; 3 ; 5,5), support en (1 ; 1 ; 1) : la distance horizontale seule dépasse 5,65 blocs. Le mixin de placement applique la portée réelle (vanilla ou progression), avant les assertions métier. | Rapprocher le joueur et viser la face réelle ; conserver les assertions de compteurs, événements et empreinte. Ajouter/conserver un cas hors portée refusé. |
| [GravesFood038GameTests.ordinaryPlacementAndCreativeBreakPreserveGrave](/Users/koka/Documents/sanctuary/sanctuary-community-beta154/mods/sanctuary/src/gametest/java/fr/koka/sanctuary/gametest/GravesFood038GameTests.java:120) | Test mal positionné — forte confiance | Joueur novice à 3 blocs horizontalement du clic, yeux au-dessus : distance supérieure aux 3 blocs de portée initiale. Le test échoue avant la conservation des composants et la casse créative. | Remettre le clic à portée, puis vérifier impérativement les 8 diamants après pose et casse ; le résultat actuel ne prouve aucune perte d’objets. |
| [Inventory010GameTests.machineQuickMovesAndMounts](/Users/koka/Documents/sanctuary/sanctuary-community-beta154/mods/sanctuary/src/gametest/java/fr/koka/sanctuary/gametest/Inventory010GameTests.java:77) | Initialisation de test incompatible — certaine | Boucle sur tout BuiltInRegistries.MENU et appel create(id, inventory). Des menus Sanctuary sont ExtendedMenuType et exigent des données supplémentaires ; exception avant les comparaisons. | Séparer menus vanilla et menus étendus ; fournir les données requises aux menus Sanctuary, garder les tests de conservation et de shift-clic. |
| [Inventory010GameTests.everyNativeContainerKeepsItsIndices](/Users/koka/Documents/sanctuary/sanctuary-community-beta154/mods/sanctuary/src/gametest/java/fr/koka/sanctuary/gametest/Inventory010GameTests.java:58) | Initialisation de test incompatible — certaine | Boucle sur tout BuiltInRegistries.MENU et appel create(id, inventory). Des menus Sanctuary sont ExtendedMenuType et exigent des données supplémentaires ; exception avant les comparaisons. | Séparer menus vanilla et menus étendus ; fournir les données requises aux menus Sanctuary, garder les tests de conservation et de shift-clic. |
| [Inventory012GameTests.nativeAndExtraRowsHaveIdenticalMachinePriorities](/Users/koka/Documents/sanctuary/sanctuary-community-beta154/mods/sanctuary/src/gametest/java/fr/koka/sanctuary/gametest/Inventory012GameTests.java:38) | Initialisation de test incompatible — certaine | Même boucle sur tous les menus et même constructeur sans données réseau. | Même adaptation commune ; comparer les destinations et reliquats des lignes natives et supplémentaires. |
| [Companion019GameTests.switchingEggRemovesPassiveAndActive](/Users/koka/Documents/sanctuary/sanctuary-community-beta154/mods/sanctuary/src/gametest/java/fr/koka/sanctuary/gametest/Companion019GameTests.java:45) | Contrat du scénario périmé — forte confiance | L’attribut de chute du chat n’est pas actif dans le mode de combat par défaut. Le helper equip règle l’attunement, mais FamiliarBattle.prepare initialise workMode=false ; CompanionService.passive renvoie alors null. | Préparer explicitement le mode travail par le chemin prévu, puis tester aussi l’absence de bonus en mode combat. Rejouer le reste du scénario : ses assertions suivantes restent non validées. |
| [Companion019GameTests.poisonDurationAndMilkUseNativeHooks](/Users/koka/Documents/sanctuary/sanctuary-community-beta154/mods/sanctuary/src/gametest/java/fr/koka/sanctuary/gametest/Companion019GameTests.java:53) | Contrat du scénario périmé — forte confiance | La réduction de poison est attendue sans sélectionner le mode travail. Le helper equip règle l’attunement, mais FamiliarBattle.prepare initialise workMode=false ; CompanionService.passive renvoie alors null. | Préparer explicitement le mode travail par le chemin prévu, puis tester aussi l’absence de bonus en mode combat. Rejouer le reste du scénario : ses assertions suivantes restent non validées. |
| [Companion019GameTests.cropCyclesStopAfterOwnerLeaves](/Users/koka/Documents/sanctuary/sanctuary-community-beta154/mods/sanctuary/src/gametest/java/fr/koka/sanctuary/gametest/Companion019GameTests.java:152) | Contrat du scénario périmé — forte confiance | La préparation BEE n’active pas le mode travail nécessaire au bonus de culture. Le helper equip règle l’attunement, mais FamiliarBattle.prepare initialise workMode=false ; CompanionService.passive renvoie alors null. | Préparer explicitement le mode travail par le chemin prévu, puis tester aussi l’absence de bonus en mode combat. Rejouer le reste du scénario : ses assertions suivantes restent non validées. |
| [Companion019GameTests.furnaceBonusConsumesFuelAndKeepsOneOutput](/Users/koka/Documents/sanctuary/sanctuary-community-beta154/mods/sanctuary/src/gametest/java/fr/koka/sanctuary/gametest/Companion019GameTests.java:163) | Contrat du scénario périmé — forte confiance | La préparation BLAZE n’active pas le mode travail nécessaire au bonus de cuisson. Le helper equip règle l’attunement, mais FamiliarBattle.prepare initialise workMode=false ; CompanionService.passive renvoie alors null. | Préparer explicitement le mode travail par le chemin prévu, puis tester aussi l’absence de bonus en mode combat. Rejouer le reste du scénario : ses assertions suivantes restent non validées. |
| [Companion032GameTests.axolotlFoodDurationAndGolemKnockbackUseNativeHooks](/Users/koka/Documents/sanctuary/sanctuary-community-beta154/mods/sanctuary/src/gametest/java/fr/koka/sanctuary/gametest/Companion032GameTests.java:77) | Contrat du scénario périmé — forte confiance | Le bonus de durée de consommation est attendu hors du mode autorisant les anciens passifs. Le helper equip règle l’attunement, mais FamiliarBattle.prepare initialise workMode=false ; CompanionService.passive renvoie alors null. | Préparer explicitement le mode travail par le chemin prévu, puis tester aussi l’absence de bonus en mode combat. Rejouer le reste du scénario : ses assertions suivantes restent non validées. |
| [GravesFood038GameTests.familiarSaturationPreviewUsesActualServerPassive](/Users/koka/Documents/sanctuary/sanctuary-community-beta154/mods/sanctuary/src/gametest/java/fr/koka/sanctuary/gametest/GravesFood038GameTests.java:130) | Contrat du scénario périmé — forte confiance | L’assertion passive(p)==species échoue avant de comparer l’aperçu et la consommation. Le helper equip règle l’attunement, mais FamiliarBattle.prepare initialise workMode=false ; CompanionService.passive renvoie alors null. | Préparer explicitement le mode travail par le chemin prévu, puis tester aussi l’absence de bonus en mode combat. Rejouer le reste du scénario : ses assertions suivantes restent non validées. |
| [Accessory018GameTests.familiarIsHarmlessAndEscapesWalls](/Users/koka/Documents/sanctuary/sanctuary-community-beta154/mods/sanctuary/src/gametest/java/fr/koka/sanctuary/gametest/Accessory018GameTests.java:82) | Ancien contrat invulnérable — forte confiance | Le test exige que genericKill ne cause aucun dégât. hurtServer délègue désormais à FamiliarBattle.hurt ; le système possède santé et K.-O. depuis beta.054. | Remplacer l’invulnérabilité universelle par les règles actuelles : dégâts autorisés, coups amicaux, K.-O., œuf préservé. Conserver les vérifications de collisions, sortie de mur et non-duplication. |
| [Companion019GameTests.aquaticPetFlopsAndThenSwims](/Users/koka/Documents/sanctuary/sanctuary-community-beta154/mods/sanctuary/src/gametest/java/fr/koka/sanctuary/gametest/Companion019GameTests.java:113) | Ancien contrat de déplacement — forte confiance | Le test exige une vitesse ≤ 0,06 sur terre. configureMotion réserve le mode poisson ralenti aux familiers aquatiques sans profil de combat actif ; sinon la vitesse vient du profil. | Tester séparément le profil actuel sur terre et la transition dans l’eau. Ne pas rétablir automatiquement l’ancien poisson ralenti. |
| [Companion019GameTests.creatureProfilesActuallyMove](/Users/koka/Documents/sanctuary/sanctuary-community-beta154/mods/sanctuary/src/gametest/java/fr/koka/sanctuary/gametest/Companion019GameTests.java:104) | À isoler — navigation | Le profil aérien est reconnu ; c’est le déplacement > 1 et l’altitude > propriétaire + 0,5 qui échouent. Le test appelle pet.tick 120 fois sans faire avancer normalement le monde ; l’errance actuelle dépend de destinations sûres, du pathfinding et comporte des pauses. | Reproduire dans un monde de test avec ticks réels, graine/personnalité fixées, propriétaire déplacé, cible et chemin enregistrés. Si l’immobilité persiste, corriger la navigation. |
| [Collections035GameTests.exhaustiveCatalogueMatchesLoadedVanilla](/Users/koka/Documents/sanctuary/sanctuary-community-beta154/mods/sanctuary/src/gametest/java/fr/koka/sanctuary/gametest/Collections035GameTests.java:17) | Constantes périmées — certaine | Le dernier comptage attend 1658 items / 1286 blocs / 1815 identifiants distincts. Recompte des 67 JSON : 1780 / 1374 / 1937. Les 2042 recettes uniques sont toujours présentes ; le test a déjà passé les contrôles précédents des identifiants et recettes. | Comparer la couverture aux registres et au contrat de collections ; documenter les nombres actuels, éviter qu’un total historique soit l’unique preuve d’exhaustivité. |
| [Progression003GameTests.serverCustomNameConfiguration](/Users/koka/Documents/sanctuary/sanctuary-community-beta154/mods/sanctuary/src/gametest/java/fr/koka/sanctuary/gametest/Progression003GameTests.java:120) | Permission testée au mauvais niveau — certaine | Le test exige que toute la racine /sanctuary soit inaccessible aux joueurs. intro et community link sont maintenant publics ; names et community admin portent leur propre condition opérateur. | Tester un joueur ordinaire et un opérateur sur chaque sous-commande sensible. Vérifier names reload en particulier ; ne pas rebloquer toute la racine. |
| [UnifiedWorldGameTests.populationTerrainAndNaturalSpawnRemainPresent](/Users/koka/Documents/sanctuary/sanctuary-community-beta154/mods/sanctuary/src/gametest/java/fr/koka/sanctuary/gametest/UnifiedWorldGameTests.java:56) | Hauteur historique périmée — certaine | Attend minY=0 et hauteur=384. La dimension Sanctuary actuelle déclare hauteur et logical_height=640, minY=0 ; le test client de création attend lui aussi 640. | Actualiser le contrat de hauteur, puis rejouer les assertions suivantes sur le spawn naturel. Ne pas modifier la dimension ni les mondes existants. |
| [Building009GameTests.slabMergingCeilingAndEntityCollisions](/Users/koka/Documents/sanctuary/sanctuary-community-beta154/mods/sanctuary/src/gametest/java/fr/koka/sanctuary/gametest/Building009GameTests.java:107) | À isoler — construction groupée | Échec au premier start (ligne 113), avant fusion des dalles. Le fixture vise le centre d’une dalle basse depuis une position conçue pour un cube plein ; la portée au rang 1 est 3,5 et le point visé sur la dalle est plus éloigné. | Journaliser hit réel, bloc touché, distance, portée et raison de refus ; reproduire à portée courte puis à la limite. Conserver fusion, plafond et exclusion des entités. |
| [Building009GameTests.containerSupportsAndUnsupportedItems](/Users/koka/Documents/sanctuary/sanctuary-community-beta154/mods/sanctuary/src/gametest/java/fr/koka/sanctuary/gametest/Building009GameTests.java:165) | À isoler — construction groupée | Échec au premier start sur coffre (ligne 170), avant toute vérification du contenu. Le coffre n’a pas la collision d’un cube plein ; le raycast peut toucher le support voisin ou dépasser la portée. | Même diagnostic que la dalle ; vérifier que le coffre ne s’ouvre pas et garde ses 3 diamants. Ne pas assimiler l’échec à une corruption d’inventaire. |
| [UnifiedWorldGameTests.retainedHydrologySurvivesNativeDecoration](/Users/koka/Documents/sanctuary/sanctuary-community-beta154/mods/sanctuary/src/gametest/java/fr/koka/sanctuary/gametest/UnifiedWorldGameTests.java:39) | À isoler — génération, priorité haute | Échec dans PopulationDiagnostics.checkOwnership sur la densité naturelle du support à x=-141, z=-86, bedY=238, sedimentDepth=0, waterY=-1 (terrasse sèche). Ce contrôle survient avant la vérification finale des blocs décorés ; son nom ne prouve donc pas une fuite d’eau en jeu. | Comparer l’admission de cette cellule et la densité réellement utilisée, puis les blocs finaux dans un monde jetable, graine 0 / 10 joueurs / diamètre 724. Le contrat Cell annonce un support naturel : ne pas supprimer cette assertion sans explication. |
## Priorités proposées
1. **Remettre les tests en mesure de vérifier leur sujet** : placements à portée,
construction correcte des menus, sous-commandes protégées, inventaire de
collections et hauteur 640. Changements de tests ciblés, sans affaiblir leurs
vérifications métier. Les tombes et transferts d’inventaire sont prioritaires
parce qu’ils protègent les objets des joueurs.
2. **Isoler dalle/coffre et hydrologie** : les deux premiers concernent une action
courante ; le dernier touche la génération et exige un monde jetable, jamais
une régénération de la sauvegarde de test actuelle. Aucun changement de
génération n’est autorisé implicitement par cet audit.
3. **Actualiser les scénarios familiers** selon le mode travail/combat, puis
reproduire le vol du dragon avec une horloge de monde réelle. Garder les
contrôles d’anti-duplication, de ressources consommées et d’annulation.
4. Rejouer les familles corrigées, puis la suite complète. Toute nouvelle
assertion atteinte doit être réévaluée ; ne pas annoncer « 19 réglés » avant cela.
La correction des fixtures et constantes paraît contenue. Les quatre cas à
reproduire ne permettent pas encore une estimation fiable. Aucune suppression de
fonctionnalité ni suppression de test n’est recommandée à ce stade.
## Points d’entrée dans le code
- [BlockPlacementReachMixin](/Users/koka/Documents/sanctuary/sanctuary-community-beta154/mods/sanctuary/src/main/java/fr/koka/sanctuary/mixin/BlockPlacementReachMixin.java:14)
- [BuildingLimits](/Users/koka/Documents/sanctuary/sanctuary-community-beta154/mods/sanctuary/src/main/java/fr/koka/sanctuary/building/BuildingLimits.java:6)
- [CompanionService](/Users/koka/Documents/sanctuary/sanctuary-community-beta154/mods/sanctuary/src/main/java/fr/koka/sanctuary/companion/CompanionService.java:71)
- [FamiliarBattle](/Users/koka/Documents/sanctuary/sanctuary-community-beta154/mods/sanctuary/src/main/java/fr/koka/sanctuary/familiar/FamiliarBattle.java:125)
- [FamiliarEntity](/Users/koka/Documents/sanctuary/sanctuary-community-beta154/mods/sanctuary/src/main/java/fr/koka/sanctuary/cosmetics/FamiliarEntity.java:262)
- [FamiliarRoam](/Users/koka/Documents/sanctuary/sanctuary-community-beta154/mods/sanctuary/src/main/java/fr/koka/sanctuary/familiar/FamiliarRoam.java:16)
- [Statuary](/Users/koka/Documents/sanctuary/sanctuary-community-beta154/mods/sanctuary/src/main/java/fr/koka/sanctuary/plans/Statuary.java:39)
- [Multiblocks](/Users/koka/Documents/sanctuary/sanctuary-community-beta154/mods/sanctuary/src/main/java/fr/koka/sanctuary/multiblock/Multiblocks.java:36)
- [ProgressionService](/Users/koka/Documents/sanctuary/sanctuary-community-beta154/mods/sanctuary/src/main/java/fr/koka/sanctuary/progression/ProgressionService.java:178)
- [PopulationDiagnostics](/Users/koka/Documents/sanctuary/sanctuary-community-beta154/mods/sanctuary/src/gametest/java/fr/koka/sanctuary/gametest/PopulationDiagnostics.java:333)
- [PopulationHydrology](/Users/koka/Documents/sanctuary/sanctuary-community-beta154/mods/sanctuary/src/main/java/fr/koka/sanctuary/worldgen/PopulationHydrology.java:56)
Contrat fonctionnel des familiers : [beta.054](familiar-combat-beta054.md).
+299
View File
@@ -0,0 +1,299 @@
# Audit du regroupement de Sanctuary beta.203
Audit du 4 octobre 2026, demandé avant toute refonte. **Le regroupement est
réalisable, mais Sanctuary Test contient maintenant une partie du jeu livré,
et JEI constitue un moteur conséquent.** La voie recommandée est de conserver
le comportement de la beta.203, absorber Demeure et les fonctionnalités du
laboratoire, puis traiter le catalogue comme un chantier distinct.
Cette livraison contient une sauvegarde et un audit. Aucun regroupement de
code, changement de génération, migration de monde ou déploiement n'a été
effectué. Les travaux proposés ci-dessous restent à réaliser.
## État de référence et sauvegarde
La référence est le commit `454d9a5c08717af807f233759d3a96d639a39d44`, branche
`codex/north-fracture-beta203`, dans
`/Users/koka/.codex/worktrees/storyquest-beta173/sanctuary-beta`.
Son état Git était propre. Les propriétés du mod et du pack déclarent
`beta.203`, pour Minecraft 26.3, Java 25, Fabric Loader 0.19.5 et Fabric API
0.160.5+26.3.
Le dossier principal ouvert au début de l'audit était encore sur `main`, en
beta.168. La branche 203 possède 40 commits absents de ce `main` local. Cela
explique le décalage initial ; le présent audit porte bien sur la 203.
Sauvegarde locale :
`/Users/koka/Documents/sanctuary-backups/beta.203-before-single-mod-20261004-225340/`.
- Branche de sauvegarde : `codex/snapshot-beta203-before-single-mod-20261004-225340`.
- `sanctuary-all-refs.bundle` : historique Git et références commises présentes
au moment de la sauvegarde, dont la 203 et le `main` local.
- `sanctuary-beta.203-sources.tar.gz` : fichiers suivis de la 203.
- `jei-upstream-aae2dfc.tar.gz` : sources amont exactes nécessaires au port JEI ;
le patch et le script de préparation sont dans les sources Sanctuary.
- `artifacts/` : JAR existants de la 203, JAR de sources, MRpack Test, template
graphique et guide. Le JAR Sanctuary contient également JEI et MariaDB.
- Reçu de livraison et deux journaux de validation 203 ; empreintes SHA-256
dans `manifest.json`, procédure dans `RESTORE.txt`.
Les 16 fichiers du manifeste ont été contrôlés. Le bundle a été vérifié, puis
cloné dans un répertoire neuf : commit attendu retrouvé, copie restaurée
propre. Les 4 628 fichiers de l'archive source ont également été comparés
octet pour octet à la copie 203 d'origine. Le MRpack sauvegardé a pour SHA-256
`ba160b4134c0b9dbe402e1834bdbec4f5561bfd6bd9dc147590dd76ee30dbf4f`, identique
au [reçu documenté de la 203](north-fracture-beta203.md).
**Limite de sauvegarde :** la tentative de copie de la branche vers Gitea a
échoué faute d'authentification HTTPS. Aucune sauvegarde distante nouvelle
n'est confirmée. Cette sauvegarde locale du projet ne contient pas les mondes,
réglages personnels, caches Gradle, essais ignorés ni changements non commis
des autres worktrees. Elle ne constitue pas une installation complète hors ligne.
## Inventaire mesuré
Comptage des fichiers Java et de leurs lignes physiques, commentaires et
lignes vides inclus. Les sources JEI sont celles préparées au commit fixé,
dans les six répertoires effectivement compilés par son build.
| Ensemble | Java livré | Lignes Java | Responsabilité actuelle |
| --- | ---: | ---: | --- |
| Sanctuary | 963 fichiers | 84 353 | Terrain historique, progression, inventaires, recettes côté serveur, Atlas, communauté, rendu, expansions |
| Demeure | 4 fichiers | 347 | Empreintes du vivant, activité et persistance pour l'Atlas |
| Sanctuary Test | 105 fichiers | 8 775 | Nouveau terrain, palais et cavernes, ancres, adaptation des expansions, outils de labo |
| JEI embarqué | 924 fichiers | 69 104 | Recherche, catalogue, écrans de recettes, favoris, catégories, synchronisation et transferts |
Sanctuary possède également 381 fichiers Java de tests, soit 60 380 lignes,
répartis entre tests purs, GameTests serveur et tests client. Demeure et le
module Test ont chacun un fichier de test supplémentaire. Le volume de tests
ne prouve pas leur exécution sur chaque livraison.
Les configurations déclarent 286 mixins Sanctuary, un Demeure, sept Sanctuary
Test et sept JEI. Ces injections dans Minecraft rendent l'ordre
d'initialisation et les interactions client/serveur sensibles aux déplacements.
Le graphe de construction comprend quatre sous-projets. Sanctuary embarque
Demeure, JEI et MariaDB dans son JAR ; JEI embarque deux bibliothèques d'indexation.
Le MRpack 203 contient deux JAR de jeu au premier niveau, Sanctuary et Sanctuary
Test, et télécharge Fabric API. L'inspection des archives confirme que les
identités Fabric `demeure`, `jei` et `sanctuary_test` existent encore réellement.
Sources : [settings.gradle](../settings.gradle),
[dépendances Sanctuary](../mods/sanctuary/build.gradle),
[manifeste Sanctuary](../mods/sanctuary/src/main/resources/fabric.mod.json),
[port JEI](../mods/jei/build.gradle).
## Les points qui rendent une fusion directe dangereuse
### Sanctuary Test porte le terrain utilisé dans la 203
Ce module n'est plus seulement un moyen de sauter l'introduction dans un monde
plat. Il fournit 46 presets, 44 paramètres de bruit, 93 biomes et 13
enregistrements de codecs. Son adaptateur `LabExpansions199` branche le terrain
récent sur le journal, les réservations et la file de génération de Sanctuary.
Il existe deux fichiers différents à l'emplacement
`data/sanctuary/worldgen/world_preset/sanctuary.json`. Celui de Sanctuary
sélectionne `sanctuary:island_v24`. Celui du module Test sélectionne le terrain
natif du laboratoire, avec `sanctuary_test:ascent_1600` et les biomes du labo.
Une copie avec écrasement implicite pourrait donc changer le terrain proposé
par le bouton Sanctuary. Le tag des presets publics présente aussi une collision.
Les classes `NorthTerrain200` à `NorthTerrain203`, déjà dans Sanctuary,
référencent des biomes `sanctuary_test:*` fournis par l'autre module : la frontière
des fonctionnalités ne suit pas celle des sous-projets Gradle.
**Conséquence :** transférer explicitement le terrain et ses ressources, choisir
un seul preset public, conserver les anciennes clés de registre, puis déplacer
les outils de mesure vers des sources de développement. Le nom du mod peut
disparaître sans renommer immédiatement tous les identifiants de ses ressources.
Les numéros historiques ne suffisent pas à identifier du code supprimable.
Sources : [initialisation du labo](../mods/sanctuary-test/src/main/java/fr/koka/sanctuarytest/WorldgenLab.java),
[adaptateur des expansions](../mods/sanctuary-test/src/main/java/fr/koka/sanctuarytest/LabExpansions199.java),
[preset livré par le labo](../mods/sanctuary-test/src/main/resources/data/sanctuary/worldgen/world_preset/sanctuary.json),
[biomes du Nord 203](../mods/sanctuary/src/main/java/fr/koka/sanctuary/expansion/NorthTerrain203.java).
### Les raccourcis du labo ne doivent pas devenir les règles ordinaires
`QuickTestMod` active le pont pour mondes plats et enregistre plusieurs outils
de test. Son paquet de configuration peut demander de sauter l'introduction
pour les mondes du labo. `QuickTestClient` intervient dans la création de monde.
`SkySurvey175` exige même un conteneur Fabric nommé `sanctuary_test`.
Absorber aveuglément ces points d'entrée étendrait les comportements de labo
au mod principal, ou laisserait des outils dépendre d'une identité supprimée.
Il faut séparer les services nécessaires au jeu, les options explicites de
développement et les tests, puis n'enregistrer chaque événement qu'une fois.
Sources : [QuickTestMod](../mods/sanctuary-test/src/main/java/fr/koka/sanctuarytest/QuickTestMod.java),
[QuickTestClient](../mods/sanctuary-test/src/main/java/fr/koka/sanctuarytest/QuickTestClient.java),
[activation du gameplay](../mods/sanctuary/src/main/java/fr/koka/sanctuary/progression/SanctuaryGameplay.java).
### Demeure est petit mais ses données doivent garder leur adresse
Seuls deux fichiers Java de production Sanctuary référencent directement
son package : l'initialisation et le paquet Atlas. L'absorption est donc
relativement simple : service interne, événements, mixin de placement et tests.
Il faut conserver `data/demeure/footprints-v1.json`, son schéma 1, la graine et
les protections contre la corruption ou le remplacement externe. Renommer
ce chemin pendant la fusion ferait apparaître un historique vide. Préserver
aussi le filtre d'éligibilité fixé par Sanctuary et éviter un double comptage
si l'ancien JAR Demeure reste installé.
Sources : [service Demeure](../mods/demeure/src/main/java/fr/koka/demeure/DemeureService.java),
[écriture atomique](../mods/demeure/src/main/java/fr/koka/demeure/AtomicJson.java).
### JEI est le principal choix de conception
Sanctuary ne référence directement les classes JEI que dans deux fichiers
de production, `RecipeClient` et `SanctuaryJeiPlugin`. Cette frontière limitée
est favorable à un remplacement. Elle masque toutefois un moteur de 924
fichiers Java, avec un patch local de 1 491 lignes touchant 41 fichiers.
JEI fournit aussi des services serveur : transferts d'ingrédients,
sérialiseurs et synchronisation des recettes. Son démarrage dépend des points
d'entrée Fabric, de mixins, d'un access widener et de services Java. Effacer
son manifeste ne constitue donc pas une intégration fonctionnelle.
Deux trajectoires sont possibles :
- **Internaliser le moteur dérivé de JEI.** Faire disparaître le mod distinct,
adapter son démarrage et conserver ses comportements. Risque fonctionnel
plus limité, mais environ 69 000 lignes tierces restent à maintenir, avec
leurs crédits, licences, ressources et conventions.
- **Construire le catalogue Sanctuary.** Remplacer progressivement le moteur
derrière une interface interne. C'est le meilleur moyen de réduire la
dépendance à JEI, avec davantage de développement et de validation.
La seconde trajectoire doit couvrir la recherche, recettes/usages, favoris,
historique, fluides, cuisson, alchimie et autres catégories réellement utilisées,
les recettes de datapacks, les interfaces de conteneurs et les transferts avec
inventaire étendu. Les règles d'apprentissage, recettes cachées, découvertes et
révocation opérateur restent gouvernées par les services serveur Sanctuary.
L'interface native seule ne suffit pas à remplacer ce contrat.
Sources : [RecipeClient](../mods/sanctuary/src/main/java/fr/koka/sanctuary/client/RecipeClient.java),
[pont JEI](../mods/sanctuary/src/main/java/fr/koka/sanctuary/client/SanctuaryJeiPlugin.java),
[contrat des recettes](recipes-jei-beta024.md),
[provenance JEI](../mods/jei/PROVENANCE.md).
### Le build et la distribution imposent encore plusieurs mods
`scripts/pack.py` exige explicitement les JAR imbriqués Demeure et JEI,
leurs versions, licences et empreintes. `scripts/test_pack.py` exige un JAR
`sanctuary_test`. Les scripts de lancement dépendent de ses tâches Gradle
et de ses presets. Tous ces contrats devront évoluer dans les mêmes tickets
que les modules concernés, en gardant les vérifications d'intégrité.
Un seul mod de jeu Sanctuary peut continuer à utiliser Fabric Loader,
Fabric API et des bibliothèques techniques. L'exigence plus stricte « aucune
autre entrée Fabric, même technique » serait un périmètre différent : Fabric
API et certaines bibliothèques imbriquées possèdent elles-mêmes des métadonnées.
Elle n'est pas nécessaire pour réunir les fonctionnalités demandées.
Sources : [assemblage du pack](../scripts/pack.py),
[assemblage Test](../scripts/test_pack.py),
[lancement du terrain](../scripts/worldgen_lab.py).
## Nettoyage utile du dépôt
Le découpage interne de Sanctuary existe déjà : progression, inventaire,
recettes, communauté, rendu, monde, expansions, etc. Le conserver évite qu'un
mod unique devienne une classe unique. Le terrain représente à lui seul
35 599 lignes Java dans Sanctuary, avec des générations historiques enregistrées.
Les premiers gains seraient de clarifier la référence de développement,
d'extraire les paramètres et tâches répétitifs du `build.gradle` Sanctuary
(1 355 lignes), et de transformer le README de 2 881 lignes en porte d'entrée
vers un historique séparé. Certains passages annoncent toujours beta.166 et
le README Demeure indique encore 26.3-pre-2 ; les propriétés de build et le
ticket 203 sont les références utilisées ici.
Il y avait 48 worktrees avant celui de cet audit. Ce nombre augmente le risque
de travailler sur une ancienne version, comme l'a montré le décalage 168/203.
Les archiver demandera de distinguer les branches terminées des travaux encore
actifs. Aucun de ces worktrees n'a été supprimé par cet audit. Les gros dossiers
`build/` ignorés ne sont pas assimilés à des sources mortes à effacer.
## Découpage proposé en tickets vérifiables
| Ticket proposé | Résultat attendu | Vérification déterminante |
| --- | --- | --- |
| UNI 01 Référence et contrats | Branche issue de 203, inventaire des clés persistantes, fonctions de catalogue et ressources en collision | Reconstruction depuis la sauvegarde, référence de tests documentée, exemplaires de mondes de développement |
| UNI 02 Demeure interne | Plus de mod Demeure séparé ; empreintes et Atlas inchangés | Comptage unique des événements, relecture du fichier schéma 1, refus de corruption |
| UNI 03 Terrain dans Sanctuary | Terrain 203, Nord, palais et relais disponibles avec Sanctuary seul | Création Small/Medium/Large, graines 0/42/4736390610738281858, reprise d'expansion et rechargement |
| UNI 04 Outils de développement séparés | Plus de mod Sanctuary Test ; tâches de labo explicites et sources de test | Parcours normal sans raccourcis d'introduction, parcours labo opt-in, serveur dédié et client Vulkan |
| UNI 05 Catalogue interne | Interface indépendante de JEI, puis internalisation ou remplacement selon le périmètre retenu | Recherche, usages, favoris, filtres de découverte et transferts réels en solo et à deux |
| UNI 06 Distribution et documentation | Un artefact de jeu Sanctuary, installation existante débarrassée des anciens JAR, documentation actuelle | `check build`, `assemblePack`, contenu des archives et essai d'installation isolé |
Chaque ticket reçoit une branche `codex/<sujet>`. Les livraisons binaires
incrémentent la prochaine version disponible ; cet audit documentaire reste
sur beta.203. Les étapes peuvent être rapprochées, mais chacune doit laisser
un résultat vérifiable avant la suivante.
Pour les mondes existants, établir d'abord un contrat de compatibilité : garder
les codecs et noms `sanctuary:*` et `sanctuary_test:*`, les schémas et chemins,
notamment `sanctuary-lab-expansions<révision>`. Vérifier sur des copies de
développement les constructions, progression, recettes, empreintes et expansions
en attente. Un changement de nom public ou de package Java ne justifie pas
une régénération de chunks. Aucun essai sur un monde personnel n'est requis
pour commencer ces tickets.
## Difficulté estimée
Estimation de travail concentré pour une personne connaissant le dépôt,
incluant vérification et corrections ; ce ne sont pas des durées mesurées.
| Périmètre | Ordre de grandeur | Incertitude principale |
| --- | --- | --- |
| Demeure absorbé | 0,5 à 1 jour | Persistance et absence de double enregistrement |
| Terrain et fonctions Test absorbés, outils séparés | 3 à 5 jours | Anciennes clés, ressources en collision, chargement et reprise |
| JEI internalisé avec son moteur conservé | 2 à 4 jours | Initialisation, services, ressources et transferts |
| Catalogue propre remplaçant les usages actuels de JEI | 10 à 20 jours | Étendue exacte des catégories, confort d'usage et parité multijoueur |
Avec la remise au propre du build et de la distribution, compter **environ
une à deux semaines pour une unification conservatrice**, et **trois à cinq
semaines pour un ensemble nettoyé avec remplacement du catalogue**. Une
équivalence avec toutes les intégrations tierces possibles de JEI dépasserait
ce périmètre. La phase UNI 01 doit resserrer ces estimations.
Ma recommandation est d'absorber Demeure puis le terrain Test, en conservant
les formats, et d'encapsuler immédiatement le catalogue avant de le remplacer.
Renommer tous les identifiants et réécrire la génération dans cette même passe
augmenterait le risque sans être nécessaire à l'objectif d'un mod unique.
## Vérifications et limites de cet audit
Sauvegarde et restauration vérifiées, archives existantes inspectées, graphe de
dépendances et ressources en collision contrôlés. La copie d'audit possède sa
branche `codex/audit-single-mod-beta203`, distincte des branches de jeu.
Une exécution ciblée a été réalisée dans cette copie isolée :
```sh
./gradlew --no-daemon check build \
-PsanctuaryFocusedTests=demeure,recipes,operator,realtime \
-PsanctuaryAtlasOnly=true
```
**Résultat : BUILD SUCCESSFUL en 2 min 57 s ; 140 tâches, dont 139 exécutées,
et 15/15 GameTests requis réussis.** Les tests numériques rattachés à `check`,
la compilation des quatre modules, les contrôles du manifeste pack et des
ressources ont également passé. Journal : `build/audit-beta203-check-build.log`.
Le cache des sources JEI a été copié depuis le worktree 203, puis contrôlé
par le script de préparation ; ce n'est pas une reconstruction sans caches.
L'assemblage des packs n'a pas été relancé dans cet audit : les archives
203 existantes ont été inspectées et sauvegardées.
La [livraison 203](north-fracture-beta203.md) documente sept GameTests ciblés,
les tests numériques et un essai natif North Small/42. La suite historique
complète n'est plus validée depuis la [limite décrite en 200](north-expansion-beta200.md),
où une exécution a été interrompue pendant la préparation d'un test hydrologique.
Cet arrêt ne démontre pas un interblocage. Les succès antérieurs sur 265 tests
ne constituent pas une validation de la 203.
Cet audit n'est pas un test visuel, un essai Windows, une validation des
performances ou une preuve de migration des sauvegardes. Aucun client graphique
n'a été lancé ; les futures validations graphiques ciblent uniquement Vulkan.
+151
View File
@@ -1,5 +1,156 @@
# Backlog Sanctuary
## WG-RESTORE-191 — annuler les plateaux et leurs affleurements
La visite beta.190 ne valide pas le relief corrigé. [Retour exact à la base
beta.188](restore-relief-beta191.md), pour la densité et la géologie naturelle.
Conserver les aménagements indépendants ; réserver les futures singularités
de relief à quelques endroits. Nouveau profil `original` ; 134 480 densités
comparées à l’identique, génération/réouverture et 265 GameTests réussis.
## WG-NATURE-190 — revenir au relief organique
Retour beta.189 : [contrat beta.190](natural-refinement-beta190.md). Relief local
adouci, sortie d’étang large et proche, coffres de fer enchanté dans les ruines.
Profil neuf de laboratoire ; génération, réouverture, 265 GameTests et assemblages
validés. Solo Vulkan ouvert à 32 chunks.
## Réserve d’expansion — grandes surfaces ciselées
Conserver la variante de terrasses beta.189 comme piste pour une autre expansion :
grands plateaux, falaises étagées, profondeurs et verticalité variables selon
l’identité de l’île. Ne plus appliquer ces niveaux partout sur Sanctuary Island.
Aucune expansion activée. Le portail à remplissage avec ressource renouvelable
reste à concevoir ; les perles d’Ender sont une possibilité, pas une décision.
## WG-ECO-177 — écologie liée au relief
Branche `codex/relief-ecology-beta177`. Nouveau solo à 32 chunks demandé
après la visite beta.176 : plateau forestier/fleuri, vallées automnales,
hauteurs à cerisiers, cavités humides arborées, retrait de la neige et de
la mangrove, pierres mêlant strates et gisements. Profil de labo distinct ;
[contrat et vérifications](relief-ecology-beta177.md).
## WG-ECO-176 — strates, biomes et mares locales
Branche `codex/island-ecology-beta176`. Le créateur autorise la mise à
l’essai des [retours de visite](worldgen-retours-beta175.md). Nouveau monde
de labo, relief conservé ; géologie continue, larges régions automne et
cerisiers, récifs humides et mares bornées. Aucun mineshaft ni village
natif. Prototype jouable, mesures natives validées sur trois graines.
[Contrat, résultats et réserve sur la suite générale](island-ecology-beta176.md).
## WG-SKY-175 — récifs rares et ressources de l’île
Suite du labo rapide : l’île principale est conservée, les anciennes masses
flottantes sont remplacées et la bande 512–640 reste libre pour l’ISS. Le
créateur demande des relevés scientifiques, puis précise la répartition
des minerais et la présence de deepslate. Nouveau monde de labo uniquement.
[Contrat et résultats](sky-fragments-beta175.md).
## WG-LAB-174 — worldgen rapide en laboratoire
**Priorité demandée pendant la séance du 29 septembre.** Profil de relief
réservé aux nouveaux mondes de labo, sans plans d'hydrologie ni structures,
avec une référence complète séparée et des mesures reproductibles. La
génération normale reste inchangée. [Contrat et mesures](worldgen-lab-beta174.md).
Ce lot précède les maquettes de halte du Storyquest : l'outil d'itération
permet de reprendre d'abord les formes et la géographie de Sanctuary Island.
**Livré localement en beta.174 :** profil relief, lanceur protégé, comparaison
à froid/réouverture, graines 0/42/173 et client Vulkan vérifiés. `check build`
et les deux assemblages réussis ; génération normale conservée. L'optimisation
des algorithmes complets reste le travail suivant.
## SQ-173 — Refonte Storyquest et géographies indépendantes
**Direction actualisée le 29 septembre 2026.** Branche `codex/storyquest-beta173`,
issue de beta.172 (`55c745a`), avec prototype local beta.173 livré depuis.
Le [fil rouge courant](storyquest-fil-rouge.md) fait autorité pour les priorités ;
le [cadrage général](storyquest-beta173.md) conserve les autres intentions.
Le créateur abandonne le palais souterrain, ses accès et ses raccordements.
La refonte concerne Sanctuary Island, l'île de départ.
Ordre de travail proposé, à ajuster après chaque visite :
1. Un parcours extérieur arrivée → halte → ancre, comparé dans deux ambiances.
2. Une première conséquence territoriale réelle, sous contrat de nouveaux mondes.
3. Une préparation utile au parcours : familier, produit ou plan constructible.
4. Un premier accès vertical, puis la déclinaison des lieux qui fonctionnent.
Chaque étape croise géographie, récit, retour d'information et ambiance. Les
critères d'essai sont dans le fil rouge ; cet ordre n'est pas un verrouillage
de progression joueur. Pas de nouveau code ni de carte de terrain livré par
cette révision documentaire. Les autres sujets — menus, Friends, PNJ, cuisine,
économie, ISS, créatures et communauté — restent dans la réserve de conception.
## PALAIS-173 — prototype livré ; architecture abandonnée le 29 septembre
Première implémentation dans un laboratoire neuf : un palais par seed, huit
ancres extérieures, huit matériaux distincts et huit couleurs indépendantes
sur le bloc originel. [Contrat, essai et validation](palais-prototype-beta173.md).
Le code et le laboratoire sont conservés comme preuve technique. **Le placement
du palais sous l'île n'est plus prévu.** Les mécaniques d'ancres et du bloc
originel peuvent alimenter le nouveau parcours ; leur intégration indépendante
et l'ouverture effective d'une expansion restent à réaliser.
## HORDE-170 — Objet-carte natif et langage de particules
[Contrat de la carte native](horde-map-native-beta170.md). L'objet créatif
vierge découvre une identité stable et différente à sa première prise en main.
Toutes les étapes des invasions par carte sont signalées visuellement, sans chat.
La distribution en survie depuis les campements reste un ticket futur : aucune
génération existante n'est modifiée dans cette livraison.
## HORDE-169 — Cartes inconnues et invasion accélérée
[Contrat de l'invasion](horde-invasion-beta169.md). La prise en main révèle la
difficulté et le contenu. Les nouvelles cartes remplacent les vagues par un flux
de 12, 18 ou 27 monstres de plus en plus rapide, avec jusqu'à dix espèces et des
butins généreux correspondants. Les cartes beta.168 restent lisibles et jouables.
## FAMILIERS-COMBAT — Refonte à définir après essai de horde
Retour du créateur : les familiers ne lui semblent pas utiles au combat. Examiner
leurs comportements réels et définir une contribution perceptible avant de modifier
leurs statistiques. Tester avec joueurs sur une horde. Intention consignée, non implémentée.
## HORDE-168 — Cartes consommables et participation spontanée
Branche `codex/cartes-horde-beta168`. [Contrat du labo](horde-cartes-beta168.md).
Trois cartes natives illustrées, invocation immédiate sans socle, vagues ouvertes
et butins sur les monstres ramassables librement. L’ancien cercle est conservé
pour les futurs donjons. Suite générale 254/254, puis validation ciblée serveur
et client Vulkan des derniers ajustements réussies.
## HORDE-167 — Premier essai de carte d'épreuve en laboratoire
Branche `codex/communaute-economie-beta167`. [Contrat et vérifications](horde-lab-beta167.md).
Prototype local vérifié sur son parcours serveur et client Vulkan : arène ronde, bloc central, carte réutilisable,
inscriptions et trois vagues coopératives. Aucun gain d'XP ou de butin de récompense.
La simplification du tableau, la BDD et les autres cartes restent à réaliser.
La suite complète reste à 252/253 : un échec de déplacement des familiers,
reproduit au rejeu ciblé, est signalé dans le contrat de livraison.
## Direction de travail — communauté, économie et aventures
[Cadrage du 25 septembre 2026](ecosysteme-communaute-economie.md) : décisions
du créateur, état beta.166, propositions et questions ouvertes. Conception
uniquement ; branche `codex/communaute-economie-beta167`, basée sur le dernier
`main` beta.166. Le prototype beta.167 a été intégré sur `main` localement.
Dernière orientation de conception : cartes de découverte, cartes d'épreuve,
clés/reliques, avec une piste de cartes collectionnables générées. Imaginer une
arène ronde de laboratoire et son nouveau bloc central pour une carte de horde
de zombies par vagues. Le [prototype beta.167](horde-lab-beta167.md) fait maintenant
l'objet d'une réalisation séparée ; les autres familles restent en conception.
Ordre proposé : tableau à message général → statistiques existantes en BDD et
premier rapport quotidien → première bounty physique jouable → navets et machine
du dimanche avec suivi économique → huit structures et ancres d'expédition.
Le contrat de ballast doit être livré avant les Backrooms ; site et Discord
prolongent progressivement ces parcours. Aucun de ces nouveaux lots n'est livré.
## COMM-154 — Communauté dans le menu pause et sur le site
Branches `codex/community-beta154` (mod) et `codex/community-contract` (site).
+60
View File
@@ -0,0 +1,60 @@
# Récap Discord — beta.130 à beta.160
Brouillon à relire avant publication. Les captures sont des scènes de test,
pas des photos d'un serveur public. Aucune annonce envoyée automatiquement.
## Message 1 — l'image et la lumière
**Sanctuary — retour sur les beta.130 à 160**
On a d'abord beaucoup travaillé l'ambiance : bloom, minerais lumineux, rebonds
de couleur, relief des matériaux, reflets, rayons du soleil et lumières portées.
L'eau a reçu ses propres réglages, puis plusieurs passes de correction ont
amélioré les transitions, les distances et les matières.
La version du shader conservée est celle du socle beta.151. Elle reste gourmande,
mais on garde ce travail ! En beta.160, le shader devient **désactivé par défaut
sur une nouvelle configuration**, et peut être réactivé dans les options.
Images : `01-minerais-bloom-beta130.png`, `02-eau-beta144.png`.
## Message 2 — la vie du serveur
Le chantier suivant rapproche Minecraft et le site Sanctuary : **Gazette,
tableau d'affichage et intendance** partagent un contrat de publications.
Le mod fonctionne avec des fichiers par défaut ; une base MariaDB permet de
partager les données avec le site.
Le menu pause a été réorganisé autour des systèmes Sanctuary, de la carte et
des publications. La Gazette accepte les articles avec une capture obligatoire,
les réponses, les épingles et la recherche par joueur ou contenu.
Le tableau distingue les types d'annonces par couleur. Les demandes peuvent
indiquer un lieu, des matériaux, une récompense et des participants. Ce sont
des descriptions pour organiser les échanges entre joueurs : aucun coffre de
dépôt ni paiement automatique. Une demande suivie apparaît avec un **! sur la
carte**, et son résumé se lit au survol.
Images : `03-menu-et-quete-beta159.png`, `04-materiaux-beta158.png`.
## Message 3 — tester ensemble
Avec la beta.160, on prépare nos séances de test en binôme sur Mac : un petit
serveur local, un seul Minecraft à l'écran et des personnages de laboratoire.
Le but : vérifier les échanges côté technique pendant que les vrais essais en
jeu nous montrent ce qu'il faut simplifier dans l'interface.
Des scènes courtes et des captures horodatées permettront de reprendre les
moments où l'on hésite, cherche un bouton ou perd le fil. Le prochain chantier
est l'ergonomie, dans le jeu comme sur le site.
## Sources et pièces jointes
Les images originales et leurs empreintes sont rassemblées dans
`build/discord-beta130-160/` (ignoré par Git), avec `images.json` pour la provenance.
Les captures anciennes illustrent leur version d'origine, sans revendiquer une
nouvelle validation graphique. Les sondages restent une idée, pas une fonction livrée.
Références : `shader-beta130.md` à `nether-pbr-beta151.md`,
`community-beta154.md`, `pause-redesign-beta155.md`, `gazette-photos-beta157.md`,
`community-cards-beta158.md`, `community-search-beta159.md`, `duo-lab-beta160.md`.
+155
View File
@@ -0,0 +1,155 @@
# DUO-160 — laboratoire Mac et tests en binôme
Branche `codex/duo-lab-beta160`, base `beta.159`. Minecraft 26.3, Java 25,
Fabric 0.19.5 ; mêmes dépendances. Validation graphique Vulkan uniquement.
## Contrat
Le shader natif est OFF quand sa préférence est absente. Un choix sauvegardé
ON ou OFF reste respecté, ainsi que ses intensités. Aucun shader supprimé :
les ressources beta.151 restent identiques. Le client du laboratoire reçoit
explicitement `enabled:false`, indépendamment de l'installation personnelle.
Tout le laboratoire réside dans `build/duo/`, ignoré par Git. Le monde plat
`duo-flat-160`, graine 160, sert aux interfaces et aux échanges ; il ne valide
pas le terrain Sanctuary. Aucun monde personnel ouvert ou modifié.
Le module `sanctuary-test` fournit les personnages uniquement avec
`-Dsanctuary.duo=true`. Il ne fait pas partie du pack normal.
## Préparer et lancer
```sh
export JAVA_HOME=$(/usr/libexec/java_home -v 25)
./gradlew :sanctuary-test:exportDuoLaunch --max-workers=1 -Dorg.gradle.jvmargs=-Xmx1G
python3 scripts/duo_lab.py prepare
python3 scripts/duo_lab.py server
# Dans un autre terminal :
python3 scripts/duo_lab.py client
```
L'export permet ensuite de lancer Java directement, sans conserver un processus
Gradle pendant la séance. Préparer à nouveau ne remplace aucun fichier existant.
Le client rejoint automatiquement `127.0.0.1:25575`. Au premier accès,
choisir la couleur et le familier dans HELLO_WORLD puis entrer dans Sanctuary.
Depuis la console serveur : `op KokaLab` donne les commandes au client de test.
Le serveur écoute seulement sur loopback et utilise des identités hors ligne
pour ce laboratoire. Ce profil ne doit pas être exposé sur le réseau. La liaison
authentifiée d'un compte web exige toujours un vrai serveur en mode en ligne ;
elle n'est pas contournée par les outils du laboratoire.
Réglages de départ : serveur 256–768 Mio de heap, vue 4 chunks, simulation 3 ;
client 512–2048 Mio, vue 6, simulation 4, 30 FPS, fenêtre 1280×720. La mémoire
native, graphique et macOS s'ajoute au heap. Aucun engagement de tenir dans
une consommation totale de 2,75 Gio. Ne pas exécuter la compilation en même
temps qu'une séance. Augmenter seulement après mesure :
```sh
python3 scripts/duo_lab.py server --memory 1024
python3 scripts/duo_lab.py client --memory 2560
```
Le mode fichier reste le défaut. Pour une préparation neuve avec le site local :
```sh
python3 scripts/duo_lab.py prepare --storage database --server-id UUID_DU_SERVEUR \
--jdbc jdbc:mariadb://127.0.0.1:33077/BASE_DE_TEST
SANCTUARY_COMMUNITY_DB_PASSWORD='' python3 scripts/duo_lab.py server
```
La base doit déjà avoir le schéma communautaire v3. Le site doit utiliser le
même identifiant de serveur. Aucun schéma ni sauvegarde n'est migré ici.
## Personnages et séances
```text
/duo spawn Alice
/duo spawn Bob
/duo list
/duo remove Alice
/duo trace start
/duo trace stop
```
Les personnages portent le préfixe `Lab_`, sont créatifs et apparaissent près
de la source de commande. Limite de quatre, noms uniques de 1–12 caractères
ASCII alphanumériques ou `_`. Ce sont des ServerPlayer sans transport réseau
ni rendu, pas des IA et pas des clients réseau supplémentaires. On peut les
cibler avec les commandes natives, par exemple `tp Lab_Bob ~2 ~ ~`.
Ils ne valident pas seuls le protocole d'un deuxième client réel.
La trace consigne noms de test, dimension, positions et angles toutes les deux
secondes dans `build/duo/server/duo-sessions/`. Activation explicite, arrêt au
bout de cinq minutes maximum ou à la fermeture du serveur. Pas de frappe clavier,
mot de passe, navigateur ou audio enregistré.
Pour la relecture visuelle, identifier la fenêtre Minecraft puis lancer :
```sh
python3 scripts/duo_lab.py windows
python3 scripts/duo_lab.py record --window ID_FENETRE_MINECRAFT --seconds 120
```
Une séquence PNG horodatée toutes les deux secondes, un index HTML et une fiche
de notes sont écrits dans `build/duo/sessions/`. Ctrl-C termine la capture ; limite
de cinq minutes. macOS peut demander l'autorisation de capture. Cette version
ne produit pas une vidéo continue et peut manquer une interaction très courte.
On ne démarre une capture qu'au début d'une scène annoncée.
## Trois premières scènes
1. Ouvrir la Gazette, retrouver un auteur, lire un article et répondre. Vérifier
que l'autre interface retrouve la réponse, puis noter les hésitations.
2. Créer une demande avec lieu et matériaux, la suivre, lire son infobulle sur
la carte, ouvrir la discussion puis arrêter le suivi.
3. Afficher deux figurants, tester les interactions de proximité et comparer
la fluidité à un seul joueur. Garder un véritable second client pour une
vérification réseau ultérieure si nécessaire.
Le récap Discord se trouve dans `discord-beta130-160.md` ; les originaux des
quatre illustrations sont copiés avec provenance dans `build/discord-beta130-160/`.
## Vérifications
- `./gradlew check build assemblePack` : 252 GameTests, 229 réussites et les
mêmes 23 échecs que beta.159. Aucun échec ajouté ; comparaison dans
`build/duo160-server-failures.json`, log `build/beta160-check-build.log`.
- Contrôles restants, compilation, pack et client natif Vulkan réussis avec la
suite serveur déjà exécutée exclue (`-x :sanctuary:runGameTest`) : 137 tâches,
`build/beta160-release.log`. Le test `ShaderDefaults160ClientChecks` vérifie
préférence absente, objet vide, ON explicite, OFF enregistré et réactivation,
avec intensité personnelle conservée. Marqueur `SHADER160_DEFAULTS_PASS`.
- Module de test reconstruit après ajustement du transport sans réseau : build
réussi. L'export de lancement inclut les arguments fournis par Loom et élimine
son argfile imbriqué, que Java ne peut pas réinterpréter depuis un autre argfile.
- Serveur réel à 768 Mio : démarrage sur loopback, monde plat neuf ; création de
quatre acteurs, doublon et cinquième refusés, retrait et acteur absent vérifiés.
Le journal produit 56 observations JSON valides puis s'arrête sur commande.
- Client réel séparé, Vulkan, 2048 Mio, shader OFF : connexion jusqu'à l'écran
HELLO_WORLD vérifiée visuellement. La création du profil et le parcours en jeu
avec le joueur restent à faire ensemble ; aucune session ergonomique humaine
terminée ni validation d'un deuxième client réseau revendiquée.
- Mesure ponctuelle avant l'entrée du joueur : serveur ~313 Mio de heap utilisé,
client au menu ~289 Mio. Ce ne sont ni des pics ni une mesure de RAM système
totale. Les figurants ne possèdent ni sockets ni fenêtre graphique.
- Capture ciblée macOS essayée sur quatre secondes : séquence PNG, manifeste,
index HTML et fiche de notes créés. Aucun enregistrement continu ou micro.
- Export MRpack : intégrité ZIP, version et JAR embarqué vérifiés. Les 29 ressources
de shaders sont inchangées depuis beta.159. JAR et packs beta.154–159 conservés.
- Skill personnel `sanctuary-session-notes` installé et validé séparément dans
`~/.codex/skills/` pour classer les dictées en Markdown. Il ne change pas le
modèle du tour courant ; Luna/low peut être choisi dans une tâche dédiée.
Le serveur local utilise la base de développement du site, schéma v3 déjà présent,
sous le même scope. Aucune validation de liaison de comptes hors ligne : cette
opération reste réservée au mode authentifié. Aucun déploiement public, canal
packwiz, serveur personnel ou instance Prism modifié.
## Artefacts locaux
- `mods/sanctuary/build/libs/sanctuary-beta.160.jar` — SHA-256
`4c19e620e989050488c9a96aaddaf897a74115a9fb4c5099e5518ae32db5b2e8`.
- `build/Sanctuary-beta.160.mrpack` — SHA-256
`272e3460f35c8d9a4de689d30e4d952c3121923ba5fe30a23c83b3e76bd23bac`.
- `build/duo/Jouer.command` et `Serveur.command` : raccourcis locaux de séance.
+319
View File
@@ -0,0 +1,319 @@
# Communauté, économie et aventures — cadrage du 25 septembre 2026
Statut : **cadrage de conception**. La réalisation du premier prototype d'arène
autorisé ensuite est suivie séparément dans [beta.167](horde-lab-beta167.md) ;
elle ne vaut pas livraison de l'ensemble des systèmes décrits ici. Cette note
conserve la discussion du créateur et distingue ses décisions des propositions
à éprouver. Base : dernier `origin/main` vérifié, beta.166 (`7979f33`).
Le créateur demande finalement une branche dédiée, puis une intégration sur
`main` à ne pas oublier. Branche : `codex/communaute-economie-beta167`.
La prochaine livraison de code visée est **beta.167** ; ce numéro est désormais
préparé dans les métadonnées du prototype, sans publication. On avance par petits résultats
jouables, sans figer maintenant toute l'économie.
## Ce que l'on cherche à produire
Un serveur semi-anarchique dont l'État assure une administration forte, trace
les événements, régule les prix du marché et pose des frontières. Les joueurs
peuvent chercher leurs propres moyens de les franchir. Le système doit susciter
la coopération, la multiplication, la fabrication, la destruction et la recherche.
L'objectif est une émulation collective, avec des conséquences dans le monde.
Minecraft, le site et Discord prolongent la même vie communautaire. La BDD doit
permettre d'analyser les activités à la journée et d'en rendre compte sur le site.
Le serveur Minecraft conserve l'autorité sur les actions et récompenses de jeu.
## Décisions et intentions exprimées par le créateur
### Tableau et Gazette
- Le tableau propose **une seule sorte de message général**, sans les quatre
catégories obligatoires actuelles. Annonce, demande, besoin, information,
rendez-vous et découverte sont des usages de ce même message.
- La Gazette accueille les récits, photos et discussions durables ; le tableau
sert aux messages immédiats et à l'organisation. Les références Facebook et
Twitter expriment ces usages, pas une demande de reproduire leurs fonctions.
- Un message peut être associé à une quête donnant de l'XP aux autres joueurs.
Une découverte peut être publiée pour organiser son exploration ensemble.
Tout message n'a pas à devenir une quête.
### Quêtes canoniques, coopération et récompenses de prestige
- Les panneaux émeraude, rubis et saphir testés avec de vrais joueurs conviennent
au gain d'XP solo, mais tendent à isoler les participants. Conserver un intérêt
solo tout en développant des raisons concrètes de coopérer.
- Le tableau accessible dans un menu et ses quêtes proposées pour une durée
limitée sont une piste appréciée par le créateur.
- Prévoir des quêtes canoniques reconnaissables, par exemple **chasse aux
zombies**, avec XP selon les objectifs accomplis, quotas et paliers de
récompenses. La référence aux passes de progression de jeux comme Rocket
League concerne cette progression visible ; aucun modèle payant n'est demandé.
- Des quêtes difficiles à obtenir ou à accomplir peuvent donner des **capes et
familiers exclusifs**, recherchés pour leur valeur cosmétique et intrinsèque.
Nature de l'exclusivité, disponibilité et capacités des familiers restent à
préciser ; ne pas conclure que tous les familiers sont purement cosmétiques.
- La discussion retient les deux usages dans une même quête : avancer seul
pendant sa partie normale et rejoindre une horde collective déclenchée par
bounty. Les zombies remplacent les squelettes comme première piste liée à
l'île abandonnée ; aucun scénario d'ossuaire n'est retenu.
### Familiers comme incubateurs d'XP
Idée ajoutée par le créateur : les familiers **multiplient leur XP stockée au
fil des blocs parcourus à pied, posés et cassés**. Ils servent d'**incubateurs
d'XP**, avec une croissance modérée pour éviter un système trop puissant.
Ce mécanisme peut donner un rôle aux familiers dans les quêtes.
« Multiplier » décrit ici l'intention de faire fructifier la réserve ; aucune
formule, croissance exponentielle par action, valeur de rendement ou limite
n'est décidée. L'origine de l'XP déposée, sa récupération et les conditions de
présence du familier restent à choisir, ainsi que la personne dont les actions
comptent. Cette idée est à concevoir, pas une nouvelle capacité livrée.
### État, navets et loterie
- L'État joue à la fois un rôle d'arbitre et d'acteur du monde. Ses contraintes
doivent donner des occasions de jouer et de s'organiser.
- Les navets s'achètent auprès d'un **PNJ le dimanche**. Ils se revendent pendant
la semaine et **pourrissent après une semaine s'ils ne sont pas vendus**.
Il ne s'agit pas d'une culture récoltée par les joueurs.
- Des tickets trouvés ou achetés pendant la semaine servent à la loterie du
dimanche, avec de **vraies machines manipulant des stocks d'objets**.
- Ces machines peuvent notamment dupliquer ou diviser un stock. Les propositions
précédentes de simples permis et réductions ne définissent pas ce mécanisme.
Les privilèges temporaires restent une idée initiale possible, sans catalogue
de récompenses approuvé.
- Le rapport entre ces opérations et le **ballast des Backrooms** doit être
pensé dès leur conception.
### Bounties, cartes et extensions
- Les bounties sont des **objets collectionnables que l'on utilise quand on est
prêt**. Elles peuvent être offertes comme occasions d'aventure.
- Réutiliser le système de cartes et en créer à la volée pour ces aventures.
La forme exacte de la carte et son geste d'activation restent à choisir.
- Prévoir **huit structures** permettant d'étendre les huit extensions de l'île
principale, en lien avec un **bloc originel sur l'île**, puis des **ancres**
permettant de générer des structures d'aventure.
- Les objectifs évoqués comprennent primes, objets clés à retrouver et
structures à détruire. L'ensemble doit aussi permettre la contrebande
organisée et les initiatives des joueurs.
- L'ordre de déblocage entre structures, extensions et bloc originel n'est pas
encore fixé. Ne pas transformer cette intention en règle « huit sur huit ».
### Familles d'objets retenues et prochaine scène du labo
Le créateur retient le tableau fonctionnel suivant :
| Famille | Fonction |
| --- | --- |
| **Carte de découverte** | Indiquer un lieu existant à explorer ; transmettre une information. |
| **Carte d'épreuve** | Déclencher une activité à l'activation : horde, défense, recherche… |
| **Clé ou relique** | Ouvrir un accès ou activer un mécanisme précis dans le monde. |
Piste accessoire ajoutée : des **cartes collectionnables générées par le jeu**.
Leur sujet, leur présentation, leur rareté et leur éventuel lien avec les cartes
fonctionnelles restent ouverts. Ne pas leur attribuer automatiquement un pouvoir
ou une récompense : collection et activation sont deux usages à distinguer.
La prochaine scène à **imaginer dans le laboratoire** est une **arène ronde
avec un nouveau bloc interactif au centre**. La carte de horde est la première
carte d'épreuve envisagée : elle fait apparaître un groupe de monstres par vagues.
Le nom, l'apparence, les dimensions et les règles du bloc restent à concevoir.
Cette décision portait initialement sur la conception. Le créateur a ensuite
autorisé un premier essai jouable ; son état réel est dans le
[contrat du prototype](horde-lab-beta167.md).
Parcours proposé pour ce prototype : présenter une carte au bloc → lire l'épreuve
et ses récompenses → rejoindre le groupe → lancer explicitement → affronter les
vagues → consulter le résultat. Une activation au clic droit, des apparitions
réparties au bord du cercle et un état visuel du bloc sont des propositions.
La consommation de carte, l'engagement des participants, les arrivées tardives,
la défaite, la déconnexion et les récompenses seront précisés avant le code.
Le laboratoire doit utiliser une scène de développement neuve et isolée ; ne pas
réécrire le monde du labo déjà conservé ni une sauvegarde personnelle. La forme
transportable d'une balise d'épreuve reste une possibilité ultérieure. Ce bloc
d'arène n'est pas encore assimilé au bloc originel ou à une ancre d'expansion.
### Données et liens entre les services
- Intégrer les statistiques du jeu à la BDD quand elles sont disponibles et
raccorder progressivement les systèmes déjà en place.
- Viser la traçabilité des échanges et événements, les analyses quotidiennes et
les comptes rendus sur le site.
- Exploiter le lien d'identité Discord/site/Minecraft pour de futures interactions
personnelles, dont les notifications. Leur contenu et leur fréquence restent
à définir ; cette discussion n'autorise aucun envoi de message.
## Ce qui existe réellement sur le socle beta.166
| Socle vérifié dans les sources et contrats | Limite actuelle |
| --- | --- |
| Gazette, photos, réponses, tableau, abonnements et repères de demandes ; stockage fichier ou MariaDB partagé | Quatre catégories `info/work/need/event` ; matériaux et récompenses descriptifs, sans livraison ni XP automatique |
| Compteurs serveur publiés en base toutes les 30 secondes | Dernier instantané : durée de fonctionnement, morts, joueurs connectés, état ; pas un historique individuel quotidien complet |
| Activité matérielle locale datée du Blocodex : minage, pose, fabrication, ramassage et jet | Agrégats journaliers sans distinction par joueur ou dimension ; ne prouvent ni échange, ni stock, ni ballast |
| Cartes au trésor physiques et import de leurs repères dans l'atlas | Destinations provenant de plans existants ; aucun moteur de bounty activable ni de génération d'aventure à la demande |
| Génération d'expansions et quatre anciennes expéditions ouvertes | Les huit nouveaux déblocages, le bloc originel et les ancres restent à concevoir |
| Inscription web et reprise des codes d'accès ; lien aux identités Discord | Pas de service de notifications personnelles livré par ce chantier ; le dernier contrat conserve une validation OAuth réelle à terminer |
Références : [communauté](community-contract-v1.md),
[compteurs beta.161](session-fixes-beta161.md),
[activité datée](blocodex.md#relevés-datés--portée-de-lalpha23),
[cartes beta.059](atlas-markers-beta059.md), [expansions](expansion.md),
[inscription beta.166](inscription-web-beta166.md).
Cet état décrit le dépôt, pas une vérification du déploiement public.
## Propositions de fonctionnement à valider
Pour les quêtes canoniques, la proposition discutée associe des **paliers
personnels à un effort collectif**. Émeraude pourrait accueillir les contrats
accessibles, rubis les opérations coordonnées, saphir les aventures rares et
exigeantes. Cette répartition n'est pas une règle arrêtée.
Premier essai désormais envisagé : une chasse aux zombies avec paliers d'XP
personnels, à laquelle contribue aussi une horde collective activée par carte.
Une jauge commune et des préparatifs restent des options, sans scénario imposé.
Les nombres 10/30 cités pendant la discussion sont illustratifs. Participation
au-delà du dernier coup et conditions de maîtrise pour les trophées restent à définir.
Un carnet pourrait présenter les objectifs et gains ; l'échéance de l'offre et
le moment d'activation d'une bounty obtenue seraient distincts.
L'incubation d'XP pourrait accompagner ces parcours d'exploration, de construction
et de minage. Avant tout essai, proposer puis mesurer un rendement et un plafond,
en examinant les trajets répétitifs, les boucles pose/casse et le cumul de
familiers. Ce sont des points d'équilibrage à décider, sans taux ni interdiction
déjà validés. Toute future variation de réserve doit pouvoir être expliquée par
les actions serveur enregistrées et rester cohérente avec les récompenses de quête.
Le message général pourrait recevoir des éléments facultatifs : lieu, rendez-vous,
objectif et récompense. Le tableau rassemble les participants ; la Gazette garde
le récit. La bounty peut être liée à une publication sans que poster un message
crée automatiquement une aventure.
Parcours proposé : obtenir une carte → la conserver ou l'échanger → réunir un
groupe → l'activer → accomplir l'objectif → recevoir la récompense. Conserver
ensuite une carte souvenir portant le résultat et les participants est une option.
Échangeabilité, perte, vol, copie et consommation de l'objet restent à décider.
Deux usages possibles des cartes : révéler un lieu existant, ou préparer une
nouvelle aventure à l'activation dans une ancre. Une copie cartographique pourrait
partager les indications sans multiplier les droits à récompense. Ce n'est pas
encore un contrat implémenté. Les Backrooms conservent leur intention spécifique
de découverte sans coordonnées ni carte automatique.
Exemple de machine, **sans valeur d'équilibrage approuvée** : un ticket et
64 lingots engagés donnent 128 ou 32 lingots. Probabilités, stocks admissibles,
fréquence, financement et comportement des objets uniques sont à définir.
Conserver l'échéance d'origine des navets lors d'un transfert ou d'une duplication
est proposé pour que ces opérations ne rajeunissent pas les lots.
Pour le ballast, une perte pourrait laisser un dépôt, une duplication une trace
architecturale ou une anomalie. Aucune équivalence quantitative n'est décidée.
Le [contrat cosmologique](cosmologie.md#le-ballast-de-léconomie) reste ouvert sur
matière retirée, empreinte ou combinaison des deux. Une trace ne donne pas à elle
seule le droit de créer des objets récupérables.
Boucle envisagée : **production et échanges → machine → ballast → lieu à
explorer → bounty → expédition → trouvailles et nouveaux échanges**.
Le lien automatique entre chaque étape est une proposition à éprouver.
## Ordre de travail proposé
**Dernière orientation : cadrer d'abord la scène d'arène ronde du labo et son
bloc central**, pour rendre la carte d'épreuve concrète. Le tableau ci-dessous
conserve les dépendances générales ; son ordre initial n'impose pas de terminer
la BDD ou la simplification du tableau avant de concevoir cette scène.
| Étape | Résultat concret à obtenir | Ce qui doit être précisé juste avant |
| --- | --- | --- |
| 1. Simplifier le tableau | Publier et lire un message général en jeu et sur le site, retrouver les anciennes annonces et leurs suivis | Présentation des champs facultatifs ; compatibilité des anciennes catégories sans effacer l'historique |
| 2. Observer l'existant | Produire un premier compte rendu quotidien depuis des données serveur réelles en BDD | Périmètre des statistiques, unités, identité joueur/monde, jours, visibilité et reprise après panne |
| 3. Jouer une première bounty | Obtenir une carte, la garder, l'activer et terminer un objectif vérifié par le serveur, avec une seule attribution de récompense | Un objectif simple, par exemple une livraison ; rôle du groupe, financement et nature de l'XP |
| 4. Éprouver l'économie du dimanche | Un PNJ vend des navets qui vieillissent ; une machine engage un stock et rend son résultat, chaque opération étant tracée | Cours et revente, échéance exacte, tickets, hasard et conversion en ballast |
| 5. Relier les aventures au territoire | Définir les huit structures, puis éprouver une première activation et une expédition par ancre avant de décliner les huit | Articulation avec les quatre anciennes régions, emplacement, coûts, graine/version et protection des terrains existants |
| 6. Faire vivre les prolongements | Alimenter les nouvelles régions des Backrooms avec le ballast validé ; ouvrir les notifications choisies et les récits du site | Contrat de ballast livré avant BR-01 ; règles de publication et préférences Discord |
Cet ordre est une proposition de départ, pas six grosses livraisons promises.
Chaque étape peut être divisée selon les essais. Les rapports du site commencent
à l'étape 2 ; les Backrooms et notifications sont des suites distinctes.
Les nouveaux systèmes produisent leurs événements dès leur première livraison.
Le premier parcours bounty peut utiliser un objectif existant sans attendre la
génération des huit structures.
## Points à trancher au fil de ces premières étapes
1. **Récompenses** : quelle XP, payée par qui, attribuée à qui dans un groupe,
pour quelle preuve d'accomplissement ?
2. **Bounties** : carte physique liée à l'atlas ou autre présentation ; échange,
copie, vol, perte, activation, abandon, échec et souvenir ?
3. **Navets** : sept jours après achat ou échéance hebdomadaire commune ; horloge
pendant les arrêts, cours de revente et devenir des navets pourris ?
4. **Machines et ballast** : quels stocks, probabilités et résultats ; quelle
part est une trace, une perte ou une matière récupérable ?
5. **Territoire** : emplacement des huit structures, ordre d'ouverture et rôle
du bloc originel ; frontières franchissables par quels moyens de jeu ?
6. **Information** : ce que l'administration technique enregistre, ce que l'État
sait dans la fiction, ce que le public voit et ce que Discord signale ?
7. **Incubateurs d'XP** : dépôt initial, actions reconnues, croissance et plafond,
familier porté ou présent, cumul, transfert/retrait et articulation avec les
quêtes ? L'XP incubée et les récompenses directement attribuées doivent être
distinguées pour éviter un double compte.
Ces distinctions doivent laisser exister secrets, découverte et contrebande.
Un objet ramassé après un jet n'est pas automatiquement une vente. Les compteurs
cumulés historiques ne permettent pas de reconstruire les journées antérieures.
Une période non observée doit rester identifiable, sans inventer des zéros.
## Conditions de réalisation
Avant des écritures réelles : contrat de données additif, événements identifiés
et datés, reprise sans double récompense ni double consommation, comportement
explicite en cas de panne et séparation entre résultat tenté et résultat acquis.
La BDD sert les analyses sans imposer des requêtes bloquantes au thread de jeu.
Les frontières, prix, récompenses et pertes sont des règles serveur.
Aucune modification de monde, migration, régénération ou activation d'expansion
n'est autorisée par cette note. Leurs futurs contrats doivent préserver les
sauvegardes et secteurs existants. L'évolution des anciens contenus communautaires
devra également être explicitée avant de changer leur stockage.
La première passe était documentaire. La réalisation autorisée ensuite porte
uniquement sur le laboratoire de horde, selon son contrat propre. Les autres
systèmes décrits comme futurs le restent.
### Retour sur main et prochaine version
- **À faire avant livraison : intégrer le travail validé sur `main`**, vérifier
le résultat après intégration et reprendre le travail depuis cette base.
- Le premier périmètre proposé était la simplification du tableau. La discussion
se concentre maintenant sur la conception de l'arène du labo et du bloc central ;
le périmètre de code de beta.167 est maintenant le prototype de horde du labo.
L'ensemble de cette feuille
de route n'est pas promis dans une seule version.
- À la première livraison de code, revérifier le compteur disponible, synchroniser
`mod_version`, `pack_version` et `packwiz/pack.toml`, puis exécuter les contrôles
requis. Le prototype prépare désormais ces trois valeurs à beta.167.
- Aucun tag, push, artefact public, canal packwiz ou déploiement personnel n'est
effectué par cette prise de notes.
## Ajustement du 26 septembre — cartes de horde
La carte est consommable et invoque immédiatement des vagues là où on se trouve.
Aucune inscription : on participe spontanément en arrivant sur le combat.
Les monstres portent les butins spéciaux, ramassés librement par les joueurs.
Chaque carte possède une illustration mappifiée (monstres, textures des butins),
une identité et une difficulté lisible. Le décor circulaire reste une piste
pour les futurs donjons. [Premier labo à trois cartes](horde-cartes-beta168.md).
## Retour de combat du 26 septembre — familiers et horde
Le créateur constate que les familiers ne sont pas utiles au combat : prévoir
une refonte de leur contribution, à évaluer en combat réel contre une horde.
Ce constat ne prouve pas une panne technique ; diagnostic des comportements,
rôles et lisibilité encore à faire. Aucune refonte des familiers livrée dans ce lot.
Les [cartes beta.169](horde-invasion-beta169.md) restent inconnues avant leur
prise en main. Elles ouvrent une invasion continue de monstres variés, dont la
cadence accélère, avec butins propres aux espèces, rubis et saphirs. Apparition et
mort réelle utilisent les runes SGA et les particules natives Minecraft.
+85
View File
@@ -0,0 +1,85 @@
# WG-ERODED-192 — couronnes et faces du massif
Branche `codex/eroded-massif-beta192`, Minecraft 26.3, graine de visite 42.
Profil `eroded`, preset neuf `sanctuary_test:eroded_massif_v1`.
## Retour et résultat attendu
Après la restauration beta.191, les captures du 30 septembre à 22:02–22:05
montrent des marches de terre et d’herbe sur les sommets arrondis. Le créateur
souhaite de vrais changements de forme : quelques dessus plus plats sous les
arbres, des faces plus franches, des creux d’érosion et des pentes conservées.
Ne pas reproduire les terrasses périodiques 189 ou simplement peindre la roche.
## Variante
Trois couronnes sont choisies dans les points hauts du massif existant. Leur
altitude dépend du terrain original. Les trois cerisiers réservés occupent ces
couronnes, avec exclusion des points de plantation encore trop raides. Une coupe légèrement inclinée rabote le
sommet ; une face plus raide regarde son versant descendant. Les empreintes
ont des limites adoucies et irrégulières ; les petites entailles entre couronnes
s’appuient sur la distance aux deux centres de Voronoï les plus proches.
Les coupes retirent au plus 24 blocs du plafond géométrique local. Une cavité
préexistante découverte par la coupe peut donner un sol visible plus bas.
C’est une érosion géométrique locale sur le bruit 3D existant, sans simulation
physique. Pas de grille de niveaux Y, de remplissage des cavités ni de lissage
global. Aucun changement de densité à Y≤240, dans les îlots aériens ou hors du
massif central. Les formes originales restent la base sous le plafond sculpté.
La couverture végétale du massif n’est posée que sur les surfaces capables de
la porter : les fortes pentes gardent la géologie réelle (stone, gisements,
strates). Le gradient est mesuré sur des colonnes du massif en coordonnées
monde, traversant les limites des chunks et ignorant les îlots aériens. Les
hauteurs et pentes sont mises en cache dans un domaine borné. Cette variante
n’utilise pas le solveur d’hydrologie ; les étangs et déversoirs existants restent
responsables de l’eau.
Ancres, donjon, gemmes, soufre, ruines à coffres, galerie, rosace et palais sont
conservés. La règle de roche historique garde son comportement lorsque son
nouveau champ optionnel `slope` est absent. Les anciens presets gardent leurs
paramètres ; aucun monde existant n’est modifié ou régénéré.
## Vérifications et limites
Génération native finale et réouverture réussies, graine 42 :
`build/eroded192e-cold.json` et `build/eroded192e-warm.json`.
Les hauteurs mesurées sont identiques après rechargement ; les trois troncs
sont relus dans les chunks sauvegardés.
- 1 008 colonnes modifiées dans le relevé espacé de deux blocs ; 392 échantillons
de sommet peu pentus. Trois couronnes et trois cerisiers contrôlés en blocs réels.
- 39 366 densités profondes et aériennes identiques à la base ; comparaison
supplémentaire hors du massif. Aucun niveau Y périodique réintroduit.
- 1 545 échantillons rocheux contre 8 de terre/herbe parmi les fortes pentes
contrôlées. Les plantations trouvent un sol de pente ≤0,75 dans leur chunk.
- Ancres, 49 cartes/cadres, rosaces, galerie, quatre coffres de ruines, donjon,
gemmes, soufre et étangs contrôlés. Cascade large et absence d’arbres dans
les colonnes d’eau et de berge contrôlées conservées.
Démarrage final : 12,126 s à froid et 0,613 s à chaud, zéro région d’hydrologie ;
83,610 s à froid et 18,501 s à chaud avec
l’ensemble des contrôles et le remplissage de l’atlas. Ces contrôles ne tournent
pas au lancement du solo. Les mesures intermédiaires pendant la compilation
concurrente ne servent pas de référence de performance.
`./gradlew check build assemblePack assembleTestPack` réussi en 9 min 9 s,
265 GameTests réussis. Après ajustement du placement des cerisiers et de son
contrôle de réouverture, `:sanctuary-test:check :sanctuary-test:build` réussi et
pack de test réassemblé : 175 classes et 121 ressources vérifiées dans le JAR,
identique à sa copie distribuée (`build/eroded192-pack-receipt.json`).
La validation technique ne vaut pas validation esthétique du créateur. Cette
itération vise le massif central de la graine de visite ; elle ne remodèle pas
les récifs aériens. Pas de publication ni de mise à jour de l’installation Prism.
## Visite
Nouveau solo `Sanctuary-Eroded-Massif-192-Solo`, run `visite192`, profil `eroded`,
graine 42, vue 32 et simulation 12, spectateur et commandes autorisées.
Position préparée (94,316,153), face au massif. Les 49 cartes et cadres ainsi
que les huit ancres éteintes et l’origine sans relais ont été relus dans cette
copie neuve. Options et shader repris du solo 191, fermé et sauvegardé à 22:14.
Client Vulkan lancé et entrée dans le monde confirmée à 22:34 ; vue 32,
simulation 12 et visibilité de l’ascension contrôlées dans
`build/eroded192-solo.log`.
+83
View File
@@ -0,0 +1,83 @@
# STEM-185 — ligne fine, portail de toiture et vitres reliées
Ticket du 30 septembre 2026, branche `codex/fine-stem-beta185`.
Le créateur valide la fleur et le palais beta.184, ainsi que la galerie inférieure.
Il demande de supprimer l'escalier, les paliers et la grosse colonne : une ligne
d'un bloc doit guider visuellement le bateau-poule vers le palais. Compléments
pendant la réalisation : incruster le portail horizontal au sommet du dôme,
libérer l'espace au-dessus de la carte, fermer le jour sous les vitres supérieures.
## Contrat
Nouveau profil de labo `stem`, preset `sanctuary_test:fine_stem_v1`, réglages
`sanctuary_test:fine_stem_v1_10`, graine de visite **42**, version **beta.185**.
La dimension 1600, le champ de bruit 640, l'île et les eaux retenues restent
ceux de beta.184. Aucun changement de format, d'ancien preset ou de sauvegarde.
La visite beta.184 a été arrêtée proprement à la demande du créateur ; une
nouvelle sauvegarde est préparée pour la relance.
## Résultat
- Rosace et Bugrock toujours en (0,640,0), palette et huit axes conservés.
- **Une seule colonne de calcite en (0,Y,0)** au-dessus du dôme inférieur,
de Y=652 jusqu'au départ de l'évasement à 1160. Pas de spirale, de marches,
de plateforme intermédiaire, de balustrade ou de pont d'escalier.
- Les huit nervures s'élargissent progressivement près du sommet, en partant
du même bloc central. Fleur, balcon et pièce du palais à Y=1278 conservés.
- Portail horizontal 7×7, ouverture 5×5, **incrusté à Y=1309 dans le dôme**.
Son cadre remplace les vitres sommitales et touche la toiture sur tout son
pourtour extérieur. Le passage central traverse la voûte ; plus de cadre
suspendu au milieu de la salle, au-dessus des 49 cartes au sol.
- Un soubassement de calcite ferme le bloc d'air sous les panneaux de verre.
Les huit passages restent ouverts. Leur dégagement est vérifié avec les
collisions natives, pour une coque standard de 1,375 bloc et son pilote.
- Les moteurs du bateau-poule existant ne comportent pas de plafond fixe à
640. Aucun changement de mécanique de vol ni de comportement de familier.
La révélation de la couronne en montant reprend beta.184. La galerie inférieure,
son portail 5×5, le soufre, les secteurs miniers, les étangs et le donjon restent
ceux validés. Les portails restent des infrastructures, sans voyage dimensionnel.
## Vérifications et visite
Contrôle natif final `solo185c/stem/42` réussi : **508 tranches d'un seul bloc**,
16 approches libres (huit par lieu, coque et pilote légèrement au-dessus des
tapis de mousse), 104 colonnes vitrées appuyées sur calcite, 24 blocs de cadre
reliés à la toiture. Les 16 868 entrées du plan correspondent aux blocs générés.
La voûte hors de l'ouverture centrale reste identique à beta.184. Aucune marche
ni palier dans cette variante. Reçu : `build/stem185-quick-result.json`.
L'île conserve ses 567 échantillons de densité vérifiés, zéro région d'hydrologie,
les trois cerisiers, cinq spawners, quatre wagonnets à butin, la galerie, le soufre
et les trois secteurs de gemmes. Le serveur démarre en 5,2 s ; préparation avec
sondages, atlas et contrôles en 42,3 s. Ce dernier temps inclut les tests.
Lecture NBT du monde arrêté : 49 cartes complètes et figées, cadres horizontaux
et identifiants uniques. Ancien cadre suspendu en air, nouveau cadre de toiture
en obsidienne, tige centrale isolée et soubassement du vitrage confirmés.
Reçus : `build/stem185-saved-atlas.json`, `build/stem185-saved-blocks.json`.
`./gradlew check build assemblePack assembleTestPack :sanctuary-test:exportDuoLaunch`
réussi en **7 min 3 s**, **265/265 GameTests**. Journal :
`build/stem185-check-build.log`. Le daemon de compilation a été arrêté pour
libérer la mémoire avant le client Vulkan.
Solo Vulkan ouvert le 30 septembre à **07:32:51**. Nouveau monde
`visite185/stem/42`, `Sanctuary-Fine-Stem-185-Solo`, créatif, commandes et vol,
vue 32 chunks et simulation 12. Départ préparé dans le palais supérieur.
Entrée confirmée en (6.5,1280,8.5), vue 32 et simulation 12 dans le journal
`build/stem185-solo.log`. Vérification native du frustum réussie à 07:32:53 :
couronne cachée depuis le sol, révélée en arrivant à la rosace. Inspection de
la fenêtre du jeu : carte au sol présente et vitrages reliés au soubassement.
L'ancienne sauvegarde beta.184 reste conservée. Aucun canal public ou instance
Prism modifié ; seuls les artefacts locaux et le laboratoire sont livrés.
Les contrôles de passage ne
remplacent pas un trajet intégral piloté en bateau-poule.
| Lieu | Téléportation |
| --- | --- |
| Rosace du Bugrock | `/tp 0.5 639 5.5 180 -20` |
| Palais et carte au sol | `/tp 6.5 1280 8.5 140 35` |
| Portail dans la toiture | `/tp 0.5 1312 0.5 180 75` |
| Liaison fine, vue extérieure | `/tp 24 920 24 135 -20` |
| Portail inférieur | `/tp 0.5 177 7.5 180 30` |
+101
View File
@@ -0,0 +1,101 @@
# WG-GALLERY-198 — création Large interrompue par la galerie centrale
Branche `codex/gallery-generation-beta198`, depuis beta.197. Minecraft 26.3,
Fabric 0.19.5, Java 25. Retour R013 et journal `message-11.txt` fourni par le
créateur : arrêt Windows à la création Large, graine `4736390610738281858`.
## Cause et correction
L'exception `No supported central gallery floor` dans `Cavern183.create`
interrompt la décoration du premier chunk. Le repli beta.197 exige encore de
la roche exactement en (0,0), sous la surface de cette même colonne. Sur cette
graine, son sommet n'est qu'à Y=168 et presque toute la colonne traverse du
vide, alors que les colonnes voisines possèdent des assises. L'erreur a été
reproduite sur macOS avec la même graine avant modification. Ce journal ne
montre pas une panne du pilote Vulkan ni un manque de mémoire.
Les nouveaux profils beta.198 conservent d'abord toutes les recherches
existantes. Un dernier repli ne s'exécute que lorsqu'elles échouent : il compare
l'assise et la couverture sur l'emprise de la chambre, de Y=32 à Y=276, sans
condition éliminatoire sur la seule colonne centrale. Au plus 12 250 sondes,
une seule planification mise en cache par source de biomes. Même un puits
entièrement vide conserve la salle et sa fondation locale existante, plutôt
que d'annuler la création du monde. Dans ce cas limite, la chambre peut être
ouverte sur le vide : ce correctif ne prétend pas créer une cavité fermée dans
une roche absente.
Le portail reste horizontal en X=0, Z=0. Les dimensions de la chambre,
maçonnerie et règles du relief, de l’eau, des biomes et des minerais restent
identiques.
Aucun calcul régional d'hydrologie ajouté.
## Sauvegardes et distribution
`stable_gallery198` est un paramètre de codec additif, faux par défaut.
Les trois nouveaux profils `shared_island198_{small,medium,large}` et le preset
public Sanctuary l'activent. Les profils 196/197 restent présents et gardent
leur comportement ; leur personnalisation conserve leur révision exacte.
Les protections de spawn introduites en 197 restent actives en 197 et 198.
Le pack de test est destiné à un **nouveau monde**. Une création interrompue
beta.197 ne migre pas automatiquement en beta.198 : recréer un nouveau monde
avec la même graine et la taille souhaitée. Aucun monde personnel modifié,
aucun chunk régénéré, aucune publication du canal ni installation Prism.
## Vérifications
- Échec beta.197 reproduit dans `build/worldgen-lab/gallery198-baseline/`.
- Tests synthétiques réussis : puits vide, appuis autour d'un centre vide,
fine couche haute, roche pleine, déterminisme et borne de calcul.
- Matrice native réussie : Small/Medium/Large, graines 0, 17, 42,
`4736390610738281858` et `-9223372036854775808`. Plans répétés à l'identique ;
toutes les galeries admises par beta.197 conservent exactement leur plan.
Le nouveau repli s'active pour la graine du journal, dans les trois tailles.
- Small, Medium et Large sur la graine fournie : création puis réouverture
réussies. Portail en `(0,183,0)`, trois salles accessibles, respectivement
1 927, 1 925 et 1 930 cellules accessibles ; géométrie et ornements identiques
après reprise. Démarrages serveur à froid : 18,4 / 24,2 / 21,1 secondes ;
ces mesures locales ne sont pas un temps de chargement garanti sous Windows.
- Client intégré **Vulkan**, vue 8 chunks et mémoire Java 2 Gio : Large sur
la graine exacte, arrivée réelle en `(-15.5,254,-15.5)`, huit recherches
natives de spawn sur l’île. Parcours FR/EN des trois tailles, annulation,
aller-retour disque et maintien des révisions historiques 196 et 197.
Réussi en 1 min 2 s, trace `build/gallery198-client.log`.
Commande du contrôle ciblé, rejouable dans un nouveau dossier :
```sh
python3 scripts/worldgen_lab.py verify --profile large \
--seed 4736390610738281858 --run galerie-regression-neuve \
--checks gallery198 --memory 2048
```
Les traces finales sont dans `build/worldgen-lab/gallery198-targeted/`.
Le mode normal `--checks full` conserve toutes ses assertions. Son premier
passage dans `gallery198-fixed/` a démarré correctement puis s'est arrêté sur
un **autre contrôle de laboratoire** : le secteur émeraude contient 171 blocs,
dont 120 exposés, contre le minimum attendu de 200. Ce seuil est uniquement
vérifié par le banc de test, pas pendant une partie ordinaire. Ni l'assertion
ni les minerais ne sont modifiés ici. La vérification complète de toutes les
décorations sur cette graine reste donc ouverte ; seuls les contrôles ciblés
explicitement indiqués sont déclarés réussis.
Aucun essai effectué directement sous Windows ; le défaut du journal est
reproduit avec le même générateur natif et la même graine sur cette machine.
`./gradlew check build assemblePack assembleTestPack -PsanctuaryFocusedTests=menus`
réussi en **4 min 27 s**, 139 tâches : contrôles purs dont le nouveau repli,
quatre GameTests menus et assemblages. Journal : `build/gallery198-check-build.log`.
Le parcours client utilise une instance de développement et ne produit pas de
campagne de captures.
Export local **Sanctuary-Test-beta.198.mrpack**, **12 282 084 octets**.
SHA-256 : `647498a0da3330ce0483e65fe6510ec8d09b6975365261fe59a57d61f3ef0688`.
Archive ZIP, versions, dépendances et classes de chaque JAR vérifiées contre
le build. Toutes les anciennes ressources de données restent identiques à
beta.197, sauf le preset public qui sélectionne maintenant Medium beta.198.
Les nouvelles ressources activent explicitement le correctif ; les anciens
profils ne le font pas. Les tests natifs finaux correspondent à l'empreinte
finale des sources de génération. Reçu : `build/gallery198-artifact.json`.
Copie et guide dans `Downloads/`, sans sauvegarde, réglage personnel ni journal
inclus dans l'archive.
+26
View File
@@ -0,0 +1,26 @@
# beta.165 — Gazette en article et conversation
Photo en tête, titre et description dessous dans le même conteneur sombre.
Auteur, visage, date et actions sous le conteneur. Réponses à droite avec
défilement indépendant et saisie fixe ; sur GUI étroit, réponses sous l’article.
Retour et Actualiser restent en haut, comme pour le tableau.
Interface cliente uniquement, sans changement de stockage ni de monde.
Test client Vulkan réussi en FR/EN aux échelles GUI 2, 3 et 4 :
position du panneau de réponses, repli sous l’article, défilement disponible,
brouillon conservé après redimensionnement et réponse envoyée puis relue.
Le scénario existant du tableau et celui du menu pause passent également.
Captures conservées dans `build/qa-beta165/` (image synthétique de test).
Inspection visuelle FR GUI 2 et GUI 4 effectuée.
Journal : `build/beta165-client.log`.
`check build assemblePack --continue` exécuté : mêmes 23 GameTests en échec
qu’en beta.164, aucun nouvel échec. Comparaison enregistrée dans
`build/session165-server-failures.json`. La vérification générale reste en échec.
Assemblage séparé réussi avec `build assemblePack :sanctuary-test:exportDuoLaunch
-x :sanctuary:check` après cette vérification.
JAR local : `mods/sanctuary/build/libs/sanctuary-beta.165.jar`.
SHA-256 : `03a954caf68089862b1d237dcf1ebaa501e3ce3cfedb1f5184e9412e73fd711d`.
Anciens JAR conservés ; aucune publication distante.
+125
View File
@@ -0,0 +1,125 @@
# ANCHORS-186 — rosaces ornementées et huit refuges
Ticket du 30 septembre 2026, branche `codex/hidden-anchors-beta186`.
Retour sur beta.185 : retirer les barrières supérieures, conserver la silhouette,
ajouter de fins liserés au verre et intégrer huit ancres dans le terrain de leur
secteur de couleur, en surface, en grotte et sur les récifs.
## Contrat
Nouveau laboratoire `anchors`, preset `sanctuary_test:hidden_anchors_v1`,
graine de visite 42, beta.186. Génération déterministe, recherche bornée dans
le champ de densité existant et écritures limitées au chunk en décoration.
Aucune régénération des visites précédentes ni modification des anciens presets.
Les huit couleurs reprennent la palette du Bugrock, dans le même ordre
que les rosaces. Les ancres conservent le bloc et ses propriétés existantes.
Les dépôts distincts restent locaux à ce prototype : ils allument l'ancre et
sa gemme sur le Bugrock ; ils ne génèrent pas encore d'expansion. Les coûts
sont des matériaux accessibles sur l'île, sans or ni redstone.
## Réalisation
Le balcon supérieur perd ses murets. Deux liserés alternent calcite et cuivre
patiné sur chaque dôme, en remplaçant une mince bande de verre. Huit petits
losanges ponctuent les grandes verrières. Le volume, les couleurs dominantes,
la tige d'un bloc, le Bugrock à Y=640, le portail de toiture et l'atlas restent
ceux de beta.185. Les passages sur les huit axes restent dégagés.
La recherche échantillonne une grille finie et les grands récifs existants.
Elle exige une base rocheuse et un débouché naturel praticable. Deux secteurs
aériens sont retenus lorsqu'ils disposent de récifs compatibles ; les autres
privilégient alternativement une grotte ou la surface, avec repli sur un autre
emplacement naturel admissible du même secteur. Le choix varie avec la graine.
Le manque de site sûr fait échouer ce laboratoire explicitement plutôt que de
fabriquer une ancre au-dessus du vide. Seule la graine de visite est certifiée.
Les refuges sont de petites poches irrégulières : sol légèrement ondulé,
reliefs dans la roche présente, accès court décalé, crêtes érodées, mousse,
azalées et verre de couleur au piédestal. Certains reliefs cachent une source
de lumière, d'autres restent sombres. Les réservations évitent la galerie,
le donjon, le soufre et les bassins connus. Elles empêchent aussi la décoration
d'un chunk voisin de reboucher les volumes, sans bloquer les actions des joueurs.
Aucun calcul d'hydrologie régionale ni recherche par tick n'est ajouté.
| Secteur | Couleur | Dépôt en main |
| --- | --- | --- |
| Nord | Bleu clair | 32 pierres |
| Nord-est | Vert clair | 16 bûches de chêne |
| Est | Vert | 16 blés |
| Sud-est | Orange | 16 lingots de cuivre |
| Sud | Rouge | 16 charbons |
| Sud-ouest | Magenta | 8 éclats d'améthyste |
| Ouest | Jaune | 8 lingots de fer |
| Nord-ouest | Cyan | 16 verres |
Clic droit sur l'ancre : le serveur valide le site, le matériau et la quantité.
Un dépôt exact allume l'ancre et le bit de couleur associé au Bugrock. Les
états restent dans les propriétés natives des blocs ; aucune nouvelle base de
progression ou migration. Les textes FR/EN existants sont réutilisés. Le
profil 173 et ses anciens coûts ne sont pas modifiés.
## Vérification
Premier contrôle natif réussi sur `solo186b/anchors/42` : huit ancres, deux
récifs aériens, deux grottes et quatre surfaces/flancs. Plan déterministe de
8 650 entrées dans 26 chunks, calculé en 2,3 s. Huit dépôts vérifiés dans un
ordre non séquentiel ; tous les bits sont indépendants, les mauvais dépôts et
les répétitions conservent les objets. Les états initiaux sont restaurés après
ce test, avant la sauvegarde de visite.
Palais : 144 murets retirés, 384 blocs de liserés/losanges incrustés,
4 944 blocs de verre conservés, seize approches libres. Le volume et le cadre
du portail restent inchangés. Les huit teintes existent dans les deux rosaces.
La première réouverture a révélé un ancien contrôle de cerisiers basé sur un
compteur de génération en mémoire. Le contrôle lit désormais les troncs déjà
sauvegardés lorsqu'il n'y a pas de compteur ; aucun arbre n'est régénéré.
Validation finale sur `solo186c/anchors/42` réussie à froid puis après
réouverture : 567 échantillons de densité conservés et zéro région d'hydrologie.
Démarrage serveur 6,9 s à froid / 0,66 s à chaud ; prêt avec l'ensemble des
sondages et contrôles en 44,7 s / 6,4 s. Le plan des huit refuges prend 2,28 s
à froid. Ces mesures incluent du travail de vérification et ne constituent pas
une promesse de temps de chargement du client.
Lecture de la sauvegarde arrêtée : huit ancres de secteurs 0 à 7, éteintes,
Bugrock allumé avec masque de relais nul ; 49 cartes sauvegardées, complètes,
figées, et cadres horizontaux uniques. Aucun dépôt de contrôle ne reste activé
sur le monde proposé au créateur.
Reçus ignorés : `build/anchors186-quick-result.json`,
`build/anchors186-warm-result.json`, `build/anchors186-saved-blocks.json`,
`build/anchors186-saved-atlas.json`.
`./gradlew check build assemblePack assembleTestPack :sanctuary-test:exportDuoLaunch`
réussi en **6 min 49 s**, **265/265 GameTests**. Journal
`build/anchors186-check-build.log`. Le daemon de compilation a été arrêté avant
le client pour libérer la mémoire. Solo Vulkan ouvert le 30 septembre à **08:08:19**, vue 32 et simulation 12
confirmées dans `build/anchors186-solo.log`. Contrôle de visibilité de la couronne
réussi à 08:08:20. La fenêtre du jeu confirme le balcon sans murets et les
vitrages reliés au soubassement. La visite est laissée au créateur sans téléportations d’inspection supplémentaires. L'aspect des huit
refuges reste à apprécier ensemble en jeu. Aucun canal public ni instance
Prism n'a été modifié.
## Visite — graine 42
Solo neuf `visite186/anchors/42`, `Sanctuary-Hidden-Anchors-186-Solo`,
vue 32 chunks, simulation 12, créatif, vol et commandes ; départ dans le
palais supérieur. Sauvegarde beta.185 conservée. Les emplacements suivants
sont des raccourcis de test, pas des marqueurs visibles dans le jeu.
| Secteur | Milieu | Lumière du refuge | Accès de visite |
| --- | --- | --- | --- |
| Nord | Surface / flanc | Lumineux | `/tp -71.5 243 -226.5 0 0` |
| Nord-est | Surface / flanc | Sombre | `/tp 96.5 186 -82.5 0 0` |
| Est | Récif aérien | Lumineux | `/tp 213.5 444 21.5 180 0` |
| Sud-est | Surface / flanc | Sombre | `/tp 156.5 161 193.5 0 0` |
| Sud | Grotte | Lumineux | `/tp -83.5 115 215.5 180 0` |
| Sud-ouest | Surface / flanc | Sombre | `/tp -107.5 252 181.5 0 0` |
| Ouest | Grotte | Lumineux | `/tp -96.5 223 24.5 90 0` |
| Nord-ouest | Récif aérien | Sombre | `/tp -102.5 404 -171.5 90 0` |
Palais : `/tp 6.5 1280 8.5 140 -15` ; Bugrock : `/tp 0.5 639 5.5 180 -20`.
+105
View File
@@ -0,0 +1,105 @@
# beta.168 — laboratoire de trois cartes de horde
**Actualisation beta.169 :** les [nouvelles cartes à invasion continue](horde-invasion-beta169.md)
se révèlent en main, mélangent les espèces et accélèrent leurs apparitions. Les
cartes beta.168 conservent les règles historiques décrites ci-dessous.
Suite du [prototype beta.167](horde-lab-beta167.md), branche
`codex/cartes-horde-beta168`. Minecraft 26.3, dépendances inchangées.
## Décision de séance
Le décor circulaire est conservé comme piste pour de futurs donjons. Le nouveau
labo le reproduit **sans socle** : c'est la carte consommable qui invoque la horde
à la position du joueur. Apparition immédiate, sans inscription, préparation,
chef de groupe ni commande pour rejoindre. Les joueurs arrivent et combattent
spontanément. Les récompenses sont des objets lâchés par les monstres : ramassage
libre, sans attribution, partage automatique ni coffre final.
## Essai jouable
Trois cartes natives Minecraft, tenues à deux mains avec la main secondaire vide :
| Carte | Vagues de zombies | Butins possibles |
| --- | --- | --- |
| Errants | 3 / 5 / 7 | Fer, émeraude |
| Meute | 4 / 6 / 8 | Or, émeraude |
| Légion | 6 / 9 / 12, un zombie sur deux casqué | Diamant, émeraude |
Chaque exemplaire reçoit une graine et une illustration 128 × 128 persistante,
verrouillée contre les mises à jour géographiques. Composition libre en palette de carte, sans cadre ni grille, à partir des
visages texturés des zombies et des textures natives de butin. Positions continues,
tailles et inclinaisons variées. Chaque visage correspond à **un monstre réel**
sur les trois vagues : 15, 18 ou 27 visages ; les casques correspondent aux porteurs
réels. Chaque icône de butin correspond aussi à un objet porté. La densité et les
casques traduisent la difficulté. Ce rendu assemble des visages texturés, sans corps. Les trois familles ont des vagues fixes ; leur graine fait varier
l'illustration, le choix des butins et leurs porteurs.
Au moins un porteur de butin par vague ; les autres ont une chance sur quatre
d'en porter. Chaque porteur laisse un seul objet, choisi à parts égales dans la
paire annoncée. Les casques ne sont pas du butin. Pas d'XP économique ajoutée.
Les butins déjà lâchés restent au sol si la horde est interrompue.
Les joueurs dans les 15 blocs participent automatiquement ; partir ou mourir
ne supprime pas la horde. Les nouveaux arrivants peuvent intervenir pendant
n'importe quelle vague. Six secondes entre vagues, trois minutes maximum par
vague ; disparition d'un monstre vivant ou sortie d'un monstre du périmètre
interrompt et nettoie les survivants sans butin supplémentaire.
L'emplacement des trois vagues est contrôlé avant consommation : chunks chargés,
sol, collisions, absence de fluide et frontière. Une horde voisine bloque une
nouvelle invocation. Les cartes copiées gardent la même identité : le registre
serveur des identités consommées empêche une seconde invocation. Il utilise la
sauvegarde native du monde ; aucune garantie transactionnelle supplémentaire en
cas de coupure brutale avant sauvegarde n'est revendiquée dans ce labo.
## Monde et lancement
Nouveau dossier `build/horde-168/`, monde `horde-flat-168`, graine **168**, génération
de scène **ready-v1**, port **127.0.0.1:25578**. L'ancien laboratoire beta.167 et ses
sauvegardes sont conservés. Aucun monde existant n'est converti. L'ancien identifiant
`sanctuary:trial_pedestal` et la carte beta.167 restent enregistrés.
Les cartes beta.168 sont des cartes natives avec données `sanctuary_horde_v1` ;
le nouveau SavedData `sanctuary:horde_spent_v1` ne concerne que leurs consommations.
```sh
export JAVA_HOME=$(/usr/libexec/java_home -v 25)
./gradlew :sanctuary-test:exportDuoLaunch
python3 scripts/horde_lab.py prepare
python3 scripts/horde_lab.py server
# Autre terminal
python3 scripts/horde_lab.py client
```
Trois cartes, équipement et vision nocturne de test à la première connexion. Clic droit dans le vide avec une
carte près du centre du cercle. `/horde cartes` redonne trois nouveaux exemplaires
hors combat ; `/horde retour` ramène et soigne hors combat. Pour cet essai,
l'invocation est volontairement limitée au centre du labo enregistré, et le rayon
d'apparition est de neuf blocs autour de l'utilisateur. Aucun usage dans les
mondes ordinaires, aucune recette ni distribution économique de ces cartes.
## Vérifications
- `check build assemblePack` et parcours client Vulkan réussis : **254/254**
GameTests dans `build/horde168-validation.log`. Cette passe précède les derniers
ajustements de composition libre et le verrouillage des anciennes commandes du socle.
- Rejeu final sur ces ajustements : groupe `horde168`, deux tests serveur requis
réussis, parcours client Vulkan sur les trois cartes, puis assemblage du pack :
**BUILD SUCCESSFUL**, journal `build/horde168-final.log`. Le rejeu cible exclut
`:sanctuary:check`, déjà exécuté dans la passe complète.
- Serveur : refus sans consommation sur terrain bloqué, consommation immédiate,
trois vagues de chaque difficulté, images déterministes par graine, copie épuisée
refusée, anciennes commandes du socle inopérantes sur les cartes, nombre exact
d'objets lâchés conforme à l'illustration, nettoyage sans butin en cas d'interruption.
- Client réel : tenue native à deux mains, clic droit, consommation, arrivée/départ
spontanés sans annulation de la horde, retour sans inscription, trois victoires,
butins physiques. Marqueur `HORDE168_CLIENT_PASS` ; six captures dans
`build/horde168-evidence/`. Inspection visuelle des cartes sans cadre ajouté.
- Préparation du script répétée sans modifier ses fichiers existants ; Python compilé,
libellés FR/EN et liens documentaires vérifiés. Nouveau serveur dédié démarré avec
marqueur `ready-v1` dans le monde beta.168 ; l'ancien monde beta.167 est préservé.
L'équilibrage humain reste à essayer : le client automatisé est invulnérable.
La suite générale a réussi lors de cette exécution ; cela ne constitue pas une
correction du test de familier intermittent signalé en beta.167 (source inchangée).
Pas de publication ni de mise à jour Prism pour cet essai.
+87
View File
@@ -0,0 +1,87 @@
# beta.171 — familles dominantes et chaos dimensionnel
> La beta.172 retire la restriction au laboratoire et permet de consommer ces
> cartes directement en Survie ou en Créatif. Voir
> [le contrat runtime beta.172](horde-runtime-beta172.md).
Suite de la [carte native beta.170](horde-map-native-beta170.md), branche
`codex/horde-families-beta171`. Minecraft 26.3, dépendances inchangées.
## Profil mémorisé par la carte
La première prise en main fixe, côté serveur, la dimension de découverte et une
famille dominante. Cette identité rejoint la difficulté et la graine dans les
données de l'objet (`loot_version=3`) : déplacer ou cloner ensuite la carte ne
peut pas relancer son tirage.
Les familles sont zombies, squelettes, creepers, arthropodes, pillards,
cauchemars de surface, infernaux, piglins, flétris et créatures de l'End. Dans
92 % des tirages, la famille appartient à la dimension de découverte ; une
incursion complète étrangère reste donc exceptionnelle.
Chaque carte conserve au minimum 10/12, 13/18 ou 17/27 positions de sa famille
dominante. Les autres positions sont mélangées de façon déterministe : monstres
différents mais natifs de la dimension, puis intrus interdimensionnels plus
rares. Errants peut ne recevoir aucun intrus, Meute en reçoit un et Légion deux ;
les 8 % d'incursions étrangères constituent l'exception spectaculaire.
Le catalogue contient 34 créatures adaptées à une apparition dans le cercle :
variantes de zombies et squelettes, creeper, slime, araignées, silverfish,
sorcière, pillards, évocateur, ravageur, breeze, phantom, creaking, blaze,
squelette Wither, cubes, piglins, hoglin, zoglin, ghast, enderman, endermite et
shulker. Les boss et monstres strictement aquatiques ne sont volontairement pas
invoqués : ils demanderaient un contrat d'arène différent. Piglins et hoglins
de carte ne se transforment pas hors du Nether.
## Carte et récompenses
L'illustration 128 × 128 est divisée horizontalement. En haut, une piste en
serpentin montre chaque monstre dans l'ordre exact où il jaillira. En bas, toutes
les piles garanties sont affichées sans hiérarchie ; elles peuvent légèrement
se chevaucher quand la récompense est dense.
Chaque espèce apporte une ressource cohérente et généreuse : poudre pour les
creepers, bâtons de blaze, larmes de ghast, perles de l'End, carapaces de shulker,
or des piglins, émeraudes des pillards, etc. Rubis et saphirs Sanctuary restent
garantis sur chaque carte. Les butins natifs peuvent encore s'ajouter.
Les cartes beta.168 restent à trois vagues de zombies. Les cartes beta.169/170
déjà découvertes gardent leur roster version 2. Seules les nouvelles cartes
beta.171 emploient le profil dimensionnel version 3.
## Communication et essai
Aucun parcours Horde, y compris le socle beta.167, n'envoie désormais de texte
dans le chat ou la barre d'action. Découverte, refus, brèche, arrivées,
progression, morts et résultat passent par les connexions et glyphes de
particules natifs décrits en beta.170.
Importer `Sanctuary-Test-beta.171.mrpack` dans Prism et créer un **nouveau** monde
nommé exactement `horde-flat-168`. Le profil construit le cercle et donne trois
cartes. Pour un tirage neuf, prendre « Carte de horde inconnue » dans **Outils et
utilitaires**, la tenir en main, revenir en survie puis l'utiliser dans le cercle.
La distribution depuis les campements de survie reste un ticket futur. Ce
chantier ne modifie aucune génération, aucun chunk, aucune sauvegarde, aucune
instance Prism et aucun canal packwiz. L'objet suit le modèle des cartes
d'exploration distinctes de Minecraft 26.3, dont la carte des campements
abandonnés décrite dans les
[notes officielles Minecraft Java 26.3](https://www.minecraft.net/en-us/article/minecraft-java-edition-26-3).
## Vérifications
- GameTests `horde171` : majorité garantie, pondération par dimension, intrus
possibles, large catalogue, profil versionné, image et récompenses exactes.
- Régressions `horde167` à `horde170` : compatibilité des trois anciens contrats.
- Parcours client Vulkan : découverte, trois cartes séparées, familles visibles,
accélération, particules, morts, victoire et butins physiques.
- Matrice complète `./gradlew check build` : 259/259 GameTests requis et
compilation complète réussis.
- Pack normal : `Sanctuary-beta.171.mrpack`, SHA-256
`8f2642adfbcf499330889070b7c05efed09a181b82115e93da7023cdd5aee554`.
- Pack laboratoire : `Sanctuary-Test-beta.171.mrpack`, SHA-256
`2d64d6b046e265ccee4686aedc98d73d8ec8c012f5b5f6fd64e1a6180adfabbc`.
- JAR Sanctuary embarqué dans les deux packs : SHA-256
`0622cbd45cb20c763e3db9f351ebf26ea4869a3065655f01f66f97cc9c45d5c1` ;
JAR laboratoire :
`9c646a95a106680d34f05a4c4d77a9893153c04ab9ad8683f636a99eabc74016`.
+114
View File
@@ -0,0 +1,114 @@
# beta.169 — cartes de horde à invasion continue
> La [beta.170](horde-map-native-beta170.md) remplace ces cartes remplies par un
> objet-carte Sanctuary natif et remplace les messages par un langage de
> particules. La compatibilité avec les exemplaires beta.168/beta.169 demeure.
Suite du [laboratoire beta.168](horde-cartes-beta168.md), branche
`codex/horde-interruption-beta169`. Minecraft 26.3, dépendances inchangées.
## Résultat jouable
Une nouvelle carte est **inconnue tant qu'un joueur ne la tient pas**. Son nom,
sa difficulté, son contingent et son principe de butin se révèlent alors et
restent attachés à cet exemplaire. Le dessin natif 128 × 128 existait déjà mais
n'est visible en jeu qu'une fois la carte dépliée en main. Une copie non tenue
reste inconnue ; toutes les copies gardent cependant la même identité de
consommation.
Le clic droit consomme la carte et ouvre une invasion à la position du joueur.
Il n'y a plus de vague, de pause ni de compte à rebours : le premier monstre
jaillit immédiatement, puis les suivants arrivent un par un, chaque fois plus
vite. Une victoire est accordée seulement quand tout le contingent est apparu
et que tous ses membres ont réellement été vaincus.
| Carte révélée | Contingent | Espèces | Cadence d'arrivée |
| --- | ---: | --- | --- |
| Errants · accessible | 12 | zombie, zombie momifié | 2,9 s → 0,9 s |
| Meute · périlleuse | 18 | précédents, noyé, squelette, araignée | 2,4 s → 0,5 s |
| Légion · féroce | 27 | précédents, vagabond, embourbé, desséché, araignée venimeuse, sorcière, pillard | 2,1 s → 0,3 s |
La graine de chaque carte décale l'ordre du roster et détermine les quantités de
butin et les gemmes bonus. Le dessin montre un œuf pour **chaque monstre réel**
et les objets garantis qu'il apportera. Sa densité et sa variété rendent la
difficulté lisible après révélation.
## Butin généreux et lié aux monstres
Chaque ennemi porte au moins une pile garantie, en plus de ses éventuels butins
Minecraft natifs : chair putréfiée, sable, cuivre, flèches, ficelle, glace
compacte, champignons, os, yeux d'araignée, redstone/poudre lumineuse ou
émeraudes selon son espèce. Les quantités sont volontairement généreuses.
Le premier et le deuxième monstre garantissent respectivement un rubis et un
saphir. Les suivants ont une chance sur cinq d'ajouter une gemme alternée. Les
objets sont physiques, sans propriétaire : ils restent au sol, peuvent être
ramassés par tous et les gains déjà obtenus survivent à une interruption.
Chaque apparition conserve la spirale de glyphes SGA natifs et chaque mort réelle
disperse les runes avec une impulsion de fumée. Un nettoyage technique ne produit
ni rune de mort ni récompense supplémentaire.
## Combat ouvert et sécurité
Les joueurs vivants dans les 32 blocs et 16 blocs de hauteur participent
automatiquement. Partir, mourir ou revenir n'annule pas l'invasion. Les monstres
peuvent dépasser l'ancienne limite du socle sans être supprimés ni compter comme
morts. Une disparition réelle d'entité, un chunk central déchargé, un délai total
de six minutes ou un futur point d'arrivée devenu impraticable interrompent la
session avec un motif distinct.
Tous les emplacements prévus sont vérifiés avant consommation, sans chargement ni
écriture de terrain. Au moment d'une arrivée, douze positions proches sont
essayées afin qu'un joueur ou un autre monstre ne bloque pas artificiellement la
brèche. Une horde voisine empêche toujours une seconde invocation.
## Compatibilité et laboratoire
Les cartes beta.168 sans `loot_version` restent en version 1 : trois vagues de
zombies, dessin et récompenses historiques inchangés. Les nouvelles cartes sont
en version 2 et utilisent l'invasion continue. Le registre sauvegardé
`sanctuary:horde_spent_v1`, les identifiants et le monde
`build/horde-168/server/horde-flat-168` sont conservés ; aucune conversion de
monde ni régénération de chunk.
```sh
export JAVA_HOME=$(/usr/libexec/java_home -v 25)
./gradlew :sanctuary-test:exportDuoLaunch
python3 scripts/horde_lab.py prepare
python3 scripts/horde_lab.py server
# Autre terminal
python3 scripts/horde_lab.py client
```
`/horde cartes` fournit trois cartes inconnues supplémentaires hors combat.
`/horde retour` ramène et soigne hors combat. Le prototype reste limité au centre
du labo : aucune recette ou distribution économique n'est encore ajoutée aux
mondes ordinaires.
Pour l'essai le plus simple, importer `Sanctuary-Test-beta.169.mrpack` dans Prism,
puis créer un **nouveau** monde nommé exactement `horde-flat-168`. Le profil de
test présélectionne **Sanctuary — Test rapide** et reconnaît ce nom pour construire
une seule fois le cercle, remettre les cartes et placer le joueur. Un autre nom
crée un monde de test plat ordinaire ; le MRpack normal n'active pas ce laboratoire.
## Vérifications
- `horde169` : 3/3 GameTests ciblés ; `horde167` et `horde168` restent verts
dans la matrice de compatibilité.
- Parcours client Vulkan : PASS sur les trois cartes, avec révélation en main,
départ/retour, cadence accélérée, roster varié, victoire et butins physiques.
- Matrice complète rejouée par l'assemblage : 256/256 GameTests requis, puis
`check`, `build`, `assemblePack` et `assembleTestPack` réussis. Une première
passe sous forte charge avait fait échouer le déplacement aérien historique
`companion019` ; ses 21 tests isolés puis la seconde matrice complète passent.
- Pack normal : [Sanctuary-beta.169.mrpack](../build/Sanctuary-beta.169.mrpack),
SHA-256 `72fcf169004cc4dc7e1bb3caf72c1e66cc935c79ac16173de9db8ef86d66eaea`.
- Pack laboratoire :
[Sanctuary-Test-beta.169.mrpack](../build/Sanctuary-Test-beta.169.mrpack),
SHA-256 `a162f7e9ce88a177ac99934d4c9a7061ac67be062a31a36965a191dd3eb0423a`.
Les deux manifestes exportés déclarent `beta.169`, Minecraft 26.3 et Fabric
Loader 0.19.5. Le pack laboratoire contient en plus
`sanctuary-test-beta.169.jar` ; aucun canal packwiz ni aucune instance Prism n'a
été modifié.
+120
View File
@@ -0,0 +1,120 @@
# beta.167 — prototype de carte de horde et socle d'épreuve
Branche `codex/communaute-economie-beta167`, socle beta.166. Premier essai issu du
[cadrage communautaire](ecosysteme-communaute-economie.md). Minecraft **26.3**,
dépendances inchangées ; vérifications graphiques Vulkan uniquement.
## Contrat de ce prototype
Une arène ronde de laboratoire, diamètre extérieur 41 blocs, accueille un
**socle d'épreuve** au centre (`sanctuary:trial_pedestal`). Une **carte d'épreuve ·
Horde de zombies** (`sanctuary:horde_trial_card`) permet d'ouvrir les inscriptions.
Ces identifiants sont nouveaux et stables. Le clic droit ouvre les contrôles.
De un à quatre joueurs s'inscrivent volontairement, passent prêts, puis l'hôte
lance. Cinq secondes précèdent la première vague ; six secondes séparent les
suivantes. Trois vagues : 4, 6 et 8 zombies en solo ; deux zombies supplémentaires
par coéquipier et par vague. Chaque vague dispose de trois minutes. L'effectif
est fixé au départ, sans renfort tardif. Tous les zombies doivent être vaincus.
Le socle passe du vert au bleu pendant l'attente, rouge pendant le combat, or
après victoire, sombre après interruption. Un affichage indique la vague et les
ennemis restants. La carte du prototype reste réutilisable : **aucun butin ni XP
de récompense** n'est distribué. Les statistiques natives de combat continuent
d'exister. Ni capes, ni familiers exclusifs, ni incubation d'XP ne sont livrés.
Quitter le rayon de 15 blocs, mourir, se déconnecter ou passer en créatif/spectateur
retire de l'épreuve. Les autres participants peuvent continuer. Le départ de
l'hôte avant lancement annule les inscriptions. Un groupe vide, un délai écoulé,
une difficulté Paisible ou la disparition d'un ennemi vivant interrompt l'épreuve.
Un ennemi disparu n'est jamais compté comme une victoire. Les zombies de test ne
brûlent pas au soleil, ne ramassent pas d'objets et ne frappent que les participants.
Le laboratoire désactive le PvP et conserve l'inventaire à la mort.
## Données, interruption et limites
Les sessions sont volontairement éphémères. Un arrêt interrompt la partie ; les
zombies de l'épreuve sont retirés. Après un crash, leurs tags permettent de retirer
les survivants au chargement. Aucune dette, récompense différée ou carte consommée
n'existe à récupérer. Le socle est réarmé à la réouverture du labo.
Le socle et la carte n'ont pas de recette ni de génération naturelle. Un socle
posé hors de la scène explicitement préparée ne lance rien. Le client ne peut
ni installer une arène, ni choisir ses monstres, ni charger une région par paquet.
Le serveur contrôle identité de session, révision, distance, inscription et état.
Le seul nouveau marqueur de scène est `horde-lab-167.txt`, dans le nouveau monde
de développement : préparation réservée avant construction, état prêt après.
Une préparation incomplète ou un socle manquant est conservé et refusé au
redémarrage. Le volume entier doit être vide avant la première pose. Aucune
ancienne sauvegarde, aucun chunk Sanctuary ni format de données existant n'est
converti. Ne pas installer les nouveaux blocs dans un monde destiné à être
rouvert avec beta.166 ; conserver le nouveau labo pour ce prototype.
Il reste à éprouver le rythme et la coopération avec des joueurs humains. Les
cartes de découverte, clés/reliques, cartes collectionnables, quêtes quotidiennes,
échanges, ballast et huit extensions restent de la conception.
## Essayer le laboratoire
```sh
export JAVA_HOME=$(/usr/libexec/java_home -v 25)
./gradlew :sanctuary-test:exportDuoLaunch
python3 scripts/horde_lab.py prepare
python3 scripts/horde_lab.py server
# Dans un autre terminal :
python3 scripts/horde_lab.py client
```
Dossier neuf `build/horde-167/`, monde `horde-flat-167`, graine **167**, port
loopback **25576**. Le script reprend seulement les arguments de lancement exportés,
pas les sauvegardes ni paramètres du labo duo. Chaque connexion rejoint le cercle
en aventure ; un équipement initial et des capacités de combat 10 cœurs/10 icônes
de faim sont fournis à ce personnage de test. Hors épreuve, `/horde retour` ramène
et soigne. L'ancien `build/duo/` et les installations personnelles sont préservés.
À l'accueil, créer son personnage si demandé. Au socle : **Présenter ma carte**,
**Prêt**, puis **Lancer l'épreuve**. Les amis rejoignent et passent prêts depuis
le même socle. Ce profil local hors ligne ne valide pas l'identité Discord et ne
doit pas être exposé sur Internet.
## Vérifications et livraison
- `./gradlew check build assemblePack :sanctuary-test:exportDuoLaunch` exécuté :
**252/253 GameTests réussis**, dont le scénario de horde. Le contrôle général
échoue sur `Companion019GameTests.creatureProfilesActuallyMove` (déplacement
aérien attendu). Journal : `build/horde167-full.log`.
- Rejeu `check build` avec les groupes `companions,horde167` : **21/22 tests**,
même échec de déplacement, horde réussie. Le code et le scénario de familier
sont inchangés par ce chantier ; aucune conclusion de non-régression complète
n'est revendiquée. L'[audit antérieur](audit-dragon-beta165.md) décrit notamment
l'aléa du scénario. Journal : `build/horde167-focused-client.log`.
- Validation finale ciblée : `runGameTest runClientGameTest assemblePack
:sanctuary-test:exportDuoLaunch`, groupe `horde167`, client Horde167/Vulkan,
avec `-x :sanctuary:check` pour ne pas rejouer les contrôles précédents.
**BUILD SUCCESSFUL**, deux tests serveur requis réussis (dont le scénario
horde), puis parcours client complet. Cette exclusion ne transforme pas le
contrôle général précédent en réussite. Journal : `build/horde167-delivery.log`.
- Serveur : deux personnages simulés, carte requise, consentement de chacun,
lancement réservé à l'hôte, double lancement refusé, trois vagues adaptées
à l'effectif, victoire, carte conservée, nettoyage à la sortie et interruption
sur disparition d'un ennemi vivant. Une scène existante est refusée sans
réécriture par le constructeur du labo.
- Client natif Vulkan : clic droit réel sur le bloc, paquets d'inscription,
préparation et lancement, trois vagues avec dégâts serveur natifs, victoire
après 18 zombies en solo. Captures FR/EN, GUI 2/3 ; inspection visuelle de
l'arène, du socle, des contrôles, du combat et du résultat. Marqueur
`HORDE167_CLIENT_PASS`. Huit captures conservées dans `build/horde167-evidence/`.
- Script Python compilé ; préparation répétée avec **cinq fichiers identiques**.
Libellés FR/EN et liens locaux vérifiés. Démarrage dédié réel sur loopback,
création de la nouvelle scène puis arrêt propre, journal
`build/horde167-lab-server.log`. Redémarrage dédié réussi avec le marqueur
`ready-v1` et le socle existant, sans reconstruire la scène ; journal
`build/horde167-lab-restart.log`.
- Packwiz assemblé et vérifié ; JAR interne beta.167 avec classes et ressources
attendues. SHA-256 du JAR :
`4d6cb640177d7904043c3eefe693a75fbdc4f4df48407adaae04cc7d632057e6`.
La coopération entre plusieurs clients humains et l'équilibrage restent à
éprouver ; les personnages simulés ne les remplacent pas. Aucun artefact public,
tag, canal packwiz ou instance Prism mis à jour. Prototype intégré localement sur `main`, conformément à la demande du créateur.
+102
View File
@@ -0,0 +1,102 @@
# beta.170 — carte de horde native et langage de particules
> La [beta.171](horde-families-beta171.md) ordonne les monstres et les butins sur
> la carte, ajoute les familles dominantes et étend le silence visuel au socle.
Suite de l'[invasion continue beta.169](horde-invasion-beta169.md), branche
`codex/horde-map-native-beta170`. Minecraft 26.3, dépendances inchangées.
## Objet et découverte
`sanctuary:horde_trial_card` est désormais une sous-classe de la carte native
Minecraft, au lieu d'un objet ordinaire ou d'une carte remplie renommée. Son
identifiant reste stable. Un exemplaire vierge est disponible dans l'onglet
créatif **Outils et utilitaires** sous le nom « Carte de horde inconnue ».
Le premier passage en main lui attribue côté serveur :
- un identifiant de carte natif distinct ;
- une difficulté parmi Errants, Meute et Légion ;
- une graine propre, donc un ordre de monstres et des butins propres ;
- une illustration verrouillée 128 × 128 correspondant au contenu réel.
Deux nouveaux exemplaires vierges découvrent deux identités différentes. Ranger
puis reprendre le même exemplaire ne le relance jamais : son identité, son dessin
et sa difficulté restent stables. Cette distinction évite de choisir sa
difficulté en changeant simplement d'emplacement d'inventaire. Si plusieurs
cartes créatives vierges sont empilées, seule celle tenue se découvre ; les
autres retournent vierges dans l'inventaire et attendent leur propre prise en main.
La carte appartient à `#minecraft:clonable_maps`. La recette native 26.3 avec
des cartes vierges peut donc dupliquer un exemplaire déjà découvert ; les copies
conservent volontairement la même identité et la même consommation, comme les
cartes d'exploration natives. Les cartes remplies beta.168 et beta.169 restent
reconnues et jouables.
Cette approche s'inspire des cartes d'exploration devenues des objets distincts
en 26.3, et notamment de la nouvelle carte des campements abandonnés. Sanctuary
fait un choix différent sur un point : sa carte inconnue est volontairement
accessible dans l'inventaire créatif pour le laboratoire. Voir les
[notes officielles Minecraft Java 26.3](https://www.minecraft.net/en-us/article/minecraft-java-edition-26-3).
## Langage visuel sans chat
Le parcours des cartes de horde n'envoie plus de message Sanctuary dans le chat
ni dans la barre d'action. Toute l'information immédiate est spatiale :
| Moment | Signal visuel |
| --- | --- |
| Découverte | 1, 2 ou 3 connexions convergent vers la carte selon la difficulté, avec glyphes SGA |
| Activation valide | un anneau de connexions converge vers la brèche centrale |
| Arrivée | la brèche se relie au monstre qui jaillit, entouré de glyphes |
| Invasion en cours | une pulsation relie chaque monstre vivant à la brèche toutes les secondes |
| Mort réelle | les glyphes se dispersent avec une brève impulsion |
| Victoire | douze connexions convergent avec une explosion de totems |
| Interruption | les connexions se dispersent avec une fumée sombre |
| Usage impossible | un filet de fumée descend de la carte vers le joueur |
Les connexions utilisent la particule native `minecraft:vault_connection` ; les
glyphes restent ceux de `minecraft:enchant`, donc l'alphabet SGA fourni par le
jeu. Aucune texture, interface ou canal réseau de HUD propre à la carte n'est
nécessaire.
Les messages du vieux socle beta.167 sont conservés uniquement pour ce prototype
historique. Ils ne font pas partie du parcours de la carte native.
## Essai dans le laboratoire
Importer `Sanctuary-Test-beta.170.mrpack` dans Prism, puis créer un **nouveau**
monde nommé exactement `horde-flat-168`. Le profil de test construit le cercle
et fournit les trois cartes déterministes de démonstration. Pour vérifier la
découverte aléatoire, prendre « Carte de horde inconnue » dans **Outils et
utilitaires**, la tenir en main, revenir en survie puis l'utiliser dans le cercle.
Le MRpack normal contient le même objet, mais la distribution en survie depuis
les campements n'est pas encore activée. Ce chantier adopte leur modèle d'objet
et de découverte sans modifier la génération, les chunks ou les sauvegardes.
## Vérifications
- GameTests `horde170` : type natif, entrée créative vierge, découverte unique,
stabilité à la reprise, diversité entre deux exemplaires, illustration et
compatibilité avec la recette native de duplication.
- Parcours client Vulkan : présence dans Outils et utilitaires, découverte en
main, trois difficultés, accélération continue, signaux de particules, victoire
et butins ; aucun message de jeu Sanctuary émis par les cartes.
- Matrice complète : 257/257 GameTests requis, puis `check build` réussi.
- `assemblePack assembleTestPack` réussi après cette matrice ; intégrité ZIP,
version `beta.170`, Minecraft 26.3, Fabric Loader 0.19.5 et JAR embarqués
vérifiés.
- Pack normal : [Sanctuary-beta.170.mrpack](../build/Sanctuary-beta.170.mrpack),
SHA-256 `e96ad55115074e898e94c3eccff983bd76301b0aa577c83b8a5e3feca0a719f8`.
- Pack laboratoire :
[Sanctuary-Test-beta.170.mrpack](../build/Sanctuary-Test-beta.170.mrpack),
SHA-256 `d84aa95c230b6484c9282534245401de0dcf069e9d4e5c9d5e7d6238bb2f84e0`.
Le JAR Sanctuary embarqué correspond au build vérifié, SHA-256
`c8cd436bdc65c8f97ea6383f6f4ae63836b4b6e7dbf35918b2be86152e38d344`.
Le profil laboratoire contient en plus `sanctuary-test-beta.170.jar`, SHA-256
`ddb1023285a39f47d4fccd8377aa4ec297f41b9750eab3f6fe2868abcb62da50`.
Aucun canal packwiz, serveur personnel, monde existant ou instance Prism n'est
modifié par cette livraison locale.
+46
View File
@@ -0,0 +1,46 @@
# beta.172 — cartes de horde utilisables en jeu
Suite du profil dimensionnel [beta.171](horde-families-beta171.md). Le contenu
des cartes, leurs familles et leur format sauvegardé ne changent pas.
## Contrat runtime
Le clic droit sur une carte révélée tente d'ouvrir la brèche à la position
actuelle du joueur. Aucun cercle, socle, monde de test ou enregistrement de
laboratoire n'est requis. Le pouvoir fonctionne en Survie et en Créatif ; le
mode Spectateur et la difficulté Paisible restent refusés.
Après validation de toutes les apparitions, l'identité de carte est marquée
comme dépensée et un exemplaire est retiré de la main côté serveur. L'inventaire
est immédiatement resynchronisé, y compris en Créatif. Une copie de la même
carte ne peut donc pas rejouer l'invasion. Si la préparation échoue, la carte
reste intacte.
L'invasion n'écrit aucun bloc. Chaque arrivée essaie plusieurs angles autour du
joueur et cherche un sol libre entre six blocs sous et six blocs au-dessus de la
hauteur d'activation. Aucun chunk supplémentaire n'est chargé. Une zone trop
encombrée, liquide, sans sol ou hors frontière refuse proprement l'ouverture par
le glyphe de particules existant.
Les joueurs créatifs présents dans le rayon restent associés à l'invasion de
carte. Leur invulnérabilité Vanilla n'est pas modifiée : la brèche demeure
dangereuse pour les joueurs en Survie, les créatures et l'environnement alentour.
Le vieux socle beta.167 conserve son inscription réservée aux joueurs en Survie.
## Vérifications
- GameTests `horde172` : activation et consommation hors laboratoire en Créatif
puis en Survie ; centre de session égal à la position du joueur.
- Terrain étagé : préparation, consommation et première apparition à la hauteur
du sol local, sans plancher de laboratoire.
- Régressions `horde167` à `horde172` : 10/10 GameTests requis.
- Matrice complète `./gradlew check build` : 261/261 GameTests requis et
compilation complète réussis.
- Pack normal `Sanctuary-beta.172.mrpack` : SHA-256
`9e0cc6f07195b094963c03bce52c971ad7072990ac7976dff34f7647be176d7e`.
- Pack laboratoire `Sanctuary-Test-beta.172.mrpack` : SHA-256
`60565cd9bf28708f398a32da38f1fce05789a1d6bfc513aab94cfe90932d82df`.
- JAR Sanctuary embarqué dans les deux packs : SHA-256
`fb72d4d3efcd50d3ba67b0899faf9ffca5c2451150c5f169e2a0d030c9b6d7ae` ;
JAR laboratoire :
`67a7399372f05e384a03461c45730edc0459cbe342cf57bcb280c5c849086620`.
+120
View File
@@ -0,0 +1,120 @@
# beta.166 — inscription web et reprise de l’accueil
Intégration de la PR #1 de Chris (`8de569b`), avec son historique conservé par
le merge `ad960ef`, sur beta.165 et ses 252 GameTests actualisés.
## Parcours
Le joueur ouvre son dossier `/candidature` sur le site, se connecte à Discord,
dépose sa candidature et récupère le code après acceptation/parrainage. Le
bouton **Récupérer mon code** de Hello World ouvre ce dossier, sur le site
configuré par le serveur, avec la confirmation de lien native de Minecraft.
Le code reste obligatoire avant la création du personnage. Gazette et tableau
restent en consultation seule sur le site.
Sans configuration d’accès, le mod autonome garde l’accueil sans code.
Avec une configuration partielle/invalide, l’accueil reste fermé. Un habitant
déjà arrivé n’a pas à présenter de code ni à connaître le nouveau payload d’accès.
Les autres contrôles de compatibilité Sanctuary restent appliqués.
## Contrat API de reprise
Les POST authentifiés `/api/v1/access-codes/verify` et `/redeem` reçoivent :
`code`, `minecraft_username` et `admission_id` (UUID). Le serveur calcule cette
admission à partir d’un identifiant de monde persistant et de l’UUID du joueur ;
le client ne la choisit jamais. Un autre joueur ou un autre monde possède une
admission différente, même à graine identique.
La consommation en base conserve `used_at` et `admission_id` dans la même
transaction verrouillée. Une répétition de la même admission et du même pseudo
retourne `valid: true, reason: resumed`, le pseudo, le Discord et l’admission.
`verify` accepte aussi cette reprise ; le bouton de confirmation redevient
accessible. Une autre admission retourne `already_used` sans identité. Un pseudo
incorrect ou un code révoqué reste refusé. Les codes anciennement consommés sans
reçu restent refusés ; leur récupération nécessite une réémission par le staff.
Le mod exige une réponse positive portant son admission exacte. Il n’accepte
plus `already_used` sur la seule présence d’un pseudo et d’un Discord. Une
réponse positive arrivée après déconnexion est quand même enregistrée, mais ne
crée pas le personnage hors connexion. Si la réponse est entièrement perdue,
le même code est vérifiable et consommable à nouveau au prochain accueil.
Le reçu local sauvegardé permet de terminer une création déjà autorisée.
Les erreurs réseau et de jeton proposent une nouvelle vérification.
## Migration et activation
1. Sauvegarder la base web et les fichiers de progression du serveur.
2. Appliquer la migration additive web `add_admission_receipt_to_access_codes_table`
(colonne UUID nullable, aucune donnée supprimée), puis déployer le service API.
3. Installer beta.166 sur serveur et clients des nouveaux arrivants.
4. Configurer `SANCTUARY_APP_URL` et `SANCTUARY_API_TOKEN`, ou
`config/sanctuary/access.json` (`appUrl`, `token`). Le jeton reste serveur.
Utiliser HTTPS hors du labo local et un serveur Minecraft en online-mode pour
authentifier les UUID ; le labo offline ne prouve pas l’identité d’un compte.
Dans le monde, `data/sanctuary-admission-id` est créé atomiquement avant tout
appel API et doit être sauvegardé avec `data/sanctuary-access.json`. Aucun code
brut ni jeton n’est enregistré dans ces fichiers. Leur corruption ferme l’accès
et conserve les preuves. Les formats habitants/starter et les chunks restent
inchangés. Ne pas supprimer l’identifiant d’admission pour « réparer » un accès.
Retour arrière : conserver la colonne web et les reçus, désactiver explicitement
l’accès si l’on revient à un ancien mod (cela réouvre l’accueil sans code).
Ne pas exécuter le rollback de migration après consommation : il supprimerait
les reçus nécessaires à la reprise. Pas de déploiement public implicite.
## Laboratoire
Le monde beta.160 existant est conservé. La reprise de l’introduction utilise
un nouveau monde de développement ; les réglages graphiques et anciens JAR sont
conservés. `scripts/duo_lab.py server --intro` joue la vraie introduction sur le
monde plat ; sans cette option, le profil de test continue de la sauter.
## Vérifications
- `check build assemblePack` réussit (10 min 12 s), avec **252/252 GameTests**.
Trace : `build/access166-full.log`.
- Smoke du contrat et du ledger : reprise, mauvais compte, mauvais reçu, absence
de reçu, isolation entre mondes de même graine, persistance et corruption.
- `access166Interop` réussit contre la vraie API PHP/MariaDB locale : réponse de
consommation ignorée, nouveau ledger, nouvelle vérification et confirmation,
refus d’un autre joueur et d’une autre admission (`build/access166-interop.log`).
- Test client Vulkan : vrai handshake Hello World, endpoint HTTP contrôlé qui
consomme puis répond 503, nouvelle vérification, confirmation et un seul reçu
bound. Ce test n’utilise pas Discord et ne prétend pas vérifier OAuth.
- Site : 12 tests inscription/candidature (68 assertions), puis 31 tests ciblés
inscription/redirection Discord/communauté (258 assertions), tous réussis.
Pint passe. Suite web entière : 74 réussis, 1 ignoré et 1 échec préexistant
`PlayerModerationTest::test_an_administrator_can_open_a_player_profile`
(ancien libellé « Nommer admin »). Reproduit dans un instantané de HEAD avant
modification ; pas une régression de l’inscription.
- Base locale sauvegardée avant la migration, puis migration additive appliquée.
Le vrai OAuth Discord reste à valider avec les identifiants de l’application ;
aucun contournement d’authentification ou compte de démonstration pour le joueur
n’est installé. Un compte API jetable distinct sert uniquement à l’interop.
- Aucun déploiement sur sanctuary-minecraft.net, aucun push/fusion distante,
aucune modification de l’ancienne sauvegarde du labo ni des JAR archivés.
Après la passe complète, le retour immédiat de la vérification en cache a été
ajusté pour qu’une nouvelle tentative ne reste pas sans réponse pendant la
limite d’une seconde. Le scénario client exerce précisément cette reprise.
`check build assemblePack` a ensuite réussi avec les 6 GameTests de progression
ciblés (`build/access166-final.log`). La disposition compacte a été resserrée
pour garder tous les contrôles dans un GUI de 240 pixels de haut ; ses captures
FR/EN et assertions de limites sont vérifiées par le scénario client.
Le correctif web est enregistré dans `f36cc36`. Les identifiants OAuth fournis
par l’utilisateur sont chargés depuis le `.env` privé et la redirection réelle
vers Discord fonctionne avec le callback local. Le retour authentifié reste
à confirmer après la connexion/autorisation de l’utilisateur.
La livraison finale et les captures compactes passent dans
`build/access166-delivery.log` : scénario client Vulkan FR/EN et assemblage du
pack. Cette dernière commande exclut `:sanctuary:check` pour éviter une troisième
exécution générale après les deux contrôles réussis ci-dessus ; elle ne remplace
pas ces résultats. La seule modification de jeu depuis le contrôle ciblé est
la disposition compacte, couverte par ce dernier test client.
Le labo reste arrêté, configuré pour `duo-registration-166`. L'ancien
`duo-flat-160` n'est pas modifié. Lancement prévu après connexion au dossier web :
`python3 scripts/duo_lab.py server --memory 768 --intro`, puis le client habituel.
+128
View File
@@ -0,0 +1,128 @@
# DISCOVERY-183 — lieux et ressources de Sanctuary Island
Ticket du 30 septembre 2026, branche `codex/island-discovery-beta183`.
Le créateur a visité le solo beta.182 et valide la palette naturelle, la rosace,
les étangs et la génération sous l'île. Il demande un portail **horizontal**,
les secteurs miniers et le soufre manquants, l'ISS séparée et un exemple d'eau
haute qui suit le dénivelé. Son complément relie les vitres de la rosace aux
huit couleurs du Bugrock et à une géographie lisible, froide au nord, chaude au sud.
## Contrat
Nouveau preset facultatif `sanctuary_test:discovery_v1`, profil `discovery`.
Graine de visite : **42**. Anciens presets, sauvegardes et chunks conservés.
La source de biomes possède une option `discovery` absente/false pour les
anciens profils : seule la nouvelle génération active la poche de soufre.
Aucune migration, expansion, activation de portail ou progression ajoutée.
Minecraft 26.3, blocs de soufre natifs et minerais Sanctuary existants.
## Contenu
- Galerie centrale agrandie, toujours taillée dans la roche, avec ses deux
annexes et passages en boucle. Cadre d'obsidienne de **5 × 5**, trou central
de **3 × 3**, fond un bloc plus bas, sec. Le gardien viendra ultérieurement.
- Rosace : positions, nervures, passages et Bugrock à Y=320 conservés.
Seules les vitres changent. Ordre du dessin source, depuis le nord :
bleu clair, vert clair, vert, orange, rouge, magenta, jaune, cyan.
Le petit sommet blanc conserve le rappel du logo central blanc.
Cette rose colorée donne une convention géographique ; elle ne transforme
pas encore les biomes de l'île en huit régions ni n'allume les relais.
- Trois secteurs de gemmes : saphir au nord (0,-176), émeraude à l'est
(176,0), rubis au sud (0,176), chacun de rayon 64 blocs autour des centres
de chunks. Petits amas sur les faces de grotte, entre Y=40 et 287,
toujours au moins 13 blocs sous la surface, au plus douze blocs par chunk
retenu. Les blocs exposés et stocks sont comptés dans
le monde effectivement généré. Diamants, cuivre, charbon, fer et lapis
gardent leur distribution précédente ; aucun or ni redstone ajouté.
- Une caverne de soufre au sud-ouest, altitude choisie dans la roche,
soufre/cinabre/calcite, ouverture vers le jour et trois formations de soufre
enracinées à la surface pour la repérer. Pas de lave ni de nouveau mob.
- Un lac supérieur supplémentaire et son bassin aval, reliés par un chenal
en marches sur quatre blocs de dénivelé. La partie aval s'ouvre au jour.
Les deux systèmes d'étangs beta.180 restent présents. Recherche locale
bornée et plan mémorisé ; aucune hydrologie régionale calculée.
- ISS entre Y=575 et 591 : trois modules métalliques reliés et praticables,
quatre ailes solaires, poutre centrale, antenne, porche d'arrivée et
observation vitrée vers l'île. Architecture originale, sans schematic
externe. La salle nord contient une table de cartes dans des cadres lumineux
horizontaux. Le rayon du générateur détermine la mosaïque et la salle :
5 × 5 pour le petit format (512), 7 × 7 pour le moyen (724), 9 × 9 pour le
grand (1024). Ce profil de terrain reste au format moyen ; les autres
dimensions de station sont vérifiées géométriquement, sans ajouter de
nouveaux profils de terrain à cette livraison. Aucun familier spécial,
quête ou portail vers les hauteurs fonctionnel ajouté.
Toutes les nouvelles écritures appartiennent au chunk en cours de décoration.
Plans locaux calculés une fois, aucune reconstruction dans une sauvegarde
ouverte. Les cartes sont des objets Minecraft natifs, persistés avec leurs
cadres. Une file finie les prépare par tranches de quatre lignes avec un budget
cible de 3 ms par tick. Projection topographique de la graine au pas de quatre
blocs, palette réelle pour les chunks déjà chargés, biomes pour les autres :
c'est une vue d'ensemble du terrain initial, pas une photographie détaillée
de toutes les constructions. Aucun chargement de chunks éloignés demandé pour
la carte. Les cartes terminées sont figées comme une trace de l'ancienne
expédition, et ne sont ni recréées ni regagnées si un joueur retire un cadre.
Une interruption reprend une carte inachevée avec son identifiant existant.
## Vérifications et visite
Monde neuf final `solo183e/discovery/42`, serveur arrêté et sauvegardé proprement :
567 échantillons de densité conservés sous Y=216, zéro région d'hydrologie,
trois cerisiers, les deux bassins antérieurs et les cinq spawners/quatre caches
conservés. Galerie : 1 939 surfaces reliées sur 1 979 colonnes, cadre 5 × 5 et
fosse sèche 3 × 3 vérifiés. Rosace : géométrie inchangée, huit couleurs présentes
et huit approches libres.
Stocks réellement comptés dans les trois districts : 235 saphirs (176 exposés),
624 émeraudes (441 exposées), 576 rubis (402 exposés). Nouveau système d'eau :
4 311 blocs d'eau reliés et débouché extérieur vérifié. La ventilation du soufre
est ouverte jusqu'au terrain local du débouché, plutôt qu'à la hauteur plus
basse du centre de la poche.
ISS : 4 742 entrées de plan, trois modules reliés, quatre ailes solaires,
49 cartes distinctes et supportées dans 49 cadres horizontaux. Les géométries
5 × 5, 7 × 7 et 9 × 9 couvrent les rayons 288, 395 et 544 avec leurs marges de
terrain. La mosaïque courante couvre 896 × 896 blocs. Calcul de son aperçu :
2 039 ms au total ; aucun chargement de chunk distant dans ce calcul. Lecture
NBT après arrêt : 49 cartes pleines et figées, 49 identifiants uniques, tous
les cadres orientés vers le haut, centres jointifs et aucun marqueur de travail
inachevé. Assemblage des pixels sauvegardés inspecté visuellement ; il ne s'agit
pas d'une capture du jeu. Rapport local `build/discovery183-saved-atlas.json`.
Démarrage serveur mesuré à 4,4 s ; préparation avec sondages, génération des
zones visitées et contrôles terminée à 41,1 s. Ce dernier chiffre inclut les
tests synchrones, ce n'est pas un temps de démarrage normal du solo.
Rapport `build/discovery183-quick-result.json`.
`./gradlew check build assemblePack assembleTestPack` réussi en 7 min 20 s,
**265/265 GameTests** (`build/discovery183-check-build.log`). Après les deux
retouches finales limitées au laboratoire (ventilation et lecture des couleurs
réelles de la carte), compilation, contrôles du mod de labo, assemblages et
export du lanceur ont été refaits ; les checks des trois modules inchangés,
déjà réussis, n'ont pas été répétés dans cet assemblage final. Journal
`build/discovery183-final-assembly.log`. Le monde `solo183e` vérifie ce code final.
Solo neuf `visite183/discovery/42`, `Sanctuary-Discovery-183-Solo`, vue 32,
simulation 12, créatif, vol et commandes. Départ préparé devant la table de
l'ISS. Entrée en jeu confirmée dans le journal le 30 septembre à 03:11:11 :
KokaLab en (4.5,580,-12.5), vue 32. Passage en spectateur à 03:11:48.
Client Vulkan actif.
Aucune sauvegarde antérieure ni instance Prism modifiée.
| Lieu | Téléportation de visite |
| --- | --- |
| Salle des cartes ISS | `/tp 4.5 580 -12.5 140 35` |
| Porche ISS | `/tp 0.5 577 28.5 180 0` |
| Rosace | `/tp 0.5 319 5.5 180 0` |
| Portail inférieur | `/tp 0.5 177 7.5 180 30` |
| Poche de soufre | `/tp -152 180 152` |
| Indice de soufre en surface | `/tp -158 281 149` |
| Bassin supérieur | `/tp -96 254 -192` |
| Saphirs | `/tp -62 236 -204` |
| Émeraudes | `/tp 114 172 -30` |
| Rubis | `/tp -60 192 146` |
Graine 42 testée en génération native ; les autres graines restent à éprouver.
Palette, volumes, positionnement des cartes et proportions de l'ISS à valider
en visite. Les cartes sont une vue d'ensemble figée de l'île principale,
pas encore un atlas vivant des expansions.
+197
View File
@@ -0,0 +1,197 @@
# WG-ECO-176 — strates, biomes et mares du laboratoire
Branche `codex/island-ecology-beta176`, base de visite beta.175 conservée au
commit `ba4cf19`. Le créateur autorise l'essai décrit dans
[les retours de visite](worldgen-retours-beta175.md).
## Contrat avant implémentation
Nouveau preset `sanctuary_test:ecology_v1`, réglage `ecology_v1_10`, graine
initiale 42 puis témoins 0 et 173, diamètre 724. Relief brut identique à
`sky_v1` ; matériaux, biomes, décorations et petites excavations des mares
peuvent différer. Limite du ciel réservé : aucun bloc généré de 512 à 639.
Les presets historiques et la génération normale restent inchangés.
- Géologie en couches ondulées de pierre, andésite, tuf, deepslate et touches
de calcite. Conserver le diamant profond et enfoui.
- Grandes régions automnales et de cerisiers, lisières et prairie/forêt.
- Trois récifs avec identité humide/tropicale ; deux fragments supérieurs
avec végétation courte. Le sélecteur sait dépasser l'ancienne limite 384.
- Mares locales retenues, nombre d'essais borné, écriture dans le chunk
propriétaire, sans demande de plan hydrologique régional.
- Biomes propres au labo avec décorations sélectionnées : pas de structures
natives, notamment mineshafts et villages. Aucun village Sanctuary créé
dans cet essai ; aucun nouveau contenu d'expansion.
Aucune migration : nouveaux mondes de test seulement. La sauvegarde de
visite beta.175 est conservée et ne doit pas être ouverte avec une autre
empreinte de génération. Le nouveau monde peut être copié après arrêt
propre vers un solo avec commandes et rendu 32 ; conserver l'original et
les identifiants de génération. Aucun déploiement Prism ni publication.
## Vérification prévue
Compilation, `check build assemblePack assembleTestPack`, mesures natives
sur trois graines, réouverture de 42, coupes géologiques et cartes de biomes
à la surface réelle. Contrôler la stabilité des mares après ticks, l'absence
de structures admissibles, le ciel vide et l'absence de plans hydrologiques.
Le recensement exhaustif des minerais doit fonctionner avec mémoire bornée ;
aucun petit échantillon ne sera présenté comme un total d'île.
## Réalisation
Le module optionnel `sanctuary-test` fournit trois composants natifs :
`EcologyBiomes176` sélectionne les biomes, `EcologyStrata176` remplace les
matériaux pendant leur génération, et `EcologyWater176` pose les mares avant
les décorations. Le preset normal n'utilise aucun de ces composants.
Les biomes propres au laboratoire réutilisent des décorations de Minecraft
26.3 : forêt automnale, cerisiers, jungle clairsemée, mangrove, marais et
cavernes. Les deux fragments les plus hauts reçoivent de la végétation courte.
Les minerais natifs, sources, lacs et structures sont exclus de cette palette ;
les dépôts du laboratoire beta.175 restent responsables des ressources.
Une mare occupe un seul chunk, avec un rayon de 3 ou 4 blocs et deux niveaux
d'eau. Le placement vérifie le fond et les parois avant toute écriture ; cinq
essais au maximum, dans un tiers des chunks. Aucun calcul de bassin versant,
chargement de voisin ni recherche hydrologique régionale. Les décorations
natives peuvent ensuite ajouter des plantes ou de petites flaques de grotte.
Le test d'eau compare les positions de **toutes** les sources dans le voisinage
immédiat avant et après 240 ticks natifs, y compris les blocs gorgés d'eau.
Il refuse tout écoulement et toute perte/apparition de source, et vérifie
qu'il reste de l'eau dans chaque mare. Les flaques indépendantes produites
par les groupes de stalagmites ne doivent pas être confondues avec une fuite.
Le recensement exhaustif conserve une enveloppe finie de chunks jusqu'à la
fin de la mesure. Il ne force plus des cycles d'expiration/sauvegarde durant
`SERVER_STARTED`, qui dupliquaient les copies des chunks voisins en mémoire.
Ce parcours est réservé au diagnostic `--whole-island`, jamais au lancement
ordinaire du laboratoire.
## Reproduction
```sh
./gradlew :sanctuary-test:exportDuoLaunch
python3 scripts/worldgen_lab.py prepare --profile ecology --seed 42 --run nouvel-essai
python3 scripts/worldgen_lab.py server --profile ecology --seed 42 --run nouvel-essai
```
Ajouter `--verify` pour les relevés et le contrôle des fluides. Pour un
recensement complet, ajouter `--whole-island` à la préparation **et** au serveur.
Le lanceur refuse de réutiliser un monde dont l'empreinte des sources a changé.
Les fichiers `ecology-v1-cold/survey.json`, les coupes binaires et
`worldgen-lab-metrics.json` sont écrits dans le dossier serveur du laboratoire.
`scripts/ecology_atlas.py` produit l'atlas avec les dépendances de
`scripts/sky-atlas-requirements.txt`.
## Mesures natives du 29 septembre
Série `science176d`, Java 25, Minecraft 26.3, heap serveur limité à 1 536 MiB.
L'[archive des relevés](/Users/koka/Documents/sanctuary/retours-sessions/2026-09-29_1506-storyquest/releves-beta176/)
conserve les JSON, coupes, journaux, figures et empreintes SHA-256.
| Contrôle | Graine 42 | Graine 0 | Graine 173 |
| --- | ---: | ---: | ---: |
| Densités identiques à beta.175 | 50 000 / 50 000 | 50 000 / 50 000 | 50 000 / 50 000 |
| Ensembles de structures natives admissibles | 0 | 0 | 0 |
| Plans hydrologiques régionaux | 0 | 0 | 0 |
| Mares témoins stables après 240 ticks | 8 / 8 | 8 / 8 | 8 / 8 |
| Blocs d'air contrôlés en Y=512–639 | 1 474 560 | 1 474 560 | 1 474 560 |
Les cinq récifs de chaque graine portent de la végétation. Les mares témoins
comprennent de la surface, des grottes et des récifs hauts. Le contrôle du ciel
porte sur neuf chunks autour de chacun des cinq récifs ; ce n'est pas un scan
exhaustif de toutes les décorations possibles sur toutes les graines.
Réouverture de 42 : cartes de biomes identiques, deux coupes de blocs identiques
octet par octet, mêmes sources d'eau avant/après les 240 nouveaux ticks.
### Ressources de toute l'île, graine 42
Recensement des **2 000 chunks de l'enveloppe de l'île**, Y=0–639, après
décoration ; aucune extrapolation des 81 chunks centraux. Durée du comptage :
154,391 s, réservée au diagnostic. Les points de contrôle mémoire montrent
341–607 MiB de heap utilisé ; cela ne mesure pas le pic total du processus.
| Ressource | Blocs de minerai, variantes pierre + deepslate |
| --- | ---: |
| Cuivre | 193 771 |
| Charbon | 138 998 |
| Fer | 5 343 |
| Lapis | 7 124 |
| Diamant | **336** |
| Or | 0 |
| Redstone | 0 |
Les 336 diamants sont en deepslate, sous Y=136, sans voisin d'air. L'améthyste
comprend 5 578 blocs ordinaires, 1 422 blocs bourgeonnants et 219
bourgeons/cristaux. Ces totaux décrivent ce monde enregistré ; les deux autres
graines ont seulement un relevé minéral régional, pas un total d'île.
Sur la carte de surface échantillonnée de 42, l'automne occupe 19,4 % des points
et les cerisiers 25,5 %. Leurs plus grandes composantes regroupent respectivement
642/646 et 846/850 points : il s'agit bien de grandes régions, avec quelques
points isolés aux bords. Grille de 8 blocs, connexité à quatre voisins ; ces
proportions ne sont pas des surfaces exactes au bloc près.
### Durées et interprétation
| Passage | Initialisation serveur hors bootstrap JVM | Processus → fin des diagnostics |
| --- | ---: | ---: |
| 42 neuve, comptage intégral | 6,403 s | 237,523 s |
| 42 réouverte, relevé régional | 0,922 s | 36,606 s |
| 0 neuve, relevé régional | 4,782 s | 86,856 s |
| 173 neuve, relevé régional | 2,792 s | 86,125 s |
La seconde colonne chronomètre les événements de démarrage du serveur, pas
l'ouverture complète du client. La dernière inclut les cartes, chargements
de chunks, coupes, minerais et 240 ticks de vérification. Ces parcours sont
activés seulement par `--verify`/`--whole-island` et ne tournent pas pendant
la visite solo. Aucun gain de FPS ni coût isolé de l'eau n'est déduit ici.
## Livraison locale et limites
Client solo ouvert le 29 septembre à 20:22 : backend Vulkan confirmé
(MoltenVK 1.4.2, Apple M1), entrée de KokaLab à `(-8.5, 249, -13.5)`,
serveur intégré et rendu passé à 32 chunks dans le journal. Les commandes
sont autorisées dans la copie.
`./gradlew check build assemblePack assembleTestPack` : contrôles statiques et
smokes terminés, puis **264/265 GameTests réussis**. Le seul échec est
`familiar029game_tests_carried_pair_and_invulnerability_still_apply`, assertion
« Carrier fixture » au tick 0. Ce code de portage n'est pas modifié par ce lot
et cette suite ne charge pas le module écologique `sanctuary-test`.
Relance ciblée sans modification de code :
`./gradlew :sanctuary:runGameTest -PsanctuaryFocusedTests=familiarhit -PsanctuaryExpansionReload=true`
→ **7/7 réussis**, `BUILD SUCCESSFUL`. Cela indique un échec intermittent ou
une interaction de suite à examiner ; cela ne transforme pas la première
suite complète en passage réussi. Aucun correctif du portage n'est livré ici.
Assemblage local **réussi** après ces contrôles, sans les relancer :
`./gradlew build assemblePack assembleTestPack -x :check -x :sanctuary:check -x :sanctuary-test:check -x :demeure:check -x :jei:check`.
`BUILD SUCCESSFUL` en 12 s ; packs générés dans `build/packwiz` et
`build/packwiz-test`. Les journaux complets sont conservés dans l'archive des relevés. Aucun canal
packwiz, serveur personnel ni instance Prism n'est mis à jour.
La copie `Sanctuary-Ecology-176-Solo` provient du serveur `science176d/42`
après arrêt et réouverture vérifiée. Commandes autorisées, mode créatif,
réglages de visite repris de beta.175 avec rendu 32 chunks. L'ancien solo
est conservé. Pour observer librement : `/gamemode spectator`.
Repères de visite, graine 42, points d'observation au-dessus du terrain :
| Zone | Téléportation en spectateur |
| --- | --- |
| Grande région automnale | `/tp @s -4 300 180` |
| Grande région de cerisiers | `/tp @s 20 280 -180` |
| Récif jungle | `/tp @s -116 390 160` |
| Mare du récif mangrove | `/tp @s -105 425 -152` |
| Récif marais | `/tp @s 219 470 22` |
| Transition profonde près d'une mare en grotte | `/tp @s 41 166 -55` |
Premier essai : les mares restent petites et arrondies ; leur forme, les
proportions de pierres et les lisières doivent encore être jugées en visite.
Les grands lacs, rivières et cascades, les villages Sanctuary, les donjons,
les expansions et la station ISS ne sont pas implémentés par ce lot.
+40
View File
@@ -0,0 +1,40 @@
# WG-ECO-179 — grottes vivantes et complexes souterrains
Retour de visite beta.178 : automne validé ; trop de cerisiers bas, bordures
rocheuses sur les pentes, grottes devenues trop sèches et trop dépouillées.
Nouveau preset `sanctuary_test:living_ecology_v1`, profil `living`, graine 42,
branche `codex/living-caves-beta179`, base `db6194c`. Nouveau monde uniquement.
- Vallées automnales conservées. Sol végétal sur les pentes supérieures ; la
roche nue reste sur les flancs profonds sous Y=200.
- Cerisiers à partir de Y=286, une tentative tous les quatre chunks du sommet
au lieu de dix arbres par chunk. Pas de forêt de cerisiers sur le plateau.
- Poches lush avec bassins retenus à plusieurs niveaux, sous-bois de chênes
noirs, marais à lucioles, mycélium violet mêlé d’herbe et champignons géants.
Mooshrooms et grenouilles placées à la génération dans leurs habitats.
- Trois réseaux miniers avec hall, ramifications, rails, passerelles et sorties
extérieures ; une cabane de sorcière dans une cavité marécageuse. Première
version procédurale, sans butin ni progression. Le retour actuel remplace
l’interdiction des mineshafts exprimée avant la visite beta.178.
La couleur vient des biomes, du feuillage, de l’eau et du brouillard ; aucune
nouvelle simulation de lumière colorée. Bruit du terrain, récifs, minerais et
espace ISS conservés ; pas d’hydrologie régionale. Structures rendues par chunk
selon un plan déterministe borné, aucune expansion activée ni migration.
Compilation et contrôle natif `solo179c` réussis : 567 densités profondes
inchangées, zéro région d’hydrologie, automne conservé (14 colonnes de contrôle),
cerisiers uniquement au sommet et pentes supérieures végétalisées. Les témoins
natifs contiennent eau, mousse, mycélium, buissons à lucioles, chênes noirs et
champignons géants. Sauvegarde des habitants vérifiée : 6 mooshrooms,
17 grenouilles, une sorcière et un chat dans les chunks chargés, sans prétendre
recenser toute l’île. La suite `check build assemblePack assembleTestPack` a réussi
en 36 min 7 s, après la visite.
Visite préparée : `visite179/living/42`, `Sanctuary-Living-179-Solo`, créatif,
commandes activées, difficulté normale pour conserver la sorcière, vue 32 et
simulation 12. Points de visite (graine 42) : halls miniers (112,147,72),
(-160,185,24), (-72,129,160) ; cabane (-192,114,96) ; mycélium vers
(-216,201,48), marais vers (-216,113,96), lush vers (-216,161,120).
Client Vulkan ouvert le 29 septembre à 23:16, KokaLab connecté ; distance
serveur 32 et simulation 12 confirmées dans le journal.
+78
View File
@@ -0,0 +1,78 @@
# DETAILS-188 — fonds d'étangs, soufre vivant et palettes des ancres
Ticket du 30 septembre 2026, branche `codex/living-details-beta188`.
Le créateur valide l'orientation des ancres et les formes des bassins beta.187.
Il demande des refuges adaptés au milieu et à la couleur, des fonds sédimentaires
végétalisés sans arbres dans l'eau, et les pics/geysers du biome de soufre.
Nouveau profil `details`, preset `sanctuary_test:living_details_v1`, graine 42.
Aucune modification des anciennes sauvegardes ou générations. Même densité,
mêmes bassins et mêmes emplacements/orientations d'ancres que beta.187.
Livraison locale de laboratoire ; objectif de visite avant 9 h.
## Réalisation
Géométrie et orientation des refuges conservées. Terre, herbe et terre stérile
pour la surface ; tuff, deepslate et calcite sous roche ; calcite et diorite
sur les récifs. Les incrustations reprennent la teinte du secteur. Le verre
reste au-dessus de l'améthyste, du côté de l'expansion.
Les bassins sont remplis avant les arbres. Leur lit existant reçoit des plages
cohérentes d'argile, boue, sable et gravier, sans agrandissement ni coque ajoutée.
Les colonnes de plantation immergées sont réservées pendant la décoration.
Herbes marines et quelques touffes de kelp occupent les fonds ; les matériaux
meubles exigent un support rocheux. Contours et niveaux restent ceux de 187.
Le soufre reçoit des stalagmites et stalactites natives, avec des longueurs
variables, et des sources périodiques : magma, soufre puissant, eau source.
Les volumes humides réservés, le donjon et les refuges sont évités. Les entités
de bloc sont explicitement créées et sauvegardées pour activer les minuteries
natives des geysers. Le contrôle vérifie l'entité et le ticker après réouverture.
Table de butin propre à ce nouveau preset : une ou deux pièces en fer par
wagonnet, parmi quatre armures, épée, pioche, hache et pelle. Enchantement par
la fonction native, puissance de table 12 à 20, sans enchantement trésor.
Les gemmes et les ressources déjà présentes restent dans le butin. Les anciens
wagonnets et tables ne changent pas. La progression du futur monde minéral
reste une intention, hors de cette livraison.
## Vérifications
Premier monde : les huit refuges gardent exactement leurs positions et axes.
Quatre sédiments, 611 colonnes végétalisées et zéro tronc dans les bassins.
Plan de 496 spires et 39 geysers ; 138 blocs de pic et huit sources contrôlés.
32 tirages de butin : toujours une ou deux pièces enchantées en fer, les huit
types d'équipement observés. Génération finale `solo188c/details/42` puis réouverture réussies, avec contrôle
des entités de geyser et de leurs tickers natifs. Les blocs et entités sont
relus dans la sauvegarde arrêtée : huit ancres éteintes, Bugrock allumé sans
relais, 49 cartes complètes et 49 cadres horizontaux. Les quatre wagonnets sauvegardés portent tous la nouvelle table
`sanctuary_test:chests/mine_cache_188` ; reçu `build/details188-saved-loot.json`.
`./gradlew check build assemblePack assembleTestPack :sanctuary-test:exportDuoLaunch`
réussi en 7 min 54 s : 265 GameTests réussis. Les contrôles natifs finaux passent
après la correction des entités de geyser, à froid et après réouverture.
Le JAR du labo a ensuite été reconstruit et le pack optionnel resynchronisé
avec `scripts/test_pack.py` : ses 146 classes correspondent aux classes compilées,
sa table de butin correspond à la source et le JAR distribué est identique.
Lors des 32 tirages après réouverture, 51 pièces enchantées couvrent les huit
types d'équipement. Reçu de la suite générale : `build/details188-check-build.log`.
Solo `visite188c/details/42`, monde `Sanctuary-Living-Details-188-Solo`, ouvert
sous Vulkan : connexion à 09:00:41, vue 32 et simulation 12 confirmées dans le
journal, puis passage du joueur en spectateur à 09:00:58. L'objectif avant 9 h
a donc été dépassé. Le contrôle visuel des décors et des éruptions reste à faire
pendant la visite ; la présence des blocs, entités et tickers est vérifiée.
## Raccourcis de visite
- Bassin ouest : `/tp -104.5 269 119.5 180 35`
- Soufre et geysers : `/tp -217.5 159 -16.5`
- Autre source : `/tp -201.5 150 8.5`
- Wagonnet du donjon : `/tp 45.5 144 60.5`
- Ancre nord : `/tp 36.5 250 -168.5 180 0`
- Ancre sud, grotte : `/tp -71.5 176 205.5 0 0`
- Ancre est, récif : `/tp 214.5 444 16.5 -90 0`
Nouveau solo uniquement, vue 32, simulation 12, créatif avec vol et commandes.
Aucune publication du canal public ni modification de Prism.
Mesures du labo final : démarrage 12.839 s à froid / 0.912 s après réouverture ; prêt avec tous les contrôles en 71.525 s / 14.114 s. Mesurées pendant la suite générale, hors chargement graphique. Zéro région d’hydrologie. Reçus ignorés `build/details188-final-cold-result.json` et `build/details188-final-warm-result.json`.
+50
View File
@@ -0,0 +1,50 @@
# UI-194 — retirer Amis du menu principal
Branche `codex/main-menu-beta194`, depuis beta.193, Minecraft 26.3.
Le créateur suspend les retouches du terrain et les tests de screenshots,
puis reprend le cadrage des interfaces. Le retrait d’Amis est la première
modification demandée, déjà prévue dans [Storyquest](storyquest-beta173.md).
## Résultat
Le menu principal propose Solo, Multijoueur, puis Options et Quitter sur une
même ligne. Cette dernière suit Multijoueur avec l’espacement natif de 24 unités
GUI. Le bouton Amis n’est plus créé à la place de Realms ; l’entrée Realms et
la petite icône sociale restent retirées. Les commandes restantes gardent
leur ordre clavier et la version Sanctuary reste affichée.
Le parcours de démonstration, dépourvu de Multijoueur, conserve sa disposition
antérieure. Les autres accès sociaux et paramètres du compte ne sont pas
modifiés. Aucun nouveau libellé : les commandes natives restent traduites FR/EN,
les anciennes clés `sanctuary.menu.friends` sont conservées pour compatibilité.
Le menu pause, Habitant, Combat, Factions et Storyquest restent des suites
à concevoir. Aucune source de génération, donnée de biome ou sauvegarde n’est
modifiée par ce lot. Aucune nouvelle série de captures n’est lancée.
## Vérifications
Le parcours natif existant `Title045ClientChecks` est actualisé pour vérifier
l’absence d’Amis et de Realms, la disposition sans ligne vide, l’ordre clavier
et l’ouverture d’Options en FR/EN aux quatre réglages GUI. Ses captures et son
ancien parcours vers la liste d’amis sont retirés.
Validation réussie : `./gradlew check build assemblePack assembleTestPack
:sanctuary:runClientGameTest -PsanctuaryFocusedTests=menus
-PsanctuaryClientTests=true -PsanctuaryTitleClientTests=true
-PsanctuaryClientGraphicsBackend=vulkan`.
Journal : `build/menu194-check-build.log`. Les contrôles purs et les quatre
GameTests serveur ciblés passent. Le parcours client Vulkan passe en FR/EN,
sans capture, avec ordre clavier, absence des boutons retirés et ouverture
d’Options vérifiés. Dans la fenêtre 854 × 480 du test, les réglages GUI 3 et 4
sont plafonnés par Minecraft à l’échelle effective 2.
Les packs normal et de test sont assemblés. La comparaison des JAR beta.193 et
beta.194 confirme que seule `TitleMenuMixin.class` change dans le code livré ;
les données de génération sont identiques. Le module Demeure embarqué ne change
que de version dans ses métadonnées. Reçu : `build/menu194-artifact.json`.
Le labo beta.193 déjà ouvert n’est ni arrêté ni mis à jour par cette livraison.
Livraison locale uniquement ; publication et mise à jour du jeu personnel
ne sont pas demandées.
+115
View File
@@ -0,0 +1,115 @@
# ASCENT-184 — rosace à 640 et palais des hauteurs
Ticket du 30 septembre 2026, branche `codex/natural-ascent-beta184`.
Le retour sur beta.183 rejette l'ISS métallique visible depuis le sol et les
nouveaux bassins supérieurs ovales en marches. La direction retenue est une
machine naturelle : rosace, longue tige parcourable, couronne de palais comme
un parapluie, carte au sol et portail horizontal au-dessus. Dernière correction
explicite du créateur : **la rosace est à Y=640**, pas à 320.
## Contrat de génération
Nouveau profil de laboratoire `ascent`, preset `sanctuary_test:ascent_v1`,
réglages `sanctuary_test:ascent_v1_10`, graine de visite **42**. Nouvelle dimension
`sanctuary_test:ascent_1600`, constructible de Y=0 à 1599. Minecraft 26.3 accepte
ce type de dimension dans la génération native vérifiée. Le bruit du terrain
reste calculé entre 0 et 639 ; la décoration écrit les structures plus haut
dans les sections du nouveau monde. Aucun bruit supplémentaire dans le ciel
vide, aucune hydrologie régionale, aucun changement des sauvegardes précédentes.
Les presets 180–183 restent distincts. Le nouveau profil reprend l'île, ses
minerais, biomes, cavernes, soufre et donjon, mais ne place ni l'ISS métallique
ni les bassins `Water183`. Les étangs souterrains beta.180 précédemment validés
restent présents. Pas d'expansion, migration, nouveau mob ou portail fonctionnel.
## Construction et découverte
- Rosace et Bugrock translatés à **(0,640,0)**, sol à 638. Les huit couleurs et
approches de la rosace sont conservées ; la sortie est rejoint l'escalier.
- Tige de tuf, deepslate, calcite et mousse, nervures enroulées, haltes latérales.
Escalier continu avec marches natives, de 638 à 1278 : 1 923 positions de
parcours, environ 1,9 km de chemin pour 640 blocs gagnés. Largeur généralement
de trois blocs, resserrée aux virages. Le parcours depuis l'île jusqu'à la
rosace reste à concevoir ; le test permet le vol et le spectateur.
- Palais : nervures déployées à partir de 1160, sol à **1278**, voûte à **1310**.
Huit pétales vitrés, passages minéraux, cuivre oxydé, balcons. Pas de panneaux
solaires ni de modules métalliques. Le Bugrock reste uniquement sur la rosace.
- Atlas natif au sol, 49 cartes sur ce terrain moyen, dans des cadres horizontaux.
Dimensions 5×5, 7×7 et 9×9 pour les rayons déjà pris en charge. Identifiants et
marqueurs propres à ce nouvel atlas ; l'atlas 183 reste inchangé.
- Portail supérieur : cadre horizontal 7×7 d'obsidienne, coins en obsidienne
pleureuse, vide central 5×5, à 1287 au-dessus des cartes. Le portail inférieur
conserve son cadre 5×5 et son creux 3×3 à Y=175. Ce sont des infrastructures.
À 32 chunks, la caméra native peut garder un plan lointain de 2 048 blocs :
la hauteur seule ne suffit pas à dissimuler la couronne. Dans cette dimension
uniquement, le frustum refuse les volumes entièrement au-dessus de 1152 lorsque
la caméra est sous 560. La couronne devient donc éligible à l'affichage pendant
l'approche de la rosace ; les règles normales de visibilité restent applicables.
C'est une révélation côté rendu, pas une génération conditionnelle à un joueur.
Un contrôle à l'entrée du client teste les deux API natives de frustum, depuis
le sol et depuis 640. La couronne est aussi au-delà du volume maximal des ombres
Sanctuary depuis le plateau (distance 256, extrusion solaire comprise). Cela
ne supprime pas l'éclairage Minecraft inhérent aux blocs placés dans une colonne.
## Eau dans les creux existants
Recherche locale et bornée dans le champ réel de densité. Chaque candidat est
rempli par parcours des cellules d'air adjacentes sous un niveau horizontal ;
un chemin sortant du volume de recherche, vers le vide ou une zone réservée
fait rejeter le candidat. On monte le niveau par essais bornés tant que la roche
retient l'eau. Aucun mur de retenue, fond d'argile artificiel, marche ou excavation
n'est dessiné. Seules les cellules encore en air reçoivent de l'eau à la décoration.
Les cavités existantes, roches saillantes et végétaux conservés dictent le contour.
Première génération native `solo184a`, graine 42 : 45 essais, plan en 852 ms,
10 402 blocs d'eau dans la grande poche (surface de 1 045 cellules à Y=234),
739 blocs dans la seconde (290 cellules à Y=242). Aucun débouché latéral/inférieur
non retenu détecté après la décoration. Ce sont des eaux retenues ; aucune
simulation de bassin versant ou de rivière à longue distance n'est introduite.
## Vérification et visite
Premier contrôle natif réussi : 567 échantillons profonds identiques, zéro région
d'hydrologie, trois cerisiers, cinq spawners, quatre caches à butin, galerie,
soufre et trois secteurs de gemmes présents. Les 56 246 entrées du plan du palais
correspondent aux blocs générés ; toutes les positions de montée ont un support
et deux blocs libres. Les 49 cartes sont uniques, remplies, figées et supportées.
Serveur démarré en 5,3 s ; prêt en 43,6 s avec génération des sondages et contrôles.
Ce dernier temps inclut les tests et ne mesure pas un démarrage normal du solo.
Validation finale : `./gradlew check build assemblePack assembleTestPack
:sanctuary-test:exportDuoLaunch` réussi en 6 min 54 s, **265/265 GameTests**.
Journal `build/ascent184-check-build.log`. Nouveau monde final `solo184b/ascent/42`
contrôlé et arrêté proprement : serveur en 5,25 s, prêt avec contrôles en 41,63 s ;
plan d'eau en 806 ms, 10 401 et 739 blocs d'eau retenus sans fuite. Le nombre de blocs d'eau diffère d'une cellule entre les deux essais ;
les niveaux et surfaces des plans restent identiques.
Rapport `build/ascent184-quick-result-final.json`.
Lecture NBT du monde arrêté : 49 cartes complètes, figées, identifiants uniques,
49 cadres orientés vers le haut à Y=1279. Air confirmé aux anciens centres
(0,320,0) et (0,576,0), bloc originel à (0,640,0), plancher d'atlas en calcite
et portail supérieur creux. Reçus `build/ascent184-saved-atlas-final.json` et
`build/ascent184-saved-blocks-final.json`.
Solo neuf `visite184/ascent/42`, `Sanctuary-Ascent-184-Solo`, commandes, créatif,
vol, vue 32 et simulation 12. Entrée client Vulkan confirmée le 30 septembre,
contrôle natif du frustum réussi à **03:45:07** : couronne cachée à Y=250,
rosace visible et couronne révélée à Y=640. Journal `build/ascent184-solo.log`.
Le profil local KokaLab de ce monde utilise la couleur Ciel et le familier de
labo par défaut. Le daemon de compilation a été arrêté pour libérer la mémoire.
Aucune sauvegarde antérieure ni instance Prism modifiée. Les archives locales
sont assemblées ; aucun canal public n'est avancé.
| Lieu | Téléportation de visite |
| --- | --- |
| Rosace et départ de la montée | `/tp 0.5 639 5.5 180 -20` |
| Palais et carte au sol | `/tp 6.5 1280 8.5 140 35` |
| Vue extérieure de la couronne | `/tp 45 1295 45 135 10` |
| Grande cavité en eau | `/tp -144 236 -96` |
| Seconde cavité en eau | `/tp -16 245 128` |
| Portail inférieur | `/tp 0.5 177 7.5 180 30` |
Cette première silhouette reste à évaluer en visite, notamment le rythme de
l'ascension et les proportions du palais. La graine 42 est la référence vérifiée ;
la disponibilité de grandes cavités sur d'autres graines reste à éprouver.
+100
View File
@@ -0,0 +1,100 @@
# CAVES-187 — soufre naturel, ancres orientées et bassins visibles
Ticket du 30 septembre 2026, branche `codex/natural-caves-beta187`.
Retour beta.186 : coque de soufre artificielle, pylônes mal orientés et verre
latéral, bassins de surface invisibles. Le relief et les trois secteurs de
gemmes sont appréciés et conservés.
Nouveau profil `natural`, preset `sanctuary_test:natural_caves_v1`, graine 42.
Les anciens presets et sauvegardes restent inchangés. Le laboratoire ajoute
un climat de soufre en trois dimensions dans la roche existante, une approche
en mousse tournée vers le centre pour chaque ancre et des pylônes opposés,
vers l'expansion, portant le verre au-dessus de l'améthyste. L'eau de surface
est recherchée dans des dépressions réellement ouvertes au ciel ; toute fuite
vers le vide fait rejeter le bassin. Aucun calcul régional d'hydrologie.
Le climat de soufre utilise un bruit volumique et respecte la priorité des
marais humides. La palette forme des dépôts cohérents de soufre, cinabre et
calcite dans les blocs rocheux existants. Musique et couleurs du biome natif
26.3 sont conservées. Les carvers, minerais et structures vanilla ne sont pas
importés : l'île conserve ses cavités et sa distribution de ressources.
L'ancienne coque et ses cheminées ne sont plus placées dans ce preset.
Les pylônes portent améthyste puis verre, sur la même verticale. Leur porte
pointe vers la direction exacte du secteur, y compris les diagonales. Le
chemin en mousse est du côté opposé. La sélection vérifie le support naturel
de la porte et du chemin ; les huit emplacements changent pour respecter
cette orientation. La galerie, les rosaces et le palais restent conservés.
L'eau de surface monte dans un creux par parcours prioritaire : chaque voxel
est échantillonné une fois à la hauteur minimale qui le rend accessible.
Une fuite, une structure réservée ou la limite de recherche arrête la montée.
Les surfaces retenues doivent être majoritairement ouvertes au ciel. Aucun
fond ajouté ni découpage elliptique ; les arbres voisins ne peuvent reboucher
les cellules d'eau réservées. Les étangs souterrains appréciés sont conservés.
L'atlas projette aussi les nouvelles eaux et perd le faux repère jaune de la
cheminée supprimée. Les recherches sont bornées, en génération uniquement.
## Contrôles natifs
Graine 42, `solo187e/natural/42` : génération à froid réussie. Recherche des
bassins en 2,674 s, contre 21,546 s au premier essai ; mêmes trois positions,
niveaux et volumes retenus. Démarrage serveur 9,881 s, prêt après l'ensemble
des contrôles en 50,190 s. Ces durées ne mesurent pas le démarrage graphique.
Zéro région d'hydrologie et 567 échantillons de densité profonde conservés.
| Bassin | Niveau | Surface totale | Surface sous ciel ouvert | Blocs d'eau |
| --- | --- | --- | --- | --- |
| Ouest, près de (-104, 88) | 248 | 1 994 | 1 475 | 11 935 |
| Nord-est, près de (120, -136) | 244 | 839 | 536 | 2 329 |
| Sud, près de (8, 136) | 254 | 1 321 | 904 | 9 446 |
Aucune fuite native détectée. Quatre points de visite confirment le biome
sulfureux, de la roche minéralisée et des cavités déjà présentes dans la densité.
Les huit refuges passent le contrôle d'orientation et de verre vertical.
Les dépôts distincts activent les huit gemmes ; les tests restaurent ensuite
les ancres éteintes et le Bugrock allumé sans relais. Les secteurs de gemmes,
les cinq spawners et quatre wagonnets à butin restent vérifiés.
Réouverture réussie : démarrage serveur 0,618 s, prêt avec vérifications en
8,226 s. Les huit ancres sont relues dans la sauvegarde éteintes et le masque
du Bugrock vaut zéro ; 49 cartes complètes, figées, avec 49 cadres horizontaux
uniques. Reçus ignorés : `build/natural187-final-cold-result.json`,
`build/natural187-final-warm-result.json`, `build/natural187-saved-blocks.json`,
`build/natural187-saved-atlas.json`.
`./gradlew check build assemblePack assembleTestPack :sanctuary-test:exportDuoLaunch`
réussi en **7 min 10 s**, **265/265 GameTests**. Journal :
`build/natural187-check-build.log`. Daemon Gradle arrêté avant le client.
Solo Vulkan rejoint à **08:40:37**, départ (-104,5 ; 269 ; 119,5), vue 32
et simulation 12 confirmées dans `build/natural187-solo.log`. Contrôle client
de visibilité verticale réussi à 08:40:39. Le créateur a pris la main et est
passé en spectateur ; aucune téléportation d'inspection supplémentaire.
L'appréciation visuelle des nouveaux bassins et du soufre reste celle de
cette visite ; les mesures ci-dessus sont des vérifications natives.
Seule la graine de visite est certifiée ; pas de migration des anciennes
sauvegardes ni de modification du canal public ou de Prism.
## Visite — graine 42
Nouveau solo `visite187/natural/42`, `Sanctuary-Natural-Caves-187-Solo`.
Vue 32, simulation 12, créatif, vol et commandes. Départ au-dessus du bassin
ouest ; les anciennes visites restent conservées.
| Lieu | Raccourci de visite |
| --- | --- |
| Bassin ouest | `/tp -104.5 269 119.5 180 35` |
| Bassin nord-est | `/tp 120.5 258 -116.5 180 35` |
| Bassin sud | `/tp 8.5 270 156.5 180 35` |
| Soufre, cavité haute | `/tp -215.5 219 -35.5` |
| Soufre, cavité profonde | `/tp -203.5 150 -11.5` |
| Soufre, cavité nord-ouest | `/tp -191.5 152 -179.5` |
| Ancre nord, approche intérieure | `/tp 36.5 250 -168.5 180 0` |
| Ancre nord-est | `/tp 193.5 251 -84.5 -135 0` |
| Ancre est, récif | `/tp 214.5 444 16.5 -90 0` |
| Ancre sud-est | `/tp 157.5 226 145.5 -45 0` |
| Ancre sud, grotte | `/tp -71.5 176 205.5 0 0` |
| Ancre sud-ouest | `/tp -96.5 252 181.5 45 0` |
| Ancre ouest | `/tp -96.5 230 0.5 90 0` |
| Ancre nord-ouest, récif | `/tp -102.5 404 -160.5 135 0` |
+77
View File
@@ -0,0 +1,77 @@
# WG-NATURE-190 — relief organique, déversoirs et butin des ruines
Branche `codex/natural-refinement-beta190`, graine de visite **42**, Minecraft 26.3.
Nouveau profil de laboratoire `refined`, preset `sanctuary_test:natural_refinement_v1`.
Les sauvegardes précédentes restent des témoins ; aucune régénération ni migration.
## Contrat de cet essai
Après la visite beta.189 : supprimer les terrasses systématiques du relief principal,
conserver quelques inflexions locales douces, des affleurements suivant la pente et
sans masque en cases de quatre blocs. Les captures du 30 septembre à 20:44 et 20:45
montrent le relief principal ; les îlots aériens ne sont pas la cause des paliers.
Comparer les débouchés voisins des étangs au lieu de retenir le premier rayon ;
privilégier une sortie courte, large de quatre ou cinq blocs et peu creusée.
Conserver les cavités, le portail et les ruines appréciés. Remplacer la table de
craft de chaque ruine par un coffre de matériel en fer enchanté.
Aucun calcul d’hydrologie régionale ajouté ; travail local borné, décoration existante.
## Réserve de conception, non implémentée
- Expansion à grandes surfaces étagées et falaises ciselées : conserver la variante
beta.189 comme référence, avec une profondeur et une verticalité adaptées au thème.
- Portails horizontaux : remplissage progressif avec une ressource renouvelable.
Perles d’Ender, source Endermen ou butin sont des pistes ; ressource, nombre,
recette et destination ne sont pas décidés. Aucun mécanisme d’activation ajouté ici.
## Vérifications
Compilation et génération/réouverture natives réussies, graine 42 :
`build/refined190b-cold.json` et `build/refined190b-warm.json`.
- 704 des 745 sommets échantillonnés gardent exactement la hauteur naturelle
antérieure aux terrasses ; 41 épaules retouchées, écart maximum 3 blocs.
30 246 densités des profondeurs et des îlots aériens identiques. Les corniches
fines sont conservées ; une première interpolation qui pouvait les supprimer
a été corrigée avant la visite.
- Une sortie d’étang de cinq blocs de large, depuis X=150, Y=244, Z=-157 à -153
vers X=154–155. Chenal de 4–5 blocs, dont une partie dans l’étang ; huit blocs
retirés. Bord de chute irrégulier, 64 blocs préremplis puis écoulement natif.
La recherche compare les ouvertures proches, reste bornée à dix blocs et
deux cascades, et renonce si aucune bouche assez large ne convient.
- 1 052 blocs de plage sèche, 434 plantes aquatiques, zéro tronc dans les
colonnes contrôlées des plages et étangs. Fonds : 1 337 sable, 249 gravier,
2 832 argile. Le choix du chenal ne crée pas de fond ou barrage artificiel.
- Quatre ruines conservées, coffres en (25,164,-63), (-7,148,-79),
(65,164,-79), (57,121,-79). Couvercles dégagés, table de butin conservée
après réouverture. Une ou deux pièces en fer par coffre, enchantements
de table de niveau 12–20 : armure, épée, pioche, hache ou pelle.
32 tirages vérifiés à froid, puis 32 à chaud ; aucune pièce non enchantée.
- Huit ancres, cartes du palais, gemmes, soufre, donjon et galerie contrôlés.
Ni nouvelle dimension ni activation des portails ajoutée.
Démarrage natif : 12,331 s à froid / 0,673 s à chaud ; avec toute la batterie
et le remplissage intégral des cartes : 81,505 s / 26,656 s. Plans locaux
berges/déversoirs : 174 / 170 ms. Zéro région d’hydrologie calculée.
Mesures obtenues pendant la suite de tests générale, sans garantie de durée
identique sur chaque machine. Le solo ne lance pas ces contrôles lourds.
`./gradlew check build assemblePack assembleTestPack` réussi en **9 min 22 s**,
avec **265 GameTests réussis**. Après les derniers correctifs validés par le labo
natif, le JAR optionnel a été réassemblé et le pack de test resynchronisé.
Ses classes et ressources correspondent aux fichiers compilés, et sa copie
assemblée est identique : `build/refined190-pack-receipt.json`.
Aucune publication de canal ni mise à jour Prism. La validation esthétique reste
celle de la prochaine visite ; les contrôles structurels ne la remplacent pas.
## Visite
Solo `Sanctuary-Natural-Refinement-190-Solo`, run `visite190`, profil `refined`,
graine 42. Copie neuve du monde de diagnostic arrêté, sauvegardes antérieures
préservées. Spectateur et commandes activés, vue 32, simulation 12, Vulkan.
Connexion confirmée à **21:01:12**, position (178,260,-146), face à la cascade.
Les 49 cartes et cadres horizontaux sont présents, complets et verrouillés ;
les huit ancres et le Bugrock sont revenus à l’état initial après les contrôles.
`build/refined190-saved-atlas.json`, `build/refined190-saved-blocks.json` et
`build/refined190-solo.log` conservent ces vérifications.
+88
View File
@@ -0,0 +1,88 @@
# EXP-202 — étagement du Nord et village enneigé
Branche `codex/north-ecology-beta202`, depuis beta.201. Retour R021 et trois
captures du labo 201 examinées (12:11:22, 12:11:46 et 12:12:57, 4 octobre 2026).
Conserver les volumes, les cavités et les deux bassins 201, appréciés par le
joueur. Refaire uniquement leur écologie et leurs sols : taïga, vallées ouvertes,
podzol, épicéas géants natifs, neige progressive avec l'altitude, glaciers sur
les hauteurs. Fin des aplats indépendants et du camouflage glace/glace bleue.
Glace de cave limitée aux cavités protégées par une épaisseur de roche dans
chaque direction ; aucune couche glacée appliquée aux falaises ou au dessous.
Les cinq igloos doivent trouver une plaine réellement enneigée avant placement.
Les pics de glace restent vanilla, comme demandé en R020.
Nouveaux profils 202 et journal distinct, diamètre nordique 1024. Aucune migration
ni régénération d'un monde existant ; 200 et 201 gardent leurs générateurs.
L'île de départ et les autres directions restent inchangées. Le chantier est
isolé de la visite 201 encore ouverte. Validation native neuve Small/42, contrôles
numériques sur 0, 42 et 4736390610738281858 ; pas de campagne de captures.
## Réalisation
Le champ de densité et les deux bassins délèguent exactement à la génération
201. Seuls les sols, la végétation, la couverture neigeuse et le placement du
village changent. La limite de neige suit l'altitude, avec un versant nord plus
froid. Son épaisseur progresse de quelques couches à un bloc après la végétation.
Les glaciers sont réservés aux hauts versants ; aucune glace bleue ne peint
la surface. Les vallées restent en herbe, les grands boisements en podzol.
La taïga ordinaire et les épicéas géants réemploient les features 26.3 de taïga
et `old_growth_spruce_taiga`. Aucun arbre géant custom ajouté. Les pics utilisent
toujours `minecraft:ice_spike` et `minecraft:ice_patch`, sans remplacement des
features natives. Les matériaux de cave glacée nécessitent du recouvrement et
une assise rocheuse autour de la colonne ; les marges et dessous restent rocheux.
Les cinq igloos cherchent une zone sèche, entièrement enneigée et peu pentue,
avec classement par dénivelé. Aucun disque blanc ajouté sous le village.
Caches numériques bornés ; pas de simulation hydrologique supplémentaire.
## Vérifications du 4 octobre 2026
- `north202Smoke` : neuf couples graine/taille (0, 42, 4736390610738281858 ;
512/724/1024), égalité des colonnes et densités avec 201, présence de taïga,
vallées, neige progressive et glacier minoritaire, protection du dessous.
Recherche du village vérifiée aussi sur les graines 1 à 100. Toutes passent.
- Ressources historiques de génération inchangées, sauf le preset public qui
sélectionne désormais 202 et l'ajout du biome 202 au tag des ours polaires.
Les trois noise settings de l'île de départ sont identiques aux fichiers 201.
- Essai natif final : `build/worldgen-lab/north202-native-b`, Small, graine 42,
diamètre de l'expansion 1024. Offrande réelle, interruption à 0/58 chunks,
reprise, cinq igloos, une cave de guérison, relais de continuation orienté Nord.
- Échantillon de taïga : 1517 blocs de troncs, 7677 blocs de feuilles,
troncs atteignant 27 blocs, 13 empreintes de troncs larges, 3339 blocs de podzol.
Pics natifs : 2910 blocs de glace au-dessus du terrain, maximum sondé 43 blocs.
Ces nombres décrivent des échantillons, pas toute l'expansion.
- Village : 333 des 339 colonnes sondées hors maisons portent de la neige.
Lac : 8995 blocs de glace et 41795 blocs d'eau, sans fuite dans l'air.
Caverne sondée : 49 blocs glacés et 2585 blocs d'air. Bordure sondée sur
16 directions : 340 blocs rocheux, aucune glace compacte/bleue.
La validation esthétique reste celle de la visite. Pas de nouvelle campagne de
captures. Visite native Small/42 uniquement : Windows et Medium/Large natifs
ne sont pas vérifiés ici. Les sept autres directions restent provisoires.
## Livraison locale
`./gradlew check build assemblePack assembleTestPack
-PsanctuaryFocusedTests=operator,realtime -PsanctuaryAtlasOnly=true` : succès,
143 tâches, dont 126 exécutées ; sept GameTests ciblés et les smokes du projet.
La suite native historique complète n'a pas été relancée (limite documentée
pour beta.200). Logs : `build/north202-check-build.log` et
`build/north202-native-final.log`.
MRpack : `build/Sanctuary-Test-beta.202.mrpack`, copie identique dans Downloads
avec `Sanctuary-Test-beta.202-Guide.txt`. ZIP, versions 26.3 / Loader 0.19.5,
hashes Fabric API, deux JAR et leurs 2258 classes compilées vérifiés.
Reçu local : `build/north202-artifact.json`.
Taille : 12407853 octets ; SHA-256 :
`b5ecdfcb2c4b1988f9402653a16c823b50f0661e8b1db0f0e7d6c21e9841f6f8`.
Aucun monde inclus dans le MRpack. Aucun canal public ni instance Prism modifié.
Visite neuve `Sanctuary-North-202-42`, issue de l'essai natif final, créatif,
commandes activées, Vulkan, vue 32, simulation 4. Départ en vol près du village.
Village `(-212,191,-1292)`, taïga géante `(144,215,-976)`, lac `(-247,258,-765)`,
pics vers `(-208,281,-880)`, relais `(-128,202,-1240)`.
Ouverture confirmée à 12:41:46 par `NORTH202_VISIT_OPEN`, joueur
`(-160,228,-1247)` ; log `build/north202-visit-client.log`.
+120
View File
@@ -0,0 +1,120 @@
# EXP-200 — premier Nord glacial
Branche `codex/north-expansion-beta200`, depuis beta.199. Retours R015–R017.
## Résultat visé
Un continent nordique de diamètre nominal 2048 : crêtes, pics, vallées de taïga
neigeuse, neige poudreuse localisée, cavernes de glace et lacs gelés fermés.
Chèvres et ours polaires natifs de Minecraft 26.3 (choix confirmé).
Un village de plusieurs igloos ; un seul contient le laboratoire souterrain
vanilla avec villageois zombie. Pas d'autre famille de structures dans cette
première tranche. Le Sud, l'Est, l'Ouest et les diagonales seront travaillés à part.
## Placement et ouverture
L'identité du relais reste celle de son secteur. Recherche proche dans un
éventail de ±15° devant l'ancre, sans basculer dans une autre direction.
La première version 200 porte le Nord à 2048 ; les sept autres reliefs gardent
leur taille/profil provisoire de 199. Le relais de continuation du Nord conserve
la direction, le climat et la taille du Nord.
Toute l'emprise est vérifiée vierge, réservée durablement et publiée avant les
travaux. À cette échelle, `ready` signifie accès et relais préparés ; le reste
est généré nativement pendant l'exploration. L'annonce SGA décrit cette ouverture,
pas une pré-génération intégrale. Préparation bornée, sans simulation hydrologique
globale ni réutilisation silencieuse de chunks déjà explorés.
## Contrat des sauvegardes
Nouveaux profils `shared_island200_small/medium/large`, journal distinct
`sanctuary-lab-expansions200`. Aucun monde existant modifié ou migré ; les profils
et ressources 196–199 restent inchangés. IDs historiques conservés. Les essais
activent uniquement des expansions dans des mondes de développement neufs,
graine 42 puis relevés déterministes sur 0 et 4736390610738281858.
## Vérifications prévues
Direction et diamètre, déterminisme, cavités, confinement des eaux, village
et laboratoire habitable, vraie activation, sauvegarde et reprise. Compilation,
`check build` et assemblages. Ne pas confondre vérification technique et validation
esthétique : le rendu reste une première version à visiter.
## Implantation et contrôles intermédiaires
Le champ nordique compose des crêtes déformées, des sommets secondaires et des
vallées, puis soustrait des passages et chambres 3D. Cache numérique borné par
worker ; aucune lecture de chunks voisins pour dessiner le terrain. Six biomes
nordiques reprennent l'écologie native de 26.3 ; les sources d'eau/lave dispersées
et salles de monstres vanilla sont retirées de ces seuls profils. Les bassins
ont un fond rocheux continu et une couverture de glace, sans hydrologie globale.
Village : cinq igloos natifs orientés et ajustés individuellement au sol. Un
seul laboratoire, avec échelle, coffre vanilla, potion de faiblesse, villageois
et villageois zombie persistants. Les arbres évitent les maisons et les lacs.
La faune initiale utilise les attributs du biome à la surface réelle, car la
sélection vanilla à Y=1600 échantillonnait le vide ; règles de spawn natives
conservées. Ours polaires également admis sur la glace des lacs.
- `north200Smoke` : 9 couples graine/taille, huit éventails, relief/cavités,
bassins analytiquement fermés, journal 2048 et refus de 2048 en génération 199.
- Essai intermédiaire Small/42, 2 Gio : vraie offrande nord, interruption à
0/58 chunks, puis reprise. Village de cinq igloos et un laboratoire contrôlés ;
lac avec 3475 blocs de glace et 10525 blocs d'eau, sans voisin d'air sous l'eau ;
136 blocs de glace et 1926 blocs d'air dans les colonnes de caverne sondées.
Ce sont des sondages locaux, pas les totaux du continent.
- Le premier passage a révélé un barreau manquant sous la trappe : raccord
corrigé et vérifié lors du second passage. Les essais antérieurs restent
conservés sous `build/worldgen-lab/north200-native-a` et `north200-native-b`.
- Les 148 ressources historiques de génération du module Test restent identiques
à beta.199 (hors preset public, qui sélectionne maintenant 200).
## Essai final et livraison locale — 4 octobre 2026
Le parcours serveur final `north200-native-final/small/42` répète l'activation
réelle et l'interruption/reprise sur les sources livrées. Les 58 chunks d'accès
et de relais passent à FULL ; cinq igloos, un laboratoire, le lac et la cavité
glacée sont vérifiés. Les entités sauvegardées comprennent six ours polaires,
trois renards, huit lapins, un villageois et un villageois zombie. Aucune chèvre
observée dans ce petit échantillon : leurs règles natives sont présentes dans
les sommets et pentes, sans validation d'une rencontre dans ce parcours.
Le client Vulkan a ouvert une copie de visite neuve, `Sanctuary-North-200-42`,
avec confirmation `NORTH200_VISIT_OPEN` à `(-200,224,-968)`. Créatif, commandes,
vue 32 chunks et simulation 4 ; shaders désactivés. Le village se trouve autour
de `(-200,212,-988)`, la trappe spéciale vers `(-193,210,-961)`, le lac sondé en
`(-64,187,-1204)` et le relais suivant en `(-176,215,-2121)`.
Archive locale : `build/Sanctuary-Test-beta.200.mrpack`, également copiée dans
`/Users/koka/Downloads/`, avec `Sanctuary-Test-beta.200-Guide.txt`.
12 366 771 octets ; SHA-256 :
`2a29b35613f95540ec9d910e043a1f6f26b2dfe7d6d95c50997ba4bbde203cdc`.
Intégrité ZIP, version, dépendances Minecraft 26.3 / Fabric Loader 0.19.5,
empreintes Fabric API, deux JAR identiques au build et 2246 classes compilées
vérifiés. Les trois tailles sont présentes ; aucune sauvegarde embarquée.
Reçu : `build/north200-artifact.json`. Aucun canal public ni Prism synchronisé.
La commande complète `check build assemblePack assembleTestPack` a été lancée,
puis interrompue volontairement pendant
`retainedHydrologySurvivesNativeDecoration` (ancienne génération 24, préparation
de 112 chunks FULL). Les captures de threads montrent l'attente de génération ;
elles ne démontrent pas un interblocage. Cette suite générale n'est pas validée.
Logs : `build/north200-check-build.log`, `north200-fullcheck-threads*.txt`.
Les essais serveur nordiques sont distincts de cette suite historique.
Les sept GameTests `operator,realtime` passent avec le fixture plat
`-PsanctuaryAtlasOnly=true`, indépendamment de l'essai natif nordique.
Cette sélection ne remplace pas les 265 tests de la suite générale.
La commande `./gradlew check build assemblePack assembleTestPack
-PsanctuaryFocusedTests=operator,realtime -PsanctuaryAtlasOnly=true` termine avec
succès : 141 tâches, dont 110 exécutées et 31 à jour. Elle inclut les tests
numériques, notamment `north200Smoke`. Log complet :
`build/north200-check-build-focused.log`. Archive revérifiée après l'assemblage
final, identique aux JAR et classes finaux ; daemon de build arrêté ensuite.
Limites : validation native détaillée sur Small/42, neuf couples graine/taille
pour les contrôles numériques, pas de lancement Windows ni de visite native
Medium/Large sur cette version. Les 2048 blocs désignent le diamètre nominal,
pas une pré-génération intégrale. Le démarrage à 32 chunks a produit des retards
de ticks avec les contrôles et un autre client simultanés sur le Mac 8 Gio :
aucune conclusion de fluidité ni correction des saccades n'est revendiquée.
+87
View File
@@ -0,0 +1,87 @@
# EXP-203 — fractures, lisières et taïga géante
Branche `codex/north-fracture-beta203`, depuis beta.202. Retour R022.
Le rendu 202 est globalement validé. Passe ciblée demandée : ajouter de fortes
variations locales de volume et des trous par bruit 3D, retirer la neige ajoutée
après la végétation, brouiller les limites du podzol, donner aux grands épicéas
une hauteur pouvant atteindre 64 et varier les matières des sommets glacés.
Aucun nouveau village : la proposition supplémentaire est retirée par le joueur.
Les cinq igloos existants restent présents.
Nouveaux profils 203 seulement, Nord 1024. Aucun monde existant régénéré ni
migré ; profils 200–202 inchangés. Bassins et basses plaines d'accueil protégés
de la déformation. Pics de glace vanilla conservés. Arbres avec moteur natif,
configuration propre au biome géant ; aucune modification globale des épicéas.
Pas de simulation d'érosion ou d'hydrologie supplémentaire, caches bornés.
## Réalisation
Le champ 203 combine des déplacements verticaux locaux (deux échelles de bruit)
et un bruit volumique négatif de plus grande portée. Les nouvelles poches
peuvent traverser le toit ou le dessous, contrairement aux caves fermées 202.
Le bruit est atténué sur les basses plaines, les bords et tout autour des lacs.
Les seuils de sommet restent lisibles ; le détail 3D module les roches et glaces.
La passe de neige après décoration n'est pas appelée pour 203. Les prairies
froides gardent leur herbe ; la neige demeure comme matériau de sommet,
substrat nécessaire aux pics vanilla et matériau des igloos existants.
Le champ de podzol comporte deux fréquences de détail aux lisières. Le
`alter_ground` circulaire des nouveaux grands arbres est supprimé.
Les arbres réemploient `giant_trunk_placer` et `mega_pine_foliage_placer` de
Minecraft 26.3. Configuration locale : hauteur 32 + aléa 0–16 + aléa 0–16,
soit 32 à 64 blocs, couronne de 18–24 ; mélange de géants et de petits épicéas.
Les features `ice_spike` / `ice_patch` restent natives et inchangées.
## Contrôles
`north203Smoke` passe sur neuf couples graine/taille (0, 42,
4736390610738281858 ; Small/Medium/Large). Les déplacements locaux sondés vont
jusqu'à environ −62 / +56 blocs selon le cas. Ouvertures de surface présentes,
densité bornée, lacs et leurs coques inchangés, lisières de podzol irrégulières.
Recherche des cinq igloos vérifiée également sur les graines 1 à 32.
Les ressources worldgen historiques sont inchangées, hors sélection du nouveau
preset public et ajout du biome 203 au tag des ours polaires. Les noise settings
de l'île de départ restent identiques à 202. Aucun test visuel Windows ni
nouvelle campagne de captures : le rendu est à juger dans le labo.
Essai natif final : `build/worldgen-lab/north203-native-b`, Small/graine 42.
Vraie offrande, interruption à 0/58 chunks, reprise et relais prêts. Cinq igloos
et un laboratoire conservés. Recherche de cave orientée vers un intérieur
suffisamment épais, plutôt que le sommet le plus haut désormais fracturé.
Dans les échantillons : 4218 blocs de troncs, 10450 de feuilles, 21 empreintes
de troncs larges ; tronc le plus haut mesuré 59 blocs (plafond configuré 64).
Sommet : 309 blocs de glace bleue, 63 de glace ordinaire, 2154 de glace compacte,
1219 de neige pleine et 1694 de roche. Pics vanilla : maximum sondé 13 blocs
au-dessus du terrain. Prairie : zéro couche de neige parmi 339 colonnes.
Lac : 8995 blocs de glace et 41795 d'eau, sans fuite ; caverne sondée : 226 blocs
glacés et 1878 d'air. Bordure : 339 blocs rocheux, aucune glace compacte/bleue.
Il s'agit de contrôles locaux, pas d'un comptage de toute l'expansion.
## Livraison
`./gradlew check build assemblePack assembleTestPack
-PsanctuaryFocusedTests=operator,realtime -PsanctuaryAtlasOnly=true` : succès,
144 tâches dont 120 exécutées, sept GameTests ciblés et les smokes du projet.
La suite native historique complète n'a pas été relancée (limite beta.200).
Logs : `build/north203-check-build.log` et `build/north203-native-final.log`.
MRpack vérifié : `build/Sanctuary-Test-beta.203.mrpack`, copie identique dans
Downloads avec le guide. Archives, versions, dépendances, empreintes Fabric API,
JAR et 2265 classes compilées vérifiés ; reçu `build/north203-artifact.json`.
12433661 octets ; SHA-256 :
`ba160b4134c0b9dbe402e1834bdbec4f5561bfd6bd9dc147590dd76ee30dbf4f`.
Visite neuve `Sanctuary-North-203-42`, issue de l'essai natif final. Créatif,
commandes activées, Vulkan, vue 32, simulation 4 ; départ au-dessus de la taïga.
Village `(-212,191,-1292)`, forêt `(144,215,-976)`, pics `(-208,278,-944)`,
premier lac `(-247,258,-765)`. Aucun canal public ni instance Prism modifié.
Pas de sauvegarde embarquée dans le pack ni de revendication sur les saccades.
Essai natif Small/42 seulement ; Medium/Large sont contrôlés numériquement.
Ouverture confirmée à 13:12:20 par `NORTH203_VISIT_OPEN`, départ
`(196,292,-931)`, backend Vulkan confirmé dans `build/north203-visit-client.log`.
+90
View File
@@ -0,0 +1,90 @@
# EXP-201 — Nord compact, glacier et bosquets
Branche `codex/north-glacier-beta201`, depuis beta.200 ; retours joueur R019–R020.
## Résultat demandé
Le diamètre du Nord passe de 2048 à 1024 blocs. Remplacer la nappe blanche perçue
par un paysage de glace lisible : pics de glace, glacier, deux lacs gelés visibles,
bosquets localisés et contraste roche/glace/neige. Conserver les cinq igloos et
le laboratoire avec villageois zombie. Les lacs doivent contenir de l'eau sous
leur glace ; la végétation doit être réellement générée, pas seulement déclarée.
Les pics utilisent explicitement `minecraft:ice_spike` et `minecraft:ice_patch`
de Minecraft 26.3, avec leurs placements natifs. Aucune forme de pic recodée.
Le Nord ne doit pas devenir le catalogue de tous les biomes froids. Son identité
est le glacier habité ; les grandes forêts boréales, côtes froides et autres
paysages restent des pistes pour NO/NE. Leur répartition n'est pas décidée ici.
## Contrat de génération
Nouveaux profils 201 et journal `sanctuary-lab-expansions201`, destinés uniquement
aux nouveaux mondes. Les profils et le champ nordique 200 restent disponibles
pour les sauvegardes historiques ; aucune conversion des expansions 2048 déjà
créées, ni régénération de chunks. Île de départ Small/Medium/Large inchangée.
Placement dans l'éventail existant de ±15°. Préparation bornée de l'arrivée et du
relais, puis exploration. Les sept autres directions restent provisoires.
## Vérifications attendues
Contrôle de taille et de direction, stabilité de la génération 200, proportions
et visibilité des surfaces glaciaires, lacs fermés, relief vertical, bosquets et
village. Vérification native de vrais troncs/feuilles, glace de surface non
recouverte et pics élevés. Graines numériques 0, 42 et 4736390610738281858 ; visite
native neuve Small/42. Pas de campagne de screenshots ni d'hydrologie globale.
Les contrôles techniques ne remplacent pas le retour esthétique du joueur.
## Intégration
La 200 posait une couche de neige pendant le traitement du sol, avant les
features végétales ; les arbres natifs de taïga passent par les saplings
`spruce_checked` / `pine_checked`, qui exigent un support viable. La 201 laisse
le sol des bosquets en herbe jusqu'à leur plantation. Les lacs et le glacier ne
reçoivent plus la feature `freeze_top_layer`, afin que leur glace reste visible.
Les zones de pics conservent au contraire le bloc de neige requis par la feature
native `minecraft:spike`. Ses références et sa fréquence restent vanilla.
Le substrat compact associe une crête, des épaules glaciaires, deux champs de
pics natifs, deux bosquets et deux bassins fermés. Caches numériques bornés ;
aucune simulation hydrologique globale. Le village est rapproché du premier lac.
Le relief, les matériaux et le choix de biomes de la 200 gardent leur branche
de génération historique.
## Résultats — 4 octobre 2026
- `north201Smoke` : neuf couples graine/taille, Nord 1024, relief, parts de glace,
bosquets, lacs et champs de pics, confinement des bassins, déterminisme et
routage historique 200. `north200Smoke` reste passant. Les 155 ressources
historiques worldgen du module Test restent identiques, hors preset public.
- Serveur neuf Small/42, `build/worldgen-lab/north201-native-a` : vraie offrande,
interruption à 0/58 chunks, reprise et accès préparés. Cinq igloos, une cave
avec trappe/échelle/coffre/potion ; relais de continuation orienté Nord.
- Échantillon du bosquet : 395 blocs de troncs et 3294 blocs de feuilles
d'épicéa, répartis sur 56 colonnes de troncs (pas un comptage d'arbres entiers).
Champ de pics natifs : 1838 blocs de glace compacte au-dessus du terrain,
maximum sondé de 11 blocs. Lac : 8995 blocs de couverture gelée et 41795 blocs
d'eau ; 7602 surfaces de glace visibles contrôlées, zéro couche de neige dessus.
Caverne : 561 blocs de glace et 2585 blocs d'air sondés. Totaux locaux uniquement.
- `./gradlew check build assemblePack assembleTestPack
-PsanctuaryFocusedTests=operator,realtime -PsanctuaryAtlasOnly=true` : succès,
sept GameTests ciblés et tous les smokes associés, 142 tâches dont 118 exécutées.
La suite native historique complète n'a pas été relancée (limite beta.200).
Logs : `build/north201-check-build.log` et `build/north201-native-a.log`.
Livraison locale : `build/Sanctuary-Test-beta.201.mrpack`, copie dans Downloads
avec le guide. 12 386 623 octets ; SHA-256 :
`37925f012e4096c253f25e573305237e71114448d866a57f3fbad6e3da6b3d89`.
ZIP, dépendances 26.3 / Loader 0.19.5, empreintes Fabric API, JAR et 2252 classes
compilées vérifiés. Les trois tailles sont incluses, aucune sauvegarde embarquée.
Reçu : `build/north201-artifact.json`. Aucun canal public ni Prism modifié.
Visite préparée : `Sanctuary-North-201-42`, nouveau monde issu de l'essai natif.
Départ près du lac en `(-142,323,-705)`, créatif, commandes, Vulkan, vue 32 et
simulation 4. Ouverture confirmée par `NORTH201_VISIT_OPEN` dans
`build/north201-visit-client.log`. Village `(-128,205,-637)`, lac `(-247,258,-765)`, bosquet
`(-324,258,-780)`, champ de pics vers `(-273,-1059)`, relais `(-128,202,-1240)`.
Limites : visite native Small/42 seulement, pas d'essai Windows ou Medium/Large
natif sur cette version. Pas de revendication sur les saccades. Les paysages
NO/NE ne sont pas implémentés ; les anciens mondes ne changent pas de taille.
+66
View File
@@ -0,0 +1,66 @@
# beta.162 — scène du tableau et conversation
Ticket issu du retour de séance du 24 septembre : conserver Retour/Actualiser en
haut, montrer l’auteur avec son skin orienté vers la souris et sa demande dans
une bulle colorée, puis les réponses à droite (dessous sur GUI étroit).
Modification client uniquement, sans migration, écriture dans les mondes ni
changement des contrats fichier/MariaDB. Réponses, modération et suivi gardent
leurs validations serveur. Le portrait est un rendu GUI, pas une entité.
## Présentation
- Retour et Actualiser restent hors de la zone défilante.
- Le portrait utilise le skin de la connexion de l’auteur s’il est présent,
sinon le cache de profils Minecraft asynchrone. Le skin natif de repli apparaît
pendant le chargement ou si le profil ne peut pas être résolu (notamment les
comptes fictifs/hors ligne). Aucun téléchargement synchrone dans le rendu.
- Une bulle persistante encadrée montre catégorie/état, titre puis texte complet.
Information : jaune clair sur fond sombre jaune ; les autres catégories gardent
leurs couleurs. Les cartes de liste et du menu pause suivent aussi ce jaune.
- Le portrait tourne doucement dans des angles bornés selon la position de la
souris, sans capturer ses mouvements et sans clic requis.
- Demande et discussion défilent séparément si l’espace le permet ; le champ de
réponse et son bouton restent en bas de la discussion. Sur GUI étroit/bas,
un défilement unique garde les réponses sous la scène.
- Matériaux, lieu, récompense, suivi, visages des réponses et modération sont
conservés. Les détails d’une demande vide ne sont plus affichés artificiellement.
- Aucune animation de parole ni synchronisation vocale : « parle » désigne ici
la mise en scène visuelle avec la bulle.
## Vérification
- Compilation Java 25 / Minecraft 26.3 réussie.
- Parcours natif Vulkan : `NOTICE162_SCENE_PASS`, puis
`SEARCH159_CLIENT_PASS mode=file`, build réussi en 1 min 59 s.
- FR/EN, GUI 2/3/4 : scène, passage en colonne unique, boutons de retour au-dessus,
réponse au-dessus du footer en deux colonnes, angles du portrait bornés et
changement d’orientation avec la souris.
- Brouillon conservé pendant les redimensionnements ; publication d’une réponse
et lecture de son contenu après rechargement serveur vérifiées.
- Régressions communautaires : photos, recherche, suivi/désabonnement, marqueur
de carte et infobulle, restrictions de l’Intendance toujours validés.
- Captures relues : `build/qa-beta162/0042_notice162-scene-fr_fr-2.png` et
`0043_notice162-scene-fr_fr-3.png`. Conversation : `0048_notice162-conversation.png`.
- `./gradlew check build assemblePack` lancé : 252 GameTests terminés, 229 réussis
et 23 échecs, exactement les mêmes qu’en beta.161 (aucun ajouté/retiré).
Liste comparée : `build/session162-server-failures.json`.
- Contrôles restants et assemblage avec exclusion ponctuelle de `runGameTest` :
réussis en 3 min 16 s (`check build assemblePack -x :sanctuary:runGameTest`).
Aucune assertion désactivée dans les sources. Log : `build/beta162-assembly.log`.
Aucune modification de persistance : cette passe native utilise le mode fichier ;
le laboratoire MariaDB partagé a été relancé avec la même interface, KokaLab
connecté sur le monde existant `duo-flat-160`, Alice/Bob recréés, shader désactivé. Les figurants
et profils hors ligne peuvent afficher un skin Minecraft de repli.
## Livraison locale
JAR : `mods/sanctuary/build/libs/sanctuary-beta.162.jar`.
Pack assemblé : `build/packwiz`.
SHA-256 : `35c5d82eba110ee2150f73baf6341efc90f87971965a3b4dd2bcd04add0a59ff`.
Branche `codex/notice-scene-beta162`, tag local `beta.162`. Anciens JAR conservés.
Aucune publication distante ni synchronisation de Prism dans cette livraison.
Les 23 échecs préexistants restent ouverts ; les essais graphiques ne démontrent
pas la résolution réseau d’un skin officiel absent dans ce laboratoire hors ligne.
+68
View File
@@ -0,0 +1,68 @@
# ORIGIN-182 — galerie inférieure et rosace
Suite du retour en jeu sur beta.181, le 30 septembre 2026. Branche
`codex/origin-gallery-beta182`, dans le checkout de développement réutilisé
`rocky-ecology-beta178`. La palette naturelle de l’origine est validée par le
créateur. Les élévations gênent les huit directions ; elles doivent être
placées entre les axes. Essayer une cloche vitrée en rosace et développer
la scène souterraine. L’origine reste en l’air.
La mention « le truc dans les airs » est interprétée ici comme la rosace,
en attendant une éventuelle précision ; l’ISS reste un chantier séparé.
## Contrat du nouveau monde
Nouveau preset facultatif `sanctuary_test:gallery_v1`, profil `gallery`,
graine 42. Les profils précédents et la génération normale restent disponibles
avec leur génération propre. Aucun ancien monde, chunk ou format de sauvegarde
n’est modifié. Aucune expansion ni dimension n’est activée par ce prototype.
La galerie sélectionne une cavité centrale sous X=0/Z=0 par un échantillonnage
borné du champ de densité existant. Deux chambres voisines forment une boucle
avec elle ; les passages suivent des failles inclinées, les sols ondulent et
les plafonds sont voûtés. Roche dominante, calcite par veines, mousse ponctuelle,
quelques reliefs de tuf. Pas de murs rectangulaires plaqués sur toute la caverne.
Un socle d’obsidienne central et deux montants sombres interrompus donnent un
premier emplacement au portail inférieur. C’est une scène architecturale :
ni portail actif, ni recette d’allumage ou téléportation ne sont implémentés.
L’origine conserve la palette, le sol à huit directions et le bloc en
(0,320,0), lumineux et monochrome. Une cloche de verre clair et blanc avec
huit nervures de cuivre patiné remplace les trois élévations. Les supports
sont entre les axes ; huit ouvertures basses conservent vues et passages.
L’ISS, les huit salles d’ancres et les trois secteurs de gemmes ne sont pas
ajoutés dans ce lot.
Les deux lieux sont posés par chunk à la génération, sans ticker, sans
recherche globale d’eau et sans journal de progression. Le plan souterrain
est calculé une fois pour la source de biomes puis réutilisé. Une sauvegarde
recharge les blocs, elle ne reconstruit pas les lieux.
## Vérifications
Compilation et export du lanceur réussis. Monde neuf `solo182a/gallery/42` :
contrôles de la beta.180 conservés (567 échantillons de densité, trois cerisiers,
bassins de 1 232 et 1 221 blocs, cinq spawners et quatre caches avec les gemmes).
Zéro région d’hydrologie calculée.
La galerie contient 1 558 colonnes sculptées ; 1 514 surfaces praticables
appartiennent au même réseau, connecté par marches d’un bloc maximum.
Les trois centres de salle sont en (0,175,0), (-34,169,-26), (-38,146,24).
Le socle porte l’obsidienne en (0,177,0). La rosace compte 1 841 blocs avec
le fragment rocheux : huit axes libres à hauteur du joueur et noyau lumineux
sans relais activé. Rapport local `build/gallery182-quick-result.json`.
Arrêt du serveur de préparation et sauvegarde terminés proprement.
Solo neuf préparé sous `visite182/gallery/42`, `Sanctuary-Gallery-182-Solo`,
vue 32, créatif et commandes, première vue sur la galerie.
Visites : galerie `/tp 0.5 177 7.5 180 0` ; rosace
`/tp 0.5 319 5.5 180 0`. Rendu et proportions à juger en jeu sur la graine 42 ;
aucune autre graine ni capture du rendu n’est revendiquée.
Suite générale `check build assemblePack assembleTestPack` réussie en 8 min 1 s,
265/265 GameTests ; journal `build/gallery182-check-build.log`.
Entrée dans le solo confirmée depuis par le créateur. Le journal
`build/gallery182-solo.log` enregistre KokaLab connecté le 30 septembre à
02:25:12, vue 32 chunks. Son retour valide la palette, la rosace et les étangs ;
la correction du portail et les lieux manquants sont suivis dans
[DISCOVERY-183](island-discovery-beta183.md).
+96
View File
@@ -0,0 +1,96 @@
# ORIGIN-181 — repère central dans le ciel
Retour du 30 septembre 2026 sur beta.180. Branche `codex/origin-landmark-beta181`.
Le créateur apprécie le relief global et le donjon minier. Le prochain résultat
à visiter est une petite construction ouverte qui signale le centre du monde,
avec le bloc originel exactement en **X=0, Y=320, Z=0**. Cette décision remplace
les positions antérieures envisagées pour ce bloc ; elle ne rétablit pas le palais.
## Premier résultat
Nouveau preset facultatif `sanctuary_test:origin_v1`, profil `origin`, graine 42.
Terrain, écologie, bassins, cerisiers et donjon de beta.180 réutilisés. Un fragment
rocheux porte une rose des vents à huit directions, en tuf, calcite et deepslate,
avec cuivre patiné, quelques mousses et un croissant brisé. Le sol principal est
à Y=318, le piédestal à Y=319 et le Bug Rock à Y=320. La silhouette culmine à 328.
Circulation ouverte, pas de toiture ni de conteneur. Environ 19 blocs de large.
Le bloc réutilise `sanctuary:origin_block` et sa texture existante : allumé en
noir et blanc, lumière 15, masque des huit relais à zéro. Aucune ancre n’est
activée. Ce premier lieu est un repère visuel ; pas encore d’interaction, de
quête ni de trajet d’accès en survie. Le créatif et les commandes du labo
permettent de juger le volume avant de définir le parcours.
La pose se fait une seule fois par chunk, pendant la décoration native, sur
quatre chunks autour de l’origine. Aucune recherche régionale, aucun calcul
par tick, aucune entité ni hydrologie supplémentaire. La patine et la base
rocheuse varient avec la graine ; la silhouette reste celle de ce premier essai.
## Sauvegardes et validation
Nouveau monde uniquement. Le preset beta.180 et la génération normale ne
reçoivent pas ce lieu. Aucun ancien chunk ni format de sauvegarde n’est migré.
La reprise recharge les blocs sauvegardés sans reconstruction. Pas de publication
du canal, de mise à jour Prism ni d’installation serveur personnel.
Le contrôle natif vérifie l’origine exacte, la présence du plan dans les quatre
chunks, la circulation, l’ouverture vers le ciel et l’état monochrome lumineux.
Les vérifications de beta.180 restent appliquées au nouveau profil.
Essai natif `solo181a`, graine 42, réussi : 1 224 blocs du repère présents,
quatre chunks, origine et passages conformes, 567 comparaisons de densité
conservées et zéro région d’hydrologie. Trois cerisiers, surfaces de bassins
de 1 232 et 1 221 blocs, cinq spawners et quatre caches avec les quatre gemmes
validés. La lecture des chunks enregistrés confirme le bloc à (0,320,0),
`lit=true`, `relays=0`. Rapport : `build/origin181-quick-result.json`.
Compilation et export du lanceur réussis. Le solo neuf
`visite181/origin/42`, nommé `Sanctuary-Origin-181-Solo`, est ouvert le
30 septembre à 01:59 : Vulkan Apple M1, KokaLab connecté en (18.5,333,23.5),
vue 32 chunks. Approche à pied pour la visite : `/tp 0.5 319 5.5 180 0`.
L’appréciation esthétique reste à faire en jeu ; aucune capture du rendu
n’est revendiquée. Seule la graine 42 est validée dans cette itération.
Suite générale `check build assemblePack assembleTestPack` réussie en
12 min 53 s, journal `build/origin181-check-build.log` dans le checkout
`storyquest-beta173`. Contrôles et assemblages terminés avant la suite beta.182.
Retour en jeu du créateur : palette et caractère naturel validés. Les trois
élévations empiètent sur les directions ; prochaine variante avec supports
entre les huit axes et piste d’une cloche vitrée en rosace. Garder l’origine
en l’air pendant l’essai. Développer d’abord la galerie souterraine, puis
le lieu aérien ; précision sur ce dernier demandée pendant la séance.
## Suite de la séance : décisions et pistes, pas encore implémentées
- **Gemmes :** trois secteurs souterrains distincts, émeraude, rubis et saphir,
avec veines exposées sur les surfaces rocheuses. Un stock limité comparable
aux diamants à l’échelle de l’île, pas par échantillon de chunks. Le chiffre
exact par gemme reste à régler. Proposition : compter séparément le stock
généré de chaque gemme ; densité locale lisible, rareté globale préservée.
- **Soufre :** intégrer une poche de sulfur caves sur l’île principale. Le
profil beta.180 ne contient pas ce biome, même si le socle historique connaît
le soufre. Choisir une poche dédiée plutôt que repeindre les cavités humides.
- **Huit ancres :** salles naturelles sculptées dans leur matière locale,
creux, reliefs irréguliers, bas-reliefs, végétation et piédestal portant le
bloc existant. Chaque lieu a sa géographie ; pas de ponts imposés vers le centre.
- **Galerie inférieure :** caverne centrale sous X=0/Z=0, salles reliées par
des failles, dénivelés et emplacement de portail en obsidienne. Altitude,
emprise et lien exact avec les dimensions à préciser. Le palais abandonné
n’est pas réintroduit. Proposition technique : sonder un nombre borné de
points du champ de densité existant, sélectionner une cavité et ne retoucher
que ses passages ; calcul à la génération, aucun raycast permanent.
- **Activation :** envie de remplacer le briquet par un objet fabriqué au
métabli, reliant préparation et découverte. Recette, composants et effet
encore ouverts ; ne pas modifier le portail vanilla avant ce contrat.
- **Expansions :** privilégier l’expérience de surface, avec des masses tantôt
horizontales, tantôt verticales. La profondeur dépendra de chaque île ; ne
pas généraliser automatiquement toutes les cavités de Sanctuary à chaque île.
- **ISS :** chantier architectural séparé dans la bande Y=512–640. Des
schematics pourront servir à analyser échelle, modules, circulation et
palette ; construire ensuite un plan original. Aucun fichier ISS reçu ici.
Fonction et quête restent à définir.
Ordre proposé pour les essais suivants : origine visible → une salle d’ancre
dans une cavité existante → trois secteurs de gemmes et une poche de soufre →
déclinaison des autres ancres et galerie inférieure. Chaque essai garde son
monde témoin et se juge en solo ; pas de grande étude scientifique imposée.
+8
View File
@@ -1,5 +1,13 @@
# Distribution packwiz et Prism
## Rattrapage des releases du 24 septembre 2026
Les binaires conservés des beta.145–151 et beta.154–166 sont publiés dans Gitea.
La beta.144 existante reste intacte ; les 152–153 demeurent des chantiers non livrés.
[Inventaire, empreintes et limites](publications-beta144-166.md).
Cette publication ne modifie ni le canal packwiz ni les installations Prism.
Sanctuary Beta utilise une seule instance Prism, synchronisée avant chaque
lancement par packwiz. Le canal reste à l'adresse :
+258
View File
@@ -0,0 +1,258 @@
# Palais originel, verticalité et International Sanctuary Station
> **Statut au 29 septembre 2026 : concept historique.** Le créateur abandonne
> le palais souterrain, ses accès et les raccordements au palais. Le
> [fil rouge courant](storyquest-fil-rouge.md) remplace ces choix d'implantation.
> Le texte ci-dessous conserve la discussion du 28 septembre et ne constitue
> plus une consigne de construction du palais. Les pistes ISS, chenil et
> ordinateurs restent ouvertes indépendamment de ce bâtiment. Aucun code ni
> monde de laboratoire n'est supprimé par ce changement documentaire.
**Discussion du 28 septembre 2026 — conception, sans code.** Précisions du
créateur après le [cadrage Storyquest](storyquest-beta173.md), sur la même
branche `codex/storyquest-beta173`, issue de beta.172. Les formes et intentions
retenues sont distinguées des mécaniques encore hésitantes. Aucun monde,
paramètre de génération, familier ou format de sauvegarde n'est modifié ici.
**Premier lot décidé ensuite :** un palais par seed, huit ancres extérieures
et huit demandes de matériaux distincts. Le bloc originel garde l'illustration
fournie ; chacune des huit gemmes se colore indépendamment. Le
[contrat de prototype](palais-prototype-beta173.md) décrit ce qui est réellement
implémenté dans le laboratoire et les limites de l'intégration au monde.
## Le secret devient un lieu commun à habiter
Le secret sous le spawn prend la forme d'un **palais sobre, haut sous plafond,
à huit coins**. Après discussion du niveau zéro et du vortex, le créateur
précise : **adapter la hauteur au terrain**. Le palais garde son centre en
**`x=0, z=0`**, avec une **altitude déterminée par le relief**. La salle n'est
donc plus contrainte à Y=0.
**Le bloc originel suit le palais**, confirmation explicite du créateur. Sa
position devient `(x=0, y=altitude adaptée, z=0)` : l'origine horizontale reste
fixe, l'altitude accompagne le lieu. L'idée initiale d'un bloc exactement en
`(0, 0, 0)` est remplacée ; aucun bloc séparé à Y=0 ni liaison vers ce niveau
n'est requis. Sa position précise dans le volume de la salle reste à dessiner.
Ce palais représente une **cartographie intérieure de Sanctuary Island**,
puis l'expédition et l'aventure de ses habitants. Le complexe est léger, assez
vide pour que les joueurs le personnalisent : une page blanche avec une
architecture reconnaissable. Sa richesse vient progressivement de leur partie.
La palette, les dimensions, les proportions et le mobilier initial restent à
dessiner. « Palais » n'implique ni dorures ni décor luxueux.
Le lieu est souterrain par son implantation et ses accès. Si le terrain à
l'origine est trop mince ou absent, **le palais ressort sous l'île et reste
visible au-dessus du vide**. Le créateur souhaite un endroit lumineux,
repérable depuis l'extérieur, notamment la nuit. Le vortex de nuages doit
participer à la vue en dessous.
La fonction recherchée est une **base d'opérations pour les expéditions** :
comprendre les destinations, voir les matériaux nécessaires, préparer le départ
et conserver la mémoire de ce qui a été accompli. Le rôle exact des machines
et la mécanique d'ouverture des territoires ne sont pas encore décidés.
### Proposition spatiale à discuter
Faire correspondre les huit secteurs de la salle aux huit directions autour
de l'île, avec une orientation lisible depuis le bloc originel. Chaque secteur
pourrait recevoir un indice découvert, l'état d'une liaison, les besoins de sa
prochaine expédition et les souvenirs rapportés. Les joueurs complètent
eux-mêmes l'aménagement avec cartes, cadres, bannières, trophées et constructions.
Le palais devient une carte que l'on parcourt à pied.
Les huit coins de l'île désignent ici des secteurs à définir sur son contour ;
ils n'imposent pas une île géométriquement octogonale. Le rapprochement entre
coins du palais, lieux périphériques et liaisons est une proposition cohérente
avec la demande, pas encore un plan d'implantation validé.
Il faut encore décider ce que les joueurs peuvent déplacer ou casser, et
comment ils réparent une installation devenue inutilisable. La personnalisation
ne vaut pas décision de protéger tout le palais. L'ancien contrat de conception
permet aussi de reconstruire une installation d'expansion ailleurs : préciser
ce qui relève du bloc originel unique et ce qui reste reproductible.
## Les accès font jouer la profondeur
Le créateur envisage plusieurs accès complémentaires, sans choisir encore
leur nombre ni leur disposition :
| Accès évoqué | Expérience recherchée | Point à dessiner ou vérifier |
| --- | --- | --- |
| Ascenseur aquatique | Monter et descendre rapidement, avec eau et bulles | Les algues et la « terre des abîmes » sont les termes de la discussion ; le bloc de propulsion et le montage exact restent à préciser |
| Puits de chute avec réception dans l'eau | Se laisser tomber vers le palais central | Continuité du bassin, visibilité de la réception, sortie et absence de fuite dans le vide |
| Escaliers | Accès progressif qui fait découvrir les volumes souterrains | Arrivée dans la salle, paliers, raccourcis et lien avec les galeries |
| Ouvertures verticales | Apercevoir en contrebas un sol d'eau et oser descendre | Lecture du trajet, réception réelle, retour et espace pour les compagnons |
Proposition : une première descente lente qui révèle le palais, puis un trajet
rapide pour les usages quotidiens. Le choix entre escalier, chute et ascenseur
peut exprimer cette différence sans obliger à construire les trois dès le début.
### Implantation retenue : une hauteur adaptée au terrain
Le preset actuel emploie `sanctuary:sanctuary_640`, avec **`min_y: 0`** et
**`height: 640`**. Le vortex a **`HEIGHT=0`**, avec une épaisseur de quatre blocs
dans son rendu. Ce sont des valeurs lues dans les sources beta.172, pas des
mesures de terrain d'une graine particulière :
- [Preset Sanctuary](../mods/sanctuary/src/main/resources/data/sanctuary/worldgen/world_preset/sanctuary.json).
- [Type de dimension](../mods/sanctuary/src/main/resources/data/sanctuary/dimension_type/sanctuary_640.json).
- [Hauteur du vortex](../mods/sanctuary/src/main/java/fr/koka/sanctuary/sky/VortexClouds.java).
- [Épaisseur et rendu](../mods/sanctuary/src/main/java/fr/koka/sanctuary/client/VortexCloudRenderer.java).
Ces contraintes ont conduit le créateur à choisir une hauteur adaptée au terrain.
La proposition précédente d'imposer la salle juste au-dessus du bloc à Y=0
est remplacée par cette décision. **Le vortex peut conserver sa hauteur actuelle** ;
aucun déplacement des nuages ni changement de limite du monde n'est nécessaire
par principe. Leur séparation visuelle devra être vérifiée sur la coupe choisie.
Proposition de placement : examiner le relief sous le centre de l'île et sur
l'emprise du palais, puis choisir un niveau permettant la grande hauteur sous
plafond et les accès. La salle peut traverser l'enveloppe inférieure du terrain
et laisser une façade lumineuse visible dans le vide. Il ne faut ni l'enterrer
entièrement à tout prix, ni aplatir toute l'île pour l'accueillir.
Avant le code, fixer la marge au-dessus du vortex, la couverture rocheuse
souhaitée, le volume de la salle et la solution lorsque le relief ne fournit
pas assez d'épaisseur. Prévoir planchers, bassins et fondations dans les limites
constructibles. La même graine et la même version de génération doivent choisir
la même implantation. Ces règles détaillées restent à dessiner puis à tester
sur de nouveaux mondes ; aucune partie existante n'est repositionnée.
## Les huit objets et la Sanctuary Key restent ouverts
Le créateur envisage des **collectibles répartis dans les huit secteurs de
l'île**, permettant d'activer de nouvelles expéditions, la génération de
nouvelles îles et un nouveau point d'intérêt à découvrir. Il hésite sur le
fait de placer ces objets derrière des défis et sur la forme d'une clé appelée
provisoirement **Sanctuary Key**. Aucun identifiant d'objet n'est fixé.
| Possibilité | Ce qu'elle favorise | Limite à discuter |
| --- | --- | --- |
| Collectible trouvé par exploration | Curiosité, lecture du paysage et surprises | Le défi peut se résumer à trouver le bon endroit |
| Clé obtenue après une épreuve | Accomplissement clair et aventures différentes | Huit épreuves identiques donneraient une progression répétitive |
| Découverte puis préparation de l'expédition | Exploreurs, constructeurs et producteurs ont chacun un rôle | Il faut rendre les besoins compréhensibles et éviter les attentes sans activité |
**Proposition recommandée pour la discussion :** découvrir dans un secteur un
objet ou un signe lié à une liaison, puis préparer l'expédition au palais. Le
défi peut varier selon le lieu : combat, exploration, mécanisme, construction
ou coopération. Cette proposition ne décide ni d'une clé pour chaque direction,
ni d'un objet consommé, ni de l'obligation de réunir les huit avant de partir.
Avant le premier essai, choisir ce que l'objet autorise, qui peut l'apporter,
où les matériaux sont réellement déposés et qui lance le départ. Prévoir le
cas d'un objet perdu et celui d'un nouveau joueur arrivé après l'ouverture.
La preuve durable d'ouverture appartient au serveur ; le trophée exposé au
palais peut raconter cette ouverture sans être la seule preuve qui la conserve.
Les huit collectibles ne remplacent pas automatiquement les **sept boules de
cristal** de la vision historique. Leur relation éventuelle est à discuter.
## La montée vers l'ISS
Le créateur souhaite développer la verticalité au-dessus de l'île par des
structures en hauteur, jusqu'à l'**ISS — International Sanctuary Station**.
Cette station métallique, inspirée visuellement de l'ISS, constitue une trace
laissée par un ancien joueur. Son identité n'est pas encore choisie.
Elle comprendrait une **carte de l'île avec des cadres d'objets posés à
l'horizontale**. La taille, l'altitude, l'accès et la mise à jour de cette carte
restent à définir. Il faut lui donner une fonction particulière, reliée à un
système ou une quête. Trouver la station pour obtenir un familier particulier
est une piste du créateur, pas une récompense déjà arrêtée.
```mermaid
flowchart TB
ISS[ISS : trace d'un ancien joueur et fonction à définir]
HAUT[Structures en hauteur : itinéraire à concevoir]
ILE[Surface : spawn, habitants et huit secteurs]
ACCES[Descente : escaliers, eau ou puits]
PALAIS[Palais octogonal : préparer et raconter les expéditions]
ORIGINE[Bloc originel : suit le palais, x=0 et z=0]
ISS --- HAUT --- ILE --- ACCES --- PALAIS --- ORIGINE
```
Schéma d'intention sans échelle : aucun ordre de déblocage ni palier d'altitude
obligatoire n'est décidé. Le palais s'adapte au terrain ; le vortex est à Y=0.
Le nœud du bloc indique son lien avec le palais, pas un étage nécessairement séparé.
Proposition : faire du palais le lieu où l'on **prépare et engage** l'expédition,
et de l'ISS un lieu où l'on **observe et repère** des phénomènes célestes. Une
fonction de reconnaissance donnerait une raison d'y retourner après avoir
trouvé le familier. Elle doit être distinguée du rôle de l'observatoire déjà
envisagé sur l'île ; ne pas créer trois lieux qui donnent la même information.
L'ISS pourrait suggérer une destination ou un événement aérien sans révéler
tous les secrets. La carte pourrait être le relevé ancien du précédent joueur
ou un outil actualisé : ce choix change ce qu'elle raconte. Prévoir l'accès des
nouveaux arrivants et le retour au sol avant de choisir une récompense rare.
## Chenil et incubateurs de familiers
Piste du créateur : un **vrai bloc** reçoit le familier pour lui faire gagner
de l'XP. **Deux yeux apparaissent sur le bloc lorsqu'il est occupé.** Le dispositif
peut devenir un chenil ou un incubateur ; son nom et sa place dans le palais
restent ouverts. Des **multiblocs de tailles différentes** pourraient accueillir
des familiers miniatures ou colossaux. Les dimensions ne sont pas fixées.
Il faut distinguer cette idée de l'incubation du 25 septembre : celle-ci faisait
fructifier une réserve avec les déplacements, poses et casses du joueur. Le
nouvel incubateur est un lieu physique où l'on dépose le compagnon. Ils peuvent
se remplacer ou se compléter ; aucune double croissance automatique n'est décidée.
Le point de conception principal est de garder un intérêt à emmener son
familier. **Proposition à éprouver :** un entraînement limité au chenil, soutenu
éventuellement par de la nourriture appréciée, tandis que l'aventure développe
le lien et l'expérience autrement. Plafond, nourriture, vitesse et arrêt hors
ligne restent des propositions, sans règle chiffrée ni rendement acquis.
Avant le code, préciser : XP propre du familier ou réserve récupérable par le
joueur ; propriétaire et personnes autorisées à le retirer ; croissance selon
temps simulé ou réel ; comportement hors ligne et hors chunks chargés ; sort
du pensionnaire à la casse ou dissociation. Le même familier ne doit pas exister
simultanément dans le chenil et sur la tête. Les deux yeux signalent l'occupation,
mais l'identité du pensionnaire doit aussi pouvoir être reconnue.
Si la forme multibloc est retenue, la rapprocher du système d'assemblage
volontaire existant. Ne pas imposer automatiquement une cage colossale à tout
familier de grande apparence avant d'avoir choisi la règle de taille.
## Ordinateurs : commencer par la tâche à accomplir
Le créateur envisage **assembleur, terminal et écran**, en référence à un mod
d'ordinateurs non identifié avec certitude. Aucun choix de dépendance n'est
effectué. Le [cahier d'installation d'expansion](structures-conception.md#ordinateurs-et-installation-dexpansion)
évoque déjà assembleur, contrôleur, terminal, dépôt et ancre, mais ces rôles
restent conceptuels. Le terminal de stockage est encore une autre fonction.
Le premier besoin est concret : **choisir une expédition, voir les matériaux
nécessaires, constater ce qui est déposé et comprendre ce qui manque**. On peut
dessiner ce parcours dans une alcôve du palais avant de décider de l'ordinateur.
Proposition de progression : une installation locale compréhensible à la main,
puis un écran de suivi partagé, puis éventuellement automatisation et programmation.
Un dépôt physique possède les matériaux ; un écran les montre ; le système
serveur valide l'expédition. Le rôle de l'assembleur doit être choisi avant
d'ajouter un bloc dont on ne sait pas ce qu'il fabrique.
Cette approche conserve de la place pour les computers sans les rendre
obligatoires par défaut pour découvrir le palais. Si un mod externe est choisi,
son identité et sa compatibilité Minecraft 26.3 seront vérifiées dans le ticket
d'intégration ; aucun nom n'est déduit de la description seule.
## Prochain résultat de conception proposé
Dessiner **un plan et une coupe du palais**, avec huit secteurs, bloc originel,
altitude adaptée au terrain, hauteur de salle, partie extérieure, accès et nuages.
Placer seulement les
emplacements fonctionnels nécessaires pour essayer **une** expédition :
destination, éventuel objet de déblocage et besoins matériels. Garder les
autres espaces disponibles pour les joueurs et les évolutions suivantes.
L'ISS et le chenil peuvent alors avoir une fiche propre, liée à cette coupe
verticale. Ils ne deviennent pas des prérequis techniques à la première salle.
Les trois décisions suivantes sont : **moment de découverte du palais**,
**premier geste d'expédition** et **ce que l'ISS permet de faire après sa visite**.
Validation documentaire : références locales et diff contrôlés. Aucun build
ou essai de jeu relancé ; aucune fonctionnalité décrite ici n'est implémentée.
+150
View File
@@ -0,0 +1,150 @@
# beta.173 — palais procédural et bloc originel
> **Statut au 29 septembre 2026 : prototype technique conservé, architecture
> abandonnée pour la suite.** Le palais souterrain, ses accès et les
> raccordements au palais sortent de la conception. Le
> [fil rouge courant](storyquest-fil-rouge.md) prépare des lieux indépendants.
> Cette fiche décrit fidèlement le laboratoire livré et ses essais ; elle
> n'annonce plus l'intégration du palais à Sanctuary Island.
Ticket d'implémentation ouvert le 28 septembre 2026, branche
`codex/storyquest-beta173`, base beta.172. Première scène jouable issue du
[palais originel](palais-originel-iss-beta173.md). Livraison locale vérifiée.
## Contrat avant implémentation
Le palais est construit par un générateur versionné, avec une graine explicite.
Le volume octogonal, la hauteur, les baies, la toiture et le dessin du sol
peuvent varier. Une même graine/version reproduit le même plan. Il conserve
une salle haute et sobre, huit secteurs ouverts, un escalier et un puits d'eau.
La première scène est centrée en X=0, Z=0 et surélevée dans un monde plat neuf.
Chaque monde de laboratoire contient **un seul palais, déterminé par sa seed**.
Pour comparer une autre architecture, créer un autre monde neuf avec une autre seed.
L'intégration au terrain de Sanctuary Island était envisagée lors de ce lot ;
elle est abandonnée le 29 septembre. Ce lot n'appelle pas le générateur dans
les mondes de production. Il ne crée ni
expansion, ni quête, ni ISS, ni ordinateur, ni récompense.
Le nouveau bloc `sanctuary:origin_block` reprend les propriétés de la bedrock
et l'image fournie par le créateur, conservée sans retouche dans
`mods/sanctuary/src/main/art/origin-logo.png`. Son rendu est dérivé de manière
déterministe à la construction des ressources :
- éteint : image sombre et désaturée, aucune lumière émise ;
- allumé sans relais : image en niveaux de gris, centre blanc, lumière 15 ;
- allumé avec des relais : chaque relais rend sa couleur à la gemme associée ;
les autres restent grises et le centre reste blanc.
Huit bits indépendants représentent les huit relais, pas un simple compteur.
L'ordre des activations est libre. L'extinction masque les couleurs sans effacer
l'état des relais. Ces états sont des propriétés de bloc natives, sauvegardées
dans les nouveaux chunks ; aucun ancien format n'est converti.
Huit blocs `sanctuary:expansion_anchor` sont disposés à l'extérieur, dans les
huit directions, sur des plateformes reliées à pied au palais. Chacun accepte
un matériau distinct, décision du créateur du 28 septembre. Clic droit avec la
quantité en main : dépôt entier, consommé côté serveur, ancre allumée et bit
associé du bloc originel activé. Un dépôt insuffisant ou incorrect reste intact ;
une ancre déjà allumée ne consomme plus. Les demandes sont communes à la partie,
sans crédit individuel ni dépôt partiel dans ce premier lot.
| Direction / gemme | Demande provisoire |
| --- | --- |
| Nord / bleu | 32 pierres |
| Nord-est / vert clair | 16 bûches de chêne |
| Est / vert | 16 blés |
| Sud-est / orange | 4 lingots d'or |
| Sud / rouge | 16 poudres de redstone |
| Sud-ouest / rose-violet | 8 éclats d'améthyste |
| Ouest / jaune | 8 lingots de fer |
| Nord-ouest / menthe | 16 verres |
Le bloc central s'allume/s'éteint par clic droit dans ce laboratoire. Il faut
l'allumer avant les dépôts ; l'éteindre conserve les ancres et leurs bits.
Les coûts servent au prototype et ne constituent pas l'équilibrage final.
Ces ancres ne déclenchent encore aucune génération d'expansion.
## Monde et écritures autorisées
Le module facultatif Sanctuary Test, le dossier dédié `palace-flat-173` et le
commutateur `sanctuary.palaceLab=true` conditionnent le laboratoire. La préparation
refuse toute autre partie. Une emprise doit être entièrement libre avant la
première écriture. Un palais prêt est rechargé tel quel ; il n'existe pas de
commande de régénération. Les écritures sont réparties sur plusieurs ticks serveur.
Un journal indépendant `palace-lab-173.json`, schéma 1 / génération 1, enregistre
graine, altitude et phase du palais. Les états des blocs restent dans les chunks.
Une génération interrompue est conservée comme incomplète ; utiliser un nouveau
dossier de laboratoire. Pas de nettoyage ou de reprise destructive.
Une version inconnue ou un journal invalide bloque la préparation et préserve
le fichier. Le retrait de cette version après création de ces nouveaux blocs
n'est pas une conversion prise en charge ; conserver le monde de développement.
## Visiter et comparer
Depuis cette branche, avec Java 25 :
```sh
./gradlew :sanctuary-test:exportDuoLaunch
python3 scripts/palace_lab.py prepare --seed 173
python3 scripts/palace_lab.py server --seed 173
# Dans un second terminal :
python3 scripts/palace_lab.py client --seed 173
```
Le client utilise Vulkan. Le serveur écoute uniquement sur `127.0.0.1:25579`.
Créatif pour visiter et aménager ; `/palais materiaux` donne les huit demandes,
`/palais retour` ramène dans la salle. Les messages sont traduits FR/EN.
Une autre seed utilise un autre dossier sous `build/palace-173/` ; arrêter le
serveur précédent avant de lancer l'autre. Le lanceur préserve les fichiers
existants et refuse une modification du nom de monde, de la seed ou du réseau.
Le palais est à Y=96 dans ce laboratoire, au-dessus du terrain plat. Le plan
accepte une altitude arbitraire. Le placement sous Sanctuary Island à une
altitude calculée n'a pas été intégré et n'est plus prévu ; la géographie des
huit ancres est à reprendre indépendamment de ce palais.
## Vérifications
- Les quatre nouveaux GameTests passent (cinq tests requis avec le témoin du
banc). Onze graines incluent `0`, `173`, des voisins et les extrêmes 64 bits :
plan entier reproductible, variation d'architecture, huit accès praticables
et toutes les écritures contenues dans l'emprise. Une emprise occupée est
refusée sans écriture. Dépôts faux, insuffisants ou répétés conservés ; les
huit dépôts valides activent les bits attendus dans un ordre non séquentiel.
- Les 512 combinaisons d'allumage et de relais survivent au codec natif de
sauvegarde. Lumière 0/15 et conservation des bits à l'extinction vérifiées.
- Client Minecraft **26.3 / Vulkan / Apple M1**, scénario
`Palace173ClientChecks` réussi : extérieur, intérieur, éteint, monochrome,
trois gemmes et huit gemmes. Six captures examinées dans
`build/palace-validation/screenshots/`. Synchronisation serveur/client du
masque de gemmes vérifiée. Le premier lancement avait omis le profil Test ;
le scénario passe avec `-PsanctuaryQuickTests=true`.
- Image source strictement identique au PNG fourni (SHA-256
`33adb8f689d55b7bf8d0824b02942ea3823f128b27fab458713ab9582061a1a0`).
Compilation Python, JSON FR/EN et préparation répétée du laboratoire : les
cinq fichiers de configuration restent identiques.
Commandes ciblées :
```sh
./gradlew :sanctuary:runGameTest :sanctuary-test:compileJava -PsanctuaryFocusedTests=palace173 -PsanctuaryAtlasOnly=true
./gradlew :sanctuary:runClientGameTest -PsanctuaryClientTests=true -PsanctuaryPalace173ClientTests=true -PsanctuaryQuickTests=true -PsanctuaryClientGraphicsBackend=vulkan
```
- `./gradlew check build assemblePack assembleTestPack :sanctuary-test:exportDuoLaunch`
**réussi**, 265/265 GameTests, contrôles du pack et autres contrôles du dépôt
réussis (12 min 35 s). Profils packwiz normal et Test assemblés localement.
- JAR vérifié : beta.173, générateur présent, 512 états du bloc et 257 textures.
SHA-256 : `fa1f92f1a84b5f863e5f323ab0afe63e01acbd08c06181e5f636ab5b372886f9`.
- Serveur dédié réellement démarré sur loopback, journal `ready`, puis reprise
du même monde. Allumage avec masque `73` et un bloc d'aménagement témoin
conservés après arrêt/reprise (`PALACE173_GEMS_RELOAD_PASS` et
`PALACE173_CUSTOMIZATION_RELOAD_PASS`). Le témoin est ensuite retiré et le
bloc éteint pour la première visite ; serveur arrêté proprement. Le lanceur
accepte aussi le `sanctuary_test\:flat` réécrit automatiquement par Minecraft
dans les propriétés Java.
Les journaux restent ignorés dans `build/palace-validation/`. Les artefacts
restent locaux ; aucun canal public ni profil Prism n'a été mis à jour.
+25
View File
@@ -0,0 +1,25 @@
# beta.164 — calendrier compact et intendance alignée
Calendrier à la largeur de la navigation : jour agrandi et heure sur la première
ligne, mois et année ensemble dessous. Le titre serveur est aligné à gauche
et couvre la carte ainsi que les panneaux Gazette/Tableau.
Interface cliente uniquement. Aucun changement de stockage ou de monde.
Vérification client Vulkan réussie : FR/EN aux échelles GUI 2, 3 et 4.
Le test contrôle la largeur du calendrier, l’heure dans son libellé accessible,
les limites de l’intendance et les interactions communautaires existantes.
Captures conservées dans `build/qa-beta164/` et journal dans
`build/beta164-client-retry.log`. Inspection visuelle FR GUI 4 et EN GUI 2 effectuée.
Le premier test a détecté que StringWidget redimensionnait le calendrier lors
du changement de texte ; la largeur de navigation est maintenant réappliquée.
`check build assemblePack --continue` exécuté : les mêmes 23 GameTests qu’en
beta.163 échouent, aucun nouvel échec (comparaison enregistrée dans
`build/session164-server-failures.json`). La suite complète reste en échec.
L’assemblage séparé `build assemblePack :sanctuary-test:exportDuoLaunch
-x :sanctuary:check` réussit après ce contrôle.
Anciennes archives conservées ; aucune publication distante.
JAR : `mods/sanctuary/build/libs/sanctuary-beta.164.jar`.
SHA-256 : `5bdfb097004a7932a075fbce9013556266f76ddd491ca21a24e12609c8600e53`.
+27
View File
@@ -0,0 +1,27 @@
# beta.163 — cadre du menu pause
Retour de séance : Découverte/Progression séparés de Habitant/Factions/Combat,
bouton Monde retiré, séparateurs natifs de Progression, reprise centrée dans le
footer, calendrier à gauche, message serveur au centre et horloge à droite.
Interface cliente uniquement, sans migration ni modification de règles ou de
mondes. « Options du monde » reste dans les réglages en bas à gauche ; seul le
raccourci de navigation Monde disparaît. La carte centrale reste disponible.
Vérifications :
- Test client natif Vulkan réussi : FR/EN aux échelles GUI 2, 3 et 4,
calendrier, séparation des groupes, limites des widgets et reprise centrée.
Captures dans `build/qa-beta163/` ; journal `build/beta163-client-retry.log`.
- Le scénario communautaire vérifie aussi la scène du tableau et les échanges.
Une première exécution a expiré pendant une publication ; le test respecte
désormais le délai serveur entre deux écritures.
- `check build assemblePack --continue` : 23 échecs GameTest, ensemble identique
à beta.162, aucun nouvel échec. Les autres tâches ont terminé ; la suite
complète reste donc en échec. Comparaison dans `build/session163-server-failures.json`.
- Assemblage réussi avec `build assemblePack :sanctuary-test:exportDuoLaunch
-x :sanctuary:check`, après la vérification complète.
JAR local : `mods/sanctuary/build/libs/sanctuary-beta.163.jar`.
SHA-256 : `72df10885d1bcb3b623c46bddf361839c23f06dfd543119f91ac5e006a8f961e`.
Aucune publication distante ; anciens JAR conservés.
+44
View File
@@ -0,0 +1,44 @@
# Publications beta.144 à beta.166
Rattrapage des releases Gitea du 24 septembre 2026. La beta.144 était déjà publiée ; ses tags et fichiers restent inchangés. Les archives suivantes sont publiées avec leurs binaires conservés, sans reconstruire ni renuméroter les anciens mods.
| Version | Commit source | Fichiers (hors SHA-256) |
| --- | --- | --- |
| [beta.145](https://git.botsu.net/koka/sanctuary-beta/releases/tag/beta.145) | `ed2d08827260` | sanctuary-beta.145.jar, Sanctuary-Test-beta.145.mrpack, Sanctuary-beta.145.mrpack, Sanctuary-Template-beta.145.zip |
| [beta.146](https://git.botsu.net/koka/sanctuary-beta/releases/tag/beta.146) | `cf9a670f4415` | sanctuary-beta.146.jar, Sanctuary-beta.146.mrpack, Sanctuary-Template-beta.146.zip |
| [beta.147](https://git.botsu.net/koka/sanctuary-beta/releases/tag/beta.147) | `96a36d99bea0` | sanctuary-beta.147.jar, Sanctuary-beta.147.mrpack, Sanctuary-Template-beta.147.zip |
| [beta.148](https://git.botsu.net/koka/sanctuary-beta/releases/tag/beta.148) | `6f871446bbbf` | sanctuary-beta.148.jar, Sanctuary-beta.148.mrpack, Sanctuary-Template-beta.148.zip |
| [beta.149](https://git.botsu.net/koka/sanctuary-beta/releases/tag/beta.149) | `1c23d0c17ec9` | sanctuary-beta.149.jar, Sanctuary-beta.149.mrpack, Sanctuary-Template-beta.149.zip |
| [beta.150](https://git.botsu.net/koka/sanctuary-beta/releases/tag/beta.150) | `448e2febff2a` | sanctuary-beta.150.jar, Sanctuary-beta.150.mrpack, Sanctuary-Template-beta.150.zip |
| [beta.151](https://git.botsu.net/koka/sanctuary-beta/releases/tag/beta.151) | `b97ec6c9e820` | sanctuary-beta.151.jar, Sanctuary-beta.151.mrpack, Sanctuary-Template-beta.151.zip |
| [beta.154](https://git.botsu.net/koka/sanctuary-beta/releases/tag/beta.154) | `0eb1a381d4d3` | sanctuary-beta.154.jar, Sanctuary-beta.154.mrpack, Sanctuary-Template-beta.154.zip |
| [beta.155](https://git.botsu.net/koka/sanctuary-beta/releases/tag/beta.155) | `3bfb3cb251a1` | sanctuary-beta.155.jar, Sanctuary-beta.155.mrpack, Sanctuary-Template-beta.155.zip |
| [beta.156](https://git.botsu.net/koka/sanctuary-beta/releases/tag/beta.156) | `32158e2e0513` | sanctuary-beta.156.jar, Sanctuary-beta.156.mrpack, Sanctuary-Template-beta.156.zip |
| [beta.157](https://git.botsu.net/koka/sanctuary-beta/releases/tag/beta.157) | `9791d178ab5e` | sanctuary-beta.157.jar, Sanctuary-beta.157.mrpack, Sanctuary-Template-beta.157.zip |
| [beta.158](https://git.botsu.net/koka/sanctuary-beta/releases/tag/beta.158) | `19aa8054a52f` | sanctuary-beta.158.jar, Sanctuary-beta.158.mrpack, Sanctuary-Template-beta.158.zip |
| [beta.159](https://git.botsu.net/koka/sanctuary-beta/releases/tag/beta.159) | `bdaad0c94032` | sanctuary-beta.159.jar, Sanctuary-beta.159.mrpack, Sanctuary-Template-beta.159.zip |
| [beta.160](https://git.botsu.net/koka/sanctuary-beta/releases/tag/beta.160) | `c1d22786745d` | sanctuary-beta.160.jar, Sanctuary-beta.160.mrpack, Sanctuary-Template-beta.160.zip |
| [beta.161](https://git.botsu.net/koka/sanctuary-beta/releases/tag/beta.161) | `8deba7cfb47d` | sanctuary-beta.161.jar, Sanctuary-Template-beta.161.zip |
| [beta.162](https://git.botsu.net/koka/sanctuary-beta/releases/tag/beta.162) | `0c55a375623b` | sanctuary-beta.162.jar, Sanctuary-Template-beta.162.zip |
| [beta.163](https://git.botsu.net/koka/sanctuary-beta/releases/tag/beta.163) | `905b3bcfa125` | sanctuary-beta.163.jar, Sanctuary-Template-beta.163.zip |
| [beta.164](https://git.botsu.net/koka/sanctuary-beta/releases/tag/beta.164) | `9a429c40a15b` | sanctuary-beta.164.jar, Sanctuary-Template-beta.164.zip |
| [beta.165](https://git.botsu.net/koka/sanctuary-beta/releases/tag/beta.165) | `dbb9e3428da2` | sanctuary-beta.165.jar, Sanctuary-Template-beta.165.zip |
| [beta.166](https://git.botsu.net/koka/sanctuary-beta/releases/tag/beta.166) | `460c4706d37a` | sanctuary-beta.166.jar, Sanctuary-Template-beta.166.zip |
## Historique et intégration
La PR [#1 de Chris](https://git.botsu.net/koka/sanctuary-beta/pulls/1), intitulée « beta.144 — Code d’accès », est intégrée par le merge `ad960ef`, puis corrigée et livrée dans `460c470` (beta.166). Son numéro de version historique ne remplace pas la release beta.144 dédiée à l’eau. `main` a été avancé en fast-forward sur beta.166. La PR est clôturée : Gitea refuse le mode « manually-merged » dans ce dépôt, elle reste donc affichée comme fermée plutôt que fusionnée. Sa branche et ses commits sont conservés.
Les beta.152 et beta.153 sont des chantiers shaders non finalisés : aucun commit de livraison ni JAR Sanctuary vérifiable retrouvé. Aucun tag ou binaire artificiel n’est créé pour remplir ces numéros. Les fichiers de travail locaux sont conservés.
## Contrôles et limites
- Intégrité ZIP, version Fabric et version source contrôlées pour chaque JAR ; sources Java archivées comparées aux commits de livraison.
- Le ZIP de sources beta.145 est antérieur à une correction : il n’est pas publié. Le JAR correspond exactement au SHA-256 documenté dans le ticket, et la classe concernée correspond au binaire beta.146 dont les sources sont vérifiées.
- Les MRpack embarquent le JAR de leur release, vérifié par SHA-256. Le MRpack beta.150 conservé contenait un binaire intermédiaire : une copie a été réassemblée avec le JAR final ; l’original local reste intact.
- Chaque pièce jointe est retéléchargée anonymement et comparée à son SHA-256 local. Chaque release comporte son fichier SHA256SUMS.
- Les anciens échecs de tests restent documentés dans les tickets liés aux releases historiques. Cette publication rétrospective ne prétend pas les avoir corrigés dans les anciens binaires.
- La beta.166 a passé `check build assemblePack`, avec 252/252 GameTests ; voir [le contrat et les vérifications](inscription-web-beta166.md). Le parcours OAuth Discord et l’inscription sous le pseudo du dossier ont ensuite été testés localement.
- Site compagnon : branche `codex/inscription-web-beta166`, commit `89973c1` publié. Les 12 tests ciblés accès/parrainage passent (162 assertions), y compris le refus d’un autre pseudo et la génération d’invitation refusée à un candidat en attente même administrateur.
Le canal packwiz et les installations personnelles ne sont pas modifiés par ce rattrapage. Aucun monde, ancien JAR ou secret n’est ajouté à Git. Les fichiers `.env`, sauvegardes SQL et configurations privées du labo restent locaux.
+196
View File
@@ -0,0 +1,196 @@
# WG-ECO-177 — écologie liée au relief
Branche `codex/relief-ecology-beta177`, base beta.176 `96433d3` conservée.
Demande : nouveau test solo à 32 chunks intégrant les retours R019–R021.
## Contrat
Nouveau preset de laboratoire `sanctuary_test:relief_ecology_v1`, graine 42.
Aucune migration ni réouverture des anciens laboratoires avec de nouveaux
chunks. Les sauvegardes beta.175 et beta.176 restent conservées.
- Plateau : forêts normales et plaines fleuries, sans bandes géographiques
automne/cerisiers. Classification par hauteur de surface et couverture.
- Creux à ciel ouvert : automne. Hauteurs : cerisiers. Une élévation locale
autour d'un sommet existant crée un relief arboré, sous le plafond 320 ;
la roche profonde et les cinq récifs conservent le champ précédent.
- Sous une couverture de roche : cavités humides, mousse, chêne noir et
champignons géants. Ne pas confondre sous-sol et vallée basse extérieure.
- Marais sur un récif uniquement, aucune mangrove. Petits fragments hauts
sobres, sans tapis fleuri. Températures chaudes et aucune décoration de gel.
- Géologie : conserver des strates lisibles, les interrompre avec des
gisements cohérents de pierre/andésite/granite/diorite ; deepslate profonde,
ressources conservées, aucun ajout d'or ni de redstone.
- Mares locales, pas d'hydrologie régionale, pas de structures natives.
Villages et structures aériennes restent à concevoir, pas livrés ici.
Le signalement « map cassée » reste sans symptôme précis : le créateur ne se
rappelle plus s'il parlait de la carte opérateur ou du monde. Vérifier le
chemin de lecture de l'atlas et les données du nouveau monde ; ne pas
inventer de diagnostic ni modifier une interface sans défaut identifié.
## Vérifications prévues
Relevés natifs du relief, biomes à la surface et sous roche, végétation des
cavités, eau après ticks, absence de neige/glace/mangrove, ciel 512–639 libre,
répartition minérale sur toute l'île 42 et carte lisible depuis les chunks.
Compilation, `check build`, assemblages, puis ouverture du solo Vulkan à
32 chunks avec commandes. La visite peut commencer dès les contrôles du
laboratoire terminés pendant que la suite générale continue, conformément
à la préférence exprimée pendant la visite précédente.
## Réalisation
Le profil CLI `terrain` sélectionne ce nouveau preset. Les anciens profils
gardent leur générateur. Tout le code de génération nouveau est dans le
module facultatif `sanctuary-test` ; le preset Sanctuary normal est conservé.
Le sélecteur de biomes partage une hauteur de surface par colonne de quatre
blocs, mise en cache dans les limites de l'île. Une couverture de roche de
plus de douze blocs prend priorité sur la tranche d'altitude : les grottes
restent des cavités humides. À l'extérieur, les seuils initiaux sont Y≈237
et Y≈264, avec une variation lente de trois blocs. Forêt et prairie fleurie
se partagent la tranche intermédiaire par un bruit de grande échelle.
Ces seuils sont des réglages de prototype, à juger pendant la visite.
Un sommet existant parmi douze candidats proches du centre reçoit une
élévation locale douce, de rayon 96 blocs, uniquement entre Y=216 et 319.
Le relief profond et le champ des récifs beta.175 sont conservés. Les trois
récifs bas reçoivent jungle, forêt et marais ; les deux fragments supérieurs
restent sobres. Le ciel 512–639 reste réservé à l'ISS.
Les biomes ont une température empêchant la neige aux altitudes du monde ;
la décoration native `freeze_top_layer` est aussi retirée. Sous roche, les
décorations natives de lush caves sont complétées par des tentatives bornées
de chêne noir et de champignons géants dans les cavités existantes. Un arbre
demande de l'air, un plafond de roche et un socle solide ; aucune salle n'est
excavée. Le sol autour reçoit de la mousse, puis les features natives 26.3
contrôlent et placent le végétal.
Sous la transition profonde, les lits rocheux précédents sont conservés.
Plus haut, deux bruits tridimensionnels forment des gisements d'andésite,
granite et diorite, avec des régions de pierre ordinaire et d'autres où les
strates restent visibles. Les minerais et mares utilisent les composants
précédents ; leurs quantités réelles sont remesurées après décoration.
## Reproduction
```sh
./gradlew :sanctuary-test:exportDuoLaunch
python3 scripts/worldgen_lab.py prepare --profile terrain --seed 42 --run nouvel-essai
python3 scripts/worldgen_lab.py server --profile terrain --seed 42 --run nouvel-essai --verify
```
Ajouter `--whole-island` à la préparation et au serveur pour le recensement
intégral. Le lanceur refuse de réutiliser un monde dont l'empreinte des
sources a changé. Les mesures sortent dans `relief-ecology-v1-cold` et
`relief-ecology-v1-warm`. `scripts/ecology_atlas.py` rend cartes, coupes et
recensement minéral à partir de ces données natives.
## Mesures natives — graine 42
Série `science177b`, Java 25 / Minecraft 26.3, heap serveur 1 536 MiB.
[Atlas et coupes](/Users/koka/Documents/sanctuary/retours-sessions/2026-09-29_1506-storyquest/releves-beta177/graine-42/atlas.png)
et [recensement minéral](/Users/koka/Documents/sanctuary/retours-sessions/2026-09-29_1506-storyquest/releves-beta177/graine-42/minerals.png).
L'archive conserve données, coupes binaires, identité et journaux.
| Contrôle | Résultat |
| --- | ---: |
| Densités profondes et aériennes identiques à beta.175 | 42 500 / 42 500 |
| Témoins de biome sous couverture rocheuse | 358 / 358 en cavités humides |
| Climats vérifiés contre la neige, neuf biomes × quatre altitudes | 36 / 36 |
| Neige, glace et blocs de mangrove dans l'enveloppe recensée | 0 |
| Végétaux natifs sous roche retrouvés dans les blocs | 8 chênes noirs, 3 champignons bruns, 6 rouges |
| Mares générées / mares témoins stables pendant 240 ticks | 60 / 8 |
| Ensembles de structures natives admissibles | 0 |
| Plans hydrologiques régionaux calculés | 0 |
| Air contrôlé en Y=512–639, autour des cinq récifs | 1 474 560 blocs |
| Tuile d'atlas du spawn | 256 pixels lisibles, aucun chargement de chunk ajouté |
La première tentative `science177a` avait été rejetée par le test des
chênes noirs. Les critères d'espace et de socle ont été assouplis : quatre
cases sous le tronc au lieu d'une fondation plane de six blocs de large,
et précontrôle d'air moins large. Le test passe dans le nouveau monde,
avec les vérifications natives des collisions conservées.
La carte de surface échantillonnée tous les huit blocs contient 32,86 % de
forêt, 24,87 % de prairie fleurie, 33,25 % d'automne et 8,36 % de cerisiers.
Le reste, 0,66 %, correspond à de petits reliefs périphériques classés en
vide par le sélecteur de surface. Ce ne sont pas des surfaces exactes au
bloc près. Le sommet local atteint Y≈302 ; son centre est `(38, 296, 88)`.
La proportion des vallées, les lisières et la silhouette restent à juger en jeu.
### Ressources sur toute l'île
Recensement réel de **2 000 chunks**, Y=0–639, après décoration, sans
extrapolation des chunks du spawn. Variantes pierre et deepslate additionnées :
| Ressource | Blocs |
| --- | ---: |
| Cuivre | 202 008 |
| Charbon | 144 464 |
| Fer | 5 592 |
| Lapis | 7 139 |
| Diamant | **304** |
| Or | 0 |
| Redstone | 0 |
Tous les diamants sont en deepslate, sous Y=136, sans voisin d'air.
Améthyste : 6 513 blocs ordinaires, 1 630 bourgeonnants et 269
bourgeons/cristaux. La décoration et les nouvelles surfaces changent certains
totaux par rapport à beta.176 ; aucune augmentation des paramètres de
placement des minerais n'est faite dans ce lot.
Réouverture : mêmes cartes de surface/récifs, mêmes palettes et coupes de
blocs octet par octet, mêmes sources d'eau après 240 ticks supplémentaires.
Les 17 végétaux témoins sous roche sont retrouvés. Ces résultats concernent
la graine 42 ; aucune généralisation à toutes les graines n'est revendiquée.
### Durées et portée
| Passage | Initialisation serveur hors bootstrap JVM | Processus → fin du diagnostic |
| --- | ---: | ---: |
| Création et recensement intégral | 6,126 s | 317,799 s |
| Réouverture et recensement intégral | 0,961 s | 41,350 s |
Le recensement à froid prend 207,176 s. Ce coût de diagnostic ne tourne pas
pendant la visite ; ces durées ont aussi été mesurées pendant les contrôles
généraux du pack et ne constituent pas un benchmark isolé ni une mesure de FPS.
## Solo et livraison
Le client est entré dans `Sanctuary-Relief-177-Solo` le 29 septembre à
22:07 CEST : serveur intégré, KokaLab à `(-9.5, 249, -4.5)`, backend Vulkan
(MoltenVK 1.4.2 / Apple M1), distance de vue passée à **32 chunks** dans le
journal. Commandes autorisées et mode créatif dans la copie neuve ;
`/gamemode spectator` permet la visite libre. Les réglages de la visite
beta.176 sont repris, shader de laboratoire désactivé. Les diagnostics ne
sont pas activés dans le client.
Le monde provient de la copie du serveur scientifique arrêté proprement
après réouverture. Les anciennes sauvegardes sont conservées. Aucun canal,
serveur personnel ni instance Prism n'est mis à jour.
Repères d'observation en spectateur :
| Lieu | Commande |
| --- | --- |
| Sommet à cerisiers | `/tp @s 38 318 88` |
| Chêne noir de cavité | `/tp @s -89 168 119` |
| Champignon rouge de cavité | `/tp @s 23 169 -57` |
| Récif forêt, ancienne place de la mangrove | `/tp @s -102 430 -172` |
| Récif marais | `/tp @s 219 470 22` |
Compilation et diagnostics du laboratoire réussis.
`./gradlew check build assemblePack assembleTestPack` : **BUILD SUCCESSFUL**
en 25 min 50 s, **265/265 GameTests réussis**, contrôles statiques et smokes
réussis. Le test de portage intermittent de beta.176 passe dans cette suite,
sans modification du code des familiers ; son ancien échec n'est pas effacé.
Versions, icône et hashes d'index du pack vérifiés ; distributions locales
dans `build/packwiz` et `build/packwiz-test`. Journal complet archivé dans
`releves-beta177/check-build.log`. Aucune nouvelle dépendance ajoutée.
L'esthétique attend les retours de visite. Les grands plans d'eau, cascades,
structures aériennes, villages Sanctuary et station ISS restent hors de ce lot.
Le contrôle d'atlas vérifie la lecture des données ; il ne diagnostique pas
le signalement imprécis « map cassée » ni l'ensemble de son interface.
+61
View File
@@ -0,0 +1,61 @@
# WG-RESTORE-191 — revenir avant les plateaux
Branche `codex/restore-relief-beta191`, Minecraft 26.3, graine de visite 42.
Nouveau profil `original`, preset `sanctuary_test:pre_terraces_v1`.
## Contrat
Le retour beta.190 rejette toujours le relief et les affleurements ajoutés depuis
les plateaux. Restaurer exactement le champ de densité **beta.188**, avant les
terrasses beta.189 et les épaules beta.190. Retirer leurs passes d’affleurements
et leur exclusion d’arbres sur ces affleurements. La géologie d’origine retrouve
sa responsabilité : pierre, strates, gisements, cavités et pointes existantes.
Conserver les corrections indépendantes appréciées : ancres au sol, berges,
cascade large, galerie éclairée et coffres de fer enchanté dans les ruines.
Cela restaure le relief et la géologie de base, pas tous les blocs d’un monde
beta.188 puisque ces aménagements récents restent présents.
L’intention pour la suite est un récif avec creux, cavités et pointes, avec
quelques formations singulières par endroits. Aucun nouveau lissage, plateau
ni déformation généralisée dans cette remise à plat du travail.
Les variantes 189/190 restent des références conservées, hors de ce nouveau profil.
Nouveaux mondes seulement ; aucune sauvegarde existante modifiée ou régénérée.
## Vérifications
Génération et réouverture natives réussies, graine 42 :
`build/original191a-cold.json` et `build/original191a-warm.json`.
- **134 480 densités identiques** aux paramètres enregistrés de beta.188, de
Y=0 à Y=632 sur l’île et ses îlots ; aucun remappage de hauteur activé.
- Passes d’affleurements 189/190 court-circuitées dans ce profil. Leur masque
est inactif sur les 1 681 colonnes contrôlées ; la règle de géologie d’origine
et les plantations natives reprennent la surface hors des aménagements réservés.
- Comparaison au commit beta.188 `658aaf8` : corps du calcul de densité, paramètres
de bruit, îlots aériens et règle de géologie identiques. Preuve locale :
`build/original191-baseline-receipt.json`.
- Huit ancres, 49 cartes et cadres du palais, salle inférieure et éclairage,
quatre ruines avec coffres enchantés, donjon, gemmes et soufre contrôlés.
- Cascade de cinq blocs de large conservée, chenal de 4–5 blocs et huit blocs
de roche retirés ; aucune nouvelle déformation de montagne pour la produire.
Zéro tronc dans les colonnes contrôlées des bassins et berges.
Démarrage natif : 12,281 s à froid et 0,738 s à chaud ; avec tous les contrôles
et le remplissage des cartes : 79,283 s / 22,798 s. Zéro région d’hydrologie.
Ces contrôles de laboratoire ne sont pas lancés au démarrage du solo.
`./gradlew check build assemblePack assembleTestPack` réussi en **7 min 40 s**,
**265 GameTests réussis**. Le JAR est identique aux classes et ressources compilées
et à sa copie distribuée dans le pack de test : `build/original191-pack-receipt.json`.
Aucune publication ni modification de l’installation Prism. Le client Vulkan est lancé avec la nouvelle
sauvegarde `Sanctuary-Original-Relief-191-Solo`, run `visite191`, profil `original`,
vue 32 et simulation 12, spectateur, commandes autorisées, position préparée
(94,316,153) face au relief. Connexion confirmée à **21:46:39**, vue 32 et simulation 12 ; Vulkan confirmé.
Les cartes et l’état initial des ancres ont été relus dans cette copie neuve.
Les sauvegardes 188/189/190 restent conservées.
Le créateur a confirmé l’ouverture ; la connexion est vérifiée dans
`build/original191-solo.log`. Cette confirmation porte sur l’entrée dans le solo,
pas encore sur l’appréciation esthétique du terrain restauré.
+91
View File
@@ -0,0 +1,91 @@
# Revue PR #1 de Chris — intégration sur beta.165
PR : [beta.144 — Code d’accès à la création Hello World](https://git.botsu.net/koka/sanctuary-beta/pulls/1).
Révision examinée : `8de569bbf188f7b76ede9235d3a2e5449907bcd5`.
Socle local : beta.165 + audit documentaire ; les corrections de GameTests restent
un chantier distinct. Dépôt web local examiné : `sanctuary-web-community`, commit
`1feab73`. Vérification du distant web après fetch : `6640966b7306b36735c622cc4e75f31467e6f249`, aucun écart sur `AccessCodeService.php` et `routes/api.php`. Inventaire API : une PR mod ouverte, aucune PR web ouverte.
## Verdict
**Intégrable, mais ne pas fusionner telle quelle.** Le principal blocage est la
reprise après consommation du code côté site. Aucun merge distant, publication,
activation du contrôle d’accès ou commentaire à Chris n’a été effectué.
## Constats
### P1 — code consommé sans possibilité de reprendre l’accueil
Dans `ProgressionService.java` de la PR, lignes 375–382, le serveur appelle
`redeem` puis ignore le résultat si la session s’est déconnectée avant le retour.
Le site peut donc avoir consommé le code alors que le ledger local n’a jamais
été écrit. Une coupure réseau après consommation, avant réception de la réponse,
produit la même fenêtre.
Le site actuel (`AccessCodeService.php:71–72`) répond `already_used` sans
`minecraft_username` ni `discord_id`. `AccessCodes.redeem` exige ces champs pour
la reprise et refuse donc. De plus, même si le site est corrigé pour les renvoyer,
`AccessCodes.verifyKey:50` transforme encore tout `already_used` en `code_used` ;
`HelloWorldScreen.updateSelection` ne permet de confirmer que `code_recognized`
ou un pending local. La réparation doit donc couvrir **le site, le serveur et
le parcours client**, pas seulement ajouter deux champs à la réponse API.
Correction recommandée : un contrat de consommation rejouable pour le même compte
et la même admission, avec identité confirmée par l’API serveur authentifiée ;
réconciliation du résultat après déconnexion et reprise au prochain accueil.
Un code révoqué, un autre compte et une requête non autorisée doivent rester
refusés. Vérifier l’identité complète et la portée du reçu avant de l’accepter.
Le ledger ne doit jamais conserver le code brut ni le jeton API.
La documentation actuelle de la PR dit qu’une déconnexion avant `redeemed`
ne consomme pas le code : ce n’est pas garanti dès que le POST est parti.
Le smoke teste un ledger après `remember`, pas la fenêtre avant cette écriture.
### P2 — compatibilité des clients existants à expliciter
Le nouveau contrôle de capacité `AccessPayloads.Gate` dans CONFIGURE est placé
avant la recherche de l’habitant. Activer l’accès impose donc aussi la mise à
jour des clients des habitants déjà enregistrés. La promesse « habitant existant
entre sans écran » ne signifie pas compatibilité avec un ancien client.
Décider et tester ce contrat avant activation : mise à jour coordonnée ou
capacité exigée uniquement pour les accueils qui utilisent le code.
## Compatibilité avec nos changements
`git merge-tree --write-tree HEAD origin/pr-1` a été exécuté sans changer le
checkout. Trace ignorée : `build/pr1-merge-tree.txt`.
- Conflits textuels uniquement dans `gradle.properties` et `packwiz/pack.toml`.
**Ne pas reprendre beta.144** : réserver le prochain numéro libre au moment
de la livraison, synchroniser les manifestes et garder les archives existantes.
- Les changements de progression et Hello World s’appliquent sans conflit textuel
au socle examiné. Cela ne constitue pas une preuve de compatibilité fonctionnelle.
- Pas de changement proposé aux tables communautaires ou aux pages Gazette/Tableau.
L’admission appelle une API HTTP du site ; elle n’utilise pas directement le
stockage des publications. Tester quand même fichier et MariaDB en régression.
- Sans URL/jeton, la fonctionnalité se désactive. Le laboratoire actuel ne doit pas
être activé implicitement : ses comptes locaux doivent rester accessibles.
- Le ledger ajoute `data/sanctuary-access.json` sans réécrire le registre des
habitants. Prévoir sauvegarde et comportement de retour arrière avant activation.
## Plan d’intégration concret
1. Finaliser et enregistrer les corrections de tests séparément.
2. Préparer une branche d’intégration depuis ce socle et importer le changement
de Chris, en résolvant uniquement les numéros de version puis les adaptations
réellement nécessaires. Conserver son attribution.
3. Corriger et documenter ensemble le contrat de reprise site/mod ; écrire les
tests du POST consommé avec réponse perdue et déconnexion concurrente.
4. Valider l’accueil sans configuration, l’accueil configuré neuf, un habitant
existant, client ancien, code invalide, mauvais pseudo, code révoqué, panne
du site, jeton rejeté, deux confirmations et redémarrage entre les écritures.
Vérifier qu’un seul habitant et un seul familier sont créés et qu’aucun coût
n’est dupliqué.
5. Jouer le parcours réel sur un serveur jetable avec API de test, puis rejouer
les échanges Gazette/Tableau en fichier et MariaDB. Garder le site public
communautaire en lecture seule.
6. Livrer et taguer le nouveau compteur ; activer seulement sur le serveur
explicitement visé, après configuration et sauvegarde.
Pas de compilation ni d’essai de handshake de cette PR revendiqués ici.
La suite en cours concerne les tests actualisés de beta.165, pas le code de Chris.
+30
View File
@@ -0,0 +1,30 @@
# WG-ECO-178 — vallées bornées et grottes minérales
Retour de visite beta.177 : automne trop envahissant sur le plateau et les
bords, cavités trop uniformément lush. Le créateur précise : roche dominante,
petites eaux et mousse localisée ; priorité au solo, sans nouveaux atlas.
Branche `codex/rocky-ecology-beta178`, base `9613322`. Nouveau preset de labo
`sanctuary_test:rocky_ecology_v1`, profil CLI `rocky`, graine 42. Aucune
migration : le solo beta.177 ouvert reste sur son ancien générateur.
Automne limité aux sols Y=200–232, entourés de relief plus haut et sans
chute périphérique. Sous Y=200 et sur les flancs raides : géologie exposée.
Les grottes gardent surtout leur roche ; mousse en plaques, poches végétales
rares, petits bassins retenus et décorations de stalactites. Les bosquets de
chênes noirs/champignons restent ponctuels. Relief, récifs, minerais et
absence d'hydrologie régionale conservent leurs composants précédents.
Contrôles ciblés de chargement et du comportement modifié avant le nouveau
solo ; `check build assemblePack assembleTestPack` exigé pour la livraison.
Pas de recensement complet ni de cartes scientifiques dans cette passe.
Contrôle natif `solo178d`, graine 42 : réussi, 567 comparaisons de densité
identiques au socle profond, aucun calcul d’hydrologie régionale. Dans les
neuf chunks centraux contrôlés, 380 sols rocheux sur 405, 23 en mousse et
cinq sources d’eau observées ; ce petit échantillon ne décrit pas toute l’île.
Solo neuf `visite178/rocky/42`, sauvegarde `Sanctuary-Rocky-178-Solo`,
vue 32, simulation 12, commandes activées. Compilation et `check build assemblePack assembleTestPack` réussis
(23 min 35 s, résultat du 29 septembre). Aucun ancien monde ni canal de distribution modifié.
Ouverture confirmée le 29 septembre à 22:47 : Vulkan sur Apple M1,
KokaLab connecté en solo et distance serveur passée à 32 chunks.
+143
View File
@@ -0,0 +1,143 @@
# EXP-199 — affiner les expansions et les raccorder au laboratoire
Branche `codex/runtime-expansions-beta199`, depuis beta.198. Retour R014.
Implémentation et validation locales ; aucune publication du canal packwiz ni mise à jour de Prism.
## Contrat retenu
Réutiliser `ExpansionJournal`, `ExpansionRuntime`, les contrôles d'occupation,
les contextes de bruit natifs et les biomes d'expansion existants. Les ancres
actuelles du laboratoire doivent désormais demander une vraie expansion.
Recherche automatique sur 360°, priorité à la proximité du parent parmi les
sites admissibles, une région immuable par relais, et un nouveau relais sur
chaque région prête. Relief fin : surface large et ondulée, quelques plateaux
souples, volumes amincis en conservant les cavités du bruit 3D.
Les lectures d'admission et la préparation native sont échelonnées entre les
ticks. Annonce SGA aux joueurs seulement après les chunks FULL et le relais.
Les doubles activations ne créent ni second continent ni double prélèvement.
La sauvegarde reprend une région réservée après interruption.
## Contrat de nouveaux mondes
Les profils 196–198 gardent leurs paramètres et leurs ancres de démonstration.
Les nouveaux profils 199 initialisent un journal d'expansions propre. Aucun
ancien monde n'est migré, aucun chunk existant régénéré, aucun ancien format
réécrit. Première admission limitée aux régions réellement vierges ; même du
vide déjà généré reste protégé. Cette contrainte peut éloigner une expansion
si le joueur a déjà exploré les alentours ; ce n'est pas une promesse de
contact bord à bord. La réutilisation du vide certifié reste le mécanisme
historique distinct, à ne pas activer implicitement dans une partie.
## Ciel et performance
Rotation stellaire : sept journées Minecraft, sur l'horloge du monde faisant
autorité ; conserver la même transformation pour la visée et les constellations.
Les saccades sont également signalées à l'arrêt, y compris shader Sanctuary désactivé (confirmation du créateur). Aucune cause n'est attribuée
au worldgen sans mesure ; préserver cette enquête séparément du réglage des
expansions. Les débordements de mares restent consignés sans nouvelle refonte.
## Première tranche jouable
Le relais demande les mêmes huit offrandes distinctes que le laboratoire. Le
joueur reste à proximité, offrande en main, pendant la recherche. Les matériaux
ne sont retirés qu'après réservation durable. Une seule préparation est active
à la fois ; chaque relais ouvre une région de diamètre nominal 256 blocs, avec
un relais de continuation. Le climat suit la couleur du relais d'origine ; la
position réelle est choisie automatiquement parmi 64 angles, par distances
croissantes sur la grille de chunks. Le relief réutilise la densité 3D historique,
comprimée verticalement autour de Y=220 et déformée par son bruit cohérent.
Les biomes naturels décorent ces nappes. Les villages et forteresses vanilla ne
sont pas réactivés par l'ajout de ces biomes à la palette : leurs structures
seront une tranche distincte. Les structures et reliefs de l'île centrale sont
conservés. Pour Small/42, une recherche locale plus précise peut retrouver les
sols d'ancres manqués par la grille historique ; les réservations d'eau suivent
alors leurs emprises réelles avec marge au lieu d'exclure un secteur entier.
Le moteur conserve son journal et sa file native, avec un adaptateur réservé aux
profils 199. Admission : 32 lectures asynchrones au maximum par lot, contrôle des
chunks chargés et des sauvegardes concurrentes, mémorisation des refus
pour éliminer les candidats qui recouvrent un chunk déjà trouvé occupé, puis réservation et publication
sur le même tour serveur. Le relais suivant est placé après les chunks FULL ;
l'annonce SGA possède une infobulle lisible donnant ses coordonnées.
Limites : pas de régénération du vide déjà exploré, pas de choix de direction,
pas encore de variation du prix/taille pour les relais successifs. La limite
historique reste de 64 régions, Sanctuary incluse. Le journal et l'inventaire
joueur sont deux fichiers Minecraft distincts : la protection contre le double
clic ne constitue pas une transaction atomique entre ces fichiers lors d'une
coupure brutale au milieu d'une sauvegarde.
## Périmètre de validation
Placement proche sur tous les angles et refus des emprises occupées, densités
bornées et nappes fines, horloge stellaire cyclique, activation réelle, deuxième
relais, double interaction, interruption/reprise et notification après FULL.
`check build`, assemblages et essai client Vulkan avant livraison locale.
## Relevés de validation
- Compilation et assemblages : `check build assemblePack assembleTestPack`,
avec les suites natives `operator,realtime`, réussis (7 GameTests et contrôles
purs). La vérification du placement couvre 9 couples graine/taille : 0, 42 et
4736390610738281858 × Small/Medium/Large, les huit secteurs, l'ordre de distance,
les conflits et la réouverture du journal.
Dernier passage complet : `build/expansion199-check-build-delivery.log`,
réussi en 3 min 40 s. Compilation finale des tests client réussie également.
- Nouveau Small, graine 42, 2 Gio : serveur prêt vers 14,2 s ; première expansion
au centre (368, -368), relais Y=231, après 418 chunks FULL. Le deuxième relais
réserve son enfant environ 11 s après l'annonce du premier. L'arrêt volontaire
a lieu avec cet enfant encore réservé. Cette durée inclut les essais et n'est
pas le temps normal de création d'un monde.
- Journaux finaux : `build/expansion199-check-build-final.log` et
`build/worldgen-lab/expansion199-native-final/small/42/`. Les premiers essais
interrompus restent conservés et ne sont pas des validations.
- Réouverture du même Small/42 : enfant repris, 418 chunks FULL, relais
(912, 236, -368), une seule annonce pour cet enfant et aucune répétition de
celle du parent. Sur 81 colonnes échantillonnées, 1380 blocs non vides, entre
Y=191 et Y=275 (végétation comprise) : ce relevé local ne mesure pas le volume
total de toutes les expansions.
- Client Vulkan, Small/42 : arrivée sur l'île, synchronisation native des étoiles
et pause de l'horloge, vraie interaction du joueur avec le relais, double clic
sans double prélèvement. Un bloc d'or placé dans le premier candidat reste
intact et force l'admission d'un autre emplacement. Les interfaces FR/EN,
trois tailles, profils historiques et aller-retour disque passent également.
- Deux essais client consécutifs terminent l'expansion puis sauvegardent et
ferment normalement (`build/expansion199-client-pass-a.log` et
`build/expansion199-client.log`). Le tout premier essai avait réussi les
assertions puis rencontré une exception dans `ChunkMap.tick`, lors du suivi
des entités. Cause non isolée : un contrôle de réentrance et 100 ticks
d'observation supplémentaires n'ont pas reproduit le défaut. Ce résultat
n'est pas un correctif prouvé du crash initial. Pas d'essai direct Windows.
### Saccades : capture locale, cause non isolée
Première capture client macOS Vulkan, Small/42, vue 8 chunks, shader désactivé, joueur immobile :
1241 images relevées, médiane 31,15 ms (limitation de cadence de l'environnement
de test), P95 33,12 ms, P99 33,71 ms, maximum 98,01 ms, 7 images au-delà de 50 ms.
258 sections ont encore été compilées pendant la capture. Le JFR contient 4152
échantillons de workers dans la génération, et des pauses GC jusqu'à 88,45 ms.
Le joueur immobile ne signifiait donc pas que le chargement avait terminé.
Ce relevé n'isole pas les saccades d'une partie complètement stabilisée, et ne
prouve pas leur cause chez le créateur. Le shader n'est pas nécessaire pour
reproduire quelques images longues dans cet environnement. Aucun correctif
général des saccades n'est revendiqué dans cette livraison.
Mesures ignorées par Git :
`build/expansion199-stationary-first/stationary.json` et `stationary.jfr`.
Les captures suivantes restent sous
`mods/sanctuary/build/run/clientGameTest/diagnostics/expansion199/`.
Pas de nouvelle campagne de screenshots.
## Livraison locale
`Sanctuary-Test-beta.199.mrpack`, 12 322 965 octets, copié dans
`/Users/koka/Downloads/` avec `Sanctuary-Test-beta.199-Guide.txt`.
SHA-256 : `fd2ebe4f43bf0d455377a2f456106165ff5e3519e5c3dce4126818c03f4ae44f`.
Intégrité ZIP, manifeste Minecraft 26.3/Fabric Loader 0.19.5, dépendances externes,
JAR intégrés comparés aux classes compilées et anciennes ressources vérifiés.
Les 142 ressources historiques de génération restent identiques à beta.198 ;
le preset public vise désormais 199. Reçu : `build/expansion199-artifact.json`.
Le canal packwiz et l'installation Prism n'ont pas été modifiés.
+78
View File
@@ -0,0 +1,78 @@
# beta.161 — correctifs de la séance du 23 septembre
Ticket : R002–R009 du carnet de séance. Branches `codex/session-fixes-beta161`.
## Contrat de données et migration additive
Aucun monde ni chunk n’est régénéré, aucun format existant n’est modifié.
Le nouveau fichier `data/sanctuary-server-metrics.json` version 1 conserve le temps
réel cumulé depuis l’activation du compteur, les morts connues par UUID et les
UUID des figurants exclus. Au premier démarrage, les morts sont importées des
statistiques Minecraft déjà présentes ; le temps antérieur n’est pas inventé.
Les valeurs vivantes sont relevées côté serveur, sauvegardées toutes les 30 secondes
et à l’arrêt. Les arrêts ne comptent pas dans la durée. Un crash peut perdre au
plus la période depuis le dernier relevé enregistré. Un fichier corrompu ou
modifié extérieurement arrête la collecte sans le remplacer.
En mode base, appliquer la migration Web additive `create_sanctuary_server_metrics_table`
avant de lancer le mod. Elle crée une table indépendante, une ligne par server_id ;
le schéma communautaire v3 et ses données restent inchangés. Le serveur publie un
instantané toutes les 30 secondes et un état arrêté à l’arrêt propre. En cas de
panne SQL le compteur local continue ; le site marque la dernière mesure périmée
après 90 secondes. Une table absente ne bloque pas les publications communautaires.
La suppression de cette nouvelle table au rollback perd uniquement les instantanés,
recréés depuis le fichier local à la prochaine publication. Ne pas supprimer le fichier
local pour revenir en arrière ; les anciennes versions l’ignorent.
Un seul serveur actif par server_id, comme pour les publications. Les morts de tous
les joueurs ayant des statistiques sont cumulées, y compris les joueurs déconnectés.
Les figurants du laboratoire sont exclus des morts et des joueurs en ligne.
## Résultat livré
Liste des joueurs sur U (migration unique de Tab ; autres choix conservés), suivi
et place unique expliqués dans les demandes. Site : retour au dessin d’origine,
intendance limitée au titre, page officielle avec retour, pas de navbar de rubriques
ni de sélecteur de langue ni d’incitation à se connecter dans les publications.
Gazette en accordéon exclusif : le dernier article visible est ouvert au chargement,
ouvrir un autre referme le précédent. Photo entière à gauche, extrait court à droite,
visage/pseudo/date et lien de lecture alignés en bas. Calendrier jour/mois encadré,
message officiel à droite, sans heure ; année dans le footer.
Les pages publiques du site sont en lecture seule, y compris leurs anciennes
routes d’écriture pour les comptes connectés et le staff. L’administration
Intendance réservée au staff reste disponible. Les joueurs publient, répondent
et suivent les demandes dans Minecraft. Compteurs réels et état périmé explicite.
## Vérifications
| Vérification | Résultat |
| --- | --- |
| `./gradlew check build assemblePack` | 252 GameTests : 229 réussis, les mêmes 23 échecs que beta.160, aucun nouvel échec dans cette passe. Comparaison dans `build/session161-server-failures.json`. |
| Contrôles JVM et persistance fichier/SQL | Réussis, dont `SERVER_METRICS161_PASS` et `COMMUNITY154_SQL_PASS` dans `build/beta161-release.log`. |
| Démarrage et raccourci U | `SESSION161_KEYS_PASS` : défaut U, migration unique depuis Tab, choix personnalisé conservé. |
| Parcours natif communautaire | `SEARCH159_CLIENT_PASS mode=file` : photos/galerie, FR/EN, GUI 2/3/4, recherche, bulletin en lecture seule, suivi et infobulle du repère. Captures dans `mods/sanctuary/build/run/clientGameTest/screenshots/`. |
| Web SQLite et MariaDB isolée | 29 tests, 252 assertions réussies ; lecture seule visiteurs/joueurs/staff, contrats de persistance, statistiques absentes/périmées/arrêtées. Pint et `git diff --check` réussis. |
| Navigateur | Accordéon exclusif, lien vers l’article, calendrier sans heure, visage/auteur/date et lecture en bas ; vérification desktop et 390 × 844. |
| Assemblage final | `./gradlew build assemblePack :sanctuary-test:exportDuoLaunch -x :sanctuary:check` réussi, après les vérifications ci-dessus. Exclusion ponctuelle des tests connus en échec, pas de suppression d’assertion. |
| Laboratoire réel | Monde existant `duo-flat-160` relancé, serveur 768 MiB/client 2048 MiB, shaders désactivés. KokaLab non OP, 26 niveaux conservés. Deux figurants, compteur SQL et Web : 1 humain. Journal de durée persisté et exclusions vérifiées. |
Le premier démarrage de test a révélé une mauvaise déclaration du nouveau mixin :
`PlayerListKeyMixin` au lieu de `client.PlayerListKeyMixin`. Elle est corrigée ;
la classe et sa déclaration sont présentes dans le JAR final. Le test natif a
ensuite réussi. L’assemblage avait redéclenché une seconde passe de GameTests
serveur ; cette répétition a été interrompue, la première passe complète faisant
foi. Logs distincts : `beta161-check-build.log`, `beta161-release.log`,
`beta161-client.log`, `beta161-assembly.log`, sous `build/`.
Livraison locale uniquement : `mods/sanctuary/build/libs/sanctuary-beta.161.jar`
et pack assemblé dans `build/packwiz`. SHA-256 du JAR :
`c6e0424bf091c41827730bc260dad3b95b7c949fb0dcc41811f0ae3d94bce79c`.
Les 29 ressources shader sont identiques à beta.160. Les anciens JAR beta.154–160
sont conservés. Aucun canal public ni instance Prism personnelle n’a été modifié.
Le téléchargement public du site reste l’ancien lien beta.151, hors de cette
livraison locale. Les 23 échecs préexistants restent ouverts.
Le compteur de jours débute à l’activation de cette version : le fonctionnement
antérieur n’était pas mesuré. Le site reçoit les nouveaux compteurs au rechargement
de la page. La mesure d’une mort réelle supplémentaire n’a pas été provoquée sur
le personnage de la séance ; l’import et l’incrément sont couverts par le test du
journal et les tests de lecture Web.
+126
View File
@@ -0,0 +1,126 @@
# WG-SHARE-196 — laboratoire partageable, trois tailles
Branche `codex/lab-mrpack-sizes-beta196`, depuis beta.195, Minecraft 26.3,
Fabric Loader 0.19.5, Java 25. Demande : donner le dernier labo à un ami sous
forme de `.mrpack`, avec Small, Medium et Large, puis diagnostiquer la lenteur.
## Parcours et périmètre
Dans le pack **Sanctuary Test**, Solo → Créer un monde → Monde → type
**Sanctuary** → **Personnaliser** propose Small (512), Medium (724, défaut),
Large (1024). Les types vanilla restent disponibles. Le module Test fournit le
preset public ; le pack ordinaire sans ce module conserve sa génération.
L’interface ne propose plus les anciens laboratoires. Annuler et Échap ne
modifient rien ; la taille est conservée lors du rechargement des registres et
dans les réglages enregistrés. Libellés FR/EN.
Les nouveaux réglages sont `sanctuary_test:shared_island196_small`, `medium`,
`large`. Les anciens codecs et presets restent présents. Aucun monde existant
n’a été migré, modifié ou régénéré. Le preset caché `production_reference196`
conserve le témoin de l’ancienne génération pour le lanceur scientifique.
Le terrain reprend le profil `coherent` : rayon natif selon la capacité, sans
étirer les voxels. Les bornes des biomes, l’écartement des récifs, la recherche
des bassins et ancres, les secteurs de gemmes et la sortie du donjon suivent
la taille. Les pièces conservent leurs dimensions physiques. Atlas de 25/49/81
cartes ; rosace à Y=640 et palais vers Y=1280 dans la dimension haute de 1600.
Aucune nouvelle direction esthétique ni campagne de screenshots.
En Small/Large, la réservation des ancres tient compte de la hauteur : un
refuge sec peut surplomber une autre installation, sans empiéter sur son
volume. C’est nécessaire pour que le donjon ne condamne pas tout un secteur
d’une petite île. Les poches d’eau naturelles restent conditionnées à un creux
fermé : l’absence d’une poche supplémentaire ne crée pas un bassin artificiel.
Les grands bassins souterrains et les étangs de surface sont vérifiés séparément.
La graine 0 en Large a révélé une poche native de dripstone dans le sol d’un
couloir, puis des pointes sèches dans un recoin immergé d’un étang. Les nouveaux
profils protègent les sols de circulation via un index par colonne et toutes
les cellules d’eau retenues, y compris celles sous un surplomb. Les protections
historiques restent utilisées pour les anciens profils.
## Diagnostic du chargement beta.195
Le créateur a quitté la visite puis lancé un nouveau monde Large depuis le
menu. Les journaux identifient la graine `3466058668915588406` et **l’ancien
générateur**, pas le labo `coherent`. Du démarrage du serveur à la sélection
du spawn : 18:09:26 → 18:11:05, soit environ 99 secondes.
Les chronos internes indiquent notamment l’hydrologie régionale (plusieurs
millions de sondes par région), Transit24 (23,4 s), le sanctuaire minéral
(33,7 s), Heritage24 (15,5 s) et les anciennes expéditions. Certains chronos
s’imbriquent : ne pas les additionner.
Un JFR de 43 secondes a capturé ensuite la préparation des chunks de cette
ancienne génération : 2509 échantillons Java, nombreuses piles TransitPlan21
et HeritagePlan19, bruit natif et allocations de streams/maps. Les huit GC
mesurés totalisent environ 2,16 s (pas uniquement des pauses du jeu). Le nouveau
Voronoï n’apparaît pas dans ce relevé, car ce générateur ne l’utilise pas.
Un second relevé intermédiaire sur **Small, graine 0**, avant les diagnostics détaillés,
contient 1578 échantillons CPU : 1151 (72,9 %) incluent les bruits natifs,
118 (7,5 %) les recherches d’eau et 11 (0,7 %) l’érosion Erosion192, qui contient
les joints Voronoï locaux. Ces ensembles peuvent se recouvrir ; ce n’est pas
une décomposition additive du temps mural. La piste prioritaire serait de
réduire les évaluations répétées du relief et les sondages de bassins, en
comparant exactement les blocs produits. Cette optimisation n’est pas livrée
ici : le raccordement au bon générateur élimine déjà les anciens planificateurs.
Le lanceur accepte maintenant `--jfr CHEMIN` pour produire des enregistrements
séparés à froid et à chaud. Exemple :
```sh
python3 scripts/worldgen_lab.py verify --profile large --seed 0 \
--run mesure-nouvelle --memory 2048 --jfr build/profils/large.jfr
```
`serverStartedMs` mesure l’arrivée au spawn serveur. `readyForVisitMs` inclut
ensuite les diagnostics qui chargent d’autres chunks, inspectent les structures
et calculent l’atlas. Ne pas présenter cette deuxième durée comme l’attente
normale du joueur. Ces essais ne mesurent pas la fluidité Vulkan à 32 chunks.
Les JFR bruts restent ignorés et ne sont pas intégrés au pack.
## Vérifications et distribution
`./gradlew check build assemblePack assembleTestPack -PsanctuaryFocusedTests=menus`
réussi : suite des contrôles purs, quatre GameTests menus, modules et pack.
Parcours natif client Vulkan final réussi en FR/EN, sans captures : entrée
publique du labo, trois tailles, Annuler/Échap, rechargement des registres,
aller-retour des réglages sur disque et personnalisation vanilla.
Création et réouverture de la graine 0 réussies pour les trois tailles :
| Taille | Diamètre | Spawn à froid | Spawn après réouverture | Froid avec diagnostics | Cartes |
|---|---:|---:|---:|---:|---:|
| Small | 512 | 14.34 s | 0.72 s | 62.0 s | 25 |
| Medium | 724 | 19.86 s | 0.64 s | 120.2 s | 49 |
| Large | 1024 | 23.96 s | 1.26 s | 144.5 s | 81 |
Huit ancres et offres distinctes, donjon et butin, étangs sans cellules perdues,
portail inférieur, palais, soufre, ruines et secteurs de gemmes contrôlés.
Zéro région d’hydrologie lourde dans les six passages. Les chronos ont été pris
avec JFR, parfois pendant d’autres contrôles : **pas un benchmark isolé**, ni
une garantie de chargement graphique à 32 chunks. Les essais antérieurs Small
sans concurrence atteignaient environ 11 s au spawn serveur. Les autres graines
et le matériel de l’ami restent à éprouver.
Les rapports Medium/Large sont dans `shared196f`, Small dans `shared196g`.
Entre les deux, seule la résolution des identifiants de taille a été mise en
cache pour éviter les allocations dans les protections : mêmes chaînes,
capacités, données et algorithmes de génération. Après ce dernier ajustement,
contrôle/construction du module Test et réassemblage réussis ; contrôles du
mod principal déjà passés réutilisés. Une relance redondante de leur suite est
interrompue, sans être comptée comme une seconde réussite complète.
Export final `Sanctuary-Test-beta.196.mrpack`, 12 268 171 octets, copié dans
`/Users/koka/Downloads/`, avec guide séparé. SHA-256 :
`f73db69953641237399d4d3f0210ba0252e1ea45377b5ff57d6e231d67aa5988`.
Archive ZIP et manifeste vérifiés ; classes identiques aux classes construites,
trois capacités dans les ressources, dépendance Fabric API téléchargée et
vérifiée par SHA-512/SHA-1. Aucun monde, log, secret ou réglage personnel inclus.
Demeure et JEI sont imbriqués dans Sanctuary. Export local uniquement, sans
publication du canal packwiz, synchronisation Prism ou envoi à un tiers.
Traces ignorées : `build/shared196-check-build.log`,
`build/shared196-final-assemble.log`, `build/shared196-client-final.log`,
`build/shared196-artifact.json`, `build/worldgen196-profile/` et les rapports
`build/worldgen-lab/shared196{f,g}/.../{cold,warm}.json`.
+87
View File
@@ -0,0 +1,87 @@
# WG-SKY-175 — récifs rares au-dessus de Sanctuary Island
Branche `codex/sky-fragments-beta175`, issue du labo beta.174.
## Contrat de génération
Demande du créateur : conserver l'île et sa hauteur actuelle ; ajouter des
fragments rares jusqu'à Y=512, puis laisser **Y=512–639 vide pour l'ISS**.
Le plafond de construction reste 640. L'ISS elle-même n'est pas générée par
ce lot. Les nouveaux blocs sont une idée ouverte, pas une palette décidée.
Les relevés scientifiques remplacent les captures en jeu pour cet essai.
**Correction suivante du créateur : retirer les anciennes îles flottantes**
dans cette variante. Le nouveau preset optionnel `sanctuary_test:sky_v1`
emploie son propre réglage `sanctuary_test:sky_v1_10`. Il conserve le champ
de l'île principale, sans sa couche `FloatingTerrain306`. Au-dessus,
une poignée de volumes bornés, déterminés par la graine, reçoit une érosion
3D à deux échelles. Ils deviennent plus petits et moins nombreux en altitude.
Pas de recherche de sites, de chargement de chunks ni de plan hydrologique
pour placer ces formes. Les matériaux existants restent provisoires.
Réservé aux **nouveaux mondes de laboratoire**, diamètre 724. Les presets
`relief_v1` et Sanctuary normal restent stables. Aucun ancien monde n'est
modifié ni complété ; pas de migration. Le lanceur exige un nouveau `--run`
après changement de génération et conserve les expériences antérieures.
Le module Sanctuary Test est nécessaire pour relire ce profil.
La conservation porte sur le relief : une nouvelle roche au-dessus d'une
colonne peut modifier sa lumière ou le placement des décorations fondées sur
la hauteur de surface. Ce profil ne représente pas encore l'écologie finale.
## Relevés prévus
**Suite de demande — minerais :** cuivre et charbon abondants, peu de fer,
aucun or ni redstone, lapis et améthyste présents. Le diamant reste rare,
uniquement enfoui dans la deepslate profonde ; contrôle d'enveloppe solide
de deux blocs avant placement. Après un premier relevé de trois diamants sur
81 chunks, le créateur précise : **des centaines sur l'île entière, pas sur
81 chunks**. Les petits filons restent enfouis ; le comptage exhaustif doit
servir à régler leur quantité totale. Les strates sont conservées, avec
deepslate, basalte et roche noire. Les quantités sont à régler sur les mesures.
Cette première répartition appartient au nouveau labo, pas aux mondes normaux.
- Graines 0, 42 et 173 ; comparaison bit à bit du champ inférieur avec le
profil relief, bornes d'altitude, répétabilité et absence d'hydrologie.
- Échantillonnage du champ natif compilé par Minecraft : coupes verticales,
empreinte horizontale, épaisseur cumulée et fraction de roche par altitude.
Indiquer le pas, le domaine, les limites et les empreintes des données.
- Témoins dans de vrais chunks : roche aux fragments et ciel vide au-dessus
de 512. Réouverture du monde ; conserver les données et les temps à froid.
- Mesurer séparément arrivée serveur, demande de chunks et diagnostic.
Comparer au labo relief avec le même protocole ; aucun chiffre anticipé.
- `./gradlew check build assemblePack assembleTestPack`. Pas de déploiement
dans Prism ni sur le canal public.
## État
Prototype local compilé et visité sur la graine 42. Les relevés de géométrie
sur 0, 42 et 173 ont été produits ; la quantité de diamants a ensuite été
ajustée et son total sur l'île entière reste à mesurer. La première tentative
exhaustive a manqué de mémoire : elle ne valide pas la cible globale.
La validation complète `check build` et les assemblages de livraison beta.175
restent à terminer. Ne pas confondre cette visite avec une livraison validée.
Le [retour de visite](worldgen-retours-beta175.md) conserve les captures et
précise les prochains essais : géologie, biomes, eau locale et retrait des
mineshafts. Ces changements ne sont pas appliqués à cette sauvegarde.
## Contrat de copie pour la visite solo
Le créateur demande ensuite une vue à **32 chunks en solo**. Le serveur de
visite `visite175` est sauvegardé et arrêté proprement, puis son monde est
**copié** dans un dossier solo encore absent du client de ce même labo.
Le monde serveur original est conservé. La copie garde le même code, les
chunks, la graine, les identifiants de génération et les données du joueur.
Aucun chunk n'est régénéré ni format converti. Seuls le nom de la copie et
son autorisation native de commandes sont ajustés, après copie et avant
ouverture. Le client conserve explicitement l'UUID de KokaLab.
Le rendu à 32 chunks a été confirmé dans les logs du serveur intégré. La
simulation effective est à 12 à l'ouverture, malgré le réglage initial à 4 ;
ne pas annoncer 4 comme valeur vérifiée du solo. Le shader reste désactivé.
Un manifeste `solo.json` nomme cette copie. Relancement par
`python3 scripts/worldgen_lab.py solo --profile sky --seed 42 --run visite175`.
Le lanceur contrôle toujours l'empreinte de génération ; le serveur de visite
sur 25581 n'est plus nécessaire pour cette copie locale. Le recensement
scientifique utilise un autre monde, sur le port local 25582.
+400
View File
@@ -0,0 +1,400 @@
# Sanctuary — refonte Storyquest et priorités pour la 173
**Vision générale ouverte le 27 septembre, actualisée le 29 septembre 2026.**
Le [fil rouge courant](storyquest-fil-rouge.md) situe le chantier et remplace
les anciennes priorités par des parcours à éprouver. Le présent document
conserve les intentions de la refonte et les sujets encore en réserve.
Branche `codex/storyquest-beta173`, créée
depuis **beta.172**, commit `55c745a`. Ce document décrit la refonte globale.
Le premier lot de code réalisé ensuite est le [prototype du palais et de ses
huit ancres](palais-prototype-beta173.md), en beta.173 ; les autres intentions
restent à implémenter.
**Décision du 29 septembre :** le palais souterrain, ses accès et les
raccordements au palais sont abandonnés. La refonte de Sanctuary Island part
de plans géographiques indépendants. Le [concept du 28 septembre](palais-originel-iss-beta173.md)
reste une archive ; les pistes ISS, chenil et ordinateurs ne sont pas annulées
par l'abandon de ce bâtiment. Le rôle et la position du bloc originel restent
à reprendre, sans nouvelle contrainte de hauteur décidée.
[État du socle et provenance](storyquest-socle-beta172.md) ·
[Vision](vision.md) · [Discussion communauté et économie](ecosysteme-communaute-economie.md).
## La direction demandée
Sanctuary doit devenir une expérience commune, étrange et réconfortante —
« Minecraft cursed & healed ». L'histoire se découvre en habitant ce monde :
cuisiner, s'occuper d'un familier, explorer, construire, combattre, commercer
et se retrouver font avancer des histoires qui se croisent.
Le **Storyquest** est le fil qui relie ces actions. Il commence dès le code de
connexion et l'introduction en SGA, se poursuit dans les hordes, puis dans
l'île, ses secrets et ses expansions. Le SGA permet de raconter indirectement.
L'objectif est une expérience soignée et cohérente ; « premium » ne définit
ici aucun modèle payant.
### Intentions confirmées par le créateur
- Refaire le menu pause et les pages **Habitant, Combat, Factions** ; retirer
**Friends / Amis** du menu principal.
- Relier des quêtes **individuelles, collectives, de groupes spontanés et de
factions** dans le Storyquest.
- Reprendre Sanctuary Island comme départ, son worldgen, une liste de bâtiments
générés, ses lieux manquants et le spawn ; la forme du secret est à reprendre
après l'abandon du palais souterrain le 29 septembre.
- Organiser les **huit liaisons** de l'île, puis les **relais des expansions**.
- Reprendre les nouveaux zombies de 26.2, les Mooblooms, les fantômes et la
baleine fantôme ; retirer les phantoms.
- Concevoir les navets, la loterie et le shop spécial autour de PNJ de
l'ancienne partie qui circulent à la surface de l'île.
- Faire des donjons des étapes de progression en réinterprétant les anciennes
fins du jeu. **Cavernes / diamants, Nether, Océan et End** étaient les repères
initiaux ; la redistribution du Nether et de l'End dans de nouvelles
dimensions est depuis une piste à explorer, pas une géographie arrêtée.
- Concevoir des apparitions de structures dans le ciel : bateaux pirates,
diligences, arènes et donjons volants thématiques.
- Optimiser le shader d'ambiance ; ajouter du vent en altitude et une ambiance
sourde dans les profondeurs.
- Relier le jeu, sanctuary-minecraft.net et Discord : connexion, événements,
rappels, forum, messages et activités communautaires.
- Réintroduire **It's Alive !**, sa cuisine combinatoire et ses aliments ;
découvrir les produits préférés des familiers et leur donner un passif qui
s'active ou s'intensifie lorsqu'ils sont portés sur la tête.
- Fournir une véritable bibliothèque de constructions à assembler comme des
LEGO avec les schematics et les outils de construction déjà présents.
Tout ce qui suit est une **proposition de conception**, sauf rappel explicite
d'une décision. Les noms de quêtes, prix, puissances, lieux et conditions de
déblocage ne sont pas encore arrêtés.
## Une histoire qui se découvre par ses effets
Proposition : construire un réseau de découvertes. Un joueur peut comprendre
un indice par une ruine, un plat, une rencontre ou une épreuve ; ces chemins
convergent vers des transformations communes. Les activités gardent leur
intérêt même quand leur contribution à l'histoire n'est pas encore comprise.
Le SGA doit conserver des associations reconnaissables : un même motif rencontré
dans l'intro, une carte de horde et une ancienne machine suggère un lien. Définir
quelques motifs, leurs usages et les indices de compréhension avant de multiplier
les textes chiffrés. Éviter de donner à chaque glyphe aléatoire une signification
canonique après coup. Le code d'accès réel reste une donnée d'authentification :
les indices de fiction utilisent des signes distincts, jamais le secret du joueur.
Le carnet Storyquest mémorise **ce que l'habitant a observé**, l'action qu'il peut
tenter et les conséquences qu'il connaît. Il ne révèle pas l'arbre entier des
secrets. Instructions de jeu, erreurs et narration d'accessibilité restent
compréhensibles en FR/EN ; le SGA porte le mystère.
| Portée d'une quête | Exemple proposé | Règle à concevoir |
| --- | --- | --- |
| Individuelle | Reconnaître un motif, retrouver une recette, comprendre un familier | Découverte et récompense personnelles ; un serveur avancé ne saute pas toute l'histoire du nouvel arrivant |
| Groupe spontané | Rejoindre une horde ou une expédition rencontrée en chemin | Contribution utile, participation tardive, départ et déconnexion ; le dernier coup ne définit pas seul la participation |
| Faction | Préparer un relais, une cuisine ou un chantier d'expédition | Projet et ressources de faction ; adhésion/départ sans double attribution ; solo encore viable |
| Collective au serveur | Rendre une liaison utilisable, stabiliser un lieu | Une transformation du monde, historique des contributions, accueil des joueurs absents lors de l'ouverture |
Une quête peut articuler plusieurs portées : observation personnelle, action en
groupe, conséquence collective. Les preuves serveur et les récompenses sont
distinctes. Une horde déjà généreuse en butin ne doit pas recevoir implicitement
une deuxième distribution complète parce qu'elle sert une quête.
### Première boucle proposée
```mermaid
flowchart LR
A[Arrivée et motif SGA] --> B[Rencontre ou lieu sur l'île]
B --> C[Préparer : cuisine, familier, construction]
C --> D[Explorer ou rejoindre une épreuve]
D --> E[Découverte et conséquence visible]
E --> F[Gazette, tableau, rendez-vous]
F --> C
E --> G[Liaison et nouvelle exploration]
G --> B
```
Exemple à discuter : un motif de l'arrivée réapparaît dans une ruine proche ;
une rencontre donne une raison d'y retourner préparé. Une épreuve permet de
comprendre un fragment du lieu. Le joueur garde sa découverte, le groupe garde
son résultat et le serveur voit un changement. Un article, une recette ou un
plan donne une raison à d'autres joueurs de venir. Le secret n'est plus lié
au palais ; l'épreuve, la révélation narrative et le premier changement restent
à choisir. Le fil rouge courant propose de commencer par un trajet extérieur
avec une halte et une ancre indépendante.
## Le menu pause accompagne la vie de l'île
Conserver la carte et les nouvelles du serveur comme repères. Le Storyquest
doit montrer ce qui relie les activités, tandis que chaque page conserve une
responsabilité claire. La disposition graphique vient après ces parcours.
| Page / entrée proposée | Ce que le joueur vient y faire |
| --- | --- |
| Storyquest / Storyquest | Retrouver une découverte, une piste suivie, une contribution et une conséquence connue |
| Habitant / Inhabitant | Voir son identité, son parcours, son familier, les goûts découverts et l'état réel du passif |
| Combat / Combat | Comprendre son familier au combat, consulter les épreuves et accéder aux duels/arènes existants |
| Factions / Factions | Voir membres, rôles et projets partagés ; rejoindre un effort de faction |
| Découvertes et progression | Consulter Blocodex, connaissances et capacités existantes sans dupliquer le carnet |
| Gazette, tableau, calendrier | Lire les récits, proposer une rencontre et retrouver les rendez-vous |
Le menu ne doit pas activer une carte à distance ou effectuer une transaction
auprès d'un PNJ absent sans règle de jeu décidée. Il donne une direction et
permet de retrouver l'action dans le monde. Le bouton Friends est à retirer
du titre, avec réagencement et navigation clavier ; cela ne décide pas du sort
des autres fonctions sociales de Minecraft.
## Sanctuary Island : dessiner les destinations avant de régler le relief
La proposition est de décrire un **graphe de lieux** : ce que l'on aperçoit au
spawn, ce que l'on cherche, où l'on revient, ce qui se trouve en dessous et ce
qui ouvre l'horizon. La reprise du worldgen sert ce parcours. Elle rouvre la
conception de l'île clôturée historiquement en alpha.30.7.
Le nouveau départ sur l'île doit être concilié avec les **quatre expéditions
déjà ouvertes à la création** en beta.172. Préparer un contrat de nouveaux
mondes ; conserver les anciennes parties et leurs régions. Aucune suppression
d'île, réécriture de chunk ou activation n'est décidée par cette note.
### Liste initiale de lieux à travailler
| Lieu / famille | Rôle proposé | Décision encore nécessaire |
| --- | --- | --- |
| Spawn commun | Arrivée sûre, identité visuelle, place pour les premières installations | Vue d'arrivée, niveau d'aménagement et lien avec l'ancien choix d'un départ naturel |
| Secret et bloc originel | Trace de l'histoire et lecture de l'état du monde, sans palais imposé | Nouvelle forme, implantation et première découverte ; aucun emplacement de remplacement décidé |
| Huit liaisons | Huit horizons et usages distincts de l'île | Forme, répartition, coûts et ordre ; aucune règle « huit sur huit » imposée |
| Observatoire | Regarder le ciel et reconnaître des signes | Maintien du lieu retenu dans la conception précédente, contenu observable |
| Salle d'expansion | Trace d'une ancienne infrastructure réappropriable | Articulation avec les huit liaisons et les installations construites par les joueurs |
| Atelier caché | Plans, fabrication et mémoire de l'ancienne partie | Quel premier plan et quelle transformation utile |
| Grande traversée souterraine | Circulation et découverte du dessous de l'île | Entrées, passages et connexion au secret, sans confondre grottes et dimension Cavernes |
| Haltes de PNJ | Rencontres et services économiques vivants | Personnages, itinéraires, horaires et solution lorsqu'un PNJ est inaccessible |
| Donjon de départ | Première épreuve liée à une conséquence de progression | Objectif, préparation, boss éventuel, récompense et reprise après échec |
| Relais d'expansion | Prolongement du réseau sur les nouveaux territoires | Différence avec liaison, ancre d'aventure et salle d'expansion |
| ISS et structures en hauteur | International Sanctuary Station métallique, trace d'un ancien joueur, carte horizontale de l'île | Parcours vertical, fonction propre et éventuelle quête de familier ; éviter la duplication de l'observatoire |
Cette liste prépare la sélection ; elle ne réactive pas automatiquement les
villes, clusters et bâtiments mis en réserve dans [WG-26](structures-conception.md).
Chaque bâtiment devra avoir une fiche : ancien usage, histoire visible,
emprise, accès, contraintes de terrain, ressources récupérables, contenu,
statut obligatoire/facultatif, nombre et espacement, rotations, rôle narratif,
plan éventuellement constructible et critères d'essai.
Versionner ensemble sélection des lieux et règles de placement. Pour chaque
taille d'île, éprouver une même série de graines consignées, avec coordonnées,
captures et temps de génération. Un lieu indispensable sans emplacement valide
doit avoir une solution explicite ; le générateur ne peut simplement l'oublier.
## Donjons et anciennes fins du jeu
Le tableau conserve les **repères de progression du 27 septembre**, à
réexaminer. La discussion « Concevoir la salle secrète » envisage des cavernes
habitées et un Haut accessible par portail, avec redistribution de contenus du
Nether/End. Quatre dimensions distinctes ne sont donc plus une hypothèse de
travail acquise. Le [fil rouge courant](storyquest-fil-rouge.md) distingue ces
intentions des propositions de l'assistant ; aucun remplacement de dimension
n'est implémenté ni autorisé dans les anciennes parties.
| Branche | Rôle envisagé | Point à trancher avant génération |
| --- | --- | --- |
| Cavernes | Accès au diamant et approfondissement de l'exploration | Gisement, filière ou accès exclusif ; contrôler aussi coffres, échanges et autres sources si l'exclusivité est voulue |
| Nether | Production, chaleur et préparation aux épreuves exigeantes | Place des portails vanilla, conditions d'accès, ressource ou capacité débloquée |
| Océan | Exploration aquatique et infrastructures associées | Région océanique existante ou autre espace ; navigation, respiration, sortie et récupération |
| End | Grand accomplissement et ouverture de possibilités nouvelles | Rôle du Dragon et du dénouement, lien avec les cycles personnels et la suite collective |
Le donjon doit définir ce qui change après sa réussite, qui en bénéficie et ce
que fait un nouvel arrivant après cette réussite. Prévoir exploration, préparation,
combat et métiers ; une faction de constructeurs ou de cuisiniers doit pouvoir
contribuer. Ne pas assimiler automatiquement une fin de donjon à un New Game+.
### Structures célestes à la demande
Garder les **cartes de découverte** pour révéler un lieu existant, les **cartes
d'épreuve** pour lancer une activité, et les **clés/reliques** pour un accès
précis. Une horde actuelle fait apparaître des entités sans écrire de blocs :
elle ne constitue pas encore un générateur de donjon.
Proposition pour une nouvelle structure : choisir un modèle autorisé → réserver
une emprise 3D et un accès → enregistrer graine et version → préparer progressivement
→ vérifier l'arrivée et la sortie → ouvrir l'aventure. Une interruption reprend
la même réservation. Deux groupes ne créent ni structures superposées ni butins
dupliqués. La visibilité d'un vide à l'écran ne prouve pas qu'il est libre.
Décliner ensuite bateau pirate, diligence, arène et donjon thématique. Commencer
par un seul modèle. L'apparition d'une structure immobile et le déplacement
réel d'un véhicule constituent deux problèmes distincts à décider. Son devenir
après l'épreuve doit permettre retour, récupération et éventuelle appropriation ;
aucun effacement automatique de constructions de joueurs n'est présumé.
## PNJ, économie et vie communautaire
Les PNJ sont des habitants de l'ancienne partie avec des occupations et des
trajets, pas seulement des boutons de boutique. Leur présence doit produire
des rencontres, des habitudes, des indices et parfois des rendez-vous.
Il faut concilier cette direction avec la cosmologie actuelle, qui décrit les
anciens personnages comme disparus. Revenants, survivants, manifestations ou
révision du canon sont des possibilités à discuter ; aucun choix n'est déduit
du seul souhait de les voir marcher sur l'île. Ne pas attribuer d'office un
métier ou le secret du spawn à un personnage nommé.
Pour une première économie, éprouver une semaine cohérente : PNJ des navets le
dimanche → conservation et revente → péremption → rendez-vous de loterie → shop
spécial et demandes de production. L'achat dominical, la revente en semaine et
le pourrissement après une semaine viennent du cadrage du 25 septembre. Définir
encore échéance exacte, fuseau serveur, cours communs, stocks, monnaie, financement,
machines de loterie et devenir des pertes. L'approvisionnement du shop ne doit
pas annuler les étapes des donjons. Le lien au ballast reste à concevoir.
| Surface | Fonction proposée dans la même boucle |
| --- | --- |
| Minecraft | Action, transactions, preuves de quête, apparition des lieux et attribution des récompenses, sous autorité serveur |
| Site | Identité liée, candidature, mémoire consultable, calendrier, projets et préparation d'une prochaine session |
| Discord | Discussion, forum, rendez-vous et rappels choisis, avec liens vers le bon objet du site ou du jeu |
Un événement ou projet possède une identité commune entre les surfaces. Prévoir
changements d'horaire, annulations, messages déjà envoyés et reprise après panne.
Les préférences règlent les rappels ; chaque action mineure en jeu ne devient
pas une notification. Les fonctions de participation web devront disposer de
leurs droits explicites : le contrat actuel Gazette/tableau côté site est en
lecture seule. Préserver les découvertes secrètes lors de toute publication.
La participation web peut préparer un événement ou prolonger sa mémoire ; une
discussion Discord ne constitue pas à elle seule une preuve de combat. Le jeu
continue lorsque les services communautaires sont indisponibles, selon les
règles d'accès déjà configurées. La synchronisation complète reste à construire.
## It's Alive ! et les familiers : découvrir, soigner, préparer
La reprise historique réunit les cultures, les postes culinaires, les recettes
et les créatures. **Porter le module vers Minecraft 26.3** demande d'examiner
ses dépendances réelles ; le JAR 26.2 ne peut être ajouté sur supposition.
It's Alive conserve la responsabilité de ses systèmes autonomes. Sanctuary
raccorde quêtes, économie et familiers par des contrats explicites.
Pour la cuisine, partir des combinaisons et substitutions existantes. Le livre
révèle une recette sans empêcher de la deviner. La combinatoire souhaitée doit
préciser jusqu'où elle va : catalogue de recettes par familles ou plats dynamiques
aux propriétés calculées. Le second modèle n'est pas établi par l'ancien catalogue.
Proposition pour un familier : essayer un produit → observer une réaction →
mémoriser un goût découvert → préparer un plat apprécié → constater l'effet du
lien en exploration ou en combat. Définir goûts d'espèce et éventuelle préférence
individuelle, sans attribuer dès maintenant un aliment favori à chaque créature.
Une préférence doit survivre aux changements de forme et à la reconnexion.
| État proposé | Utilité recherchée | Vérification de jeu |
| --- | --- | --- |
| À côté du joueur | Personnalité, présence et rôle autonome perceptible | Observer une contribution pendant une horde et une activité calme |
| Porté sur la tête | Passif activé ou renforcé, clairement indiqué | Porter/retirer change réellement l'effet ; aucune accumulation par changement rapide |
| Nourri avec un produit apprécié | Relation et préparation valorisées | Réaction compréhensible, goût mémorisé, effet mesurable et borné |
Le choix **passif seulement porté** ou **passif de base renforcé sur la tête**
reste ouvert. Réexaminer la désactivation actuelle des passifs en combat,
les chapeaux, le slot familier, les piles d'entités et l'état monté. Commencer
avec quelques espèces contrastées. L'incubation d'XP évoquée le 25 septembre
reste une piste distincte, à articuler sans ajouter un rendement implicite.
Le 28 septembre, le créateur propose aussi un **chenil physique** : déposer
un familier pour lui faire gagner de l'XP, avec deux yeux visibles sur le bloc
occupé et de possibles multiblocs adaptés aux tailles. Cette piste est détaillée
dans la [fiche palais / ISS / incubateurs](palais-originel-iss-beta173.md) ; elle
ne remplace pas encore l'incubation d'XP liée aux actions évoquée précédemment.
Pour les créatures, préparer les cinq rôles d'Infectés, les seize variantes de
Moobloom, les fantômes et la baleine ; traiter Bracken comme candidat historique
à discuter. Le retrait des phantoms doit couvrir leurs apparitions, les cartes
de horde, les œufs, les familiers et les contenus sauvegardés. Prévoir une règle
explicite pour les anciens exemplaires ; ne pas supprimer silencieusement les
compagnons ou les cartes déjà possédés. La baleine garde une place de respiration
et de surprise dans une île qui peut aussi devenir inquiétante.
## Construire et ressentir ce monde
La bibliothèque de plans doit proposer des ensembles cohérents : abris,
passerelles, cuisine, ateliers, haltes, modules de faction et grandes constructions.
Un kit comporte aperçu, dimensions, liste des matériaux, étapes, variantes,
points de raccord et fonction réelle. Commencer par un petit kit complet,
combinable et réalisable en survie, puis élargir. Les schematics personnels et
le Métabli restent les bases ; le chantier partagé persistant est encore à concevoir.
Les plans peuvent devenir des trouvailles ou des récompenses, et les ruines
montrer une ancienne version de ce que les joueurs sauront construire. Tous
les plans utilitaires de départ n'ont pas besoin d'être verrouillés par le récit.
L'ambiance accompagne les mêmes lieux : vent en altitude, poids sourd des
profondeurs, calme de surface et signes SGA ponctuels. Définir altitude absolue,
distance au sol et abri pour éviter du vent permanent dans une maison élevée.
Fondus, réglages de volume et indices visuels évitent les ruptures et rendent
les indices utilisables sans le son. Mesurer le shader sous **Vulkan**, avec
scènes, résolution, machine et réglages consignés ; séparer coût des effets,
chargement du terrain et densité des entités avant de changer les qualités.
### Ce que les inspirations doivent apporter
Ces références expriment des sensations recherchées, pas des fonctionnalités
déjà choisies ni un import de personnages ou de ressources.
| Inspiration citée | Traduction proposée pour Sanctuary |
| --- | --- |
| Nintendogs | S'attacher à un compagnon par des soins et réactions reconnaissables |
| Animal Crossing | Habitudes, habitants itinérants, dimanche, économie et souvenirs |
| DOOM | Combats lisibles, mouvement, rythme et rôles ennemis distincts |
| Undertale | Étrangeté, humour, tendresse et relecture de ce que l'on croyait comprendre |
| Pokémon | Collection, découverte des préférences et complémentarité des familiers |
| Fire Emblem | Liens entre personnages, rôles tactiques et projets de groupe |
| Final Fantasy | Monde habité, donjons marquants, progression et grands moments partagés |
| LEGO | Kits compréhensibles, assemblage modulaire et liberté de recombinaison |
## Réserve de conception et priorités courantes
La beta.173 a livré le prototype du palais, pas toute la refonte. Depuis le
29 septembre, l'ordre de travail par systèmes est remplacé par les
[parcours jouables du fil rouge](storyquest-fil-rouge.md#avancer-par-parcours-jouables).
Les anciens noms de lots ci-dessous restent des repères de conception,
pas des tickets externes créés ni des développements en cours.
| Sujets en réserve | Contribution à choisir lorsqu'une scène en a besoin |
| --- | --- |
| SQ-01 / SQ-02 — Storyquest | Une découverte et une conséquence persistante, sans doublons de récompense et avec accueil des nouveaux arrivants |
| WG-173 / EXP-173 — île et liaisons | Une destination et un accès réellement jouables ; génération versionnée et reprise sous contrat de nouveaux mondes |
| UI-173 — interfaces | Les informations nécessaires au trajet ; retrait de Friends ; refonte FR/EN, clavier et petits GUI à éprouver |
| PET-173 / FOOD-01 — familier et cuisine | Une préparation qui change réellement l'exploration ; comportement et sauvegarde vérifiés, compatibilité 26.3 examinée |
| LIFE-01 — créatures | Un rôle utile à une épreuve ou un lieu ; port vérifié et devenir explicite des phantoms existants |
| NPC-01 / ECO-01 — habitants et économie | Une rencontre récurrente et un service éprouvé avant de généraliser les navets, la loterie et le shop |
| BUILD-173 — plans | Un premier kit constructible et utile sur le terrain retenu |
| DUN-01 / SKY-01 — donjons et ciel | Une entrée, une épreuve et un retour sûrs ; chaque lieu sert une conséquence choisie |
| AMB-173 — ambiance | Les sons et le rendu de la scène courante, comparés sous Vulkan |
| WEB-173 — communauté | Prolonger un rendez-vous ou un projet réel, avec identité, droits, préférences, annulation et dédoublonnage |
## Les choix à prendre ensemble
1. Quel premier trajet extérieur fait comprendre l'île et une ancre ? Quelle
esthétique lui convient, et où l'état du bloc originel devient-il lisible ?
2. Qui sont les anciens PNJ présents à la surface, compte tenu de leur
disparition dans le récit actuel ?
3. Quel premier fragment voulons-nous faire jouer, et quelle trace visible
laisse-t-il pour la personne, le groupe et le serveur ?
4. Les huit liaisons offrent-elles des choix dès le départ, des branches ou
un ordre partiel ? Que deviennent les quatre anciennes expéditions dans
les nouveaux mondes ?
5. Le passif du familier apparaît-il uniquement sur la tête, ou y devient-il
plus fort ? Quels premiers compagnons doivent faire ressentir la différence ?
6. Quelle fonction donne envie de retourner à l'ISS ? Comment le chenil
complète-t-il l'aventure avec son familier ? Le choix d'un mod d'ordinateurs
reste ouvert après définition du parcours de préparation des expéditions.
Les montants de l'économie et l'ordre des quatre grands donjons viennent après
ces décisions. On peut dessiner les interfaces avec des exemples explicitement
fictifs, sans figer leurs règles ni les présenter comme des données du serveur.
## Réalisation et validation de ce cadrage
Le cadrage initial a livré la branche, l'inventaire sourcé et des propositions.
Le prototype beta.173 réalisé depuis a sa propre fiche de validation. La
révision du 29 septembre actualise la direction, les priorités et les liens
depuis README, vision et backlog. Code, versions, sauvegardes et distribution
inchangés par cette révision ; aucun test de jeu relancé pour la documentation.
Chaque futur lot de code devra exécuter `./gradlew check build`, puis
`./gradlew assemblePack` si la distribution change, et ses essais comportementaux
ciblés. Une évolution des sauvegardes ou de la génération exige son contrat
avant implémentation. beta.173 est déjà utilisée ; la prochaine livraison de
code consommera le prochain numéro disponible selon le compteur du dépôt.
+316
View File
@@ -0,0 +1,316 @@
# Sanctuary Island — fil rouge de la refonte
## Retour au laboratoire — 1er octobre 2026
Le créateur demande une nouvelle visite avec la graine 0 pour décider si le
worldgen reste en pause. [WG-OPTIONS-195](worldgen-options-beta195.md) conserve
le terrain beta.193 et nettoie la liste de création : Sanctuary et les types
vanilla, sans variantes de labo visibles. Le menu sans Amis sera examiné à la
sortie du solo. Aucune nouvelle retouche de relief ni série de captures décidée.
Le choix d'intégrer le profil de labo à la génération publique reste ouvert.
## Reprise du 1er octobre 2026 — interfaces et usages
**Décision du créateur :** arrêter les séries de screenshots et les retouches
de génération, biomes et chunks. Le laboratoire beta.193 reste le dernier
état de comparaison, avec ses réserves visuelles documentées ; cette pause
ne vaut pas validation esthétique complète. Les priorités worldgen ci-dessous
sont désormais historiques.
**Premier lot demandé : [UI-194](main-menu-beta194.md).** Retirer Amis du menu
principal, rapprocher Options et Quitter de Solo/Multijoueur et conserver la
navigation clavier. Ce retrait figurait déjà dans le cadrage Storyquest initial.
**Proposition de suite à discuter, sans lancement implicite :**
| Sujet déjà envisagé | Premier résultat utile |
| --- | --- |
| Habitant | Une page claire pour l’identité, la progression et le familier, à partir des données réellement disponibles |
| Combat et Factions | Clarifier les accès aux épreuves, aux membres et aux projets communs ; distinguer les fonctions présentes des projets futurs |
| Storyquest | Choisir une première découverte reliée au SGA et à une activité existante, puis afficher sa piste et sa conséquence personnelle ou collective |
| Familiers et cuisine | Revoir l’utilité d’un passif existant selon le port sur la tête et choisir un goût à découvrir ; leur lien avec la préparation d’une sortie reste à concevoir |
Le prochain travail peut utiliser l’île et ses lieux actuels. Les expansions,
la nouvelle hydrologie et de nouvelles structures ne sont pas des dépendances
de cette reprise des menus. Aucun passif, nouvelle quête ou écran supplémentaire
n’est annoncé comme implémenté par UI-194.
## Historique du chantier terrain
**Après la visite beta.190 :** le créateur demande de revenir au relief d’avant
les plateaux et de retirer aussi leurs affleurements. [WG-RESTORE-191](restore-relief-beta191.md)
restaure la base beta.188 ; cavités, creux et pointes restent la direction,
avec des formations singulières ponctuelles plutôt qu’un traitement uniforme.
**Après la visite beta.189, 30 septembre :** [WG-NATURE-190](natural-refinement-beta190.md)
reprend le relief naturel et les cascades. Ruines et salle inférieure appréciées ;
le choix de la ressource renouvelable d’activation du portail reste ouvert.
Les terrasses ciselées sont réservées à une expansion future dans le backlog.
La rosace actuelle est à Y=640 et le palais supérieur à Y=1278 ; les paragraphes
antérieurs ci-dessous conservent l’historique de conception.
**Après la visite beta.181 :** palette naturelle validée ; dégager les huit
axes. Le [lot ORIGIN-182](origin-gallery-beta182.md) prépare la galerie
souterraine et la variante aérienne en rosace. L’origine reste à Y=320.
**Actualisation du 30 septembre :** relief et donjon beta.180 appréciés en
visite. Le [lot ORIGIN-181](origin-landmark-beta181.md) fixe le bloc originel
en **(0,320,0)** dans un petit repère aérien ouvert. Il conserve les demandes
de trois secteurs de gemmes, d’une poche de soufre et de huit salles d’ancres
naturelles, ainsi que les pistes de galerie inférieure et d’ISS. Ces suites
ne sont pas encore implémentées. Les hésitations de position plus bas sont
l’historique antérieur à cette décision.
**État au 29 septembre 2026.** Point d'entrée pour la conception en cours sur
`codex/storyquest-beta173`. Ce document remplace les anciennes priorités de
[Storyquest](storyquest-beta173.md) et le projet d'intégration du palais.
Il sépare les décisions du créateur, le socle vérifié et les propositions de
travail. Les étapes ci-dessous sont un ordre de développement à discuter,
pas un ordre de déblocage imposé aux joueurs.
**Priorité révisée pendant la séance : rendre le worldgen rapide à essayer.**
Le créateur veut commencer par la génération de Sanctuary Island et couper
temporairement les calculs lourds dans **un labo uniquement**. Le
[lot WG-LAB-174](worldgen-lab-beta174.md), sur `codex/worldgen-lab-beta174`,
passe avant les maquettes de halte : relief → hydrologie mesurée → lieux
indépendants → détails réunis. Les parcours Storyquest restent la destination
de cette reprise ; ils ne bloquent pas le travail sur le terrain.
**Étape 0 livrée localement en beta.174 :** labo de relief, comparaison complète,
mesures, trois graines contrôlées et validation Vulkan. La prochaine itération
peut travailler les formes et les parcours de l'île sur cette base ; le
nouvel algorithme d'hydrologie et la nouvelle géographie ne sont pas encore faits.
**Suite décidée : [WG-SKY-175](sky-fragments-beta175.md).** Garder l’île
principale et sa hauteur ; remplacer les anciennes îles flottantes par des
récifs rares jusqu’à 512, puis réserver 512–640 à l’ISS. Le créateur préfère
les relevés scientifiques aux vues en jeu. Répartition demandée : beaucoup
de cuivre et charbon, peu de fer, pas d’or ni de redstone ; lapis, améthyste
et deepslate, diamant rare et enfoui. Ces changements sont éprouvés dans
un profil de labo neuf ; l’ISS et les éventuels nouveaux blocs restent à concevoir.
**Retour de visite du 29 septembre :** le plateau est conservé. La
[fiche de reprise beta.175](worldgen-retours-beta175.md) relie les captures
aux prochains essais : strates rocheuses plus continues, grands secteurs
automnaux et cerisiers, identité écologique des récifs et petites eaux
locales. Le créateur retire les mineshafts de l'île de départ et réserve
leurs variantes aux expansions ; les villages doivent être propres à
Sanctuary. Les constats de code et les propositions y sont séparés des
changements effectivement livrés. La sauvegarde de visite reste le témoin.
**Essai suivant réalisé : [WG-ECO-176](island-ecology-beta176.md).** Nouveau
labo avec strates ondulées, larges régions automnales et cerisiers, récifs
végétalisés et mares locales. Trois graines et une réouverture passent les
contrôles natifs ; aucune hydrologie régionale ni structure native admise.
Le recensement de toute l'île 42 donne 336 diamants enfouis. La visite sert
maintenant à régler les proportions et les transitions ; ce profil ne remplace
pas encore la génération normale ni les anciens mondes.
## Le changement de cap
**Correction demandée après cette visite : [WG-ECO-177](relief-ecology-beta177.md).**
Le plateau doit redevenir forestier et fleuri. L'automne suit les vallées
extérieures, les cerisiers les hauteurs ; les cavités couvertes restent
humides, moussues, avec chênes noirs et champignons. Retirer la neige et la
mangrove, réserver le marais aux récifs, laisser les fragments supérieurs
plus sobres. Conserver les strates appréciées et les mêler à des gisements
de roche. Le créateur demande un nouveau solo à 32 chunks ; état des
vérifications et limites dans la fiche du lot. Ce choix remplace les bandes
de surface beta.176 pour le nouvel essai uniquement.
**Décidé le 29 septembre :** abandonner le palais souterrain, ses accès par
chute dans l'eau et escaliers, et les raccordements des lieux au palais.
Concevoir des plans géographiques indépendants. Refaire **Sanctuary Island,
l'île de départ** : « Nether Island » dans la dictée désignait cette île,
comme le créateur l'a précisé ensuite.
Les huit ancres, leurs matériaux distincts et les couleurs du bloc originel
ne sont pas annulés par cette décision. Leur implantation et leur relation
de jeu sont à reprendre sans dépendance architecturale au palais. La règle
« le bloc suit le palais » n'a donc plus de lieu auquel s'appliquer ; aucune
nouvelle position du bloc, à Y=0 ou ailleurs, n'est décidée.
Le palais beta.173 reste un prototype technique documenté. Son abandon dans
la conception ne supprime ni son code ni sa sauvegarde de laboratoire. Son
intégration sous l'île sort du plan de travail.
| Statut | Ce que l'on garde en tête |
| --- | --- |
| Direction confirmée | Entrée SGA, histoire découverte par les activités, Sanctuary Island comme départ, huit ancres à demandes de matériaux distincts, ambivalence « cursed & healed » |
| Architecture abandonnée | Palais souterrain central, accès associés, disposition des lieux dictée par les raccordements au palais |
| Encore ouvert | Esthétique de l'île, emplacement et rôle du bloc originel, géographie des ancres, ordre des découvertes, devenir des dimensions |
| Idées conservées à explorer | ISS, cuisine, familiers utiles, chenil, plans constructibles, habitants, économie et vie communautaire ; elles n'exigent pas de palais |
## Où nous en sommes réellement
| Socle | État documenté | Conséquence pour la suite |
| --- | --- | --- |
| beta.172 | Arrivée et code SGA, hordes par cartes, interfaces communautaires, familiers et outils de construction existants | Partir de ces gestes ; le Storyquest qui les relie reste à développer |
| Île et expansions | Quatre régions d'expédition déjà ouvertes à la création dans le socle audité | Un départ limité à Sanctuary Island demande un contrat explicite de nouveaux mondes |
| Prototype beta.173 | Palais variant par seed ; huit dépôts ; bloc éteint, monochrome lumineux, puis huit gemmes indépendantes ; sauvegarde vérifiée | Réutiliser les mécaniques utiles après examen de leur dépendance au laboratoire ; aucune expansion n'est déclenchée |
| Validation beta.173 | `check build assemblePack`, 265/265 GameTests, essai client Vulkan et reprise du monde consignés | Preuves du prototype existant, pas validation de la nouvelle géographie |
| Refonte globale | Menus, récit persistant, nouvelles dimensions, économie, reprise d'It's Alive et nouvelle bibliothèque de plans restent des chantiers | Choisir une contribution utile par scène plutôt que lancer tous les systèmes ensemble |
Sources : [audit du socle beta.172](storyquest-socle-beta172.md) et
[livraison locale beta.173](palais-prototype-beta173.md). Cette remise à plat
est documentaire ; elle ne constitue pas un nouvel audit du code ni un essai
de jeu. beta.173 est déjà utilisée, sans publication du canal ou mise à jour
de Prism par ce chantier.
## Un fil rouge proposé : remettre l'île en relation
Le joueur arrive dans un monde qui a déjà servi. Il reconnaît des signes,
comprend un usage ancien, prépare une sortie et rend un lieu de nouveau utile.
Chaque progrès lui donne une raison d'explorer, de construire ou de revenir
avec quelqu'un. Le SGA relie les traces ; les conséquences racontent l'histoire.
```mermaid
flowchart LR
A[Arriver par le SGA] --> B[Repérer une trace dans un lieu]
B --> C[Préparer une sortie]
C --> D[Explorer et contribuer]
D --> E[Transformer un lieu]
E --> F[Revenir, habiter et partager]
F --> C
E --> G[Découvrir un nouvel horizon]
G --> B
```
**Première scène proposée :** depuis une arrivée sûre, apercevoir une ancienne
halte, y reconnaître un motif SGA et découvrir une ancre avec une demande
claire. Rapporter son matériau produit un changement visible et persistant.
Dans le premier essai, ce changement peut se limiter à l'allumage déjà
prototypé. L'ouverture d'un territoire viendra dans un lot distinct, annoncé
comme tel : une lampe allumée ne prouve pas qu'une expansion fonctionne.
Cette scène fait avancer ensemble le spawn, un bâtiment, une ancre, la lecture
du paysage, l'ambiance et le retour d'information. Un familier, une recette,
un PNJ ou une horde s'y ajoute ensuite seulement s'il change réellement ce que
le joueur prépare ou découvre. L'histoire ne dépend pas d'avoir tout fini.
## Trois plans qui répondent à trois questions
Les lieux ont une implantation propre. Un lien logique entre une ancre et le
bloc originel n'impose ni pont, ni couloir, ni alignement avec un palais.
| Plan | Ce qu'il explique | Première version à produire |
| --- | --- | --- |
| Vue du dessus de Sanctuary Island | Relief, température et humidité, arrivée, silhouettes, chemins et huit secteurs d'exploration | Une carte schématique avec un seul parcours détaillé ; les autres secteurs restent réservés sans bâtiment obligatoire |
| Coupe et accès aux autres espaces | Haut/bas, clair/sombre, visible/caché ; surface habitée, infrastructures et seuils | Une coupe de l'île et des schémas séparés pour les espaces accessibles par portail ; distinguer altitude et changement de dimension |
| Graphe de progression | Ce qu'une découverte permet, qui contribue et ce qui change | Arrivée → lieu → demande → conséquence → nouvel horizon, avec états personnels et collectifs distingués |
La conversation [« Concevoir la salle secrète »](chatgpt-conversation://6abb9cdf-9a10-83eb-ac7e-8d481ce3352c)
apporte la température et l'humidité, l'axe visible/caché, des cavernes déjà
habitées et dominées par les villageois souterrains, ainsi que l'idée d'un
portail horizontal pour le Haut. Ce sont des intentions à développer, pas des
dimensions livrées.
La redistribution du contenu du Nether et de l'End dans de nouvelles dimensions
est une **piste radicale en réflexion**, pas une suppression autorisée des
mondes actuels. Le placement cardinal des climats, la formule
« surface = territoire / cavernes = matière / Haut = information », la
symétrie observatoire/Core et les conditions de prestige venaient des réponses
de l'assistant : ce sont des propositions, sans adoption implicite.
L'ISS peut rester une trace dans le ciel de l'île. Son éventuel rôle dans
l'accès au Haut est à décider ; station en altitude et dimension du Haut ne
sont pas automatiquement le même espace. L'abandon du palais ne tranche pas
le sort de tous les souterrains ni des autres lieux existants.
## Chercher l'esthétique sur une scène comparable
**Recommandation de départ, à éprouver : une infrastructure ancienne rendue
habitable.** Elle exprime à la fois la trace inquiétante du SGA, la douceur
des usages quotidiens et la place laissée aux constructions des joueurs.
Éviter de transformer toute l'île en décor fini : le terrain doit encore
donner envie d'y construire.
Comparer deux variantes de la **même halte extérieure**, avec la même fonction,
le même gabarit et le même trajet. Les palettes ci-dessous sont des propositions
de matériaux déjà présents dans Minecraft, sans ajout de mod esthétique.
| Variante | Silhouette et matière | Sensation à éprouver |
| --- | --- | --- |
| A — Halte réhabitée, recommandée | Base en pierre, réparations en bois, touches de cuivre, végétation, abri ouvert ; repère SGA discret | Une ancienne installation que l'on comprend et que l'on a envie de prolonger |
| B — Vestige minéral | Pierre claire et ardoise sombre, métal ponctuel, volumes plus géométriques et ajourés ; quelques aménagements chaleureux | Une infrastructure étrange dont la fonction se révèle par l'usage |
Un même langage peut ensuite relier les lieux : formes et signes récurrents
pour les traces anciennes, ajouts pratiques pour l'habitation, espaces libres
pour les joueurs. Les couleurs des huit gemmes servent d'abord à lire un état ;
elles ne fixent pas huit palettes de biomes ni huit matériaux de construction.
**Essai proposé :** mêmes points de vue de jour et de nuit, trajet depuis le
spawn, lecture de l'ancre éteinte/allumée, place pour construire et perception
des sons. Retenir ce qui rend la destination visible, le geste compréhensible
et le retour agréable. Comparer le coût du rendu à réglages Vulkan identiques.
Les maquettes et cette comparaison restent à réaliser.
## Avancer par parcours jouables
Chaque étape croise quelques systèmes et se termine par un essai. On ne
développe pas toute la géographie, puis tous les menus, puis toute la cuisine.
Une seule nouvelle scène sert de référence à la fois ; les autres idées restent
en réserve. L'essai peut conduire à simplifier, déplacer ou abandonner une idée.
| Étape proposée | Résultat concret | Ce qui avance ensemble | Vérification avant d'élargir |
| --- | --- | --- | --- |
| 0. Itérer sur le worldgen — priorité actuelle | Un labo de relief rapide et un profil complet de comparaison, dans des mondes séparés | Temps de démarrage, forme de l'île, biomes et outils d'observation | Mesures à froid/réouverture, relief comparé à la référence, aucun plan hydrologique caché ; voir WG-LAB-174 |
| 1. Arriver et comprendre | Un trajet extérieur spawn → halte → ancre indépendante, deux variantes visuelles | Relief local, bâtiment, motif SGA, matériau demandé, feedback et ambiance | Trouver le lieu, comprendre le dépôt, voir son effet, revenir ; comparer les variantes et reprendre l'état après reconnexion |
| 2. Donner une conséquence | Une ancre ouvre réellement une première destination dans un nouveau monde de test | Génération, progression commune, trajet aller/retour et mémoire d'une découverte | Contrat de génération avant code ; dépôt répété, duo, interruption/reprise, emplacement libre et retour sûr ; tester plusieurs graines consignées |
| 3. Donner envie de préparer et revenir | Une préparation utile au parcours : un familier et un produit, ou un plan constructible selon le besoin révélé par l'essai | Vie quotidienne, exploration, Habitant et construction ; première rencontre si elle sert cette boucle | Effet observable avant/après, ressources compréhensibles, sauvegarde ; dépendances 26.3 vérifiées avant tout port d'It's Alive |
| 4. Ouvrir la verticalité | Un premier seuil vers les cavernes ou le Haut, choix encore ouvert | Accès, ambiance, ressource ou capacité, première épreuve et récit | Entrée/sortie, échec/récupération, intérêt après réussite et accueil d'un joueur arrivé plus tard |
| 5. Décliner ce qui fonctionne | Autres secteurs, ancres et lieux ; plusieurs usages et contributions | Variété géographique, groupes spontanés, factions et mémoire communautaire | Chaque variante apporte une raison de partir ; absence de huit répétitions du même trajet |
La lecture minimale d'une demande ou d'une découverte accompagne sa scène dans
les interfaces existantes. La refonte complète Habitant/Combat/Factions reste
dans la réserve ; elle se précisera avec les gestes réellement testés. Le
retrait demandé de Friends est traité dans [UI-194](main-menu-beta194.md), sans
en faire une dépendance de la carte de l'île.
Les navets, la loterie, le shop, l'ensemble des créatures, le chenil, les
ordinateurs et les grandes structures volantes ne sont pas des prérequis de
la première scène. Leur priorité remonte lorsqu'un parcours en a besoin.
Le site et Discord prolongeront une activité commune éprouvée ; les droits,
les préférences de rappel et les règles de synchronisation restent à concevoir.
## Préparer la prochaine séance de développement
Le labo rapide de l'étape 0 est disponible. Il permet de travailler les
volumes, les cavités, les climats et les parcours sans
attendre toutes les recherches de structures et d'hydrologie. Les mesures
et le protocole sont dans [WG-LAB-174](worldgen-lab-beta174.md).
La scène extérieure de l'étape 1 suivra ce travail sur le terrain. Pour la
préparer, compléter une fiche courte :
- une carte du trajet et une coupe du terrain, avec l'emprise des deux variantes ;
- ce que le joueur voit, comprend, apporte et observe après son dépôt ;
- où se lit l'état de l'ancre et quel rôle expérimental garde le bloc originel ;
- une fiche de génération du lieu : relief accepté, accès, variantes par seed,
contenu nécessaire et solution si son placement est impossible ;
- la graine, la version de génération et les points de capture de l'essai.
Après la visite, consigner **garder / ajuster / abandonner** avec une observation
de jeu. Mettre à jour cette page et la fiche du lot, puis choisir la prochaine
conséquence à rendre jouable. Une intention n'est cochée que lorsque son
résultat a été essayé ou vérifié.
Questions encore ouvertes : fonction et emplacement du bloc originel ; carte
des huit secteurs et correspondance climatique ; statut narratif des anciens
PNJ ; première dimension à explorer ; redistribution du Nether/End et rôle
de l'ISS. Aucune de ces questions n'impose de dessiner le monde entier avant
la première halte.
## Cadre de réalisation
La première remise à plat était documentaire. La demande suivante ouvre le
lot de code WG-LAB-174, dont la fiche distingue contrat, réalisation et mesures.
Les anciens documents du palais portent un avertissement de statut et gardent
les preuves des essais déjà effectués ; son laboratoire est préservé.
Chaque évolution future de génération devra préciser nouveaux mondes ou
migration, seed, version, reprises et autorité serveur avant toute écriture.
Les identifiants `sanctuary:*` restent stables ; aucun retrait de blocs ou
remplacement de dimension n'est déduit de cette note. Le prochain lot de code
utilisera le prochain numéro beta disponible, exécutera `./gradlew check build`,
et `./gradlew assemblePack` si la distribution change ; rendu validé sous Vulkan.
+96
View File
@@ -0,0 +1,96 @@
# Storyquest — état du socle beta.172
Relevé du **27 septembre 2026**, préparatoire à la
[refonte Storyquest](storyquest-beta173.md). Inspection de Git, des contrats et
des sources ; ce relevé n'est ni un nouveau playtest ni un audit exhaustif de
tous les comportements du pack.
## Le bon point de départ
| Élément vérifié | État |
| --- | --- |
| Checkout initial `sanctuary-beta` | Branche `codex/pixel-shadows-beta122`, HEAD `9b0104d`, changements suivis et non suivis en cours ; conservés |
| Dernier socle local de jeu | Branche `codex/horde-runtime-beta172`, commit `55c745a`, checkout `sanctuary-community-beta154` propre lors du relevé |
| `main` local | `8ab5a45`, étape beta.168 |
| `origin/main` après `git fetch origin` | `7979f33`, étape beta.166 ; ancêtre du socle 172, avec dix commits d'avance locale sur cette référence |
| Nouvelle branche de cadrage | `codex/storyquest-beta173`, depuis `55c745a`, worktree géré `storyquest-beta173/sanctuary-beta` |
| Versions du nouveau checkout | Mod et pack beta.172 ; resource pack beta.121 ; cible Minecraft 26.3, Java 25, Fabric Loader 0.19.5, API 0.160.5+26.3 |
| Ancienne branche `codex/rework-pingpong-173` | Pointe sur `9b0104d`, sans note Storyquest trouvée ; laissée intacte |
Le numéro du resource pack et le nom historique d'un checkout ne déterminent
pas la version du jeu. Aucune conclusion sur un bug de beta.122 n'est tirée
de sa simple présence dans le checkout initial. Ce chantier repart du code 172.
La publication et le canal public ne sont pas déduits des branches locales.
## Ce qui existe et ce qu'il faut relier
Les chemins Java ci-dessous sont relatifs à
`mods/sanctuary/src/main/java/fr/koka/sanctuary/` dans le socle 172.
| Sujet | Preuve locale / état observé | Conséquence pour la refonte |
| --- | --- | --- |
| Code et intro SGA | `client/HelloWorldScreen.java` formate la saisie avec `minecraft:alt` ; `client/IntroScreen.java` et `intro/IntroSequence.java` composent l'introduction | Une continuité visuelle existe ; une grammaire narrative commune reste à concevoir |
| Inscription web | [beta.166](inscription-web-beta166.md) : candidature, code, reçu lié à l'admission et reprise après coupure | Réutiliser le contrat ; l'OAuth authentifié complet et le déploiement public ne sont pas établis par ce relevé |
| Pause | `client/CommunityPausePanels.java` : Blocodex, Progression, Habitant, Faction, Combat, carte, Gazette, tableau, date et intendance | Refaire les parcours en préservant les fonctions déjà disponibles |
| Friends | `mixin/client/TitleMenuMixin.java` remplace la rangée Realms par Friends et retire le bouton social natif | Retrait explicite de la rangée et réagencement nécessaires ; pas encore réalisés |
| Habitant / factions / combat | `client/ProgressionScreen.java`, `client/CycleFactionScreen.java`, `client/ArenaScreen.java` ; contrats [factions](cycle-factions-beta011.md) et [combat](combat-beta078.md) | Réorganiser des systèmes existants ; ne pas présenter factions et combats comme absents |
| Hordes | `horde/HordeCards.java`, `HordeTrials.java`, `HordeRunes.java` ; contrats [170](horde-map-native-beta170.md), [171](horde-families-beta171.md), [172](horde-runtime-beta172.md) | Cartes identifiées, familles, runes et activation de groupe disponibles ; pas un moteur Storyquest complet |
| Expansions | [Quatre anciennes expéditions](expeditions-beta001.md), registre et placement dans `expansion/` | Les quatre régions existent dès la création ; l'île seule au départ change le contrat des nouveaux mondes |
| Lieux de l'île | [WG-26](structures-conception.md) retient observatoire, salle d'expansion, atelier caché, traversée et donjon à définir | Réconcilier cette sélection avec spawn, secret et huit liaisons avant nouvelle génération |
| Familiers | `companion/CompanionService.java` et `CompanionPassives.java` : passifs existants ; sélection par œuf du slot familier, délai d'équipement ; retour nul en combat actif hors mode utilitaire | Le besoin porte sur l'utilité, le port sur la tête et les conditions d'activation ; éviter d'empiler un second système contradictoire |
| Préférences alimentaires | `familiar/FamiliarSupplies.java` réserve notamment des aliments selon leurs propriétés ; le port culinaire n'est pas présent dans les modules de la bêta | Aucun système complet de goûts culinaires individuels n'a été établi par l'inspection ; à concevoir et vérifier |
| Phantoms | `mixin/RealtimePhantomMixin.java` bloque le spawner d'insomnie quand le temps réel est actif ; `horde/HordeCards.java` contient encore PHANTOM ; `CompanionPassives.java` le référence | Suppression partielle seulement : régler aussi hordes, familiers, objets et anciens contenus |
| Bibliothèque | [Métabli](metabli-beta096.md), [catalogue](catalogue-progression-beta099.md), `plans/ConstructionCatalog.java`, `client/PlansScreen.java` | Catalogue, aperçu, matériaux et imports existent ; il manque la bibliothèque éditoriale complète demandée |
| Communauté | [Contrat communauté](community-contract-v1.md), [beta.158](community-cards-beta158.md), [beta.161](session-fixes-beta161.md) | Gazette, tableau, réponses, suivis et stockage fichier/MariaDB sont un socle ; économie et notifications complètes ne sont pas acquises |
| Ambiance | Historique shader et règle Vulkan de `AGENTS.md` | Mesurer le rendu courant ; ne pas réappliquer les anciennes recettes OpenGL de beta.122 |
Le [cadrage du 25 septembre](ecosysteme-communaute-economie.md) conserve déjà
les cartes de découverte/épreuve, les clés, les huit structures, le bloc originel,
les navets du dimanche, les machines de loterie, le ballast et l'incubation d'XP.
Ces intentions ne sont pas remplacées silencieusement par de nouvelles règles.
## Ce que contient réellement la référence 26.2
Inspection en lecture seule du dépôt historique `/Users/koka/Documents/sanctuary/26.2`.
Les sources suivantes établissent une piste de port ; elles ne prouvent pas
leur compatibilité avec Minecraft 26.3.
| Contenu retrouvé | Source historique | Limite / travail de port |
| --- | --- | --- |
| Cinq Infectés : infecté, vomiteur, cracheur, hunter, chargeur | `itsalive/README.md`, `itsalive/src/main/java/fr/koka99cab/sanctuary26/itsalive/entity/InfectedVariant.java` | Une entité et cinq variantes ; revoir attaques, rendu, sauvegarde et raccord aux hordes |
| Seize Mooblooms | `itsalive/README.md`, `entity/MoobloomVariant.java` | Une entité et variantes ; fleurs hibiscus/narcisse rattachées à Another World dans la référence |
| Ghost et Bracken | `itsalive/README.md`, `entity/GhostEntity.java` | Distinguer le retour demandé des fantômes et le choix encore ouvert de reprendre Bracken |
| Baleine volante | `entity/FlyingWhaleEntity.java`, `world/FlyingWhaleSpawner.java` | Règles d'altitude, fréquence, persistance et rendu à éprouver sur les îles actuelles |
| Suppression historique des phantoms | `itsalive/README.md`, section Real Ghost | L'ancienne suppression de toute entité chargée ne constitue pas un contrat de migration acceptable pour les possessions actuelles |
| Cuisine et cultures | `itsalive/FOOD_WIKI.md`, `CULINARY_CATALOG.md`, paquet `culinary/` | Postes, familles d'ingrédients, cuisson, fermentation et connaissance ; réconcilier documents historiques et sources lors du port |
Le catalogue culinaire décrit une poêle à un à quatre ingrédients, des recettes
strictes et des substitutions par famille. Une recette devinée fonctionne sans
sa page. Le wiki documente le livre et ses pages comme disponibles à son étape,
alors que le catalogue conserve aussi des formulations historiques « prochain
système » : les notes n'ont pas toutes le même âge. Aucune préférence alimentaire
des familiers ne doit être inventée à partir d'une simple liste de recettes.
Le manifeste historique `itsalive/src/main/resources/fabric.mod.json` exige
`minecraft: ~26.2`, Fabric API `>=0.154.2+26.2`, **anotherworld**, **ambiance** et
**sanctuary**. Il n'est donc pas un JAR autonome prêt à ajouter à la bêta actuelle.
`settings.gradle` de beta.172 inclut Sanctuary, Demeure, JEI et Sanctuary Test,
sans module It's Alive. Le port doit établir les responsabilités et dépendances
pour 26.3 ; pas de copie globale de l'ancien pack ni de renommage improvisé
des identifiants historiques.
## Vérifications et limites de ce relevé
- Références Git inspectées et `origin` récupéré ; base 172 propre, branche
de conception isolée, modifications initiales préservées.
- Versions lues dans la configuration ; sources des parcours clés examinées.
- Le contrat beta.172 consigne **261/261 GameTests**, `check build` réussi et
les empreintes des archives. Ce sont les preuves de cette livraison : la
présente passe documentaire ne relance pas ces tests et ne revalide pas les
archives générées.
- L'ouverture en lecture seule de `https://sanctuary-minecraft.net/` par l'outil
de recherche n'a pas abouti. Cela ne prouve pas une panne du site. État public,
forum, permissions Discord et notifications non vérifiés ici ; le cadrage
s'appuie sur les contrats locaux, pas sur un déploiement supposé.
- Aucun code, dépendance, monde, format, version binaire, canal packwiz ou
instance Prism modifié ; aucun message externe envoyé.
+100
View File
@@ -0,0 +1,100 @@
# FINISH-189 — refuges au sol, berges et repères souterrains
Ticket du 30 septembre 2026, branche `codex/terrain-finish-beta189`.
Retour de visite beta.188 à partir des captures 19:55:33 et 19:57:00.
## Contrat
Nouveau preset `sanctuary_test:terrain_finish_v1`, profil `finish`, graine 42.
La partie beta.188 reste ouverte et préservée. Aucune migration ou régénération.
Corrections demandées : refuges qui suivent le sol, sédiments suivant la profondeur,
sable au-delà des berges, rejet des arbres dès leur plantation, reliefs étagés et
roche apparente. Accent sur le portail inférieur par le sol et la lumière.
Essais bornés : déversoirs vers une ouverture proche si le terrain l'autorise ;
petites ruines dans un volume de grotte sec déjà présent, sans excavation.
Pas de nouveau biome de glace. Une autre variante de lush cave reste à concevoir.
## Réalisation
Le relief n'est transformé qu'entre Y=252 et Y=316 par une fonction analytique,
sans nouveau bruit ni nouvelle passe d'hydrologie. Les grottes profondes et récifs
aériens gardent leur densité. Les affleurements suivent escarpements et épaules.
Les refuges de surface choisissent leur sol dans la densité, sans feuilles.
Les fonds passent du sable peu profond à l'argile des zones calmes, avec gravier
sur les ruptures de pente. Berges sableuses sur trois blocs au maximum et jusqu'à
deux blocs au-dessus de l'eau. Les plantations natives de type arbre et système
racinaire sont rejetées avant toute écriture près des étangs.
Le portail horizontal 5×5 avec vide 3×3 reste en place. Sol taillé et éclairage
en cuivre/pierres lumineuses soulignent le centre et les accès ; chandelier
suspendu au toit existant. Les petites ruines sont un prototype, pas une ville
avec habitants, commerce ou nouvelles quêtes.
## Vérifications
Les résultats finaux et leurs limites sont détaillés ci-dessous.
## Résultat vérifié — graine 42
- Huit ancres aux mêmes emplacements et orientations qu'en beta.188, offres
vérifiées et remises à leur état initial après le contrôle. Les surfaces des
refuges suivent la densité rocheuse, sans consulter la hauteur des feuilles.
- 1 120 blocs de sable sur les berges sèches ; fonds de 1 146 blocs de sable,
376 de gravier et 2 896 d'argile ; 408 plantations aquatiques. Zéro tronc dans
les colonnes des étangs et des plages contrôlées, de 28 blocs sous l'eau à
28 blocs au-dessus. Les arbres voisins peuvent conserver un surplomb de feuilles.
- Une cascade : départ (143,244,-147), déversoir (153,244,-147). Chenal de dix
blocs, entaille locale de treize blocs au total. Soixante-quatre blocs de chute
sont préremplis ; les fluides natifs poursuivent la chute vers le vide.
- Quatre ruines de cinq blocs de côté, aux sols (24,163,-64), (-8,147,-80),
(64,163,-80), (56,120,-80). Appuis rocheux contrôlés, zéro bloc de cavité
excavé. C'est un petit ensemble de vestiges étagés, pas une ville complète
ni un nouveau réseau de couloirs.
- Portail conservé en (0,175,0), bordure 5×5 et ouverture sèche 3×3. Sol de
pierre taillée, accès lumineux et chandelier : vingt sources lumineuses
supplémentaires dans ce plan, sans creuser de nouvelle salle.
- Sur le relevé du relief : 616 hauteurs de surface modifiées, quatre blocs
d'écart maximum ; 337 points d'affleurement. Les 25 205 échantillons de densité
des profondeurs et des récifs restent identiques. La compression conserve les
fines crêtes au lieu de les supprimer entre deux échantillons.
- Palais, 49 cartes verrouillées dans leurs 49 cadres horizontaux, gemmes,
soufre, butin en fer enchanté et passages du donjon contrôlés. Cartes et état
initial des ancres relus dans la copie destinée au solo.
Génération puis réouverture réussies : `build/finish189g-cold.json` et
`build/finish189g-warm.json`. Démarrage du serveur : 11,369 s / 0,581 s ; processus
prêt après l'ensemble des contrôles : 67,717 s / 19,973 s. Zéro région d'hydrologie.
Plans locaux des berges/déversoirs : 137 ms ; ruines : 459 ms à froid. Ce sont des
mesures de laboratoire, pas une garantie de durée identique sur chaque machine.
Le serveur de diagnostic désactive son watchdog : la vérification charge les
zones et remplit toutes les cartes dans un seul appel. Un premier contrôle a
atteint les 60 s pendant l'atlas. Le solo garde le remplissage progressif habituel
et ne lance pas cette batterie de mesures au démarrage.
`./gradlew check build assemblePack assembleTestPack` réussi en 9 min 1 s,
avec 265 GameTests réussis. Après les derniers correctifs du module de labo,
compilation/JAR ciblés, génération et réouverture natives réussies ; pack
optionnel resynchronisé. Ses 165 classes correspondent aux classes compilées
et le JAR distribué est identique : `build/finish189-pack-receipt.json`.
Aucune publication de canal, installation Prism ou ancienne sauvegarde modifiée.
## Visite
Nouvelle sauvegarde `Sanctuary-Terrain-Finish-189-Solo`, profil `finish`, run
`visite189`, graine 42. Vue 32, simulation 12, spectateur, commandes activées.
Solo Vulkan ouvert : connexion confirmée à 20:29:55, distances 32/12 et
masquage du palais depuis le sol confirmés dans `build/finish189-solo.log`.
- Étang ouest : `/tp -104.5 269 119.5 180 35`
- Ancre nord : `/tp 36.5 252 -168.5 180 0`
- Cascade : `/tp 158.5 248 -146.5 90 25`
- Portail inférieur : `/tp 0.5 179 10.5 180 20`
- Ruines : `/tp 24.5 167 -71.5 0 20`
Le jugement esthétique reste à faire pendant la nouvelle visite. La glace et
un biome supplémentaire ne sont pas ajoutés. Piste proposée pour plus tard :
une cavité claire de calcite et d'améthyste, plus calme que les jardins lush,
les marais et le soufre ; aucune décision d'implémentation à ce stade.
+4
View File
@@ -1,5 +1,9 @@
# UI-045 — menu principal
Contrat historique : [UI-194](main-menu-beta194.md) retire ensuite la ligne
Friends / Amis et rapproche les commandes restantes. Les essais ci-dessous
décrivent beta.045, pas le menu courant.
Branche `codex/title-menu-beta045`, à partir de beta.044.
## Contrat
+63 -2
View File
@@ -1,5 +1,42 @@
# Sanctuary — vision du projet
**Cap du 1er octobre 2026 :** le créateur suspend les retouches du worldgen,
des biomes et des chunks ainsi que les tests de screenshots après beta.193.
La reprise porte sur les interfaces et les idées du Storyquest, avec le retrait
d’Amis du menu principal en premier. Voir le [fil rouge courant](storyquest-fil-rouge.md)
et [UI-194](main-menu-beta194.md). Les priorités de septembre ci-dessous
conservent l’historique ; elles ne relancent pas le chantier terrain.
**Direction courante — 29 septembre 2026 :** le
[fil rouge de la refonte](storyquest-fil-rouge.md) reprend **Sanctuary Island,
l'île de départ**, autour de plans géographiques indépendants et de parcours
jouables qui croisent lieu, récit, préparation et conséquence. Le créateur
abandonne le palais souterrain, ses accès et les raccordements au palais.
Le 30 septembre, le [premier repère de l’origine](origin-landmark-beta181.md)
fixe le bloc en (0,320,0), sur une petite construction ouverte. Le plan des huit
salles d’ancres naturelles et leur esthétique restent à concevoir. Les demandes
de matériaux et les gemmes ne sont pas annulées.
Le [cadrage Storyquest du 27 septembre](storyquest-beta173.md) conserve la vision
large : SGA, quêtes, habitants, familiers, cuisine, économie et vie communautaire.
Les priorités actives sont désormais dans le fil rouge. Le
[concept du palais du 28 septembre](palais-originel-iss-beta173.md) est historique ;
ses pistes ISS, chenil et ordinateurs restent ouvertes sans dépendance au palais.
Le [laboratoire beta.173](palais-prototype-beta173.md) conserve les mécaniques
et essais réalisés ; son architecture n'est plus une cible d'intégration à l'île.
L'[inventaire beta.172](storyquest-socle-beta172.md) décrit le socle précédent.
**Priorité ajoutée pendant la séance :** reprendre d'abord le worldgen grâce
à un [labo rapide mesuré](worldgen-lab-beta174.md), sans hydrologie ni plans
de structures, puis réintroduire les couches utiles. Le créateur choisit ce
profil pour les nouveaux mondes de laboratoire uniquement ; la génération
normale conserve son comportement.
La clôture worldgen alpha.30.7 et les ordres de travail rappelés dans la suite
sont historiques. Les rôles du Nether et de l'End sont à réexaminer dans la
nouvelle conception ; aucune suppression de dimension, régénération ou migration
de partie n'est décidée par ce cadrage.
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. Le socle Fabric et la reprise du générateur de l'ancienne version 26.2 ont constitué la première étape.
@@ -399,6 +436,12 @@ ses plans orientés et ses poses natives limitées par les rangs.
## Monnaies, propriétés et échanges
Le [cadrage du 25 septembre 2026](ecosysteme-communaute-economie.md) précise
l'État régulateur, le tableau à message général, les bounties collectionnables,
les huit structures d'expansion et leurs liens avec les statistiques et le
ballast. Il distingue les décisions du créateur, les propositions et le socle
réellement livré ; ces systèmes futurs ne sont pas activés par cette vision.
### Les trois gemmes
Les trois gemmes forment la palette et la symbolique triangulaire de Sanctuary.
@@ -419,7 +462,7 @@ Le shop pourrait devenir une infrastructure coûteuse à construire, accessible
### 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.
Les **navets** s'achètent **auprès d'un PNJ, uniquement le dimanche**. Pendant la semaine, les joueurs peuvent les revendre au shop au cours variable de la **bourse du navet**. Ils **pourrissent après une semaine s'ils ne sont pas vendus**. Aucun achat de navets n'est proposé les autres jours. L'échéance exacte et les règles du cours restent à définir ; voir le [cadrage du 25 septembre](ecosysteme-communaute-economie.md).
### Coffre-fort, équipes et braquage
@@ -537,7 +580,12 @@ Un calendrier expose des nombres de jours simples : âge du serveur depuis sa cr
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.
Une **loterie du dimanche** utilise des tickets trouvés ou achetés pendant la
semaine. La précision du 25 septembre 2026 prévoit de **vraies machines pouvant
dupliquer ou diviser des stocks d'objets**, avec des conséquences à définir pour
le ballast des Backrooms. Probabilités, objets admissibles et financement restent
à concevoir. Les lootboxes, emplacements et privilèges temporaires évoqués
auparavant sont des possibilités, pas un catalogue de gains arrêté.
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.
@@ -555,6 +603,19 @@ Le joueur pourrait tracer des constellations depuis son point de vue, directemen
## Quêtes, récompenses et cosmologie
Le [cadrage du 25 septembre](ecosysteme-communaute-economie.md) ajoute les
bounties sous forme d'objets collectionnables, activables quand le joueur est
prêt, et l'utilisation de cartes créées à la volée. Le tableau accueille un
message général pouvant porter une quête ou une découverte à explorer ensemble.
Les règles d'XP, de groupe, d'activation et de récompense restent à définir.
La même discussion ajoute des quêtes canoniques à quotas et paliers, avec une
progression personnelle et une coopération à concevoir, ainsi que des capes et
familiers exclusifs liés aux épreuves difficiles. Les familiers sont aussi
envisagés comme **incubateurs d'XP** : leur réserve fructifie avec les blocs
parcourus à pied, posés et cassés, selon un rendement modéré encore à définir.
Ces intentions ne décrivent pas des fonctionnalités déjà livrées.
### 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.
+167
View File
@@ -0,0 +1,167 @@
# WG-CONTINUITY-193 — raccordements et visites sur plusieurs graines
Branche `codex/worldgen-continuity-beta193`, Minecraft 26.3.
Nouveau profil de laboratoire `coherent`, mondes neufs uniquement.
Graines vérifiées : 42 (référence des retours), 2026 et 8675309.
## Demande
Conserver la direction des montagnes appréciée en beta.192 et corriger les
raccordements d’eau, le damier de mousse des ancres et les longues marches.
Vérifier plusieurs graines et examiner des captures réelles du jeu avant de
préparer la prochaine visite. La quantité globale de roche n’est pas à réduire
systématiquement ; la réserve précédente du créateur demeure.
## Contrat de cette itération
- Propriété commune des espaces remplis d’eau : éviter les nappes indépendantes
qui se recoupent à des hauteurs différentes dans une même cavité.
- Ancres de surface et aériennes adaptées à la densité du terrain, sans fond
ondulé artificiel ni damier de mousse ; conserver secteurs et orientation.
- Éviter les sols de grotte en plein air et interrompre localement les longues
marches du massif sans remettre des niveaux périodiques sur toute l’île.
- Ajouter des contrôles de raccordement, pas seulement de nombre de blocs.
- Produire des captures Vulkan de bassins, d’ancres et du relief dans des mondes
de diagnostic neufs, avec graine, position et version identifiées.
Aucune modification, régénération ou migration des sauvegardes précédentes.
Le profil beta.192 reste disponible avec son comportement antérieur.
## Changements de génération
Les volumes des étangs de surface évitent désormais les volumes déjà retenus
dans les cavités. L’eau est posée avant la décoration et ses colonnes sont
protégées contre les arbres et systèmes de racines. Les contrôles comptent
chaque cellule humide prévue, y compris celles que l’ancien contrôle ignorait
quand un autre bloc avait remplacé l’eau. Les berges sableuses débordent moins
haut et moins loin sur les flancs raides.
Les ancres aériennes utilisent le même relevé de sol que les ancres de surface.
Le chemin suit les colonnes réelles ; son admission refuse les sauts de hauteur.
L’herbe reprend la couleur du biome et la terre remplace le damier de mousse.
Les grottes gardent leurs palettes ; au-dessus de Y=240, une couverture rocheuse
locale doit réellement exister pour attribuer une palette de grotte.
Le massif 192 reste la base. De petites entailles irrégulières interrompent ses
contours, sans paliers périodiques ni modification des densités profondes et
aériennes. Les cerisiers cherchent un emplacement sec et dégagé près des
sommets choisis ; leur altitude suit le relief de la graine.
Les essais ont aussi révélé des collisions anciennes : la galerie choisit ses
salles secondaires dans des cavités voisines séparées de la pièce centrale.
Elle conserve seulement les salles admises, avec leurs liaisons praticables.
Les bassins évitent cette galerie et peuvent être moins nombreux si l’espace
manque. Les ruines évitent son volume et les geysers évitent les ruines.
Ces recherches restent bornées et leurs plans sont mis en cache ; aucun solveur
d’hydrologie régionale ni simulation permanente n’est ajouté.
## Méthode de comparaison
Le script `scripts/worldgen_lab.py verify --profile coherent --seed 42 --run NOM`
crée un laboratoire neuf, vérifie les blocs natifs, le ferme puis le rouvre.
Répéter avec 2026 et 8675309 ; les journaux et JSON restent dans leurs dossiers.
Une modification du code ou des données impose un nouveau nom d’expérience.
`scripts/worldgen_photos.py` prépare ensuite une copie neuve du monde vérifié,
un plan de caméras et un fichier de provenance. Il requiert `nbtlib`, `--source-run`,
`--seed`, `--run` et `--previous-client` (dossier client d’un ancien labo fermé).
La commande `worldgen_lab.py solo --profile coherent --seed GRAINE --run PHOTOS
--photo-plan CHEMIN/cameras.json` prend les images avec le moteur Vulkan du jeu.
Le mode photo refuse les sauvegardes qui ne portent pas le préfixe réservé
`Sanctuary-Photo-193-`. Il masque l’interface, attend le rendu des sections,
conserve une heure de midi fixe, capture puis ferme proprement le client.
Sans cette option, le solo reste une visite manuelle normale.
Les positions viennent des aménagements réellement générés : chaque bassin,
deux côtés du massif, les ancres aériennes et une ancre de surface. Une capture
réussie doit être examinée visuellement ; un nombre de blocs correct ne valide
ni la beauté des rives ni la silhouette du relief.
## Résultats
Génération et réouverture natives réussies pour les trois graines dans les
runs `coherent193k42`, `coherent193k2026` et `coherent193k8675309`.
Les plans d’eau, emplacements des ancres et relevés de relief sont identiques
après rechargement (`build/coherent193-three-seeds.json`).
| Graine | Volumes d’eau contrôlés | Cellules humides | Salles de galerie | Coffres de ruines |
| --- | ---: | ---: | ---: | ---: |
| 42 | 5 | 28212 | 3 | 4 |
| 2026 | 5 | 21542 | 3 | 3 |
| 8675309 | 4 | 8929 | 2 | 4 |
Sur chaque graine : zéro contact entre volumes indépendants, toutes les cellules
prévues encore humides, huit ancres contrôlées en blocs réels, aucun damier de
mousse dans leurs plans exposés, trois cerisiers réellement présents et 39 366
échantillons profonds/aériens inchangés. Le donjon, les gemmes, la galerie, le
soufre, les ruines, la rosace et les 49 cartes/cadres de l’ISS passent aussi les
contrôles natifs. La graine 42 admet un déversoir large de cinq colonnes ; les
deux autres gardent leurs bassins fermés quand aucun déversoir proche n’est admis.
Démarrages à froid : 15,2 / 18,9 / 19,1 s ; réouvertures : 0,6 / 0,7 / 1,0 s.
Zéro région d’hydrologie lourde. Avec l’ensemble des diagnostics et de l’atlas,
les essais à froid durent 112 / 188 / 106 s. Les processus tournaient en partie
en parallèle ; ce ne sont pas des benchmarks isolés et le solo ne rejoue pas
ces contrôles exhaustifs à chaque ouverture.
`./gradlew check build assemblePack assembleTestPack` réussi, 265 GameTests.
Le premier passage avait échoué sur le déplacement d’un familier volant, sans
modification de ce système ; la relance a passé les 265 tests. Après les derniers
ajustements du laboratoire, `:sanctuary-test:check :sanctuary-test:build` et le
réassemblage du pack de test ont réussi. 179 classes et 124 ressources du JAR
sont identiques aux sorties courantes et le JAR distribué correspond octet pour
octet (`build/coherent193-pack-receipt.json`). Pas de publication ni de mise à
jour de Prism.
## Examen des captures
La série retenue rassemble 29 PNG natifs 2560 × 1440 : 10 pour 42 dans
`photos193d`, 10 pour 2026 dans `photos193c` et 9 pour 8675309 dans `photos193e`.
Les dossiers sont sous `build/worldgen-lab/RUN/coherent/GRAINE/` ; les positions,
l’empreinte des sources et les empreintes des PNG sont conservées dans
`photo-provenance.json`. Galerie locale : `build/worldgen193-gallery/index.html`.
Les premières images nocturnes de 42 et pluvieuses de 8675309 sont conservées
comme diagnostics, hors de la galerie finale. Les services de temps réel et de météo quotidienne sont désactivés dans les
seuls clients photo jetables ; le script prépare également une longue période
de temps clair natif. La météo quotidienne avait rétabli la pluie après un
premier essai avec seulement les durées natives modifiées. L’automate ne remplit l’identité
« Labo » et ne passe l’avertissement expérimental que dans ces copies réservées.
Constats de l’assistant, sans validation esthétique du créateur :
- Les nappes retenues sont séparées et l’eau reste présente après décoration.
Cela ne suffit pas à rendre les rives naturelles : 42 conserve notamment une
longue retenue sableuse contre le massif ; les liserés restent trop réguliers.
- Les abords des ancres aériennes suivent le sol, sans l’ancien tapis de mousse
surélevé. Le motif alterné terre/herbe et pierre/herbe reste très quadrillé,
particulièrement sur l’ancre automnale de 42 et celle de surface de 2026.
- Certains fonds de bassins de cavité restent herbeux. Leur sédimentation n’est
pas harmonisée avec celle des étangs de surface dans cette itération.
- Les trois massifs diffèrent, mais de grandes bandes et marches rocheuses
persistent, surtout sur 42 et 2026. Les petites entailles n’ont pas suffi à
résoudre cette réserve esthétique. Aucune disparition générale revendiquée.
- Une vue de l’ancre de surface de 42 est masquée par le feuillage ; les autres
points de vue et contrôles de blocs ne remplacent pas sa visite rapprochée.
De larges ombres sont aussi visibles sur 42 : leur cause n’est pas établie.
Pour la prochaine retouche, comparer les mêmes caméras, une modification à la
fois, en commençant par la retenue sur le massif et les chemins quadrillés.
Les trois graines constituent une régression reproductible, pas une preuve
pour toutes les graines ou une validation esthétique de toute la carte.
## Visite manuelle livrée
`build/worldgen-lab/visite193/coherent/42/client/saves/` contient trois copies
neuves : `Sanctuary-Coherent-193-42-Solo`, `Sanctuary-Coherent-193-2026-Solo`
et `Sanctuary-Coherent-193-8675309-Solo`. Elles proviennent des mondes photo
fermés, dont les huit ancres sont inactives et le Bugrock allumé à zéro relais.
Le client manuel est entré sur 42 le 1er octobre 2026 à 01:26:55 Europe/Paris,
vue 32 chunks, simulation 12, joueur spectateur placé devant le massif.
L’automate photo n’est pas activé. Les deux autres mondes sont disponibles
par la liste Solo ; ils n’ont pas été revisités manuellement après leur copie.
Les services d’heure et météo réelle restent désactivés dans ce labo de
comparaison ; midi et temps clair sont préparés dans ces copies seulement.
Quelques avertissements de retard de ticks apparaissent pendant le chargement
à 32 chunks : aucune mesure de fluidité stable n’est revendiquée.
+89
View File
@@ -0,0 +1,89 @@
# WG-FIX-197 — arrivée, cascades et détails du donjon
Branche `codex/worldgen-fixes-beta197`, depuis beta.196. Retours R012 :
arrivée sur l’ISS, cascades souterraines, diagonales de terre dans la mine,
toit de cabane cassé, arrêt chez un ami pendant la création Large sans rapport.
Graine de cet arrêt inconnue. Minecraft 26.3, Fabric 0.19.5, Java 25.
## Changements
Les nouveaux profils `shared_island197_{small,medium,large}` conservent les
densités et biomes du terrain beta.196. Le paramètre additif `polish197`, faux
par défaut, active uniquement les corrections de décoration. Les identifiants
beta.196, données et comportements historiques restent présents. Personnaliser
un profil historique conserve sa révision. Aucune migration ni retouche de
sauvegarde personnelle.
La hauteur du point global ne suffisait pas : `PlayerSpawnFinder` recommence
une recherche depuis la surface la plus haute de chaque colonne. Le toit de
l’ISS pouvait donc être retenu. Sur les nouveaux profils, cette recherche
reste bornée au terrain principal (Y=198–316), avec sol solide, deux blocs
d’air et exclusion des feuilles et troncs. Le rayon aléatoire natif et les
réapparitions personnelles sur un lit restent gérés par Minecraft.
Une poche d’air sous une berge ne suffit plus pour accepter une cascade.
Chaque sortie doit être ouverte au ciel et posséder une face extérieure libre
sur les huit premiers blocs de chute. Les recherches conservent leurs bornes :
dix blocs jusqu’à la sortie, au plus deux bouches de quatre ou cinq blocs.
Une cascade n’est pas forcée si le terrain ne s’y prête pas. Les blocs de chute
ne remplacent jamais un bloc solide réel ; une corniche peut arrêter l’eau.
Le sol des salles minières remplace le modulo 7 par des plaques de terre
cohérentes, dépendantes de la graine. La cabane utilise deux pans d’escaliers
continus, un faîtage, des pignons fermés et des pilotis rejoignant un support.
Son volume est vérifié avant placement et réservé contre les décorations
natives voisines. Aucun grand terrassement ajouté pour imposer une cabane.
## Erreur de création reproduite
La graine **17 en Medium** a ensuite reproduit une exception réelle pendant
la génération du premier chunk : `No supported central gallery floor`, dans
`Cavern183.create`. Aucune cavité admissible n’existait exactement sous (0,0).
Sur beta.197, une recherche locale de roche porteuse sert désormais de repli
pour sculpter la même chambre centrale ; elle ne déplace pas le portail ni les
salles déjà admises sur les autres graines. Création et réouverture de cette
graine réussies en Medium et Large. Ce défaut identifié ne prouve pas encore
la cause de l’arrêt sans rapport chez l’ami. `latest.log` reste utile ; une
ligne indique maintenant taille, graine et plafond mémoire au démarrage.
## Vérifications
- Témoin beta.196 Large, graine 42 : création et réouverture réussies.
- beta.197 : Small 0, Large 42, Medium 17 et Large 17 créés puis rouverts avec
huit ancres, donjon, gemmes, atlas, portail et contrôles d’eau. Les quatre
cabanes ont leurs 99 blocs de toiture intacts, avant et après réouverture.
- Dans ces quatre essais, aucune bouche de cascade extérieure admissible :
les anciennes chutes enfouies sont rejetées. Le contrôle reste conditionnel,
il ne constitue pas une validation visuelle d’une nouvelle cascade acceptée.
- Lecture des blocs sauvegardés des salles Large 42 : ancien motif, 75 blocs
de terre tous isolés ; nouveau sol, 160 blocs dont un seul isolé. La terre
forme donc des plaques au lieu de diagonales pointillées.
- Parcours client natif Vulkan réussi, vue 8 chunks, mémoire Java 2 Gio :
menus FR/EN, trois tailles, Annuler/Échap, aller-retour disque, ancien profil
conservé, types vanilla. Création Large 0 puis arrivée réelle à Y=252 et
huit recherches de spawn aléatoires restées sur l’île. Pas de campagne de
captures, ni de revendication de fluidité à 32 chunks sur le matériel ami.
Les essais Small 0/Large 42 et client ont précédé le dernier repli de galerie,
qui ne s’exécute que lorsqu’aucun des anciens sols n’a été trouvé. Les scénarios
17 vérifient ce changement final. Une première tentative du test client a été
arrêtée pendant l’attente inutile de tous les chunks de la vue ; une fixture
de menu historique a ensuite été corrigée pour conserver la clé publique.
Seul le passage final réussi est retenu.
Traces ignorées : `build/faults197-client-verified.log`,
`build/worldgen-lab/faults197a/{small/0,large/42}/{cold,warm}.json`,
`build/worldgen-lab/faults197b/{medium,large}/17/{cold,warm}.json`.
La panne initiale est conservée dans `faults197a/medium/17/cold.log`.
`./gradlew check build assemblePack assembleTestPack -PsanctuaryFocusedTests=menus`
réussi en 5 min 18 s : contrôles purs, quatre GameTests menus et assemblages.
Export ZIP, versions, dépendances, six presets anciens/nouveaux et toutes les
classes des deux JAR comparés aux classes construites. La dépendance Fabric API
conserve exactement le téléchargement et les hashes vérifiés en beta.196.
Export local `Downloads/Sanctuary-Test-beta.197.mrpack`, **12 276 176 octets**.
SHA-256 : `90b0e0495c7fbdee5ceb1b3c87f177274d89d505a6eea7189358aab3c7fff5b2`.
Guide séparé ; aucun monde, réglage personnel, journal ou secret dans l’archive.
Ni publication du canal ni installation dans Prism. Reçu :
`build/faults197-artifact.json` ; journal général : `build/faults197-check-build.log`.
+187
View File
@@ -0,0 +1,187 @@
# WG-LAB-174 — itérer rapidement sur Sanctuary Island
Branche `codex/worldgen-lab-beta174`, issue de beta.173 et du cadrage Storyquest
du 29 septembre. Le créateur choisit explicitement **un labo rapide uniquement**.
Ce lot prépare l'outil de travail avant la nouvelle géographie de l'île.
## Contrat avant implémentation
Le module optionnel Sanctuary Test fournit un preset distinct
`sanctuary_test:relief_v1`, réservé à de nouveaux mondes. Il reprend le champ
de densité, les règles de surface et les biomes de l'origine Sanctuary de
diamètre 724, à graine identique. Il retire les plans d'eau/lave, les structures
planifiées, les îlots aériens planifiés et les quatre expéditions initiales.
Les décorations qui requièrent les protections hydrologiques sont aussi absentes.
Les cavités et masses qui appartiennent au champ de densité restent présentes.
Ce profil sert à lire et modifier le relief ; il ne représente pas le monde fini.
Le preset et son réglage de bruit ont une identité propre, enregistrée avec
le monde. Aucun interrupteur global ne permet de changer sa génération en
rouvrant une sauvegarde. Le profil complet de référence utilise le preset
Sanctuary normal dans un **autre dossier neuf**. Changer de profil, de graine
ou de révision demande un autre dossier de labo ; aucune régénération ni
migration de chunks existants n'est livrée.
La génération normale et les anciens identifiants restent inchangés. La
présence de Sanctuary Test est nécessaire pour relire un monde de ce labo.
Les fichiers vivent sous `build/worldgen-lab/`, ignoré ; aucun profil Prism ni
canal packwiz n'est mis à jour. Le lanceur doit refuser une identité de labo
ou des propriétés de monde modifiées, et conserver les fichiers existants.
## Pourquoi couper plusieurs couches
Le [profilage beta.063](loading-gameplay-beta063.md) mesurait environ 13 s
d'hydrologie, imbriquées dans 24 s de traversées ; patrimoine, sanctuaire minier
et village ajoutaient d'autres recherches. Ces chiffres sont historiques,
pas une mesure beta.174. Couper seulement l'écriture de l'eau ne suffit pas :
les recherches de structures et les protections de décorations demandent
elles aussi le plan hydrologique.
Le [cache beta.064](loading-cache-beta064.md) accélère les réouvertures, mais
inclut les versions de tous les mods dans sa clé. Un rebuild avec nouveau
numéro peut donc refaire la planification. Le labo de relief contourne les
planificateurs ; ce gain n'est pas une accélération de leur algorithme.
## Vérifications prévues
- Comparer les paramètres de bruit et les échantillons de densité avec
`sanctuary:unified_10`, sans solliciter son hydrologie.
- Demander de vrais chunks centraux et éloignés, puis constater zéro plan
hydrologique dans le profil rapide et l'absence de journal d'expansion.
- Comparer création à froid et réouverture des deux profils, même graine,
mémoire et zone de chunks. Séparer lancement JVM, serveur prêt et exploration.
- Reprendre un monde de labo sans changement de son identité ; vérifier les
refus du lanceur et les libellés FR/EN. Inspection du terrain sous Vulkan.
- Exécuter `./gradlew check build assemblePack assembleTestPack` ; publier ici
les résultats effectivement obtenus et leurs limites.
## Fil rouge proposé pour le worldgen
1. **Itérer vite** : ce labo, une graine de référence et des mesures séparées.
2. **Lire l'île** : forme, épaisseur, silhouettes, cavités, arrivée et chemins ;
comparer plusieurs graines et des vues fixes avant les détails coûteux.
3. **Retrouver l'eau** : mesurer les sondages, partager les données de terrain,
puis comparer une planification simplifiée et la référence complète.
4. **Habiter le terrain** : un lieu et une ancre indépendants ; placement borné,
accès et fonction, puis les familles de bâtiments retenues.
5. **Réunir les couches** : écologie, ressources et structures dans un nouveau
profil complet versionné, avec contrôles d'eau, de progression et de reprise.
L'objectif est de garder les détails utiles tout en réduisant le travail fait
avant la première arrivée. Plan de relief grossier, sondages partagés, cache
par couche et calcul local sont des pistes à mesurer, pas des optimisations
déjà validées. Les passages entre couches se font dans de nouveaux mondes de
comparaison ; les régions déjà générées ne sont jamais « complétées » en place.
## Résultats
Première comparaison mesurée le 29 septembre 2026 : **Apple M1, 8 Gio de RAM,
Java 25, Minecraft 26.3**, serveur dédié local, heap 1 536 Mio, graine **42**,
diamètre **724**, vue 6 chunks et simulation 4. Aucun autre serveur ou client
Minecraft pendant ces mesures. Un seul passage par condition ; ces valeurs
ne constituent pas une garantie pour toute machine ou toute graine.
| Mesure | Relief neuf | Référence neuve | Relief rouvert | Référence rouverte |
| --- | ---: | ---: | ---: | ---: |
| Phase serveur jusqu'à `SERVER_STARTED`, arrivée sûre comprise | 5,566 s | 114,564 s | 1,054 s | 1,331 s |
| Demande du lot fixe de dix chunks | 9,660 s | 17,347 s | 0,322 s | 0,456 s |
| Processus lancé → zone de contrôle prête, bootstrap Java/mods compris | **29,197 s** | **144,569 s** | **14,563 s** | **14,594 s** |
La dernière ligne est le temps mural du processus jusqu'au marqueur
`WORLDGEN_LAB_READY`, pas le temps jusqu'à un client rendu et jouable. Elle
inclut les diagnostics de relief sur le profil rapide. Le lot de chunks est
`(0,0), (1,0), (-1,0), (0,1), (0,-1), (12,0), (-12,0), (0,12), (0,-12), (24,0)` ;
ses dépendances de génération peuvent demander des chunks voisins.
Sur cette création, le labo réduit cette attente d'environ **80 %** en retirant
des couches. Il ne prouve pas une accélération du monde complet à résultat
identique. Les réouvertures sont proches : le cache du profil complet remplit
déjà son rôle. Référence froide : région hydrologique centrale **20,168 s /
5 541 443 sondages**, traversées **38,431 s**, sanctuaire minier **12,550 s**,
patrimoine **23,138 s**, village **5,350 s**. Ces durées peuvent être imbriquées,
notamment l'hydrologie dans les traversées ; ne pas les additionner.
Vérifié à ce stade :
- Paramètres de bruit JSON identiques à `unified_10` ; **1 620 échantillons de
densité identiques bit à bit** à la référence, à froid et après réouverture.
- **Zéro région hydrologique calculée** après le parcours rapide ; pas de
journal d'expansion créé. Les deux profils ont été sauvegardés puis rouverts.
- Lanceur : préparation répétée sans écrasement, refus d'une graine/profil ou
de propriétés modifiées, acceptation des deux-points échappés par Minecraft.
- Sources et données de génération du mod normal inchangées.
Contrôles supplémentaires sur les graines **0 et 173** : arrivée sûre,
dix chunks demandés, 1 620 échantillons identiques par graine et zéro région
hydrologique. Rapports dans `build/worldgen-lab/seed-checks174/`. Ces passages
confirment trois graines de labo au total, pas une garantie sur toutes les
graines ni les formats Petit/Grand, absents de ce premier preset.
Livraison : `./gradlew check build assemblePack assembleTestPack --max-workers=1
-Dorg.gradle.jvmargs=-Xmx1G` **réussi en 13 min 13 s**, avec **265/265 GameTests**.
Les profils packwiz normal et Test sont assemblés localement. Les **2 265 entrées
de classes et de worldgen** du JAR normal sont identiques à beta.173 ; le preset
rapide appartient uniquement au JAR optionnel Sanctuary Test. Libellés FR/EN
vérifiés dans cet artefact. Compteurs mod/pack et manifeste alignés sur beta.174.
Les journaux et le rapport complet restent dans
`build/worldgen-lab/audit174a/`, avec `comparison.json` et les quatre logs.
La suite de livraison est dans `build/worldgen174-check-build.log`. Aucun
profil Prism, canal public ni serveur personnel n'a été modifié.
Client **Vulkan / Apple M1** : `Worldgen174ClientChecks` réussi, arrivée sur
un sol réel, survol et exploration, toujours zéro région hydrologique. La
première passe fonctionnelle prenait ses captures avant la fin du chargement ;
le banc attend désormais les chunks observés et la fin du rendu. La dernière
passe réussit en 1 min 35 s. Captures examinées dans
`build/worldgen174-visual/final/` : `relief-seed42.png` et `sous-sol-seed42.png`.
La seconde caméra est sous le terrain à cette position ; elle ne constitue
pas une vue d'ensemble du bord de l'île. La distance de vue 6 limite les
captures à une portion du monde, sans valider toute l'île.
```sh
./gradlew :sanctuary:runClientGameTest -PsanctuaryClientTests=true \
-PsanctuaryWorldgen174ClientTests=true -PsanctuaryQuickTests=true \
-PsanctuaryClientGraphicsBackend=vulkan
```
Journal : `build/worldgen174-client-final.log`. L'optimisation des algorithmes
du monde complet et la nouvelle carte de Sanctuary Island restent à développer.
## Utiliser le labo
Depuis cette branche, avec Java 25 :
```sh
./gradlew :sanctuary-test:exportDuoLaunch
python3 scripts/worldgen_lab.py prepare --seed 42 --run visit174
python3 scripts/worldgen_lab.py server --seed 42 --run visit174
# Dans un second terminal :
python3 scripts/worldgen_lab.py client --seed 42 --run visit174
```
Le serveur écoute sur `127.0.0.1:25581`. Le client rejoint le labo en Vulkan,
avec shader d'ambiance désactivé dans sa configuration dédiée. Il reste en
créatif, avec choix initial d'habitant/familier et introduction courte. Le
profil est aussi sélectionnable sous **Sanctuary — Labo relief v1** dans la
création de monde lorsque Sanctuary Test est installé ; le monde plat reste
le choix par défaut du module.
`--profile reference` choisit le monde complet dans un dossier séparé. Arrêter
le serveur avant de changer de profil, les deux utilisant le même port local.
`--run NOM` choisit une expérience ; après modification des sources, relancer
`exportDuoLaunch` pour compiler, puis utiliser un nom neuf. L'empreinte
conservatrice des sources/données est vérifiée par le
lanceur : aucune conversion silencieuse d'un ancien laboratoire. Les archives
de labo demandent la version du code et du module qui les a générées.
Comparaison automatisée dans un dossier encore absent :
```sh
python3 scripts/worldgen_lab.py benchmark --seed 42 --run comparaison174 --memory 1536
```
Cette commande crée, mesure, arrête proprement puis rouvre chaque profil. Elle
conserve les logs et produit `comparison.json`. `server --verify` réalise les
mêmes contrôles de chunks et de densité ; la visite normale évite ce travail
diagnostique supplémentaire.
+79
View File
@@ -0,0 +1,79 @@
# WG-OPTIONS-195 — Sanctuary et les types vanilla
Branche `codex/worldgen-options-beta195`, Minecraft 26.3, depuis beta.194.
## Demande et périmètre
Le créateur reprend la visite de Sanctuary Island pour décider si la génération
peut rester en pause. Il demande une nouvelle île avec la graine 0 et souhaite
examiner le menu principal en quittant le solo. Il précise que la création de
monde doit conserver Sanctuary et les types vanilla, sans les variantes de labo.
Le module Sanctuary Test cesse d'ajouter ses onze anciens presets au tag de
sélection normal. Les types vanilla et `sanctuary:sanctuary` restent disponibles.
Les presets et leurs codecs sont conservés pour les sauvegardes et les lanceurs
de développement ; aucune suppression, migration ou régénération de monde.
Les sélections historiques des GameTests restent dans leurs seules ressources
de test, avec références optionnelles au module Sanctuary Test.
Ce nettoyage ne remplace pas la génération publique Sanctuary par le profil
de laboratoire : leur consolidation reste à décider après la visite. Les options
de taille de Sanctuary sont conservées. Les traductions et identifiants existants
restent identiques ; aucun nouveau libellé d'interface.
## Correction révélée par la graine 0
Le premier démarrage a échoué : `No supported central gallery floor`. La
recherche historique ne sondait que Y=148–212 au centre. En cas d’échec dans le
profil `coherent` seulement, une seconde recherche bornée examine Y=48–240,
avec les mêmes exigences de sol rocheux et de couverture. Les sols déjà admis
sont conservés. Ce correctif concerne le placement de la galerie, pas la forme
de l’île. Le monde avorté est conservé ; le nouvel essai utilise un autre dossier.
## Visite préparée
Nouveau dossier ignoré `build/worldgen-lab/visite195d/coherent/0/`, profil
`sanctuary_test:coherent_landforms_v1`, graine 0, diamètre 724. Le terrain et
les aménagements sont ceux de beta.193, avec le repli de placement décrit
ci-dessus ; beta.194 fournit le menu sans Amis.
Solo spectateur, commandes autorisées, distance de rendu 32, simulation 12,
Vulkan, journée claire pour comparer le terrain. Aucune série de screenshots.
## Vérifications
`./gradlew check build assemblePack assembleTestPack
:sanctuary-test:exportDuoLaunch -PsanctuaryFocusedTests=menus` réussi en 3 min 45 s,
avec les quatre GameTests menus. Après le correctif de galerie, reconstruction
et contrôle du module de laboratoire réussis. Une passe générale supplémentaire
qui avait aussi lancé les tests de capacité est interrompue pour libérer la
machine lors de la visite : elle ne constitue pas un résultat de 265 tests.
Le réassemblage final réussit en 4 s en reprenant les vérifications déjà
réussies, sans les relancer.
Création native de la graine 0 réussie dans `visite195d` : démarrage serveur
14,8 s, contrôle des lieux terminé à 108,1 s, zéro région d’hydrologie lourde.
Trois salles reliées, portail horizontal en (0,131,0), huit ancres avec huit
matériaux distincts, cinq volumes d’eau séparés et 20 033 cellules humides
contrôlées. Les 49 cartes/cadres du palais et les autres contrôles de donjon,
soufre, ruines et gemmes passent. Ces durées incluent des diagnostics et une
construction concurrente ; elles ne sont pas un benchmark de fluidité.
Une copie neuve `Sanctuary-Labo-195-Seed-0` est préparée pour la visite, avec le
visiteur de développement KokaLab déjà inscrit et les ancres dans leur état
initial (le contrôle des offres restaure ses modifications). La graine 0 est
relue dans les données enregistrées. Le client beta.195 a démarré sous Vulkan. Après l’avertissement expérimental,
les journaux confirment KokaLab connecté le 1er octobre à 18:00:35 Europe/Paris,
en (100.5,335,150.5), rendu 32 chunks et simulation 12. Le contrôle natif de
visibilité de la couronne passe. L’examen esthétique et celui du menu principal
sont laissés au créateur.
Journaux : `build/worldgen195-check-build.log`, `build/worldgen195-floor-build.log`,
`build/worldgen195-final-pack.log` (passe interrompue),
`build/worldgen195-assemble.log`, `build/visit195-solo.log` et le rapport natif
`build/worldgen-lab/visite195d/coherent/0/server/worldgen-lab-metrics.json`.
Le reçu `build/worldgen195-artifact.json` confirme que les classes du mod principal
sont identiques à beta.194. Dans le mod de laboratoire, seul `Cavern183.class`
change ; les 22 presets et toutes les ressources du terrain restent identiques,
hormis le tag de sélection. Les anciennes sauvegardes n’ont pas été ouvertes.
Aucune capture ni validation esthétique de cette nouvelle graine revendiquée.
Pas de publication du canal packwiz ni de mise à jour de Prism.
+178
View File
@@ -0,0 +1,178 @@
# Sanctuary Island — reprise après la visite beta.175
29 septembre 2026, retour R017. Profil `sanctuary_test:sky_v1`, graine 42,
diamètre nominal 724, visite solo avec rendu à 32 chunks. Branche de travail
`codex/sky-fragments-beta175`. **Audit et conception, sans nouvelle livraison
binaire.** Les changements décrits ci-dessous ne sont pas encore appliqués.
**Suite R018 :** le créateur a ensuite autorisé l'essai. La réalisation et
les mesures du nouveau profil sont dans [WG-ECO-176](island-ecology-beta176.md).
Le présent document conserve l'audit initial du monde beta.175.
Le plateau et le relief principal sont jugés satisfaisants par le créateur.
La prochaine passe porte sur ce qui habille et rend utilisable ce relief :
roches, biomes, eau et sélection des structures. Récifs rares jusqu'à 512 ;
ciel 512–639 réservé à l'ISS. Le [contrat beta.175](sky-fragments-beta175.md)
reste la référence du monde actuellement visité.
## Observations et décisions du créateur
| Sujet | Observation ou direction | Statut |
| --- | --- | --- |
| Géologie | La transition stone/deepslate forme une bande trop nette. Plusieurs pierres doivent former des couches et des groupements cohérents | Transition visible sur les captures ; nouvelle palette à concevoir |
| Île principale | Carte jugée très verte ; introduire de grandes zones automnales et de cerisiers, avec des transitions douces | Direction décidée ; proportions non fixées |
| Récifs | Plusieurs îlots herbeux sont nus ; un récif photographié possède une forêt | Vérifié visuellement ; jungle, mangrove, marais, cerisiers et automne proposés par le créateur comme identités possibles |
| Structures | Supprimer la génération de mineshafts sur Sanctuary Island | Décidé ; variantes modifiées réservées aux expansions futures |
| Villages | Employer des villages Sanctuary, propres au projet | Direction décidée ; modèles, habitants et placement encore à définir |
| Progression | Chaque expansion doit apporter un défi et une nouvelle découverte | Décidé ; partage des contenus avec les petits récifs à préciser |
| Eau | Étudier une génération locale inspirée des lush caves | Question technique ; aucune durée cible mesurée ou adoptée |
Les neuf captures du créateur ont été ouvertes et conservées avec SHA-256
dans le [carnet de séance](/Users/koka/Documents/sanctuary/retours-sessions/2026-09-29_1506-storyquest/retours.md).
Les témoins principaux sont la [bande rocheuse](/Users/koka/Documents/sanctuary/retours-sessions/2026-09-29_1506-storyquest/captures-R017/2026-09-29_16.51.43.png),
un [récif nu](/Users/koka/Documents/sanctuary/retours-sessions/2026-09-29_1506-storyquest/captures-R017/2026-09-29_16.52.57.png)
et les [mineshafts sous le terrain](/Users/koka/Documents/sanctuary/retours-sessions/2026-09-29_1506-storyquest/captures-R017/2026-09-29_16.54.09.png).
Aucune capture de carte opérateur dans ce lot : la dominante verte à
l'échelle de l'île reste un retour du créateur, pas un relevé de surface.
## Ce que l'audit explique
**Roches.** Le preset emploie `sanctuary:unified_island`, qui délègue largement
à [population_island.json](../mods/sanctuary/src/main/resources/data/sanctuary/worldgen/material_rule/population_island.json).
Cette règle passe aux roches profondes sous Y=136, puis applique deux bandes
conditionnelles sous 152 et 168. Le bruit varie les taches, mais les limites
verticales restent fixes. La [palette profonde](../mods/sanctuary/src/main/resources/data/sanctuary/worldgen/material_rule/population_deep_strata.json)
associe deepslate, basalte et blackstone. Certains biomes de cavernes suivent
une autre règle. Ces chemins expliquent des ruptures de matière possibles ;
les captures ne donnent pas l'altitude exacte de chaque contact observé.
**Récifs nus.** Le [sélecteur de biomes](../mods/sanctuary/src/main/java/fr/koka/sanctuary/worldgen/SanctuaryBiomeSource.java)
conserve une limite historique à Y=384. Au-dessus, sa délégation à
`ExpansionBiomeSource` renvoie `minecraft:the_void`, alors que les nouveaux
volumes atteignent presque 512. Ce décalage est confirmé dans le code ;
remplacer uniquement les textures du sol ne le corrigerait pas.
**Automne et cerisiers.** `minecraft:dappled_forest` et
`minecraft:cherry_grove` figurent déjà parmi les biomes du preset. Leur
présence dans cette liste ne signifie pas qu'ils sont sélectionnés sur
l'île de départ : celle-ci utilise encore la palette forestière Population.
Les profils `autumn` et `cherry` appartiennent au sélecteur des expansions.
Les ressources natives de Minecraft **26.3 installé** ont été inspectées,
notamment les arbres du Dappled Forest ; aucun nouveau mod n'est nécessaire
pour essayer ces deux biomes.
**Mineshafts.** Le labo remplace le générateur Sanctuary par le générateur
natif `minecraft:noise`. Il coupe les planificateurs Sanctuary, mais ne
supprime pas pour autant toutes les structures natives admissibles par les
biomes. Les mineshafts sont visibles dans la visite. Le correctif doit
porter sur l'admission de leurs starts, avant placement. Modifier le
catalogue des expansions seul ne traiterait pas ce chemin du labo.
À terme, la génération normale a aussi son propre chemin
`starterMines306` à retirer dans une nouvelle version de départ ; il ne
doit pas être modifié implicitement dans les anciennes sauvegardes.
**Eau.** Dans les ressources natives 26.3, `lush_caves_clay` choisit entre
une plage d'argile et `clay_pool_with_dripleaves`. Cette dernière utilise
`waterlogged_vegetation_patch`, rayon nominal 4–7 blocs, profondeur de
substrat 3 et recherche verticale bornée à 5. Le biome possède aussi
`spring_water`. Cela fournit des exemples concrets de décorations locales,
sans construction préalable d'un réseau régional de cours d'eau.
Leur coût réel dans Sanctuary n'a pas été mesuré isolément.
Les **144,569 s** du [comparatif beta.174](worldgen-lab-beta174.md) couvrent
le lancement et la zone de contrôle du profil complet. La région
hydrologique centrale coûtait **20,168 s et 5 541 443 sondages** lors de ce
passage ; d'autres planifications contribuaient à l'attente. Les durées sont
parfois imbriquées. Ni « 145 s d'eau seule », ni « 2 s garantis pour la
nouvelle eau » ne sont des conclusions justifiées par ces mesures.
## Proposition pour la prochaine expérience
Un nouveau profil de labo réunirait ces changements, en conservant le relief
comme témoin commun. Les sous-étapes suivantes sont une proposition
d'implémentation ; palettes, dimensions et budget restent à éprouver.
1. **Strates continues.** Déformer une coordonnée de profondeur avec un bruit
lent, puis sélectionner des couches cohérentes : pierre, andésite, tuf,
deepslate, avec quelques lentilles claires. Faire varier hauteur et
épaisseur sur de grandes distances ; garder des contacts lisibles sur
plusieurs blocs. Des poches plus locales peuvent accompagner ces bancs,
sans produire un mélange aléatoire bloc par bloc. Préserver suffisamment
de deepslate profonde pour les filons de diamant et refaire leur relevé.
2. **Quelques grandes régions au sol.** Introduire automne et cerisiers par
un champ climatique à grande échelle, avec des lisières forestières et
des prairies communes. Le mélange concerne aussi les arbres, arbustes
et sols ; le simple lissage de couleur du client ne suffit pas. Mesurer
l'aire et la continuité des régions avant de régler les proportions.
3. **Une identité par récif.** Affecter un biome dominant stable à chaque
volume déjà déterminé par la seed, jusqu'à 512. Adapter les arbres au
gabarit : une mangrove exige un sol humide et un bassin retenu ; les plus
petits fragments accueillent une végétation courte ou un sujet isolé.
Limiter aussi la décoration pour garder Y=512–639 libre, pas seulement
le champ de roche. Liste exacte des identités à comparer ensuite.
4. **Petites eaux retenues.** Essayer quelques mares peu profondes en surface
et en cavité, avec une inspection bornée du fond et des berges. Refuser
les emplacements ouverts vers le vide. Employer uniquement la zone de
génération disponible, sans charger des chunks pour chercher un site.
Des formes plus grandes pourront être calculées depuis les coordonnées
et la seed, puis écrites par chaque chunk dans son emprise. Pas de
parcours global de l'île pour cette première expérience.
5. **Structures autorisées explicitement.** Exclure les mineshafts de
départ, y compris leurs starts et références. Les villages Sanctuary
feront ensuite l'objet d'une scène et d'un catalogue propres ; cette
passe de terrain ne promet pas encore de les construire.
Les petites mares peuvent suffire à rendre le monde vivant et fournir de
l'eau au joueur. Elles ne résolvent pas encore le placement de grands lacs,
les longues rivières ou les cascades entre plusieurs plateaux. Les captures
montrent déjà des colonnes de fluide vers le vide : reprendre `spring_water`
sans contrôle ne constitue pas à lui seul la solution proposée.
## Préserver l'intérêt des expansions
Proposition : Sanctuary Island offre un territoire habitable et une palette
de construction variée ; ses récifs donnent de courtes excursions. Une
expansion apporte une géographie à parcourir, une contrainte, un lieu
spécifique et une récompense ou progression propres. Exemples à concevoir :
mine reconfigurée, village Sanctuary, ruine, donjon, ressource avancée,
rencontre ou activité liée au Storyquest.
La petite taille d'un îlot ne suffit pas à préserver l'exclusivité d'une
ressource renouvelable : récupérer une pousse permet ensuite de cultiver
son bois. Il faut donc décider ce qui est librement cultivable au départ
et ce qui donne encore une raison d'ouvrir l'expansion. Proposition : ne
pas faire reposer tout l'intérêt d'une destination sur une couleur de bois ;
réserver les défis, découvertes et étapes de progression à ces territoires.
La répartition déjà demandée, notamment sans or ni redstone sur l'île,
reste une contrainte indépendante à contrôler lorsque des biomes natifs
et leurs décorations sont introduits.
Le mot « acide » est conservé dans la transcription. Aucun biome acide,
liquide, dégâts ou bloc nouveau n'est déduit de cette formulation seule.
## Relevés qui permettront de choisir
- Graines 0, 42 et 173, nouveaux mondes seulement ; conserver la visite
`visite175` et son code de génération pour comparer.
- Coupes de matériaux sur les mêmes coordonnées : continuité des couches,
épaisseur, transition vers la deepslate et absence de modification du
relief brut.
- Cartes de biomes à la surface réelle de l'île et de chaque récif : aire,
taille des régions connectées, lisières, décoration effective et limite
à 512. Une simple carte climatique à une altitude fixe serait insuffisante.
- Comptage d'eau placée et retenue après ticks de fluides, examen des bords
et du dessous, répétabilité et réouverture. Mesurer le temps de placement
par chunk et les temps de démarrage séparément, avec un nombre d'essais
borné, en vérifiant zéro plan hydrologique régional.
- Vérifier les structures enregistrées sur l'emprise complète de départ,
pas seulement l'absence de planches au spawn. Contrôler aussi les minerais
après les nouvelles décorations et terminer le comptage global de diamants.
- Procéder au recensement par lots avec mémoire bornée : la première
tentative exhaustive beta.175 a épuisé son heap de 1 Gio. Elle ne donne
aucun total exploitable pour toute l'île. Éviter de la relancer en
parallèle de la visite solo à 32 chunks sur la machine de 8 Gio.
La variante devra porter de nouveaux identifiants de génération et sa
version binaire propre lors de la livraison, avec `check build` et les
assemblages concernés. Aucun changement de preset, de chunks, de structures
ou de biomes n'est appliqué au monde actuellement visité par cette fiche.
+6
View File
@@ -1,5 +1,11 @@
# Premier monde Sanctuary
**Reprise du 29 septembre 2026 :** le
[labo worldgen beta.174](worldgen-lab-beta174.md) permet d'itérer sur le relief
sans les plans hydrologiques et structurels. Ce profil optionnel concerne
uniquement de nouveaux mondes de laboratoire ; la génération normale reste
inchangée. Les sections ci-dessous conservent l'historique des générateurs.
## Alpha.13 — génération unifiée
Le choix public **Sanctuary** (`sanctuary:sanctuary`) crée l’île principale à
+2 -2
View File
@@ -9,8 +9,8 @@ loom_version=1.17.20
fabric_api_version=0.160.5+26.3
# Release counter: beta.001, .002, .003, ... (see docs/versioning.md).
mod_version=beta.159
pack_version=beta.159
mod_version=beta.203
pack_version=beta.203
resource_pack_version=beta.121
maven_group=fr.koka.sanctuary
jei_version=30.32.0-sanctuary.3
+40
View File
@@ -16,5 +16,45 @@ processResources {
filesMatching('fabric.mod.json') { expand(values) }
}
loom { runs { client { runDir 'run/quick-test' } } }
loom {
runs {
duoServer {
server()
name 'Sanctuary duo lab'
runDir '../../build/duo/server'
vmArg '-Xms256m'
vmArg '-Xmx768m'
vmArg '-Dsanctuary.duo=true'
}
duoClient {
client()
name 'Sanctuary duo client'
runDir '../../build/duo/client'
vmArg '-Xms512m'
vmArg '-Xmx2G'
programArgs '--graphicsBackend', 'vulkan', '--username', 'KokaLab', '--width', '1280', '--height', '720'
}
}
}
// Export the Loom launcher so play sessions do not retain a Gradle JVM.
tasks.register('exportDuoLaunch') {
dependsOn tasks.named('runDuoServer').get().taskDependencies.getDependencies(tasks.named('runDuoServer').get())
dependsOn tasks.named('runDuoClient').get().taskDependencies.getDependencies(tasks.named('runDuoClient').get())
doLast {
['server': 'runDuoServer', 'client': 'runDuoClient'].each { kind, taskName ->
def launch = tasks.named(taskName).get()
def destination = rootProject.layout.buildDirectory.file("duo/${kind}.args").get().asFile
destination.parentFile.mkdirs()
def arguments = launch.allJvmArgs.findAll { !it.toString().startsWith('@') } + ['-classpath', launch.classpath.asPath, launch.mainClass.get()] + launch.args + launch.argumentProviders.collectMany { it.asArguments().toList() }
destination.text = arguments.collect { '"' + it.toString().replace('\\', '\\\\').replace('"', '\\"') + '"' }.join('\n') + '\n'
}
}
}
tasks.named('test') { failOnNoDiscoveredTests=false }
tasks.register('gallery198Smoke', JavaExec) {
dependsOn('testClasses')
classpath=sourceSets.test.runtimeClasspath
mainClass='fr.koka.sanctuarytest.GalleryFloor198Test'
}
tasks.named('check') { dependsOn('gallery198Smoke') }
tasks.named('jar') { from(rootProject.file('LICENSE')) { rename { 'LICENSE_sanctuary_test' } } }
@@ -0,0 +1,117 @@
package fr.koka.sanctuarytest;
import com.mojang.serialization.MapCodec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Stream;
import net.minecraft.core.Holder;
import net.minecraft.world.level.biome.*;
/** Bounded valley floors, exposed rock faces and sparse vegetated cave districts. */
public final class AdventureBiomes180 extends BiomeSource {
public static final int AUTUMN_MIN=200, AUTUMN_MAX=232;
public static final MapCodec<AdventureBiomes180> CODEC=RecordCodecBuilder.mapCodec(i->i.group(
Biome.CODEC.listOf().fieldOf("biomes").forGetter(s->s.biomes),
com.mojang.serialization.Codec.BOOL.optionalFieldOf("discovery",false).forGetter(s->s.discovery),
com.mojang.serialization.Codec.BOOL.optionalFieldOf("natural_sulfur",false).forGetter(s->s.naturalSulfur),
com.mojang.serialization.Codec.BOOL.optionalFieldOf("original_relief",false).forGetter(s->s.originalRelief),
com.mojang.serialization.Codec.BOOL.optionalFieldOf("polish197",false).forGetter(s->s.polish197),
com.mojang.serialization.Codec.BOOL.optionalFieldOf("stable_gallery198",false).forGetter(s->s.stableGallery198)
).apply(i,AdventureBiomes180::new));
public record Terrain(int top,boolean cliff,boolean valley,int lowestNeighbour,int higherNeighbours){}
private record State(long seed,ReliefEcology177.Field field,Map<Long,Terrain> terrain){}
private final List<Holder<Biome>> biomes;
private final boolean discovery,naturalSulfur,originalRelief,polish197,stableGallery198;
private final Map<String,Holder<Biome>> named;
private volatile State state;
volatile LabExpansions199 expansions;
private static final int[][] DIRECTIONS={{1,0},{1,1},{0,1},{-1,1},{-1,0},{-1,-1},{0,-1},{1,-1}};
public AdventureBiomes180(List<Holder<Biome>> biomes,boolean discovery){this(biomes,discovery,false);}
public AdventureBiomes180(List<Holder<Biome>> biomes,boolean discovery,boolean naturalSulfur){this(biomes,discovery,naturalSulfur,false);}
public AdventureBiomes180(List<Holder<Biome>> biomes,boolean discovery,boolean naturalSulfur,boolean originalRelief){
this(biomes,discovery,naturalSulfur,originalRelief,false);
}
public AdventureBiomes180(List<Holder<Biome>> biomes,boolean discovery,boolean naturalSulfur,boolean originalRelief,boolean polish197){
this(biomes,discovery,naturalSulfur,originalRelief,polish197,false);
}
public AdventureBiomes180(List<Holder<Biome>> biomes,boolean discovery,boolean naturalSulfur,boolean originalRelief,boolean polish197,boolean stableGallery198){
this.stableGallery198=stableGallery198;
this.polish197=polish197;
this.originalRelief=originalRelief;
this.naturalSulfur=naturalSulfur;
if(naturalSulfur&&biomes.stream().noneMatch(b->b.unwrapKey().orElseThrow().identifier().getPath().equals("natural_sulfur_187")))throw new IllegalArgumentException("Missing natural sulfur biome");
this.discovery=discovery;
this.biomes=List.copyOf(biomes);var names=new HashMap<String,Holder<Biome>>();
for(var b:biomes)names.put(b.unwrapKey().orElseThrow().identifier().getPath(),b);
named=Map.copyOf(names);
if(discovery&&!named.containsKey("discovery_sulfur"))throw new IllegalArgumentException("Missing discovery sulfur biome");
for(var n:List.of("forest","flowers","autumn","cherry","jungle","swamp","bare","cliffs","caves","grove","lush","marsh","mycelium","summit","void"))
if(!named.containsKey("adventure_"+n))throw new IllegalArgumentException("Missing rocky biome "+n);
}
public void bind(long seed,ReliefEcology177.Field field){state=new State(seed,field,new ConcurrentHashMap<>());}
@Override protected MapCodec<AdventureBiomes180> codec(){return CODEC;}
@Override protected Stream<Holder<Biome>> collectPossibleBiomes(){return biomes.stream();}
@Override public BiomeResolver createResolver(Climate.Sampler ignored){return (x,y,z)->resolve(x*4,y*4,z*4);}
public Terrain terrain(int x,int z){
var s=state;if(s==null)throw new IllegalStateException("Unbound rocky ecology");
final int qx=x>>2<<2,qz=z>>2<<2;
if(Math.hypot(qx,qz)>s.field().base().terrainBound())return new Terrain(-1,true,false,-1,0);
return s.terrain().computeIfAbsent(((long)qx<<32)^(qz&0xffffffffL),ignored->{
int top=s.field().surface(qx,qz),lowest=320,higher=0,highest=-1;boolean steep=false;
for(var d:DIRECTIONS){
int near=s.field().surface(qx+d[0]*8,qz+d[1]*8);
if(near<top-12)steep=true;
int far=s.field().surface(qx+d[0]*32,qz+d[1]*32);
lowest=Math.min(lowest,far);highest=Math.max(highest,far);
if(far>=top+5)higher++;
}
boolean cliff=top<AUTUMN_MIN || steep;
boolean valley=!cliff && top<=AUTUMN_MAX && lowest>=top-20 && higher>=3 && highest>=top+10;
return new Terrain(top,cliff,valley,lowest,higher);
});
}
/** Decoration policy only; the density function and biome selection stay identical to beta.188. */
public boolean stableGallery198(){return stableGallery198;}
public boolean polish197(){return polish197;}
public boolean originalRelief(){return originalRelief;}
public boolean naturalSulfur(){return naturalSulfur;}
public long seed(){return state.seed();}
public ReliefEcology177.Field field(){return state.field();}
public static double moisture(long seed,int x,int y,int z){
double h=y/48.0;int layer=(int)Math.floor(h);double t=h-layer;t=t*t*(3-2*t);
double a=EcologyBiomes176.noise((seed^0x178CA7EL)+layer*7919L,x/64.0,z/64.0);
double b=EcologyBiomes176.noise((seed^0x178CA7EL)+(layer+1)*7919L,x/64.0,z/64.0);
return a+(b-a)*t;
}
public Holder<Biome> resolve(int x,int y,int z){
var extra=expansions;
if(extra!=null&&extra.regionAt(x,z)!=null)return extra.source().biomeAt(x,y,z);
var s=state;if(s==null)throw new IllegalStateException("Unbound rocky ecology");
if(y<0 || y>=512 || Math.hypot(x,z)>s.field().base().terrainBound())return get("void");
if(y>=320){
int nearest=-1;double best=Double.POSITIVE_INFINITY;var reefs=s.field().base().reefs();
for(int i=0;i<reefs.size();i++){
var r=reefs.get(i);double d=Math.pow((x-r.x())/(double)(r.length()+20),2)+Math.pow((z-r.z())/(double)(r.length()+20),2)+Math.pow((y-r.y())/35.0,2);
if(d<best){best=d;nearest=i;}
}
return get(switch(nearest){case 0->"cherry";case 1->"autumn";default->"bare";});
}
if(discovery&&!naturalSulfur&&x>=-184&&x<=-120&&z>=124&&z<=180&&Sulfur183.biome(this,x,y,z))return named.get("discovery_sulfur");
var t=terrain(x,z);if(t.top()<0)return get("void");
if(y<t.top()-12 && (!s.field().coherent()||y<240||Continuity193.covered(s.field(),x,y,z))){
if(naturalSulfur&&Sulfur187.biome(s.seed(),x,y,z,t.top()))return named.get("natural_sulfur_187");
double wet=moisture(s.seed(),x,y,z);
double kind=moisture(s.seed()^0x179B10L,x+71,y,z-93);
if(wet<-.30)return get("caves");
if(kind<-.25)return get("mycelium");
if(kind>.25)return get("marsh");
return get(wet>.15?"lush":"grove");
}
if(t.top()<AUTUMN_MIN)return get("cliffs");
if(t.valley())return get("autumn");
if(t.top()>=270)return get("summit");
return get(EcologyBiomes176.noise(s.seed()^0x177F10L,x/68.0,z/68.0)>.05?"flowers":"forest");
}
private Holder<Biome> get(String name){return named.get("adventure_"+name);}
}
@@ -0,0 +1,91 @@
package fr.koka.sanctuarytest;
import net.minecraft.core.*;
import net.minecraft.core.registries.Registries;
import net.minecraft.util.RandomSource;
import net.minecraft.world.entity.*;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.block.*;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.*;
import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator;
/** Local cave gardens and retained terrace pools; no region planner or chunk loads. */
public final class AdventureCaves180 {
private AdventureCaves180(){}
static boolean natural(BlockState b){return b.is(net.minecraft.tags.BlockTags.BASE_STONE_OVERWORLD)
|| b.is(Blocks.CALCITE)||b.is(Blocks.DIRT)||b.is(Blocks.GRASS_BLOCK)||b.is(Blocks.PODZOL)
||b.is(Blocks.MYCELIUM)||b.is(Blocks.MOSS_BLOCK)||b.is(Blocks.MUD)||b.is(Blocks.CLAY);}
static String biome(AdventureBiomes180 source,int x,int y,int z){return source.resolve(x,y,z).unwrapKey().orElseThrow().identifier().getPath();}
static boolean covered(ChunkAccess c,int x,int y,int z){
for(int dy=4;dy<=72 && y+dy<316;dy++)if(natural(c.getBlockState(new BlockPos(x,y+dy,z))))return true;
return false;
}
public static void decorate(WorldGenLevel level,ChunkGenerator generator,ChunkAccess c){
if(!WorldgenLab.ecology180(generator))return;
var source=(AdventureBiomes180)((NoiseBasedChunkGenerator)generator).getBiomeSource();
int bx=c.getPos().getMinBlockX(),bz=c.getPos().getMinBlockZ();boolean animal=false;
var random=RandomSource.create(level.getSeed()^c.getPos().pack()^0x179DEC0L);
for(int x=bx+1;x<bx+15;x++)for(int z=bz+1;z<bz+15;z++){
int floors=0;
for(int y=Math.min(246,source.terrain(x,z).top()-16);y>=72;y--){
var floor=new BlockPos(x,y,z);var at=floor.above();
if(!c.getBlockState(at).isAir())continue;
var ground=c.getBlockState(floor);boolean water=ground.is(Blocks.WATER);
if(!water&&!natural(ground))continue;
if(Dungeon180.protectedAt(source,x,y,z))continue;
String b=biome(source,x,y,z);if(b.equals("adventure_caves")||!covered(c,x,y,z))continue;
if(!b.equals("adventure_grove")&&!b.equals("adventure_lush")&&!b.equals("adventure_marsh")&&!b.equals("adventure_mycelium"))continue;
if(water){if(random.nextInt(7)==0)plant(level,at,Blocks.LILY_PAD);continue;}
// Native trees get proper soil only on already existing rock, never over void.
boolean mycelium=b.equals("adventure_mycelium"),marsh=b.equals("adventure_marsh");
if(c.getBlockState(at).isAir()){
int roll=random.nextInt(28);
if(roll<2)plant(level,at,Blocks.FIREFLY_BUSH);
else if(roll<5&&mycelium)plant(level,at,random.nextBoolean()?Blocks.RED_MUSHROOM:Blocks.BROWN_MUSHROOM);
else if(roll==5)plant(level,at,Blocks.FLOWERING_AZALEA);
else if(roll<10)plant(level,at,Blocks.SHORT_GRASS);
}
if(!animal && random.nextInt(70)==0 && c.getBlockState(at).isAir()&&c.getBlockState(at.above()).isAir()){
if(mycelium&&ground.is(Blocks.MYCELIUM))animal=spawn(level,at,EntityTypes.MOOSHROOM,random);
else if(marsh)animal=spawn(level,at,EntityTypes.FROG,random);
}
if(++floors>=3)break;
}
}
}
public static void groves(WorldGenLevel level,ChunkGenerator generator,ChunkAccess c){
if(!WorldgenLab.ecology180(generator))return;
var source=(AdventureBiomes180)((NoiseBasedChunkGenerator)generator).getBiomeSource();
var random=RandomSource.create(level.getSeed()^c.getPos().pack()^0x179600DL);int placed=0;
for(int attempt=0;attempt<12&&placed<3;attempt++){
int x=c.getPos().getMinBlockX()+5+random.nextInt(6),z=c.getPos().getMinBlockZ()+5+random.nextInt(6);
for(int y=Math.min(246,source.terrain(x,z).top()-16);y>=72;y--){
var floor=new BlockPos(x,y,z);var at=floor.above();
if(!natural(c.getBlockState(floor))||!c.getBlockState(at).isAir()||!covered(c,x,y,z))continue;
if(Dungeon180.protectedAt(source,x,y,z))continue;
String b=biome(source,x,y,z);boolean mushroom=b.equals("adventure_mycelium"),oak=b.equals("adventure_grove"),marsh=b.equals("adventure_marsh");
if(!mushroom&&!oak&&!marsh&&!b.equals("adventure_lush"))continue;
boolean room=true;
for(int dx=0;dx<2;dx++)for(int dz=0;dz<2;dz++){
var p=floor.offset(dx,0,dz);
if(!natural(c.getBlockState(p))||!c.getBlockState(p.above(7)).isAir()){room=false;break;}
for(int dy=1;dy<=6;dy++)if(!c.getBlockState(p.above(dy)).isAir()){room=false;break;}
}
if(!room)continue;
for(int dx=0;dx<2;dx++)for(int dz=0;dz<2;dz++)c.setBlockState(floor.offset(dx,0,dz),(mushroom?Blocks.MYCELIUM:Blocks.GRASS_BLOCK).defaultBlockState());
String id=mushroom?(random.nextBoolean()?"huge_red_mushroom":"huge_brown_mushroom"):oak?"dark_oak":marsh?"swamp_oak":random.nextBoolean()?"huge_brown_mushroom":"azalea_tree";
var feature=level.registryAccess().lookupOrThrow(Registries.FEATURE).getValue(net.minecraft.resources.Identifier.withDefaultNamespace(id));
if(feature!=null&&feature.place(level,generator,random,at)){placed++;break;}
}
}
}
private static void plant(WorldGenLevel level,BlockPos at,Block block){var b=block.defaultBlockState();if(level.getBlockState(at).isAir()&&b.canSurvive(level,at))level.setBlock(at,b,2);}
static <T extends Mob> boolean spawn(WorldGenLevel level,BlockPos at,EntityType<T> type,RandomSource random){
var mob=type.create(level.getLevel(),EntitySpawnReason.CHUNK_GENERATION);if(mob==null)return false;
mob.snapTo(at.getX()+.5,at.getY(),at.getZ()+.5,random.nextFloat()*360,0);
mob.finalizeSpawn(level,level.getCurrentDifficultyAt(at),EntitySpawnReason.CHUNK_GENERATION,null);
mob.setPersistenceRequired();return level.addFreshEntity(mob);
}
}
@@ -0,0 +1,76 @@
package fr.koka.sanctuarytest;
import java.util.*;
import net.minecraft.core.*;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.block.*;
import net.minecraft.world.level.block.entity.SpawnerBlockEntity;
import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator;
/** Verify real trees, connected large water footprints and native dungeon contents before visiting. */
final class AdventureChecks180 {
private AdventureChecks180(){}
static Map<String,Object> check(ServerLevel level){
var source=(AdventureBiomes180)((NoiseBasedChunkGenerator)level.getChunkSource().getGenerator()).getBiomeSource();
for(var p:Cherry180.sites(source)){level.getChunk(p.getX()>>4,p.getZ()>>4);}
var trees=Cherry180.trees(source);
if(trees.isEmpty()){
// On reload the placement counter is empty; inspect the saved trunks without regenerating them.
var saved=new ArrayList<BlockPos>();
int start=source.field().eroded()?0:5,end=source.field().eroded()?15:11;
for(var site:Cherry180.sites(source))for(int x=(site.getX()>>4<<4)+start;x<=(site.getX()>>4<<4)+end;x++)
for(int z=(site.getZ()>>4<<4)+start;z<=(site.getZ()>>4<<4)+end;z++)for(int y=source.field().coherent()?251:271;y<=316;y++){
var p=new BlockPos(x,y,z);
if(level.getBlockState(p).is(Blocks.CHERRY_LOG)&&AdventureCaves180.natural(level.getBlockState(p.below())))saved.add(p);
}
trees=List.copyOf(saved);
}
int expected=source.field().coherent()?Cherry180.sites(source).size():3;
if(expected<2||expected>3||trees.size()!=expected)throw new IllegalStateException("Expected "+expected+" main-island cherry trees: "+trees);
for(var p:trees)if(!level.getBlockState(p).is(Blocks.CHERRY_LOG))throw new IllegalStateException("Missing actual cherry trunk at "+p);
var reefs=source.field().base().reefs();
if(!AdventureCaves180.biome(source,reefs.get(0).x(),reefs.get(0).y(),reefs.get(0).z()).equals("adventure_cherry")||!AdventureCaves180.biome(source,reefs.get(1).x(),reefs.get(1).y(),reefs.get(1).z()).equals("adventure_autumn"))throw new IllegalStateException("Wrong themed reefs");
var counts=new ArrayList<Integer>();
for(var b:Basins180.sites(source)){
int count=0;var visited=new HashSet<Long>();var queue=new ArrayDeque<BlockPos>();queue.add(new BlockPos(b.x(),b.y(),b.z()));
while(!queue.isEmpty()){
var p=queue.removeFirst();if(!visited.add(p.asLong())||Math.abs(p.getX()-b.x())>65||Math.abs(p.getZ()-b.z())>65)continue;
if(!level.getBlockState(p).is(Blocks.WATER))continue;count++;
for(var d:Direction.Plane.HORIZONTAL)queue.add(p.relative(d));
}
if(count<650)throw new IllegalStateException("Fragmented upper basin, water cells="+count+" "+b);
var upper=new BlockPos(b.x()+b.dx()*23,b.y(),b.z()+b.dz()*23);
var lower=new BlockPos(b.x()+b.dx()*37,b.y()-4,b.z()+b.dz()*37);
if(!level.getBlockState(upper).is(Blocks.WATER)||!level.getBlockState(lower).is(Blocks.WATER))throw new IllegalStateException("Missing spillway pair");
counts.add(count);
}
var plan=Dungeon180.plan(source);int spawners=0,caches=0,lanterns=0,frames=0;
for(var r:plan.rooms()){
var p=new BlockPos(r.x(),r.y()+1,r.z());level.getChunk(p.getX()>>4,p.getZ()>>4);
if(r.kind()==1){if(!(level.getBlockEntity(p) instanceof SpawnerBlockEntity))throw new IllegalStateException("Missing spawner at "+p);spawners++;}
if(r.kind()==2){if(!level.getBlockState(p).is(Blocks.RAIL))throw new IllegalStateException("Missing cache siding");caches++;}
var walking=p.offset(2,0,0);
if(!level.getBlockState(walking.below()).isSolidRender()||level.getBlockState(walking).isSolidRender()||level.getBlockState(walking.above()).isSolidRender())throw new IllegalStateException("Blocked room walkway "+walking);
}
// Sample all owned corridors, including chunk seams and the daylight exit.
int missingFloor=0,blocked=0;
var defects=new ArrayList<String>();
for(var cell:plan.paths().values()){
if(Dungeon180.roomAt(plan,cell.x(),cell.z())!=null)continue;
var p=new BlockPos(cell.x(),cell.y(),cell.z());
if(!level.getBlockState(p).isSolidRender()){missingFloor++;defects.add("floor:"+p);}
if(level.getBlockState(p.above()).isSolidRender()||level.getBlockState(p.above(2)).isSolidRender()){blocked++;defects.add("blocked:"+p);}
if(level.getBlockState(p.above(3)).is(Blocks.LANTERN))lanterns++;
if(level.getBlockState(p.above(4)).is(Blocks.DARK_OAK_LOG))frames++;
if(level.getBlockState(p.above()).is(Blocks.SEA_PICKLE))throw new IllegalStateException("Dry sea pickle");
}
if(missingFloor>0||blocked>0)throw new IllegalStateException("Dungeon passage defects: floor="+missingFloor+" blocked="+blocked+" "+defects);
if(spawners<4||caches<4||lanterns>frames/2+4)throw new IllegalStateException("Dungeon population or lighting mismatch");
var loot=level.getServer().reloadableRegistries().getLootTable(Dungeon180.LOOT);
var items=loot.getRandomItems(new net.minecraft.world.level.storage.loot.LootParams.Builder(level)
.withParameter(net.minecraft.world.level.storage.loot.parameters.LootContextParams.ORIGIN,new net.minecraft.world.phys.Vec3(0,150,0))
.create(net.minecraft.world.level.storage.loot.parameters.LootContextParamSets.CHEST));
var found=new HashSet<String>();for(var stack:items)found.add(net.minecraft.core.registries.BuiltInRegistries.ITEM.getKey(stack.getItem()).toString());
if(!found.containsAll(Set.of("minecraft:diamond","minecraft:emerald","sanctuary:ruby","sanctuary:sapphire")))throw new IllegalStateException("Missing requested gems in native loot: "+found);
var result=new LinkedHashMap<String,Object>();result.put("lootItems",found);result.put("cherryTrees",trees);result.put("upperBasinAreas",counts);result.put("basins",Basins180.sites(source));result.put("rooms",plan.rooms());result.put("spawners",spawners);result.put("lootCarts",caches);result.put("corridorLanterns",lanterns);result.put("supportFrames",frames);return result;
}
}
@@ -0,0 +1,62 @@
package fr.koka.sanctuarytest;
import com.mojang.serialization.MapCodec;
import net.minecraft.resources.Identifier;
import net.minecraft.world.level.block.*;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.levelgen.material.MaterialRuleContext;
import net.minecraft.world.level.levelgen.material.rule.*;
/** Expose the actual geological beds; moss is a thin local patch, never a universal cave soil. */
public record AdventureStone180(java.util.Optional<net.minecraft.world.level.levelgen.densityfunction.DensityFunction> slope) implements MaterialRule {
public static final MapCodec<AdventureStone180> CODEC=com.mojang.serialization.codecs.RecordCodecBuilder.mapCodec(i->i.group(
net.minecraft.world.level.levelgen.densityfunction.DensityFunction.CODEC.optionalFieldOf("slope").forGetter(AdventureStone180::slope)
).apply(i,AdventureStone180::new));
private static final java.util.Set<String> CAVES=java.util.Set.of("adventure_caves","adventure_grove","adventure_lush","adventure_marsh","adventure_mycelium");
private static double volume(long seed,int x,int y,int z,double size){
int layer=(int)Math.floor(y/size);double v=y/size-layer;v=v*v*(3-2*v);
double a=EcologyBiomes176.noise(seed+layer*7919L,x/size,z/size);
return a+(EcologyBiomes176.noise(seed+(layer+1)*7919L,x/size,z/size)-a)*v;
}
@Override public RuleEvaluator compile(MaterialRuleContext context){
long seed=context.getOrCreateRandomFactory(Identifier.fromNamespaceAndPath("sanctuary_test","ecology_strata_v1")).at(0,0,0).nextLong();
var slopeGetter=slope.map(d->context.getDensitiesInChunk(d,false)).orElse(null);
return new RuleEvaluator(){
int lastX=Integer.MIN_VALUE,lastZ=Integer.MIN_VALUE;double warp;
@Override public BlockState tryApply(int x,int y,int z){
if(x!=lastX || z!=lastZ){lastX=x;lastZ=z;warp=EcologyStrata176.warp(seed,x,z);}
String biome=context.getBiome().unwrapKey().orElseThrow().identifier().getPath();
boolean sulfur=biome.equals("natural_sulfur_187");
boolean cave=sulfur||CAVES.contains(biome);
if(sulfur){
// Coherent mineral beds on the original rock, including ceilings. Never carve a shell.
double deposit=volume(seed^0x1875A1L,x,y,z,17);
if(deposit>-.08)return (deposit>.51?Blocks.CINNABAR:Blocks.SULFUR).defaultBlockState();
if(deposit>-.25)return Blocks.CALCITE.defaultBlockState();
}
if(y>=48 && context.stoneDepthAbove()<=3){
if(cave){
double patch=volume(seed^0x178A055L,x,y,z,8);
if(context.stoneDepthAbove()==1){
if(biome.equals("adventure_mycelium"))return (patch>-.20?Blocks.MYCELIUM:Blocks.GRASS_BLOCK).defaultBlockState();
if(biome.equals("adventure_marsh"))return (patch>.20?Blocks.MUD:Blocks.GRASS_BLOCK).defaultBlockState();
if(biome.equals("adventure_lush") && patch>-.50)return Blocks.MOSS_BLOCK.defaultBlockState();
if(biome.equals("adventure_grove"))return (patch>.10?Blocks.MOSS_BLOCK:patch<-.35?Blocks.PODZOL:Blocks.GRASS_BLOCK).defaultBlockState();
if(patch>.72)return Blocks.MOSS_BLOCK.defaultBlockState();
}
}else if(!biome.equals("adventure_cliffs") && !biome.equals("adventure_void")
&& !(slopeGetter!=null && y>=248 && y<320 && slopeGetter.get()>.95f))
return (context.stoneDepthAbove()==1?Blocks.GRASS_BLOCK:Blocks.DIRT).defaultBlockState();
}
double depth=y+warp;var bed=EcologyStrata176.rock(depth,y);
if(depth<174)return bed;
double deposit=volume(seed^0x177DEFL,x,y,z,32);
if(deposit>.32)return Blocks.ANDESITE.defaultBlockState();
if(deposit<-.38)return (volume(seed^0x1776AAL,x,y,z,23)>0?Blocks.GRANITE:Blocks.DIORITE).defaultBlockState();
if(volume(seed^0x177BEDL,x,y,z,64)<.12)return Blocks.STONE.defaultBlockState();
return bed;
}
};
}
@Override public MapCodec<AdventureStone180> codec(){return CODEC;}
}
@@ -0,0 +1,75 @@
package fr.koka.sanctuarytest;
import fr.koka.sanctuary.palace.*;
import java.util.*;
import net.fabricmc.fabric.api.event.player.UseBlockCallback;
import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.*;
import net.minecraft.world.*;
import net.minecraft.world.item.*;
import net.minecraft.world.level.block.Block;
/** Local offers for this new preset only. Native block states own progress; no expansion is activated. */
final class AnchorOffers186 {
static final BlockPos CORE=new BlockPos(0,640,0);
static final List<PalaceAnchors.Demand> DEMANDS=List.of(
new PalaceAnchors.Demand(Items.STONE,32),new PalaceAnchors.Demand(Items.OAK_LOG,16),
new PalaceAnchors.Demand(Items.WHEAT,16),new PalaceAnchors.Demand(Items.COPPER_INGOT,16),
new PalaceAnchors.Demand(Items.COAL,16),new PalaceAnchors.Demand(Items.AMETHYST_SHARD,8),
new PalaceAnchors.Demand(Items.IRON_INGOT,8),new PalaceAnchors.Demand(Items.GLASS,16));
private AnchorOffers186() {}
static void register(){
UseBlockCallback.EVENT.register((player,world,hand,hit)->{
if(!(player instanceof ServerPlayer p)||!(world instanceof ServerLevel level)
||hand!=InteractionHand.MAIN_HAND||p.isSpectator()||!WorldgenLab.anchors186(level.getChunkSource().getGenerator()))return InteractionResult.PASS;
var pos=hit.getBlockPos();var block=level.getBlockState(pos);
if(!block.is(ExpansionAnchorBlock.BLOCK))return InteractionResult.PASS;
int sector=block.getValue(ExpansionAnchorBlock.SECTOR);
var result=offer(level,pos,p.getItemInHand(hand));var demand=DEMANDS.get(sector);
String key=switch(result){case CORE_OFF->"core_off";case ACTIVATED->"activated";case ALREADY_ACTIVE->"already";case INVALID->"invalid";default->"required";};
p.sendSystemMessage(Component.translatable("sanctuary.palace."+key,
Component.translatable("sanctuary.palace.direction."+sector),demand.count(),new ItemStack(demand.item()).getHoverName()),true);
return InteractionResult.SUCCESS;
});
}
static PalaceAnchors.Result offer(ServerLevel level,BlockPos pos,ItemStack held){
var g=level.getChunkSource().getGenerator();if(!WorldgenLab.anchors186(g))return PalaceAnchors.Result.INVALID;
var site=Anchors186.plan(Anchors186.source(g),level.getSeed()).sites().stream().filter(s->s.anchor().equals(pos)).findFirst().orElse(null);
if(site==null)return PalaceAnchors.Result.INVALID;
var anchor=level.getBlockState(pos);
if(!anchor.is(ExpansionAnchorBlock.BLOCK)||anchor.getValue(ExpansionAnchorBlock.SECTOR)!=site.sector())return PalaceAnchors.Result.INVALID;
if(anchor.getValue(ExpansionAnchorBlock.LIT))return PalaceAnchors.Result.ALREADY_ACTIVE;
var core=level.getBlockState(CORE);
if(!core.is(OriginBlock.BLOCK))return PalaceAnchors.Result.INVALID;
if(!core.getValue(OriginBlock.LIT))return PalaceAnchors.Result.CORE_OFF;
var demand=DEMANDS.get(site.sector());
if(!held.is(demand.item())||held.getCount()<demand.count())return PalaceAnchors.Result.REQUIRED;
level.setBlock(CORE,OriginBlock.relay(core,site.sector(),true),Block.UPDATE_ALL);
level.setBlock(pos,anchor.setValue(ExpansionAnchorBlock.LIT,true),Block.UPDATE_ALL);
held.shrink(demand.count());return PalaceAnchors.Result.ACTIVATED;
}
static Map<String,Object> check(ServerLevel level,List<Anchors186.Site> sites){
var before=level.getBlockState(CORE);var anchors=new HashMap<BlockPos,net.minecraft.world.level.block.state.BlockState>();
for(var s:sites)anchors.put(s.anchor(),level.getBlockState(s.anchor()));
try {
level.setBlock(CORE,before.setValue(OriginBlock.RELAYS,0).setValue(OriginBlock.LIT,true),Block.UPDATE_ALL);
int mask=0;
for(int i:new int[]{7,2,5,0,6,1,4,3}){
var site=sites.get(i);var demand=DEMANDS.get(i);
level.setBlock(site.anchor(),anchors.get(site.anchor()).setValue(ExpansionAnchorBlock.LIT,false),Block.UPDATE_ALL);
var wrong=new ItemStack(Items.DIRT,64);var shortStack=new ItemStack(demand.item(),demand.count()-1);
if(offer(level,site.anchor(),wrong)!=PalaceAnchors.Result.REQUIRED||wrong.getCount()!=64
||offer(level,site.anchor(),shortStack)!=PalaceAnchors.Result.REQUIRED||shortStack.getCount()!=demand.count()-1)throw new IllegalStateException("Invalid offer consumed");
var held=new ItemStack(demand.item(),demand.count()+1);
if(offer(level,site.anchor(),held)!=PalaceAnchors.Result.ACTIVATED||held.getCount()!=1)throw new IllegalStateException("Valid offer failed");
mask|=1<<i;
if(level.getBlockState(CORE).getValue(OriginBlock.RELAYS)!=mask||!level.getBlockState(site.anchor()).getValue(ExpansionAnchorBlock.LIT))throw new IllegalStateException("Wrong relay bit");
if(offer(level,site.anchor(),held)!=PalaceAnchors.Result.ALREADY_ACTIVE||held.getCount()!=1)throw new IllegalStateException("Repeated offer consumed");
}
return Map.of("distinctMaterials",8,"relayMask",mask,"invalidAndRepeatedOffersPreserved",true);
} finally {
level.setBlock(CORE,before,Block.UPDATE_ALL);anchors.forEach((p,b)->level.setBlock(p,b,Block.UPDATE_ALL));
}
}
}
@@ -0,0 +1,75 @@
package fr.koka.sanctuarytest;
import fr.koka.sanctuary.palace.*;
import java.util.*;
import net.fabricmc.fabric.api.event.player.UseBlockCallback;
import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.*;
import net.minecraft.world.*;
import net.minecraft.world.item.*;
import net.minecraft.world.level.block.Block;
/** Local offers for this new preset only. Native block states own progress; no expansion is activated. */
final class AnchorOffers187 {
static final BlockPos CORE=new BlockPos(0,640,0);
static final List<PalaceAnchors.Demand> DEMANDS=List.of(
new PalaceAnchors.Demand(Items.STONE,32),new PalaceAnchors.Demand(Items.OAK_LOG,16),
new PalaceAnchors.Demand(Items.WHEAT,16),new PalaceAnchors.Demand(Items.COPPER_INGOT,16),
new PalaceAnchors.Demand(Items.COAL,16),new PalaceAnchors.Demand(Items.AMETHYST_SHARD,8),
new PalaceAnchors.Demand(Items.IRON_INGOT,8),new PalaceAnchors.Demand(Items.GLASS,16));
private AnchorOffers187() {}
static void register(){
UseBlockCallback.EVENT.register((player,world,hand,hit)->{
if(!(player instanceof ServerPlayer p)||!(world instanceof ServerLevel level)
||hand!=InteractionHand.MAIN_HAND||p.isSpectator()||!WorldgenLab.natural187(level.getChunkSource().getGenerator()))return InteractionResult.PASS;
var pos=hit.getBlockPos();var block=level.getBlockState(pos);
if(!block.is(ExpansionAnchorBlock.BLOCK))return InteractionResult.PASS;
int sector=block.getValue(ExpansionAnchorBlock.SECTOR);
var result=offer(level,pos,p.getItemInHand(hand));var demand=DEMANDS.get(sector);
String key=switch(result){case CORE_OFF->"core_off";case ACTIVATED->"activated";case ALREADY_ACTIVE->"already";case INVALID->"invalid";default->"required";};
p.sendSystemMessage(Component.translatable("sanctuary.palace."+key,
Component.translatable("sanctuary.palace.direction."+sector),demand.count(),new ItemStack(demand.item()).getHoverName()),true);
return InteractionResult.SUCCESS;
});
}
static PalaceAnchors.Result offer(ServerLevel level,BlockPos pos,ItemStack held){
var g=level.getChunkSource().getGenerator();if(!WorldgenLab.natural187(g))return PalaceAnchors.Result.INVALID;
var site=Anchors187.plan(Anchors187.source(g),level.getSeed()).sites().stream().filter(s->s.anchor().equals(pos)).findFirst().orElse(null);
if(site==null)return PalaceAnchors.Result.INVALID;
var anchor=level.getBlockState(pos);
if(!anchor.is(ExpansionAnchorBlock.BLOCK)||anchor.getValue(ExpansionAnchorBlock.SECTOR)!=site.sector())return PalaceAnchors.Result.INVALID;
if(anchor.getValue(ExpansionAnchorBlock.LIT))return PalaceAnchors.Result.ALREADY_ACTIVE;
var core=level.getBlockState(CORE);
if(!core.is(OriginBlock.BLOCK))return PalaceAnchors.Result.INVALID;
if(!core.getValue(OriginBlock.LIT))return PalaceAnchors.Result.CORE_OFF;
var demand=DEMANDS.get(site.sector());
if(!held.is(demand.item())||held.getCount()<demand.count())return PalaceAnchors.Result.REQUIRED;
level.setBlock(CORE,OriginBlock.relay(core,site.sector(),true),Block.UPDATE_ALL);
level.setBlock(pos,anchor.setValue(ExpansionAnchorBlock.LIT,true),Block.UPDATE_ALL);
held.shrink(demand.count());return PalaceAnchors.Result.ACTIVATED;
}
static Map<String,Object> check(ServerLevel level,List<Anchors187.Site> sites){
var before=level.getBlockState(CORE);var anchors=new HashMap<BlockPos,net.minecraft.world.level.block.state.BlockState>();
for(var s:sites)anchors.put(s.anchor(),level.getBlockState(s.anchor()));
try {
level.setBlock(CORE,before.setValue(OriginBlock.RELAYS,0).setValue(OriginBlock.LIT,true),Block.UPDATE_ALL);
int mask=0;
for(int i:new int[]{7,2,5,0,6,1,4,3}){
var site=sites.get(i);var demand=DEMANDS.get(i);
level.setBlock(site.anchor(),anchors.get(site.anchor()).setValue(ExpansionAnchorBlock.LIT,false),Block.UPDATE_ALL);
var wrong=new ItemStack(Items.DIRT,64);var shortStack=new ItemStack(demand.item(),demand.count()-1);
if(offer(level,site.anchor(),wrong)!=PalaceAnchors.Result.REQUIRED||wrong.getCount()!=64
||offer(level,site.anchor(),shortStack)!=PalaceAnchors.Result.REQUIRED||shortStack.getCount()!=demand.count()-1)throw new IllegalStateException("Invalid offer consumed");
var held=new ItemStack(demand.item(),demand.count()+1);
if(offer(level,site.anchor(),held)!=PalaceAnchors.Result.ACTIVATED||held.getCount()!=1)throw new IllegalStateException("Valid offer failed");
mask|=1<<i;
if(level.getBlockState(CORE).getValue(OriginBlock.RELAYS)!=mask||!level.getBlockState(site.anchor()).getValue(ExpansionAnchorBlock.LIT))throw new IllegalStateException("Wrong relay bit");
if(offer(level,site.anchor(),held)!=PalaceAnchors.Result.ALREADY_ACTIVE||held.getCount()!=1)throw new IllegalStateException("Repeated offer consumed");
}
return Map.of("distinctMaterials",8,"relayMask",mask,"invalidAndRepeatedOffersPreserved",true);
} finally {
level.setBlock(CORE,before,Block.UPDATE_ALL);anchors.forEach((p,b)->level.setBlock(p,b,Block.UPDATE_ALL));
}
}
}
@@ -0,0 +1,75 @@
package fr.koka.sanctuarytest;
import fr.koka.sanctuary.palace.*;
import java.util.*;
import net.fabricmc.fabric.api.event.player.UseBlockCallback;
import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.*;
import net.minecraft.world.*;
import net.minecraft.world.item.*;
import net.minecraft.world.level.block.Block;
/** Local offers for this new preset only. Native block states own progress; no expansion is activated. */
final class AnchorOffers188 {
static final BlockPos CORE=new BlockPos(0,640,0);
static final List<PalaceAnchors.Demand> DEMANDS=List.of(
new PalaceAnchors.Demand(Items.STONE,32),new PalaceAnchors.Demand(Items.OAK_LOG,16),
new PalaceAnchors.Demand(Items.WHEAT,16),new PalaceAnchors.Demand(Items.COPPER_INGOT,16),
new PalaceAnchors.Demand(Items.COAL,16),new PalaceAnchors.Demand(Items.AMETHYST_SHARD,8),
new PalaceAnchors.Demand(Items.IRON_INGOT,8),new PalaceAnchors.Demand(Items.GLASS,16));
private AnchorOffers188() {}
static void register(){
UseBlockCallback.EVENT.register((player,world,hand,hit)->{
if(!(player instanceof ServerPlayer p)||!(world instanceof ServerLevel level)
||hand!=InteractionHand.MAIN_HAND||p.isSpectator()||!WorldgenLab.details188(level.getChunkSource().getGenerator()))return InteractionResult.PASS;
var pos=hit.getBlockPos();var block=level.getBlockState(pos);
if(!block.is(ExpansionAnchorBlock.BLOCK))return InteractionResult.PASS;
int sector=block.getValue(ExpansionAnchorBlock.SECTOR);
var result=offer(level,pos,p.getItemInHand(hand));var demand=DEMANDS.get(sector);
String key=switch(result){case CORE_OFF->"core_off";case ACTIVATED->"activated";case ALREADY_ACTIVE->"already";case INVALID->"invalid";default->"required";};
p.sendSystemMessage(Component.translatable("sanctuary.palace."+key,
Component.translatable("sanctuary.palace.direction."+sector),demand.count(),new ItemStack(demand.item()).getHoverName()),true);
return InteractionResult.SUCCESS;
});
}
static PalaceAnchors.Result offer(ServerLevel level,BlockPos pos,ItemStack held){
var g=level.getChunkSource().getGenerator();if(!WorldgenLab.details188(g))return PalaceAnchors.Result.INVALID;
var site=Anchors188.plan(Anchors188.source(g),level.getSeed()).sites().stream().filter(s->s.anchor().equals(pos)).findFirst().orElse(null);
if(site==null)return PalaceAnchors.Result.INVALID;
var anchor=level.getBlockState(pos);
if(!anchor.is(ExpansionAnchorBlock.BLOCK)||anchor.getValue(ExpansionAnchorBlock.SECTOR)!=site.sector())return PalaceAnchors.Result.INVALID;
if(anchor.getValue(ExpansionAnchorBlock.LIT))return PalaceAnchors.Result.ALREADY_ACTIVE;
var core=level.getBlockState(CORE);
if(!core.is(OriginBlock.BLOCK))return PalaceAnchors.Result.INVALID;
if(!core.getValue(OriginBlock.LIT))return PalaceAnchors.Result.CORE_OFF;
var demand=DEMANDS.get(site.sector());
if(!held.is(demand.item())||held.getCount()<demand.count())return PalaceAnchors.Result.REQUIRED;
level.setBlock(CORE,OriginBlock.relay(core,site.sector(),true),Block.UPDATE_ALL);
level.setBlock(pos,anchor.setValue(ExpansionAnchorBlock.LIT,true),Block.UPDATE_ALL);
held.shrink(demand.count());return PalaceAnchors.Result.ACTIVATED;
}
static Map<String,Object> check(ServerLevel level,List<Anchors188.Site> sites){
var before=level.getBlockState(CORE);var anchors=new HashMap<BlockPos,net.minecraft.world.level.block.state.BlockState>();
for(var s:sites)anchors.put(s.anchor(),level.getBlockState(s.anchor()));
try {
level.setBlock(CORE,before.setValue(OriginBlock.RELAYS,0).setValue(OriginBlock.LIT,true),Block.UPDATE_ALL);
int mask=0;
for(int i:new int[]{7,2,5,0,6,1,4,3}){
var site=sites.get(i);var demand=DEMANDS.get(i);
level.setBlock(site.anchor(),anchors.get(site.anchor()).setValue(ExpansionAnchorBlock.LIT,false),Block.UPDATE_ALL);
var wrong=new ItemStack(Items.DIRT,64);var shortStack=new ItemStack(demand.item(),demand.count()-1);
if(offer(level,site.anchor(),wrong)!=PalaceAnchors.Result.REQUIRED||wrong.getCount()!=64
||offer(level,site.anchor(),shortStack)!=PalaceAnchors.Result.REQUIRED||shortStack.getCount()!=demand.count()-1)throw new IllegalStateException("Invalid offer consumed");
var held=new ItemStack(demand.item(),demand.count()+1);
if(offer(level,site.anchor(),held)!=PalaceAnchors.Result.ACTIVATED||held.getCount()!=1)throw new IllegalStateException("Valid offer failed");
mask|=1<<i;
if(level.getBlockState(CORE).getValue(OriginBlock.RELAYS)!=mask||!level.getBlockState(site.anchor()).getValue(ExpansionAnchorBlock.LIT))throw new IllegalStateException("Wrong relay bit");
if(offer(level,site.anchor(),held)!=PalaceAnchors.Result.ALREADY_ACTIVE||held.getCount()!=1)throw new IllegalStateException("Repeated offer consumed");
}
return Map.of("distinctMaterials",8,"relayMask",mask,"invalidAndRepeatedOffersPreserved",true);
} finally {
level.setBlock(CORE,before,Block.UPDATE_ALL);anchors.forEach((p,b)->level.setBlock(p,b,Block.UPDATE_ALL));
}
}
}
@@ -0,0 +1,75 @@
package fr.koka.sanctuarytest;
import fr.koka.sanctuary.palace.*;
import java.util.*;
import net.fabricmc.fabric.api.event.player.UseBlockCallback;
import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.*;
import net.minecraft.world.*;
import net.minecraft.world.item.*;
import net.minecraft.world.level.block.Block;
/** Local offers for this new preset only. Native block states own progress; no expansion is activated. */
final class AnchorOffers189 {
static final BlockPos CORE=new BlockPos(0,640,0);
static final List<PalaceAnchors.Demand> DEMANDS=List.of(
new PalaceAnchors.Demand(Items.STONE,32),new PalaceAnchors.Demand(Items.OAK_LOG,16),
new PalaceAnchors.Demand(Items.WHEAT,16),new PalaceAnchors.Demand(Items.COPPER_INGOT,16),
new PalaceAnchors.Demand(Items.COAL,16),new PalaceAnchors.Demand(Items.AMETHYST_SHARD,8),
new PalaceAnchors.Demand(Items.IRON_INGOT,8),new PalaceAnchors.Demand(Items.GLASS,16));
private AnchorOffers189() {}
static void register(){
UseBlockCallback.EVENT.register((player,world,hand,hit)->{
if(!(player instanceof ServerPlayer p)||!(world instanceof ServerLevel level)
||hand!=InteractionHand.MAIN_HAND||p.isSpectator()||!WorldgenLab.finishedTerrain(level.getChunkSource().getGenerator()))return InteractionResult.PASS;
var pos=hit.getBlockPos();var block=level.getBlockState(pos);
if(!block.is(ExpansionAnchorBlock.BLOCK)||SharedIsland196.revision(level.getChunkSource().getGenerator())>=199)return InteractionResult.PASS;
int sector=block.getValue(ExpansionAnchorBlock.SECTOR);
var result=offer(level,pos,p.getItemInHand(hand));var demand=DEMANDS.get(sector);
String key=switch(result){case CORE_OFF->"core_off";case ACTIVATED->"activated";case ALREADY_ACTIVE->"already";case INVALID->"invalid";default->"required";};
p.sendSystemMessage(Component.translatable("sanctuary.palace."+key,
Component.translatable("sanctuary.palace.direction."+sector),demand.count(),new ItemStack(demand.item()).getHoverName()),true);
return InteractionResult.SUCCESS;
});
}
static PalaceAnchors.Result offer(ServerLevel level,BlockPos pos,ItemStack held){
var g=level.getChunkSource().getGenerator();if(!WorldgenLab.finishedTerrain(g))return PalaceAnchors.Result.INVALID;
var site=Anchors189.plan(Anchors189.source(g),level.getSeed()).sites().stream().filter(s->s.anchor().equals(pos)).findFirst().orElse(null);
if(site==null)return PalaceAnchors.Result.INVALID;
var anchor=level.getBlockState(pos);
if(!anchor.is(ExpansionAnchorBlock.BLOCK)||anchor.getValue(ExpansionAnchorBlock.SECTOR)!=site.sector())return PalaceAnchors.Result.INVALID;
if(anchor.getValue(ExpansionAnchorBlock.LIT))return PalaceAnchors.Result.ALREADY_ACTIVE;
var core=level.getBlockState(CORE);
if(!core.is(OriginBlock.BLOCK))return PalaceAnchors.Result.INVALID;
if(!core.getValue(OriginBlock.LIT))return PalaceAnchors.Result.CORE_OFF;
var demand=DEMANDS.get(site.sector());
if(!held.is(demand.item())||held.getCount()<demand.count())return PalaceAnchors.Result.REQUIRED;
level.setBlock(CORE,OriginBlock.relay(core,site.sector(),true),Block.UPDATE_ALL);
level.setBlock(pos,anchor.setValue(ExpansionAnchorBlock.LIT,true),Block.UPDATE_ALL);
held.shrink(demand.count());return PalaceAnchors.Result.ACTIVATED;
}
static Map<String,Object> check(ServerLevel level,List<Anchors189.Site> sites){
var before=level.getBlockState(CORE);var anchors=new HashMap<BlockPos,net.minecraft.world.level.block.state.BlockState>();
for(var s:sites)anchors.put(s.anchor(),level.getBlockState(s.anchor()));
try {
level.setBlock(CORE,before.setValue(OriginBlock.RELAYS,0).setValue(OriginBlock.LIT,true),Block.UPDATE_ALL);
int mask=0;
for(int i:new int[]{7,2,5,0,6,1,4,3}){
var site=sites.get(i);var demand=DEMANDS.get(i);
level.setBlock(site.anchor(),anchors.get(site.anchor()).setValue(ExpansionAnchorBlock.LIT,false),Block.UPDATE_ALL);
var wrong=new ItemStack(Items.DIRT,64);var shortStack=new ItemStack(demand.item(),demand.count()-1);
if(offer(level,site.anchor(),wrong)!=PalaceAnchors.Result.REQUIRED||wrong.getCount()!=64
||offer(level,site.anchor(),shortStack)!=PalaceAnchors.Result.REQUIRED||shortStack.getCount()!=demand.count()-1)throw new IllegalStateException("Invalid offer consumed");
var held=new ItemStack(demand.item(),demand.count()+1);
if(offer(level,site.anchor(),held)!=PalaceAnchors.Result.ACTIVATED||held.getCount()!=1)throw new IllegalStateException("Valid offer failed");
mask|=1<<i;
if(level.getBlockState(CORE).getValue(OriginBlock.RELAYS)!=mask||!level.getBlockState(site.anchor()).getValue(ExpansionAnchorBlock.LIT))throw new IllegalStateException("Wrong relay bit");
if(offer(level,site.anchor(),held)!=PalaceAnchors.Result.ALREADY_ACTIVE||held.getCount()!=1)throw new IllegalStateException("Repeated offer consumed");
}
return Map.of("distinctMaterials",8,"relayMask",mask,"invalidAndRepeatedOffersPreserved",true);
} finally {
level.setBlock(CORE,before,Block.UPDATE_ALL);anchors.forEach((p,b)->level.setBlock(p,b,Block.UPDATE_ALL));
}
}
}
@@ -0,0 +1,202 @@
package fr.koka.sanctuarytest;
import fr.koka.sanctuary.palace.ExpansionAnchorBlock;
import java.util.*;
import net.minecraft.core.*;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.*;
import net.minecraft.world.level.block.*;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.*;
import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator;
import net.minecraft.world.level.levelgen.densityfunction.SamplerContext;
/** Eight seed-owned refuges. Planning reads density only; placement never loads a neighbour chunk. */
public final class Anchors186 {
enum Kind { SURFACE, CAVE, SKY }
record Site(int sector,BlockPos floor,Kind kind,int dx,int dz,int rx,int rz,boolean bright) {
BlockPos anchor(){return floor.above(2);}
BlockPos entrance(){return floor.offset(dx*11,1,dz*11);}
}
record Plan(List<Site> sites,Map<ChunkPos,Map<BlockPos,BlockState>> chunks,long millis) {}
private record Candidate(BlockPos floor,Kind kind,int dx,int dz,double score) {}
private static final Map<AdventureBiomes180,Plan> PLANS=Collections.synchronizedMap(new WeakHashMap<>());
private Anchors186() {}
static AdventureBiomes180 source(ChunkGenerator g){return (AdventureBiomes180)((NoiseBasedChunkGenerator)g).getBiomeSource();}
static Plan plan(AdventureBiomes180 s,long seed){synchronized(PLANS){return PLANS.computeIfAbsent(s,key->create(s,seed));}}
private static boolean rock(AdventureBiomes180 s,int x,int y,int z){return s.field().sampleValue(SamplerContext.EMPTY_UNCACHED,x,y,z)>0;}
private static long mix(long n){n=(n^(n>>>30))*0xbf58476d1ce4e5b9L;n=(n^(n>>>27))*0x94d049bb133111ebL;return n^(n>>>31);}
private static boolean reserved(AdventureBiomes180 s,int x,int z){
if(Math.hypot(x,z)<88||Math.hypot(x+152,z-152)<52||Dungeon180.near(s,x,z,28))return true;
for(var b:Basins180.sites(s))if(Math.hypot(x-b.x(),z-b.z())<78)return true;
for(var p:NaturalWater184.plan(s).pools())if(Math.abs(x-p.seed().getX())<68&&Math.abs(z-p.seed().getZ())<68)return true;
return false;
}
private static Candidate candidate(AdventureBiomes180 s,int x,int y,int z,Kind kind,long seed){
if(!rock(s,x,y,z)||rock(s,x,y+1,z)||rock(s,x,y+3,z))return null;
// Require existing material beneath the entire centre, including the small pedestal.
for(int dx=-3;dx<=3;dx+=3)for(int dz=-3;dz<=3;dz+=3)
if(!rock(s,x+dx,y-3,z+dz)||!rock(s,x+dx,y-5,z+dz))return null;
int best=-1,ex=0,ez=0;
for(var d:Direction.Plane.HORIZONTAL){
int score=0;boolean end=false;
for(int step=1;step<=11;step++){
int px=x+d.getStepX()*step,pz=z+d.getStepZ()*step;
if(!rock(s,px,y-3,pz))break;
if(!rock(s,px,y+1,pz)&&!rock(s,px,y+2,pz))score++;
if(step==11)end=!rock(s,px,y+1,pz)&&!rock(s,px,y+2,pz);
}
if(end)for(int step=2;step<=11;step++)for(int side=-1;side<=1;side++){
int bend=step>3&&step<9?1:0;
int px=x+d.getStepX()*step-d.getStepZ()*(bend+side),pz=z+d.getStepZ()*step+d.getStepX()*(bend+side);
if(!rock(s,px,y-3,pz))end=false;
}
if(end&&score>best){best=score;ex=d.getStepX();ez=d.getStepZ();}
}
if(best<7)return null;
int air=0,roof=0;
for(int dx=-6;dx<=6;dx+=3)for(int dz=-6;dz<=6;dz+=3){
if(!rock(s,x+dx,y+3,z+dz))air++;
if(rock(s,x+dx,y+12,z+dz)||rock(s,x+dx,y+24,z+dz))roof++;
}
if(kind==Kind.CAVE&&roof<8)return null;
double jitter=(mix(seed^((long)x*73428767)^((long)y*912931)^z)>>>11)*0x1.0p-53;
return new Candidate(new BlockPos(x,y,z),kind,ex,ez,air*.1+best*.15+jitter*9);
}
private static Plan create(AdventureBiomes180 s,long seed){
long started=System.nanoTime();var candidates=new ArrayList<Candidate>();
// Sparse fixed grid, finite vertical scan. No terrain regions, raycasts or runtime search.
for(int x=-300;x<=300;x+=12)for(int z=-300;z<=300;z+=12){
if(Math.hypot(x,z)>310||reserved(s,x,z))continue;
int top=s.field().surface(x,z);if(top<140)continue;
var surface=candidate(s,x,top,z,Kind.SURFACE,seed);if(surface!=null)candidates.add(surface);
for(int y=Math.min(top-18,236);y>=92;y--){
if(!rock(s,x,y,z)||rock(s,x,y+1,z)||rock(s,x,y+4,z))continue;
var cave=candidate(s,x,y,z,Kind.CAVE,seed);if(cave!=null)candidates.add(cave);
}
}
for(var reef:s.field().base().reefs()){
if(reef.length()<30)continue;
for(int dx=-12;dx<=12;dx+=6)for(int dz=-12;dz<=12;dz+=6){
int x=reef.x()+dx,z=reef.z()+dz;
for(int y=reef.y()+reef.rise()+2;y>=reef.y()-5;y--){
if(!rock(s,x,y,z))continue;
var sky=candidate(s,x,y,z,Kind.SKY,seed);if(sky!=null)candidates.add(sky);break;
}
}
}
var chosen=new ArrayList<Site>();int skyCount=0;
// Two eligible aerial sectors are selected by seed; other sectors alternate cave and surface preference.
var aerial=new ArrayList<Integer>();
for(int sector=0;sector<8;sector++){final int k=sector;if(candidates.stream().anyMatch(c->c.kind==Kind.SKY&&Rosette183.sector(c.floor.getX(),c.floor.getZ())==k))aerial.add(k);}
aerial.sort(Comparator.comparingLong(k->mix(seed^k*0x186L)));
var skySectors=new HashSet<>(aerial.subList(0,Math.min(2,aerial.size())));
for(int sector=0;sector<8;sector++){
final int k=sector;Kind preferred=skySectors.contains(k)?Kind.SKY:((sector+(int)(seed&1))%2==0?Kind.CAVE:Kind.SURFACE);
var ranked=candidates.stream().filter(c->Rosette183.sector(c.floor.getX(),c.floor.getZ())==k)
.sorted(Comparator.<Candidate>comparingDouble(c->c.score+(c.kind==preferred?100:0)).reversed()).toList();
Candidate selected=null;
for(var c:ranked){
if(c.kind==Kind.SKY&&!skySectors.contains(k))continue;
if(chosen.stream().anyMatch(v->v.floor.distSqr(c.floor)<44*44))continue;
selected=c;break;
}
if(selected==null)throw new IllegalStateException("No naturally supported anchor in sector "+k+" for seed "+seed);
var c=selected;long grain=mix(seed^k*7919L);
chosen.add(new Site(k,c.floor,c.kind,c.dx,c.dz,6+(int)(grain&1),5+(int)((grain>>>1)&1),(grain&4)==0));
if(c.kind==Kind.SKY)skyCount++;
}
if(skyCount==0)throw new IllegalStateException("No supported aerial anchor for seed "+seed);
var chunks=new HashMap<ChunkPos,Map<BlockPos,BlockState>>();
for(var site:chosen)for(var e:sculpt(s,site,seed).entrySet())
chunks.computeIfAbsent(new ChunkPos(e.getKey().getX()>>4,e.getKey().getZ()>>4),key->new LinkedHashMap<>()).put(e.getKey(),e.getValue());
chunks.replaceAll((k,v)->Map.copyOf(v));
return new Plan(List.copyOf(chosen),Map.copyOf(chunks),(System.nanoTime()-started)/1_000_000);
}
private static void put(Map<BlockPos,BlockState> m,BlockPos p,Block b){m.put(p,b.defaultBlockState());}
private static Map<BlockPos,BlockState> sculpt(AdventureBiomes180 s,Site site,long seed){
var m=new LinkedHashMap<BlockPos,BlockState>();var o=site.floor;int y=o.getY();
Block stone=site.kind==Kind.SKY?Blocks.CALCITE:site.kind==Kind.CAVE?Blocks.TUFF:Blocks.ANDESITE;
double phase=(mix(seed^site.sector) & 1023)/163.0;
for(int dx=-site.rx-2;dx<=site.rx+2;dx++)for(int dz=-site.rz-2;dz<=site.rz+2;dz++){
int x=o.getX()+dx,z=o.getZ()+dz;
double d=dx*dx/(double)(site.rx*site.rx)+dz*dz/(double)(site.rz*site.rz)+.12*Math.sin(dx*.7+phase)*Math.cos(dz*.6);
if(d>1.15)continue;
int floor=y+(Math.abs(dx)<=3&&Math.abs(dz)<=3?0:(int)Math.round(.8*Math.sin(dx*.5+phase)*Math.cos(dz*.5)));
if(!rock(s,x,floor-3,z))continue; // Keep the edge of the host landform instead of hanging a platform over void.
var at=new BlockPos(x,floor,z);
if(d<=1){
for(int h=-2;h<=0;h++)put(m,at.above(h),h==0&&Math.floorMod(x+z+site.sector,5)==0?Blocks.MOSS_BLOCK:stone);
int height=3+(int)(4*Math.sqrt(Math.max(0,1-d)));
for(int h=1;h<=height;h++)put(m,at.above(h),Blocks.AIR);
if(d>.62&&Math.floorMod(x*7+z*13,11)==0){
put(m,at,Blocks.MOSS_BLOCK);put(m,at.above(),Blocks.FLOWERING_AZALEA);
}
}else {
// Short reliefs follow surviving rock faces; no closed constructed shell.
for(int h=0;h<=5;h++)if(rock(s,x,floor+h,z))
put(m,at.above(h),h==2&&Math.floorMod(x+z,3)==0?Blocks.CHISELED_TUFF:h==0?Blocks.MOSS_BLOCK:stone);
}
}
// A slight bend opens to an actual, dry natural floor found during selection.
for(int step=2;step<=11;step++){
int bend=step>3&&step<9?1:0;
int x=o.getX()+site.dx*step-site.dz*bend,z=o.getZ()+site.dz*step+site.dx*bend;
for(int side=-1;side<=1;side++){
var p=new BlockPos(x-site.dz*side,y,z+site.dx*side);
for(int h=-2;h<=0;h++)put(m,p.above(h),h==0?Blocks.MOSS_BLOCK:stone);
for(int h=1;h<=4;h++)put(m,p.above(h),Blocks.AIR);
}
}
// Low eroded crests shelter surface sites; all are rooted in the existing mass.
for(int side:new int[]{-1,1}){
int dx=-site.dx*4-site.dz*side*3,dz=-site.dz*4+site.dx*side*3;
var base=o.offset(dx,0,dz);if(!rock(s,base.getX(),y-3,base.getZ()))continue;
for(int h=-2;h<=3+(site.sector+side&1);h++)put(m,base.above(h),h==2?Blocks.CHISELED_TUFF:stone);
put(m,base.above(2).offset(site.dx,0,site.dz),Rosette183.GLASS[site.sector]);
if(site.bright)put(m,base.above(2),Blocks.SEA_LANTERN);
else put(m,base.above(3),Blocks.AMETHYST_BLOCK);
}
for(int dx=-1;dx<=1;dx++)for(int dz=-1;dz<=1;dz++){
put(m,o.offset(dx,0,dz),Blocks.CHISELED_TUFF);
for(int h=1;h<=4;h++)put(m,o.offset(dx,h,dz),Blocks.AIR);
}
put(m,o.above(),Rosette183.GLASS[site.sector]);
m.put(site.anchor(),ExpansionAnchorBlock.BLOCK.defaultBlockState().setValue(ExpansionAnchorBlock.SECTOR,site.sector));
return m;
}
public static void place(WorldGenLevel level,ChunkGenerator g,ChunkAccess chunk){
if(!WorldgenLab.anchors186(g))return;
var entries=plan(source(g),level.getSeed()).chunks.get(chunk.getPos());
if(entries!=null)entries.forEach(chunk::setBlockState);
}
public static boolean protects(WorldGenLevel level,ChunkGenerator g,BlockPos pos){
if(!WorldgenLab.anchors186(g)||pos.getY()<88||pos.getY()>512||Math.abs(pos.getX())>325||Math.abs(pos.getZ())>325)return false;
var entries=plan(source(g),level.getSeed()).chunks.get(new ChunkPos(pos.getX()>>4,pos.getZ()>>4));
return entries!=null&&entries.containsKey(pos);
}
static Map<String,Object> check(ServerLevel level,AdventureBiomes180 source){
var p=plan(source,level.getSeed());var again=create(source,level.getSeed());
if(!p.sites.equals(again.sites)||!p.chunks.equals(again.chunks))throw new IllegalStateException("Unstable anchor plan");
int count=0;var details=new ArrayList<Map<String,Object>>();
for(var cp:p.chunks.keySet())level.getChunk(cp.x(),cp.z());
for(var e:p.chunks.entrySet())for(var b:e.getValue().entrySet()){
if(!e.getKey().contains(b.getKey()))throw new IllegalStateException("Cross-chunk anchor write");
if(!level.getBlockState(b.getKey()).equals(b.getValue()))throw new IllegalStateException("Anchor refuge mismatch "+b.getKey()+" expected "+b.getValue()+" got "+level.getBlockState(b.getKey()));
if(b.getValue().is(ExpansionAnchorBlock.BLOCK))count++;
}
for(var site:p.sites){
if(Rosette183.sector(site.floor.getX(),site.floor.getZ())!=site.sector)throw new IllegalStateException("Wrong colour sector");
// Walk the full three-wide bent approach and verify its actual native support.
for(int step=2;step<=11;step++){
int bend=step>3&&step<9?1:0;var at=site.floor.offset(site.dx*step-site.dz*bend,1,site.dz*step+site.dx*bend);
if(!level.getBlockState(at).isAir()||!level.getBlockState(at.above()).isAir()||level.getBlockState(at.below()).getCollisionShape(level,at.below()).isEmpty())throw new IllegalStateException("Anchor approach blocked "+at);
}
if(!rock(source,site.floor.getX(),site.floor.getY()-5,site.floor.getZ()))throw new IllegalStateException("Anchor above void");
details.add(Map.of("sector",site.sector,"kind",site.kind.name(),"anchor",site.anchor().toShortString(),"entrance",site.entrance().toShortString(),"bright",site.bright));
}
if(count!=8||p.sites.stream().map(Site::sector).distinct().count()!=8)throw new IllegalStateException("Expected exactly eight anchors");
var offers=AnchorOffers186.check(level,p.sites);
return Map.of("sites",details,"planningMs",p.millis,"chunks",p.chunks.size(),"blocks",p.chunks.values().stream().mapToInt(Map::size).sum(),"deterministic",true,"offers",offers);
}
}
@@ -0,0 +1,205 @@
package fr.koka.sanctuarytest;
import fr.koka.sanctuary.palace.ExpansionAnchorBlock;
import java.util.*;
import net.minecraft.core.*;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.*;
import net.minecraft.world.level.block.*;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.*;
import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator;
import net.minecraft.world.level.levelgen.densityfunction.SamplerContext;
/** Eight seed-owned refuges. Planning reads density only; placement never loads a neighbour chunk. */
public final class Anchors187 {
enum Kind { SURFACE, CAVE, SKY }
record Site(int sector,BlockPos floor,Kind kind,int dx,int dz,int rx,int rz,boolean bright) {
BlockPos anchor(){return floor.above(2);}
BlockPos entrance(){return floor.offset(dx*11,1,dz*11);}
}
record Plan(List<Site> sites,Map<ChunkPos,Map<BlockPos,BlockState>> chunks,long millis) {}
private record Candidate(BlockPos floor,Kind kind,int dx,int dz,double score) {}
private static final Map<AdventureBiomes180,Plan> PLANS=Collections.synchronizedMap(new WeakHashMap<>());
private Anchors187() {}
static AdventureBiomes180 source(ChunkGenerator g){return (AdventureBiomes180)((NoiseBasedChunkGenerator)g).getBiomeSource();}
static Plan plan(AdventureBiomes180 s,long seed){synchronized(PLANS){return PLANS.computeIfAbsent(s,key->create(s,seed));}}
private static boolean rock(AdventureBiomes180 s,int x,int y,int z){return s.field().sampleValue(SamplerContext.EMPTY_UNCACHED,x,y,z)>0;}
private static long mix(long n){n=(n^(n>>>30))*0xbf58476d1ce4e5b9L;n=(n^(n>>>27))*0x94d049bb133111ebL;return n^(n>>>31);}
private static boolean reserved(AdventureBiomes180 s,int x,int z){
if(Math.hypot(x,z)<88||Dungeon180.near(s,x,z,28))return true;
for(var b:Basins180.sites(s))if(Math.hypot(x-b.x(),z-b.z())<78)return true;
for(var p:NaturalWater184.plan(s).pools())if(Math.abs(x-p.seed().getX())<68&&Math.abs(z-p.seed().getZ())<68)return true;
for(var p:SurfaceWater187.plan(s).pools())if(Math.abs(x-p.seed().getX())<80&&Math.abs(z-p.seed().getZ())<80)return true;
return false;
}
private static Candidate candidate(AdventureBiomes180 s,int x,int y,int z,Kind kind,long seed){
if(!rock(s,x,y,z)||rock(s,x,y+1,z)||rock(s,x,y+3,z))return null;
// Require existing material beneath the entire centre, including the small pedestal.
for(int dx=-3;dx<=3;dx+=3)for(int dz=-3;dz<=3;dz+=3)
if(!rock(s,x+dx,y-3,z+dz)||!rock(s,x+dx,y-5,z+dz))return null;
int[][] inward={{0,1},{-1,1},{-1,0},{-1,-1},{0,-1},{1,-1},{1,0},{1,1}};
int sector=Rosette183.sector(x,z),ex=inward[sector][0],ez=inward[sector][1],best=0;
// The moss approach is inward. The opposite gate looks out along the sector's exact compass axis.
for(int step=-6;step<=11;step++){
int bend=step>3&&step<9?1:0;
for(int side=-1;side<=1;side++){
int px=x+ex*step-ez*(bend+side),pz=z+ez*step+ex*(bend+side);
if(!rock(s,px,y-3,pz))return null;
}
if(step>0&&!rock(s,x+ex*step,y+1,z+ez*step)&&!rock(s,x+ex*step,y+2,z+ez*step))best++;
}
if(best<7||rock(s,x+ex*11,y+1,z+ez*11)||rock(s,x+ex*11,y+2,z+ez*11))return null;
for(int side:new int[]{-1,1})if(!rock(s,x-ex*4-ez*side*3,y-3,z-ez*4+ex*side*3))return null;
int air=0,roof=0;
for(int dx=-6;dx<=6;dx+=3)for(int dz=-6;dz<=6;dz+=3){
if(!rock(s,x+dx,y+3,z+dz))air++;
if(rock(s,x+dx,y+12,z+dz)||rock(s,x+dx,y+24,z+dz))roof++;
}
if(kind==Kind.CAVE&&roof<8)return null;
double jitter=(mix(seed^((long)x*73428767)^((long)y*912931)^z)>>>11)*0x1.0p-53;
return new Candidate(new BlockPos(x,y,z),kind,ex,ez,air*.1+best*.15+jitter*9);
}
private static Plan create(AdventureBiomes180 s,long seed){
long started=System.nanoTime();var candidates=new ArrayList<Candidate>();
// Sparse fixed grid, finite vertical scan. No terrain regions, raycasts or runtime search.
for(int x=-300;x<=300;x+=12)for(int z=-300;z<=300;z+=12){
if(Math.hypot(x,z)>310||reserved(s,x,z))continue;
int top=s.field().surface(x,z);if(top<140)continue;
var surface=candidate(s,x,top,z,Kind.SURFACE,seed);if(surface!=null)candidates.add(surface);
for(int y=Math.min(top-18,236);y>=92;y--){
if(!rock(s,x,y,z)||rock(s,x,y+1,z)||rock(s,x,y+4,z))continue;
var cave=candidate(s,x,y,z,Kind.CAVE,seed);if(cave!=null)candidates.add(cave);
}
}
for(var reef:s.field().base().reefs()){
if(reef.length()<30)continue;
for(int dx=-12;dx<=12;dx+=6)for(int dz=-12;dz<=12;dz+=6){
int x=reef.x()+dx,z=reef.z()+dz;
for(int y=reef.y()+reef.rise()+2;y>=reef.y()-5;y--){
if(!rock(s,x,y,z))continue;
var sky=candidate(s,x,y,z,Kind.SKY,seed);if(sky!=null)candidates.add(sky);break;
}
}
}
var chosen=new ArrayList<Site>();int skyCount=0;
// Two eligible aerial sectors are selected by seed; other sectors alternate cave and surface preference.
var aerial=new ArrayList<Integer>();
for(int sector=0;sector<8;sector++){final int k=sector;if(candidates.stream().anyMatch(c->c.kind==Kind.SKY&&Rosette183.sector(c.floor.getX(),c.floor.getZ())==k))aerial.add(k);}
aerial.sort(Comparator.comparingLong(k->mix(seed^k*0x186L)));
var skySectors=new HashSet<>(aerial.subList(0,Math.min(2,aerial.size())));
for(int sector=0;sector<8;sector++){
final int k=sector;Kind preferred=skySectors.contains(k)?Kind.SKY:((sector+(int)(seed&1))%2==0?Kind.CAVE:Kind.SURFACE);
var ranked=candidates.stream().filter(c->Rosette183.sector(c.floor.getX(),c.floor.getZ())==k)
.sorted(Comparator.<Candidate>comparingDouble(c->c.score+(c.kind==preferred?100:0)).reversed()).toList();
Candidate selected=null;
for(var c:ranked){
if(c.kind==Kind.SKY&&!skySectors.contains(k))continue;
if(chosen.stream().anyMatch(v->v.floor.distSqr(c.floor)<44*44))continue;
selected=c;break;
}
if(selected==null)throw new IllegalStateException("No naturally supported anchor in sector "+k+" for seed "+seed);
var c=selected;long grain=mix(seed^k*7919L);
chosen.add(new Site(k,c.floor,c.kind,c.dx,c.dz,6+(int)(grain&1),5+(int)((grain>>>1)&1),(grain&4)==0));
if(c.kind==Kind.SKY)skyCount++;
}
if(skyCount==0)throw new IllegalStateException("No supported aerial anchor for seed "+seed);
var chunks=new HashMap<ChunkPos,Map<BlockPos,BlockState>>();
for(var site:chosen)for(var e:sculpt(s,site,seed).entrySet())
chunks.computeIfAbsent(new ChunkPos(e.getKey().getX()>>4,e.getKey().getZ()>>4),key->new LinkedHashMap<>()).put(e.getKey(),e.getValue());
chunks.replaceAll((k,v)->Map.copyOf(v));
return new Plan(List.copyOf(chosen),Map.copyOf(chunks),(System.nanoTime()-started)/1_000_000);
}
private static void put(Map<BlockPos,BlockState> m,BlockPos p,Block b){m.put(p,b.defaultBlockState());}
private static Map<BlockPos,BlockState> sculpt(AdventureBiomes180 s,Site site,long seed){
var m=new LinkedHashMap<BlockPos,BlockState>();var o=site.floor;int y=o.getY();
Block stone=site.kind==Kind.SKY?Blocks.CALCITE:site.kind==Kind.CAVE?Blocks.TUFF:Blocks.ANDESITE;
double phase=(mix(seed^site.sector) & 1023)/163.0;
for(int dx=-site.rx-2;dx<=site.rx+2;dx++)for(int dz=-site.rz-2;dz<=site.rz+2;dz++){
int x=o.getX()+dx,z=o.getZ()+dz;
double d=dx*dx/(double)(site.rx*site.rx)+dz*dz/(double)(site.rz*site.rz)+.12*Math.sin(dx*.7+phase)*Math.cos(dz*.6);
if(d>1.15)continue;
int floor=y+(Math.abs(dx)<=3&&Math.abs(dz)<=3?0:(int)Math.round(.8*Math.sin(dx*.5+phase)*Math.cos(dz*.5)));
if(!rock(s,x,floor-3,z))continue; // Keep the edge of the host landform instead of hanging a platform over void.
var at=new BlockPos(x,floor,z);
if(d<=1){
for(int h=-2;h<=0;h++)put(m,at.above(h),h==0&&Math.floorMod(x+z+site.sector,5)==0?Blocks.MOSS_BLOCK:stone);
int height=3+(int)(4*Math.sqrt(Math.max(0,1-d)));
for(int h=1;h<=height;h++)put(m,at.above(h),Blocks.AIR);
if(d>.62&&Math.floorMod(x*7+z*13,11)==0){
put(m,at,Blocks.MOSS_BLOCK);put(m,at.above(),Blocks.FLOWERING_AZALEA);
}
}else {
// Short reliefs follow surviving rock faces; no closed constructed shell.
for(int h=0;h<=5;h++)if(rock(s,x,floor+h,z))
put(m,at.above(h),h==2&&Math.floorMod(x+z,3)==0?Blocks.CHISELED_TUFF:h==0?Blocks.MOSS_BLOCK:stone);
}
}
// A slight bend opens to an actual, dry natural floor found during selection.
for(int step=-6;step<=11;step++){
int bend=step>3&&step<9?1:0;
int x=o.getX()+site.dx*step-site.dz*bend,z=o.getZ()+site.dz*step+site.dx*bend;
for(int side=-1;side<=1;side++){
var p=new BlockPos(x-site.dz*side,y,z+site.dx*side);
for(int h=-2;h<=0;h++)put(m,p.above(h),h==0&&step>=2?Blocks.MOSS_BLOCK:stone);
for(int h=1;h<=4;h++)put(m,p.above(h),Blocks.AIR);
}
}
// Low eroded crests shelter surface sites; all are rooted in the existing mass.
for(int side:new int[]{-1,1}){
int dx=-site.dx*4-site.dz*side*3,dz=-site.dz*4+site.dx*side*3;
var base=o.offset(dx,0,dz);if(!rock(s,base.getX(),y-3,base.getZ()))continue;
for(int h=-2;h<=2;h++)put(m,base.above(h),h==1?Blocks.CHISELED_TUFF:stone);
if(site.bright)put(m,base,Blocks.SEA_LANTERN);
put(m,base.above(3),Blocks.AMETHYST_BLOCK);
put(m,base.above(4),Rosette183.GLASS[site.sector]);
}
for(int dx=-1;dx<=1;dx++)for(int dz=-1;dz<=1;dz++){
put(m,o.offset(dx,0,dz),Blocks.CHISELED_TUFF);
for(int h=1;h<=4;h++)put(m,o.offset(dx,h,dz),Blocks.AIR);
}
put(m,o.above(),Rosette183.GLASS[site.sector]);
m.put(site.anchor(),ExpansionAnchorBlock.BLOCK.defaultBlockState().setValue(ExpansionAnchorBlock.SECTOR,site.sector));
return m;
}
public static void place(WorldGenLevel level,ChunkGenerator g,ChunkAccess chunk){
if(!WorldgenLab.natural187(g))return;
var entries=plan(source(g),level.getSeed()).chunks.get(chunk.getPos());
if(entries!=null)entries.forEach(chunk::setBlockState);
}
public static boolean protects(WorldGenLevel level,ChunkGenerator g,BlockPos pos){
if(!WorldgenLab.natural187(g)||pos.getY()<88||pos.getY()>512||Math.abs(pos.getX())>325||Math.abs(pos.getZ())>325)return false;
var entries=plan(source(g),level.getSeed()).chunks.get(new ChunkPos(pos.getX()>>4,pos.getZ()>>4));
return entries!=null&&entries.containsKey(pos);
}
static Map<String,Object> check(ServerLevel level,AdventureBiomes180 source){
var p=plan(source,level.getSeed());var again=create(source,level.getSeed());
if(!p.sites.equals(again.sites)||!p.chunks.equals(again.chunks))throw new IllegalStateException("Unstable anchor plan");
int count=0;var details=new ArrayList<Map<String,Object>>();
for(var cp:p.chunks.keySet())level.getChunk(cp.x(),cp.z());
for(var e:p.chunks.entrySet())for(var b:e.getValue().entrySet()){
if(!e.getKey().contains(b.getKey()))throw new IllegalStateException("Cross-chunk anchor write");
if(!level.getBlockState(b.getKey()).equals(b.getValue()))throw new IllegalStateException("Anchor refuge mismatch "+b.getKey()+" expected "+b.getValue()+" got "+level.getBlockState(b.getKey()));
if(b.getValue().is(ExpansionAnchorBlock.BLOCK))count++;
}
for(var site:p.sites){
if(Rosette183.sector(site.floor.getX(),site.floor.getZ())!=site.sector)throw new IllegalStateException("Wrong colour sector");
// Walk the full three-wide bent approach and verify its actual native support.
for(int step=-6;step<=11;step++){
if(step>=-1&&step<=1)continue;
int bend=step>3&&step<9?1:0;var at=site.floor.offset(site.dx*step-site.dz*bend,1,site.dz*step+site.dx*bend);
if(!level.getBlockState(at).isAir()||!level.getBlockState(at.above()).isAir()||level.getBlockState(at.below()).getCollisionShape(level,at.below()).isEmpty())throw new IllegalStateException("Anchor approach blocked "+at);
}
if(!rock(source,site.floor.getX(),site.floor.getY()-5,site.floor.getZ()))throw new IllegalStateException("Anchor above void");
if(site.dx*site.floor.getX()+site.dz*site.floor.getZ()>=0)throw new IllegalStateException("Approach points outward");
for(int side:new int[]{-1,1}){
var pylon=site.floor.offset(-site.dx*4-site.dz*side*3,0,-site.dz*4+site.dx*side*3);
if(!level.getBlockState(pylon.above(3)).is(Blocks.AMETHYST_BLOCK)||!level.getBlockState(pylon.above(4)).is(Rosette183.GLASS[site.sector]))throw new IllegalStateException("Pylon glass must sit on amethyst");
}
details.add(Map.of("sector",site.sector,"kind",site.kind.name(),"anchor",site.anchor().toShortString(),"entrance",site.entrance().toShortString(),"bright",site.bright));
}
if(count!=8||p.sites.stream().map(Site::sector).distinct().count()!=8)throw new IllegalStateException("Expected exactly eight anchors");
var offers=AnchorOffers187.check(level,p.sites);
return Map.of("sites",details,"planningMs",p.millis,"chunks",p.chunks.size(),"blocks",p.chunks.values().stream().mapToInt(Map::size).sum(),"deterministic",true,"offers",offers);
}
}
@@ -0,0 +1,218 @@
package fr.koka.sanctuarytest;
import fr.koka.sanctuary.palace.ExpansionAnchorBlock;
import java.util.*;
import net.minecraft.core.*;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.*;
import net.minecraft.world.level.block.*;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.*;
import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator;
import net.minecraft.world.level.levelgen.densityfunction.SamplerContext;
/** Eight seed-owned refuges. Planning reads density only; placement never loads a neighbour chunk. */
public final class Anchors188 {
enum Kind { SURFACE, CAVE, SKY }
record Site(int sector,BlockPos floor,Kind kind,int dx,int dz,int rx,int rz,boolean bright) {
BlockPos anchor(){return floor.above(2);}
BlockPos entrance(){return floor.offset(dx*11,1,dz*11);}
}
record Plan(List<Site> sites,Map<ChunkPos,Map<BlockPos,BlockState>> chunks,long millis) {}
private record Candidate(BlockPos floor,Kind kind,int dx,int dz,double score) {}
private static final Map<AdventureBiomes180,Plan> PLANS=Collections.synchronizedMap(new WeakHashMap<>());
private Anchors188() {}
static AdventureBiomes180 source(ChunkGenerator g){return (AdventureBiomes180)((NoiseBasedChunkGenerator)g).getBiomeSource();}
static Plan plan(AdventureBiomes180 s,long seed){synchronized(PLANS){return PLANS.computeIfAbsent(s,key->create(s,seed));}}
private static boolean rock(AdventureBiomes180 s,int x,int y,int z){return s.field().sampleValue(SamplerContext.EMPTY_UNCACHED,x,y,z)>0;}
private static long mix(long n){n=(n^(n>>>30))*0xbf58476d1ce4e5b9L;n=(n^(n>>>27))*0x94d049bb133111ebL;return n^(n>>>31);}
private static boolean reserved(AdventureBiomes180 s,int x,int z){
if(Math.hypot(x,z)<88||Dungeon180.near(s,x,z,28))return true;
for(var b:Basins180.sites(s))if(Math.hypot(x-b.x(),z-b.z())<78)return true;
for(var p:NaturalWater184.plan(s).pools())if(Math.abs(x-p.seed().getX())<68&&Math.abs(z-p.seed().getZ())<68)return true;
for(var p:SurfaceWater187.plan(s).pools())if(Math.abs(x-p.seed().getX())<80&&Math.abs(z-p.seed().getZ())<80)return true;
return false;
}
private static Candidate candidate(AdventureBiomes180 s,int x,int y,int z,Kind kind,long seed){
if(!rock(s,x,y,z)||rock(s,x,y+1,z)||rock(s,x,y+3,z))return null;
// Require existing material beneath the entire centre, including the small pedestal.
for(int dx=-3;dx<=3;dx+=3)for(int dz=-3;dz<=3;dz+=3)
if(!rock(s,x+dx,y-3,z+dz)||!rock(s,x+dx,y-5,z+dz))return null;
int[][] inward={{0,1},{-1,1},{-1,0},{-1,-1},{0,-1},{1,-1},{1,0},{1,1}};
int sector=Rosette183.sector(x,z),ex=inward[sector][0],ez=inward[sector][1],best=0;
// The moss approach is inward. The opposite gate looks out along the sector's exact compass axis.
for(int step=-6;step<=11;step++){
int bend=step>3&&step<9?1:0;
for(int side=-1;side<=1;side++){
int px=x+ex*step-ez*(bend+side),pz=z+ez*step+ex*(bend+side);
if(!rock(s,px,y-3,pz))return null;
}
if(step>0&&!rock(s,x+ex*step,y+1,z+ez*step)&&!rock(s,x+ex*step,y+2,z+ez*step))best++;
}
if(best<7||rock(s,x+ex*11,y+1,z+ez*11)||rock(s,x+ex*11,y+2,z+ez*11))return null;
for(int side:new int[]{-1,1})if(!rock(s,x-ex*4-ez*side*3,y-3,z-ez*4+ex*side*3))return null;
int air=0,roof=0;
for(int dx=-6;dx<=6;dx+=3)for(int dz=-6;dz<=6;dz+=3){
if(!rock(s,x+dx,y+3,z+dz))air++;
if(rock(s,x+dx,y+12,z+dz)||rock(s,x+dx,y+24,z+dz))roof++;
}
if(kind==Kind.CAVE&&roof<8)return null;
double jitter=(mix(seed^((long)x*73428767)^((long)y*912931)^z)>>>11)*0x1.0p-53;
return new Candidate(new BlockPos(x,y,z),kind,ex,ez,air*.1+best*.15+jitter*9);
}
private static Plan create(AdventureBiomes180 s,long seed){
long started=System.nanoTime();var candidates=new ArrayList<Candidate>();
// Sparse fixed grid, finite vertical scan. No terrain regions, raycasts or runtime search.
for(int x=-300;x<=300;x+=12)for(int z=-300;z<=300;z+=12){
if(Math.hypot(x,z)>310||reserved(s,x,z))continue;
int top=s.field().surface(x,z);if(top<140)continue;
var surface=candidate(s,x,top,z,Kind.SURFACE,seed);if(surface!=null)candidates.add(surface);
for(int y=Math.min(top-18,236);y>=92;y--){
if(!rock(s,x,y,z)||rock(s,x,y+1,z)||rock(s,x,y+4,z))continue;
var cave=candidate(s,x,y,z,Kind.CAVE,seed);if(cave!=null)candidates.add(cave);
}
}
for(var reef:s.field().base().reefs()){
if(reef.length()<30)continue;
for(int dx=-12;dx<=12;dx+=6)for(int dz=-12;dz<=12;dz+=6){
int x=reef.x()+dx,z=reef.z()+dz;
for(int y=reef.y()+reef.rise()+2;y>=reef.y()-5;y--){
if(!rock(s,x,y,z))continue;
var sky=candidate(s,x,y,z,Kind.SKY,seed);if(sky!=null)candidates.add(sky);break;
}
}
}
var chosen=new ArrayList<Site>();int skyCount=0;
// Two eligible aerial sectors are selected by seed; other sectors alternate cave and surface preference.
var aerial=new ArrayList<Integer>();
for(int sector=0;sector<8;sector++){final int k=sector;if(candidates.stream().anyMatch(c->c.kind==Kind.SKY&&Rosette183.sector(c.floor.getX(),c.floor.getZ())==k))aerial.add(k);}
aerial.sort(Comparator.comparingLong(k->mix(seed^k*0x186L)));
var skySectors=new HashSet<>(aerial.subList(0,Math.min(2,aerial.size())));
for(int sector=0;sector<8;sector++){
final int k=sector;Kind preferred=skySectors.contains(k)?Kind.SKY:((sector+(int)(seed&1))%2==0?Kind.CAVE:Kind.SURFACE);
var ranked=candidates.stream().filter(c->Rosette183.sector(c.floor.getX(),c.floor.getZ())==k)
.sorted(Comparator.<Candidate>comparingDouble(c->c.score+(c.kind==preferred?100:0)).reversed()).toList();
Candidate selected=null;
for(var c:ranked){
if(c.kind==Kind.SKY&&!skySectors.contains(k))continue;
if(chosen.stream().anyMatch(v->v.floor.distSqr(c.floor)<44*44))continue;
selected=c;break;
}
if(selected==null)throw new IllegalStateException("No naturally supported anchor in sector "+k+" for seed "+seed);
var c=selected;long grain=mix(seed^k*7919L);
chosen.add(new Site(k,c.floor,c.kind,c.dx,c.dz,6+(int)(grain&1),5+(int)((grain>>>1)&1),(grain&4)==0));
if(c.kind==Kind.SKY)skyCount++;
}
if(skyCount==0)throw new IllegalStateException("No supported aerial anchor for seed "+seed);
var chunks=new HashMap<ChunkPos,Map<BlockPos,BlockState>>();
for(var site:chosen)for(var e:sculpt(s,site,seed).entrySet())
chunks.computeIfAbsent(new ChunkPos(e.getKey().getX()>>4,e.getKey().getZ()>>4),key->new LinkedHashMap<>()).put(e.getKey(),e.getValue());
chunks.replaceAll((k,v)->Map.copyOf(v));
return new Plan(List.copyOf(chosen),Map.copyOf(chunks),(System.nanoTime()-started)/1_000_000);
}
private static void put(Map<BlockPos,BlockState> m,BlockPos p,Block b){m.put(p,b.defaultBlockState());}
private static Map<BlockPos,BlockState> sculpt(AdventureBiomes180 s,Site site,long seed){
var m=new LinkedHashMap<BlockPos,BlockState>();var o=site.floor;int y=o.getY();
Block stone=site.kind==Kind.SKY?Blocks.CALCITE:site.kind==Kind.CAVE?Blocks.TUFF:Blocks.ANDESITE;
Block accent=switch(site.sector){case 0,7->Blocks.CALCITE;case 1,2->Blocks.MOSS_BLOCK;case 3->Blocks.TERRACOTTA;case 4->Blocks.DYED_TERRACOTTA.red();case 5->Blocks.AMETHYST_BLOCK;default->Blocks.DYED_TERRACOTTA.yellow();};
double phase=(mix(seed^site.sector) & 1023)/163.0;
for(int dx=-site.rx-2;dx<=site.rx+2;dx++)for(int dz=-site.rz-2;dz<=site.rz+2;dz++){
int x=o.getX()+dx,z=o.getZ()+dz;
double d=dx*dx/(double)(site.rx*site.rx)+dz*dz/(double)(site.rz*site.rz)+.12*Math.sin(dx*.7+phase)*Math.cos(dz*.6);
if(d>1.15)continue;
int floor=y+(Math.abs(dx)<=3&&Math.abs(dz)<=3?0:(int)Math.round(.8*Math.sin(dx*.5+phase)*Math.cos(dz*.5)));
if(!rock(s,x,floor-3,z))continue; // Keep the edge of the host landform instead of hanging a platform over void.
var at=new BlockPos(x,floor,z);
if(d<=1){
for(int h=-2;h<=0;h++)put(m,at.above(h),h==0&&Math.floorMod(x+z+site.sector,5)==0?Blocks.MOSS_BLOCK:stone);
int height=3+(int)(4*Math.sqrt(Math.max(0,1-d)));
for(int h=1;h<=height;h++)put(m,at.above(h),Blocks.AIR);
if(d>.62&&Math.floorMod(x*7+z*13,11)==0){
put(m,at,Blocks.MOSS_BLOCK);put(m,at.above(),Blocks.FLOWERING_AZALEA);
}
}else {
// Short reliefs follow surviving rock faces; no closed constructed shell.
for(int h=0;h<=5;h++)if(rock(s,x,floor+h,z))
put(m,at.above(h),h==2&&Math.floorMod(x+z,3)==0?Blocks.CHISELED_TUFF:h==0?Blocks.MOSS_BLOCK:stone);
}
}
// A slight bend opens to an actual, dry natural floor found during selection.
for(int step=-6;step<=11;step++){
int bend=step>3&&step<9?1:0;
int x=o.getX()+site.dx*step-site.dz*bend,z=o.getZ()+site.dz*step+site.dx*bend;
for(int side=-1;side<=1;side++){
var p=new BlockPos(x-site.dz*side,y,z+site.dx*side);
for(int h=-2;h<=0;h++)put(m,p.above(h),h==0&&step>=2?Blocks.MOSS_BLOCK:stone);
for(int h=1;h<=4;h++)put(m,p.above(h),Blocks.AIR);
}
}
// Low eroded crests shelter surface sites; all are rooted in the existing mass.
for(int side:new int[]{-1,1}){
int dx=-site.dx*4-site.dz*side*3,dz=-site.dz*4+site.dx*side*3;
var base=o.offset(dx,0,dz);if(!rock(s,base.getX(),y-3,base.getZ()))continue;
for(int h=-2;h<=2;h++)put(m,base.above(h),h==1?Blocks.CHISELED_TUFF:stone);
if(site.bright)put(m,base,Blocks.SEA_LANTERN);
put(m,base.above(3),Blocks.AMETHYST_BLOCK);
put(m,base.above(4),Rosette183.GLASS[site.sector]);
}
for(int dx=-1;dx<=1;dx++)for(int dz=-1;dz<=1;dz++){
put(m,o.offset(dx,0,dz),Blocks.CHISELED_TUFF);
for(int h=1;h<=4;h++)put(m,o.offset(dx,h,dz),Blocks.AIR);
}
put(m,o.above(),Rosette183.GLASS[site.sector]);
m.put(site.anchor(),ExpansionAnchorBlock.BLOCK.defaultBlockState().setValue(ExpansionAnchorBlock.SECTOR,site.sector));
// Keep the accepted geometry; the host material and sector colour shape each refuge's palette.
m.replaceAll((p,b)->{
int h=p.getY()-y;
double patch=EcologyBiomes176.noise(seed^0x188AL,p.getX()/5.0,p.getZ()/5.0);
if(b.is(stone)){
if(site.kind==Kind.SURFACE&&h<=0)return (h<0?Blocks.DIRT:patch>.3?Blocks.COARSE_DIRT:patch<-.3?Blocks.MOSS_BLOCK:Blocks.GRASS_BLOCK).defaultBlockState();
if(site.kind==Kind.CAVE)return (patch>.30?Blocks.DEEPSLATE:patch<-.35?Blocks.CALCITE:Blocks.TUFF).defaultBlockState();
if(site.kind==Kind.SKY&&patch>.25)return Blocks.DIORITE.defaultBlockState();
}
if(b.is(Blocks.CHISELED_TUFF)&&patch>-.2)return accent.defaultBlockState();
return b;
});
return m;
}
public static void place(WorldGenLevel level,ChunkGenerator g,ChunkAccess chunk){
if(!WorldgenLab.details188(g))return;
var entries=plan(source(g),level.getSeed()).chunks.get(chunk.getPos());
if(entries!=null)entries.forEach(chunk::setBlockState);
}
public static boolean protects(WorldGenLevel level,ChunkGenerator g,BlockPos pos){
if(!WorldgenLab.details188(g)||pos.getY()<88||pos.getY()>512||Math.abs(pos.getX())>325||Math.abs(pos.getZ())>325)return false;
var entries=plan(source(g),level.getSeed()).chunks.get(new ChunkPos(pos.getX()>>4,pos.getZ()>>4));
return entries!=null&&entries.containsKey(pos);
}
static Map<String,Object> check(ServerLevel level,AdventureBiomes180 source){
var p=plan(source,level.getSeed());var again=create(source,level.getSeed());
if(!p.sites.equals(again.sites)||!p.chunks.equals(again.chunks))throw new IllegalStateException("Unstable anchor plan");
int count=0;var details=new ArrayList<Map<String,Object>>();
for(var cp:p.chunks.keySet())level.getChunk(cp.x(),cp.z());
for(var e:p.chunks.entrySet())for(var b:e.getValue().entrySet()){
if(!e.getKey().contains(b.getKey()))throw new IllegalStateException("Cross-chunk anchor write");
if(!level.getBlockState(b.getKey()).equals(b.getValue()))throw new IllegalStateException("Anchor refuge mismatch "+b.getKey()+" expected "+b.getValue()+" got "+level.getBlockState(b.getKey()));
if(b.getValue().is(ExpansionAnchorBlock.BLOCK))count++;
}
for(var site:p.sites){
if(Rosette183.sector(site.floor.getX(),site.floor.getZ())!=site.sector)throw new IllegalStateException("Wrong colour sector");
// Walk the full three-wide bent approach and verify its actual native support.
for(int step=-6;step<=11;step++){
if(step>=-1&&step<=1)continue;
int bend=step>3&&step<9?1:0;var at=site.floor.offset(site.dx*step-site.dz*bend,1,site.dz*step+site.dx*bend);
if(!level.getBlockState(at).isAir()||!level.getBlockState(at.above()).isAir()||level.getBlockState(at.below()).getCollisionShape(level,at.below()).isEmpty())throw new IllegalStateException("Anchor approach blocked "+at);
}
if(!rock(source,site.floor.getX(),site.floor.getY()-5,site.floor.getZ()))throw new IllegalStateException("Anchor above void");
if(site.dx*site.floor.getX()+site.dz*site.floor.getZ()>=0)throw new IllegalStateException("Approach points outward");
for(int side:new int[]{-1,1}){
var pylon=site.floor.offset(-site.dx*4-site.dz*side*3,0,-site.dz*4+site.dx*side*3);
if(!level.getBlockState(pylon.above(3)).is(Blocks.AMETHYST_BLOCK)||!level.getBlockState(pylon.above(4)).is(Rosette183.GLASS[site.sector]))throw new IllegalStateException("Pylon glass must sit on amethyst");
}
details.add(Map.of("sector",site.sector,"kind",site.kind.name(),"anchor",site.anchor().toShortString(),"entrance",site.entrance().toShortString(),"bright",site.bright));
}
if(count!=8||p.sites.stream().map(Site::sector).distinct().count()!=8)throw new IllegalStateException("Expected exactly eight anchors");
var offers=AnchorOffers188.check(level,p.sites);
return Map.of("sites",details,"planningMs",p.millis,"chunks",p.chunks.size(),"blocks",p.chunks.values().stream().mapToInt(Map::size).sum(),"deterministic",true,"offers",offers);
}
}
@@ -0,0 +1,335 @@
package fr.koka.sanctuarytest;
import fr.koka.sanctuary.palace.ExpansionAnchorBlock;
import java.util.*;
import net.minecraft.core.*;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.*;
import net.minecraft.world.level.block.*;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.*;
import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator;
import net.minecraft.world.level.levelgen.densityfunction.SamplerContext;
/** Eight seed-owned refuges. Planning reads density only; placement never loads a neighbour chunk. */
public final class Anchors189 {
enum Kind { SURFACE, CAVE, SKY }
record Site(int sector,BlockPos floor,Kind kind,int dx,int dz,int rx,int rz,boolean bright) {
BlockPos anchor(){return floor.above(2);}
BlockPos entrance(){return floor.offset(dx*11,1,dz*11);}
}
record Plan(List<Site> sites,Map<ChunkPos,Map<BlockPos,BlockState>> chunks,long millis) {}
private record Candidate(BlockPos floor,Kind kind,int dx,int dz,double score) {}
private static final Map<AdventureBiomes180,Plan> PLANS=Collections.synchronizedMap(new WeakHashMap<>());
private Anchors189() {}
static AdventureBiomes180 source(ChunkGenerator g){return (AdventureBiomes180)((NoiseBasedChunkGenerator)g).getBiomeSource();}
static Plan plan(AdventureBiomes180 s,long seed){synchronized(PLANS){return PLANS.computeIfAbsent(s,key->create(s,seed));}}
private static boolean rock(AdventureBiomes180 s,int x,int y,int z){return s.field().sampleValue(SamplerContext.EMPTY_UNCACHED,x,y,z)>0;}
private static long mix(long n){n=(n^(n>>>30))*0xbf58476d1ce4e5b9L;n=(n^(n>>>27))*0x94d049bb133111ebL;return n^(n>>>31);}
private static boolean reserved(AdventureBiomes180 s,int x,int z){
if(Math.hypot(x,z)<88||Dungeon180.near(s,x,z,28))return true;
for(var b:Basins180.sites(s))if(Math.hypot(x-b.x(),z-b.z())<78)return true;
for(var p:NaturalWater184.plan(s).pools())if(Math.abs(x-p.seed().getX())<68&&Math.abs(z-p.seed().getZ())<68)return true;
for(var p:SurfaceWater187.plan(s).pools())if(Math.abs(x-p.seed().getX())<80&&Math.abs(z-p.seed().getZ())<80)return true;
return false;
}
private static Candidate candidate(AdventureBiomes180 s,int x,int y,int z,Kind kind,long seed){return candidate(s,x,y,z,kind,seed,false);}
private static Candidate candidate(AdventureBiomes180 s,int x,int y,int z,Kind kind,long seed,boolean precise){
if(!rock(s,x,y,z)||rock(s,x,y+1,z)||rock(s,x,y+3,z))return null;
if(s.field().base().capacity()!=fr.koka.sanctuary.worldgen.IslandCapacity.TEN&&(precise?reserved199(s,x,y,z):reservedAt(s,x,y,z)))return null;
// Require existing material beneath the entire centre, including the small pedestal.
for(int dx=-3;dx<=3;dx+=3)for(int dz=-3;dz<=3;dz+=3)
if(!rock(s,x+dx,y-3,z+dz)||!rock(s,x+dx,y-5,z+dz))return null;
int[][] inward={{0,1},{-1,1},{-1,0},{-1,-1},{0,-1},{1,-1},{1,0},{1,1}};
int sector=Rosette183.sector(x,z),ex=inward[sector][0],ez=inward[sector][1],best=0;
// The moss approach is inward. The opposite gate looks out along the sector's exact compass axis.
for(int step=-6;step<=11;step++){
int bend=step>3&&step<9?1:0;
for(int side=-1;side<=1;side++){
int px=x+ex*step-ez*(bend+side),pz=z+ez*step+ex*(bend+side);
if(!rock(s,px,y-3,pz))return null;
}
if(step>0&&!rock(s,x+ex*step,y+1,z+ez*step)&&!rock(s,x+ex*step,y+2,z+ez*step))best++;
}
if(s.field().coherent()&&kind!=Kind.CAVE){
int previous=y;
for(int step=0;step<=11;step++){
int bend=step>3&&step<9?1:0,px=x+ex*step-ez*bend,pz=z+ez*step+ex*bend;
int gy=step<=2?y:ground(s,px,pz,y);
if(!rock(s,px,gy,pz)||Math.abs(gy-previous)>1)return null;
for(int side=-1;side<=1;side++)if(Math.abs(ground(s,px-ez*side,pz+ex*side,y)-gy)>1)return null;
previous=gy;
}
}
if(best<7||rock(s,x+ex*11,y+1,z+ez*11)||rock(s,x+ex*11,y+2,z+ez*11))return null;
for(int side:new int[]{-1,1})if(!rock(s,x-ex*4-ez*side*3,y-3,z-ez*4+ex*side*3))return null;
int air=0,roof=0;
for(int dx=-6;dx<=6;dx+=3)for(int dz=-6;dz<=6;dz+=3){
if(!rock(s,x+dx,y+3,z+dz))air++;
if(rock(s,x+dx,y+12,z+dz)||rock(s,x+dx,y+24,z+dz))roof++;
}
if(kind==Kind.CAVE&&roof<8)return null;
double jitter=(mix(seed^((long)x*73428767)^((long)y*912931)^z)>>>11)*0x1.0p-53;
return new Candidate(new BlockPos(x,y,z),kind,ex,ez,air*.1+best*.15+jitter*9);
}
private static Plan create(AdventureBiomes180 s,long seed){
long started=System.nanoTime();var candidates=new ArrayList<Candidate>();
// Sparse fixed grid, finite vertical scan. No terrain regions, raycasts or runtime search.
double spread=s.field().base().spread();int step=(int)Math.round(12*spread),limit=25*step;
for(int x=-limit;x<=limit;x+=step)for(int z=-limit;z<=limit;z+=step){
if(Math.hypot(x,z)>310*spread||Math.hypot(x,z)<88
||s.field().base().capacity()==fr.koka.sanctuary.worldgen.IslandCapacity.TEN&&reserved(s,x,z))continue;
int top=s.field().surface(x,z);if(top<140)continue;
var surface=candidate(s,x,top,z,Kind.SURFACE,seed);if(surface!=null)candidates.add(surface);
for(int y=Math.min(top-18,236);y>=92;y--){
if(!rock(s,x,y,z)||rock(s,x,y+1,z)||rock(s,x,y+4,z))continue;
var cave=candidate(s,x,y,z,Kind.CAVE,seed);if(cave!=null)candidates.add(cave);
}
}
for(var reef:s.field().base().reefs()){
if(reef.length()<30)continue;
int extent=s.field().coherent()?20:12,spacing=s.field().coherent()?4:6;
for(int dx=-extent;dx<=extent;dx+=spacing)for(int dz=-extent;dz<=extent;dz+=spacing){
int x=reef.x()+dx,z=reef.z()+dz;
for(int y=reef.y()+reef.rise()+2;y>=reef.y()-5;y--){
if(!rock(s,x,y,z))continue;
var sky=candidate(s,x,y,z,Kind.SKY,seed);if(sky!=null)candidates.add(sky);break;
}
}
}
var chosen=new ArrayList<Site>();int skyCount=0;
// Two eligible aerial sectors are selected by seed; other sectors alternate cave and surface preference.
var aerial=new ArrayList<Integer>();
for(int sector=0;sector<8;sector++){final int k=sector;if(candidates.stream().anyMatch(c->c.kind==Kind.SKY&&Rosette183.sector(c.floor.getX(),c.floor.getZ())==k))aerial.add(k);}
aerial.sort(Comparator.comparingLong(k->mix(seed^k*0x186L)));
var skySectors=new HashSet<>(aerial.subList(0,Math.min(2,aerial.size())));
for(int sector=0;sector<8;sector++){
final int k=sector;Kind preferred=skySectors.contains(k)?Kind.SKY:((sector+(int)(seed&1))%2==0?Kind.CAVE:Kind.SURFACE);
var ranked=candidates.stream().filter(c->Rosette183.sector(c.floor.getX(),c.floor.getZ())==k)
.sorted(Comparator.<Candidate>comparingDouble(c->c.score+(c.kind==preferred?100:0)).reversed()).toList();
Candidate selected=null;
for(var c:ranked){
if(c.kind==Kind.SKY&&!skySectors.contains(k))continue;
if(chosen.stream().anyMatch(v->v.floor.distSqr(c.floor)<44*44))continue;
selected=c;break;
}
if(selected==null&&s.expansions!=null)selected=denseCandidate199(s,seed,k,chosen);
if(selected==null)throw new IllegalStateException("No naturally supported anchor in sector "+k+" for seed "+seed);
var c=selected;long grain=mix(seed^k*7919L);
chosen.add(new Site(k,c.floor,c.kind,c.dx,c.dz,6+(int)(grain&1),5+(int)((grain>>>1)&1),(grain&4)==0));
if(c.kind==Kind.SKY)skyCount++;
}
if(skyCount==0)throw new IllegalStateException("No supported aerial anchor for seed "+seed);
var chunks=new HashMap<ChunkPos,Map<BlockPos,BlockState>>();
for(var site:chosen)for(var e:sculpt(s,site,seed).entrySet())
chunks.computeIfAbsent(new ChunkPos(e.getKey().getX()>>4,e.getKey().getZ()>>4),key->new LinkedHashMap<>()).put(e.getKey(),e.getValue());
chunks.replaceAll((k,v)->Map.copyOf(v));
return new Plan(List.copyOf(chosen),Map.copyOf(chunks),(System.nanoTime()-started)/1_000_000);
}
/** The coarse historical grid can miss narrow valid floors on Small. New worlds retry only the missing sector. */
private static Candidate denseCandidate199(AdventureBiomes180 s,long seed,int sector,List<Site> chosen){
int limit=(int)(310*s.field().base().spread());
for(int x=-limit;x<=limit;x+=3)for(int z=-limit;z<=limit;z+=3){
if(Rosette183.sector(x,z)!=sector||Math.hypot(x,z)<88||Math.hypot(x,z)>limit)continue;
int top=s.field().surface(x,z);if(top<140)continue;
for(int y=top;y>=64;y--){
if(y>top-18&&y!=top)continue;
if(!rock(s,x,y,z)||rock(s,x,y+1,z)||rock(s,x,y+4,z))continue;
var at=new BlockPos(x,y,z);if(chosen.stream().anyMatch(v->v.floor.distSqr(at)<44*44))continue;
var candidate=candidate(s,x,y,z,y==top?Kind.SURFACE:Kind.CAVE,seed,true);
if(candidate!=null)return candidate;
}
}
return null;
}
private static final Map<AdventureBiomes180,List<net.minecraft.world.level.levelgen.structure.BoundingBox>> WATER199=Collections.synchronizedMap(new WeakHashMap<>());
private static boolean reserved199(AdventureBiomes180 s,int x,int y,int z){
var dungeon=Dungeon180.plan(s);
for(var room:dungeon.rooms())if(Math.abs(y-room.y())<20&&Math.abs(x-room.x())<room.rx()+18&&Math.abs(z-room.z())<room.rz()+18)return true;
for(var cell:dungeon.paths().values())if(Math.abs(y-cell.y())<16&&Math.abs(x-cell.x())<18&&Math.abs(z-cell.z())<18)return true;
if(dungeon.hut()!=null&&Math.abs(y-dungeon.hut().getY())<20&&Math.hypot(x-dungeon.hut().getX(),z-dungeon.hut().getZ())<28)return true;
for(var b:Basins180.sites(s)){
int u=(x-b.x())*b.dx()+(z-b.z())*b.dz(),v=-(x-b.x())*b.dz()+(z-b.z())*b.dx();
if(y>b.y()-28&&y<b.y()+14&&u>=-46&&u<=74&&Math.abs(v)<=39)return true;
}
var boxes=WATER199.computeIfAbsent(s,key->{
var out=new ArrayList<net.minecraft.world.level.levelgen.structure.BoundingBox>();
var bodies=new ArrayList<Set<BlockPos>>();
NaturalWater184.plan(s).pools().forEach(p->bodies.add(p.cells()));SurfaceWater187.plan(s).pools().forEach(p->bodies.add(p.cells()));
for(var cells:bodies)if(!cells.isEmpty()){
var b=net.minecraft.world.level.levelgen.structure.BoundingBox.encapsulatingPositions(cells).orElseThrow();
out.add(new net.minecraft.world.level.levelgen.structure.BoundingBox(b.minX()-18,b.minY()-12,b.minZ()-18,b.maxX()+18,b.maxY()+12,b.maxZ()+18));
}
return List.copyOf(out);
});
return boxes.stream().anyMatch(b->b.isInside(x,y,z));
}
/** Small/Large may stack dry refuges above/below another site, but their volumes must stay separate. */
private static boolean reservedAt(AdventureBiomes180 s,int x,int y,int z){
var dungeon=Dungeon180.plan(s);
for(var room:dungeon.rooms())if(Math.abs(y-room.y())<20&&Math.abs(x-room.x())<room.rx()+18&&Math.abs(z-room.z())<room.rz()+18)return true;
for(var cell:dungeon.paths().values())if(Math.abs(y-cell.y())<16&&Math.abs(x-cell.x())<18&&Math.abs(z-cell.z())<18)return true;
if(dungeon.hut()!=null&&Math.abs(y-dungeon.hut().getY())<20&&Math.hypot(x-dungeon.hut().getX(),z-dungeon.hut().getZ())<28)return true;
for(var b:Basins180.sites(s))if(y>b.y()-28&&y<b.y()+14&&Math.hypot(x-b.x(),z-b.z())<90)return true;
for(var p:NaturalWater184.plan(s).pools())if(y>p.surface()-40&&y<p.surface()+14&&Math.abs(x-p.seed().getX())<70&&Math.abs(z-p.seed().getZ())<70)return true;
for(var p:SurfaceWater187.plan(s).pools())if(y>p.surface()-48&&y<p.surface()+14&&Math.abs(x-p.seed().getX())<90&&Math.abs(z-p.seed().getZ())<90)return true;
return false;
}
private static void put(Map<BlockPos,BlockState> m,BlockPos p,Block b){m.put(p,b.defaultBlockState());}
private static Map<BlockPos,BlockState> sculpt(AdventureBiomes180 s,Site site,long seed){
if(site.kind==Kind.SURFACE||s.field().coherent()&&site.kind==Kind.SKY)return surfaceSculpt(s,site,seed);
var m=new LinkedHashMap<BlockPos,BlockState>();var o=site.floor;int y=o.getY();
Block stone=site.kind==Kind.SKY?Blocks.CALCITE:site.kind==Kind.CAVE?Blocks.TUFF:Blocks.ANDESITE;
Block accent=switch(site.sector){case 0,7->Blocks.CALCITE;case 1,2->Blocks.MOSS_BLOCK;case 3->Blocks.TERRACOTTA;case 4->Blocks.DYED_TERRACOTTA.red();case 5->Blocks.AMETHYST_BLOCK;default->Blocks.DYED_TERRACOTTA.yellow();};
double phase=(mix(seed^site.sector) & 1023)/163.0;
for(int dx=-site.rx-2;dx<=site.rx+2;dx++)for(int dz=-site.rz-2;dz<=site.rz+2;dz++){
int x=o.getX()+dx,z=o.getZ()+dz;
double d=dx*dx/(double)(site.rx*site.rx)+dz*dz/(double)(site.rz*site.rz)+.12*Math.sin(dx*.7+phase)*Math.cos(dz*.6);
if(d>1.15)continue;
int floor=y+(Math.abs(dx)<=3&&Math.abs(dz)<=3?0:(int)Math.round(.8*Math.sin(dx*.5+phase)*Math.cos(dz*.5)));
if(!rock(s,x,floor-3,z))continue; // Keep the edge of the host landform instead of hanging a platform over void.
var at=new BlockPos(x,floor,z);
if(d<=1){
for(int h=-2;h<=0;h++)put(m,at.above(h),h==0&&EcologyBiomes176.noise(seed^0x189A0L,x/14.0,z/14.0)>.3?Blocks.MOSS_BLOCK:stone);
int height=3+(int)(4*Math.sqrt(Math.max(0,1-d)));
for(int h=1;h<=height;h++)put(m,at.above(h),Blocks.AIR);
if(d>.62&&Math.floorMod(x*7+z*13,11)==0){
put(m,at,Blocks.MOSS_BLOCK);put(m,at.above(),Blocks.FLOWERING_AZALEA);
}
}else {
// Short reliefs follow surviving rock faces; no closed constructed shell.
for(int h=0;h<=5;h++)if(rock(s,x,floor+h,z))
put(m,at.above(h),h==2&&Math.floorMod(x+z,3)==0?Blocks.CHISELED_TUFF:h==0?Blocks.MOSS_BLOCK:stone);
}
}
// A slight bend opens to an actual, dry natural floor found during selection.
for(int step=-6;step<=11;step++){
int bend=step>3&&step<9?1:0;
int x=o.getX()+site.dx*step-site.dz*bend,z=o.getZ()+site.dz*step+site.dx*bend;
for(int side=-1;side<=1;side++){
var p=new BlockPos(x-site.dz*side,y,z+site.dx*side);
for(int h=-2;h<=0;h++)put(m,p.above(h),h==0&&step>=2?Blocks.MOSS_BLOCK:stone);
for(int h=1;h<=4;h++)put(m,p.above(h),Blocks.AIR);
}
}
// Low eroded crests shelter surface sites; all are rooted in the existing mass.
for(int side:new int[]{-1,1}){
int dx=-site.dx*4-site.dz*side*3,dz=-site.dz*4+site.dx*side*3;
var base=o.offset(dx,0,dz);if(!rock(s,base.getX(),y-3,base.getZ()))continue;
for(int h=-2;h<=2;h++)put(m,base.above(h),h==1?Blocks.CHISELED_TUFF:stone);
if(site.bright)put(m,base,Blocks.SEA_LANTERN);
put(m,base.above(3),Blocks.AMETHYST_BLOCK);
put(m,base.above(4),Rosette183.GLASS[site.sector]);
}
for(int dx=-1;dx<=1;dx++)for(int dz=-1;dz<=1;dz++){
put(m,o.offset(dx,0,dz),Blocks.CHISELED_TUFF);
for(int h=1;h<=4;h++)put(m,o.offset(dx,h,dz),Blocks.AIR);
}
put(m,o.above(),Rosette183.GLASS[site.sector]);
m.put(site.anchor(),ExpansionAnchorBlock.BLOCK.defaultBlockState().setValue(ExpansionAnchorBlock.SECTOR,site.sector));
// Keep the accepted geometry; the host material and sector colour shape each refuge's palette.
m.replaceAll((p,b)->{
int h=p.getY()-y;
double patch=EcologyBiomes176.noise(seed^0x188AL,p.getX()/5.0,p.getZ()/5.0);
if(b.is(stone)){
if(site.kind==Kind.SURFACE&&h<=0)return (h<0?Blocks.DIRT:patch<-.3?Blocks.MOSS_BLOCK:Blocks.GRASS_BLOCK).defaultBlockState();
if(site.kind==Kind.CAVE)return (patch>.30?Blocks.DEEPSLATE:patch<-.35?Blocks.CALCITE:Blocks.TUFF).defaultBlockState();
if(site.kind==Kind.SKY&&patch>.25)return Blocks.DIORITE.defaultBlockState();
}
if(b.is(Blocks.CHISELED_TUFF)&&patch>-.2)return accent.defaultBlockState();
return b;
});
return m;
}
static int ground(AdventureBiomes180 s,int x,int z,int near){
// Density only: neither leaves, trunks nor structures can become a soil support.
for(int y=near+12;y>=near-8;y--)if(rock(s,x,y,z)&&!rock(s,x,y+1,z))return y;
return near;
}
private static int pathY(AdventureBiomes180 s,Site site,int x,int z,int step){
return (site.kind==Kind.SURFACE||s.field().coherent()&&site.kind==Kind.SKY)&&Math.abs(step)>2?ground(s,x,z,site.floor.getY()):site.floor.getY();
}
private static BlockPos pylon(AdventureBiomes180 s,Site site,int side){
var p=site.floor.offset(-site.dx*4-site.dz*side*3,0,-site.dz*4+site.dx*side*3);
return (site.kind==Kind.SURFACE||s.field().coherent()&&site.kind==Kind.SKY)?new BlockPos(p.getX(),ground(s,p.getX(),p.getZ(),p.getY()),p.getZ()):p;
}
private static Map<BlockPos,BlockState> surfaceSculpt(AdventureBiomes180 s,Site site,long seed){
var m=new LinkedHashMap<BlockPos,BlockState>();var o=site.floor;
for(int dx=-site.rx-2;dx<=site.rx+2;dx++)for(int dz=-site.rz-2;dz<=site.rz+2;dz++){
double d=dx*dx/(double)(site.rx*site.rx)+dz*dz/(double)(site.rz*site.rz);
if(d>1)continue;int x=o.getX()+dx,z=o.getZ()+dz,y=ground(s,x,z,o.getY());
if(!rock(s,x,y-1,z))continue;
double patch=EcologyBiomes176.noise(seed^0x189A0L,x/14.0,z/14.0);
// One surface block, on the original rock. Broad moss only near the inward approach.
put(m,new BlockPos(x,y,z),!s.field().coherent()&&patch>.25?Blocks.MOSS_BLOCK:Blocks.GRASS_BLOCK);
}
for(int step=-6;step<=11;step++){
int bend=step>3&&step<9?1:0;
int x=o.getX()+site.dx*step-site.dz*bend,z=o.getZ()+site.dz*step+site.dx*bend;
for(int side=-1;side<=1;side++){
int px=x-site.dz*side,pz=z+site.dx*side;
int y=pathY(s,site,px,pz,step);var p=new BlockPos(px,y,pz);
for(int h=-2;h<=0;h++)if(h==0||!rock(s,px,y+h,pz))put(m,p.above(h),h==0?(step>=2?(s.field().coherent()?Blocks.COARSE_DIRT:Blocks.MOSS_BLOCK):Blocks.ANDESITE):Blocks.DIRT);
for(int h=1;h<=3;h++)put(m,p.above(h),Blocks.AIR);
}
}
for(int side:new int[]{-1,1}){
var p=pylon(s,site,side);
put(m,p,Blocks.ANDESITE);put(m,p.above(),Blocks.CHISELED_TUFF);put(m,p.above(2),Blocks.TUFF);
if(site.bright)put(m,p.above(2),Blocks.GLOWSTONE);
put(m,p.above(3),Blocks.AMETHYST_BLOCK);put(m,p.above(4),Rosette183.GLASS[site.sector]);
}
for(int dx=-1;dx<=1;dx++)for(int dz=-1;dz<=1;dz++){
for(int h=-2;h<=0;h++)put(m,o.offset(dx,h,dz),h==0?Blocks.CHISELED_TUFF:Blocks.TUFF);
for(int h=1;h<=4;h++)put(m,o.offset(dx,h,dz),Blocks.AIR);
}
put(m,o.above(),Rosette183.GLASS[site.sector]);
m.put(site.anchor(),ExpansionAnchorBlock.BLOCK.defaultBlockState().setValue(ExpansionAnchorBlock.SECTOR,site.sector));
return m;
}
public static boolean treeBlocked(AdventureBiomes180 s,BlockPos p){
for(var a:plan(s,s.seed()).sites())if(Math.abs(p.getY()-a.floor.getY())<24&&Math.hypot(p.getX()-a.floor.getX(),p.getZ()-a.floor.getZ())<15)return true;
return false;
}
public static void place(WorldGenLevel level,ChunkGenerator g,ChunkAccess chunk){
if(!WorldgenLab.finishedTerrain(g))return;
var entries=plan(source(g),level.getSeed()).chunks.get(chunk.getPos());
if(entries!=null)entries.forEach(chunk::setBlockState);
}
public static boolean protects(WorldGenLevel level,ChunkGenerator g,BlockPos pos){
if(!WorldgenLab.finishedTerrain(g)||pos.getY()<88||pos.getY()>512)return false;
var entries=plan(source(g),level.getSeed()).chunks.get(new ChunkPos(pos.getX()>>4,pos.getZ()>>4));
return entries!=null&&entries.containsKey(pos);
}
static Map<String,Object> check(ServerLevel level,AdventureBiomes180 source){
var p=plan(source,level.getSeed());var again=create(source,level.getSeed());
if(!p.sites.equals(again.sites)||!p.chunks.equals(again.chunks))throw new IllegalStateException("Unstable anchor plan");
int count=0;var details=new ArrayList<Map<String,Object>>();
for(var cp:p.chunks.keySet())level.getChunk(cp.x(),cp.z());
for(var e:p.chunks.entrySet())for(var b:e.getValue().entrySet()){
if(!e.getKey().contains(b.getKey()))throw new IllegalStateException("Cross-chunk anchor write");
if(!level.getBlockState(b.getKey()).equals(b.getValue()))throw new IllegalStateException("Anchor refuge mismatch "+b.getKey()+" expected "+b.getValue()+" got "+level.getBlockState(b.getKey()));
if(b.getValue().is(ExpansionAnchorBlock.BLOCK))count++;
}
for(var site:p.sites){
if(Rosette183.sector(site.floor.getX(),site.floor.getZ())!=site.sector)throw new IllegalStateException("Wrong colour sector");
// Walk the full three-wide bent approach and verify its actual native support.
for(int step=-6;step<=11;step++){
if(step>=-1&&step<=1)continue;
int bend=step>3&&step<9?1:0;var at=site.floor.offset(site.dx*step-site.dz*bend,1,site.dz*step+site.dx*bend);
at=new BlockPos(at.getX(),pathY(source,site,at.getX(),at.getZ(),step)+1,at.getZ());
if(!level.getBlockState(at).isAir()||!level.getBlockState(at.above()).isAir()||level.getBlockState(at.below()).getCollisionShape(level,at.below()).isEmpty())throw new IllegalStateException("Anchor approach blocked "+at);
}
if(!rock(source,site.floor.getX(),site.floor.getY()-5,site.floor.getZ()))throw new IllegalStateException("Anchor above void");
if(site.dx*site.floor.getX()+site.dz*site.floor.getZ()>=0)throw new IllegalStateException("Approach points outward");
for(int side:new int[]{-1,1}){
var pylon=pylon(source,site,side);
if(!level.getBlockState(pylon.above(3)).is(Blocks.AMETHYST_BLOCK)||!level.getBlockState(pylon.above(4)).is(Rosette183.GLASS[site.sector]))throw new IllegalStateException("Pylon glass must sit on amethyst");
}
details.add(Map.of("sector",site.sector,"kind",site.kind.name(),"anchor",site.anchor().toShortString(),"entrance",site.entrance().toShortString(),"bright",site.bright));
}
if(count!=8||p.sites.stream().map(Site::sector).distinct().count()!=8)throw new IllegalStateException("Expected exactly eight anchors");
var offers=AnchorOffers189.check(level,p.sites);
return Map.of("sites",details,"planningMs",p.millis,"chunks",p.chunks.size(),"blocks",p.chunks.values().stream().mapToInt(Map::size).sum(),"deterministic",true,"offers",offers);
}
}
@@ -0,0 +1,32 @@
package fr.koka.sanctuarytest;
import java.util.*;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.chunk.*;
import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator;
/** New world only: retained island, native cavity water and natural vertical palace. */
public final class Ascent184 {
private record Key(long seed,Station183.Layout layout) {}
private static final Map<Key,Palace184.Plan> PLANS=new java.util.concurrent.ConcurrentHashMap<>();
private Ascent184() {}
private static Palace184.Plan plan(long seed,ChunkGenerator g){return PLANS.computeIfAbsent(new Key(seed,Station183.layout(g)),k->Palace184.plan(k.seed(),k.layout()));}
public static void place(WorldGenLevel level,ChunkGenerator generator,ChunkAccess chunk){
if(!WorldgenLab.ascent184(generator))return;
var source=(AdventureBiomes180)((NoiseBasedChunkGenerator)generator).getBiomeSource();
Cavern183.place(level,generator,chunk);Sulfur183.place(source,chunk);NaturalWater184.place(source,chunk);
var pos=chunk.getPos();
if(pos.x()>=-3&&pos.x()<=2&&pos.z()>=-3&&pos.z()<=2)
plan(level.getSeed(),generator).blocks().forEach((p,b)->{if(pos.contains(p))chunk.setBlockState(p,b);});
PalaceAtlas184.populate(level,generator,chunk);
}
static Map<String,Object> check(ServerLevel level){
var generator=level.getChunkSource().getGenerator();var source=(AdventureBiomes180)((NoiseBasedChunkGenerator)generator).getBiomeSource();
var result=new LinkedHashMap<String,Object>();
result.put("palace",Palace184.check(level,plan(level.getSeed(),generator)));
result.put("naturalWater",NaturalWater184.check(level,source));
result.put("atlas",PalaceAtlas184.check(level));result.put("gallery",Cavern183.check(level));
result.put("sulfur",Sulfur183.check(level,source));result.put("gemDistricts",Gems183.check(level));return result;
}
}
@@ -0,0 +1,34 @@
package fr.koka.sanctuarytest;
import java.util.*;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.*;
import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator;
/** New world only: retained island, native cavity water and natural vertical palace. */
public final class Ascent185 {
private record Key(long seed,Station183.Layout layout) {}
private static final Map<Key,Map<BlockPos,BlockState>> PLANS=new java.util.concurrent.ConcurrentHashMap<>();
private Ascent185() {}
private static Map<BlockPos,BlockState> plan(long seed,ChunkGenerator g){return PLANS.computeIfAbsent(new Key(seed,Station183.layout(g)),k->Palace185.plan(k.seed(),k.layout()));}
public static void place(WorldGenLevel level,ChunkGenerator generator,ChunkAccess chunk){
if(!WorldgenLab.stem185(generator))return;
var source=(AdventureBiomes180)((NoiseBasedChunkGenerator)generator).getBiomeSource();
Cavern183.place(level,generator,chunk);Sulfur183.place(source,chunk);NaturalWater184.place(source,chunk);
var pos=chunk.getPos();
if(pos.x()>=-3&&pos.x()<=2&&pos.z()>=-3&&pos.z()<=2)
plan(level.getSeed(),generator).forEach((p,b)->{if(pos.contains(p))chunk.setBlockState(p,b);});
PalaceAtlas184.populate(level,generator,chunk);
}
static Map<String,Object> check(ServerLevel level){
var generator=level.getChunkSource().getGenerator();var source=(AdventureBiomes180)((NoiseBasedChunkGenerator)generator).getBiomeSource();
var result=new LinkedHashMap<String,Object>();
result.put("palace",Palace185.check(level,plan(level.getSeed(),generator)));
result.put("naturalWater",NaturalWater184.check(level,source));
result.put("atlas",PalaceAtlas184.check(level));result.put("gallery",Cavern183.check(level));
result.put("sulfur",Sulfur183.check(level,source));result.put("gemDistricts",Gems183.check(level));return result;
}
}
@@ -0,0 +1,35 @@
package fr.koka.sanctuarytest;
import java.util.*;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.*;
import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator;
/** New world only: retained island, native cavity water and natural vertical palace. */
public final class Ascent186 {
private record Key(long seed,Station183.Layout layout) {}
private static final Map<Key,Map<BlockPos,BlockState>> PLANS=new java.util.concurrent.ConcurrentHashMap<>();
private Ascent186() {}
private static Map<BlockPos,BlockState> plan(long seed,ChunkGenerator g){return PLANS.computeIfAbsent(new Key(seed,Station183.layout(g)),k->Palace186.plan(k.seed(),k.layout()));}
public static void place(WorldGenLevel level,ChunkGenerator generator,ChunkAccess chunk){
if(!WorldgenLab.anchors186(generator))return;
var source=(AdventureBiomes180)((NoiseBasedChunkGenerator)generator).getBiomeSource();
Cavern183.place(level,generator,chunk);Sulfur183.place(source,chunk);NaturalWater184.place(source,chunk);
var pos=chunk.getPos();
if(pos.x()>=-3&&pos.x()<=2&&pos.z()>=-3&&pos.z()<=2)
plan(level.getSeed(),generator).forEach((p,b)->{if(pos.contains(p))chunk.setBlockState(p,b);});
PalaceAtlas184.populate(level,generator,chunk);
}
static Map<String,Object> check(ServerLevel level){
var generator=level.getChunkSource().getGenerator();var source=(AdventureBiomes180)((NoiseBasedChunkGenerator)generator).getBiomeSource();
var result=new LinkedHashMap<String,Object>();
result.put("anchors",Anchors186.check(level,source));
result.put("palace",Palace186.check(level,plan(level.getSeed(),generator)));
result.put("naturalWater",NaturalWater184.check(level,source));
result.put("atlas",PalaceAtlas184.check(level));result.put("gallery",Cavern183.check(level));
result.put("sulfur",Sulfur183.check(level,source));result.put("gemDistricts",Gems183.check(level));return result;
}
}
@@ -0,0 +1,36 @@
package fr.koka.sanctuarytest;
import java.util.*;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.*;
import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator;
/** New world only: retained island, native cavity water and natural vertical palace. */
public final class Ascent187 {
private record Key(long seed,Station183.Layout layout) {}
private static final Map<Key,Map<BlockPos,BlockState>> PLANS=new java.util.concurrent.ConcurrentHashMap<>();
private Ascent187() {}
private static Map<BlockPos,BlockState> plan(long seed,ChunkGenerator g){return PLANS.computeIfAbsent(new Key(seed,Station183.layout(g)),k->Palace186.plan(k.seed(),k.layout()));}
public static void place(WorldGenLevel level,ChunkGenerator generator,ChunkAccess chunk){
if(!WorldgenLab.natural187(generator))return;
var source=(AdventureBiomes180)((NoiseBasedChunkGenerator)generator).getBiomeSource();
Cavern183.place(level,generator,chunk);NaturalWater184.place(source,chunk);SurfaceWater187.place(source,chunk);
var pos=chunk.getPos();
if(pos.x()>=-3&&pos.x()<=2&&pos.z()>=-3&&pos.z()<=2)
plan(level.getSeed(),generator).forEach((p,b)->{if(pos.contains(p))chunk.setBlockState(p,b);});
PalaceAtlas184.populate(level,generator,chunk);
}
static Map<String,Object> check(ServerLevel level){
var generator=level.getChunkSource().getGenerator();var source=(AdventureBiomes180)((NoiseBasedChunkGenerator)generator).getBiomeSource();
var result=new LinkedHashMap<String,Object>();
result.put("anchors",Anchors187.check(level,source));
result.put("palace",Palace186.check(level,plan(level.getSeed(),generator)));
result.put("naturalWater",NaturalWater184.check(level,source));
result.put("atlas",PalaceAtlas184.check(level));result.put("gallery",Cavern183.check(level));
result.put("surfaceWater",SurfaceWater187.check(level,source));
result.put("sulfur",Sulfur187.check(level,source));result.put("gemDistricts",Gems183.check(level));return result;
}
}
@@ -0,0 +1,36 @@
package fr.koka.sanctuarytest;
import java.util.*;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.*;
import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator;
/** New world only: retained island, native cavity water and natural vertical palace. */
public final class Ascent188 {
private record Key(long seed,Station183.Layout layout) {}
private static final Map<Key,Map<BlockPos,BlockState>> PLANS=new java.util.concurrent.ConcurrentHashMap<>();
private Ascent188() {}
private static Map<BlockPos,BlockState> plan(long seed,ChunkGenerator g){return PLANS.computeIfAbsent(new Key(seed,Station183.layout(g)),k->Palace186.plan(k.seed(),k.layout()));}
public static void place(WorldGenLevel level,ChunkGenerator generator,ChunkAccess chunk){
if(!WorldgenLab.details188(generator))return;
var source=(AdventureBiomes180)((NoiseBasedChunkGenerator)generator).getBiomeSource();
Cavern183.place(level,generator,chunk);NaturalWater184.place(source,chunk);SurfaceWater187.place(source,chunk);Lakes188.decorate(source,chunk);SulfurDetails188.place(level,generator,chunk);
var pos=chunk.getPos();
if(pos.x()>=-3&&pos.x()<=2&&pos.z()>=-3&&pos.z()<=2)
plan(level.getSeed(),generator).forEach((p,b)->{if(pos.contains(p))chunk.setBlockState(p,b);});
PalaceAtlas184.populate(level,generator,chunk);
}
static Map<String,Object> check(ServerLevel level){
var generator=level.getChunkSource().getGenerator();var source=(AdventureBiomes180)((NoiseBasedChunkGenerator)generator).getBiomeSource();
var result=new LinkedHashMap<String,Object>();
result.put("anchors",Anchors188.check(level,source));
result.put("palace",Palace186.check(level,plan(level.getSeed(),generator)));
result.put("naturalWater",NaturalWater184.check(level,source));
result.put("atlas",PalaceAtlas184.check(level));result.put("gallery",Cavern183.check(level));
result.put("surfaceWater",SurfaceWater187.check(level,source));
result.put("sulfur",Sulfur187.check(level,source));result.put("sulfurFeatures",SulfurDetails188.check(level,source));result.put("lakeBeds",Lakes188.check(level,source));result.put("ironLoot",IronLoot188.check(level));result.put("gemDistricts",Gems183.check(level));return result;
}
}
@@ -0,0 +1,37 @@
package fr.koka.sanctuarytest;
import java.util.*;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.*;
import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator;
/** New world only: retained island, native cavity water and natural vertical palace. */
public final class Ascent189 {
private record Key(long seed,Station183.Layout layout) {}
private static final Map<Key,Map<BlockPos,BlockState>> PLANS=new java.util.concurrent.ConcurrentHashMap<>();
private Ascent189() {}
private static Map<BlockPos,BlockState> plan(long seed,ChunkGenerator g){return PLANS.computeIfAbsent(new Key(seed,Station183.layout(g)),k->Palace186.plan(k.seed(),k.layout()));}
public static void place(WorldGenLevel level,ChunkGenerator generator,ChunkAccess chunk){
if(!WorldgenLab.finishedTerrain(generator))return;
var source=(AdventureBiomes180)((NoiseBasedChunkGenerator)generator).getBiomeSource();
Cavern183.place(level,generator,chunk);NaturalWater184.place(source,chunk);Water189.finish(source,chunk);Gallery189.place(source,chunk);Ruins189.place(source,chunk);SulfurDetails188.place(level,generator,chunk);
var pos=chunk.getPos();
if(pos.x()>=-3&&pos.x()<=2&&pos.z()>=-3&&pos.z()<=2)
plan(level.getSeed(),generator).forEach((p,b)->{if(pos.contains(p))chunk.setBlockState(p,b);});
PalaceAtlas184.populate(level,generator,chunk);
}
static Map<String,Object> check(ServerLevel level){
var generator=level.getChunkSource().getGenerator();var source=(AdventureBiomes180)((NoiseBasedChunkGenerator)generator).getBiomeSource();
var result=new LinkedHashMap<String,Object>();result.put(source.field().eroded()?"erodedMassif":source.originalRelief()?"originalRelief":source.field().refined()?"naturalRelief":"terraces",Terrain189.check(level,source));
if(source.field().coherent())result.put("continuity",Continuity193.check(level,source));
result.put("anchors",Anchors189.check(level,source));
result.put("palace",Palace186.check(level,plan(level.getSeed(),generator)));
result.put("naturalWater",NaturalWater184.check(level,source));
result.put("atlas",PalaceAtlas184.check(level));result.put("gallery",Cavern183.check(level));
result.put("surfaceWater",Water189.check(level,source));
result.put("sulfur",Sulfur187.check(level,source));result.put("sulfurFeatures",SulfurDetails188.check(level,source));result.put("galleryDetails",Gallery189.check(level,source));result.put("ruins",Ruins189.check(level,source));result.put("ironLoot",IronLoot188.check(level));result.put("gemDistricts",Gems183.check(level));return result;
}
}
@@ -0,0 +1,28 @@
package fr.koka.sanctuarytest;
import java.util.*;
import fr.koka.sanctuary.SanctuaryMod;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.renderer.culling.Frustum;
import net.minecraft.resources.Identifier;
import net.minecraft.world.level.levelgen.structure.BoundingBox;
import net.minecraft.world.phys.AABB;
import org.joml.Matrix4f;
/** Verify the actual client frustum hooks once per new lab level, including both terrain APIs. */
final class AscentView184 {
private static final Set<ClientLevel> CHECKED=Collections.newSetFromMap(new WeakHashMap<>());
static void register(){ClientTickEvents.END_CLIENT_TICK.register(client->{
var level=client.level;
if(level==null||!level.dimensionTypeRegistration().is(Identifier.fromNamespaceAndPath("sanctuary_test","ascent_1600"))||!CHECKED.add(level))return;
var frustum=new Frustum(new Matrix4f().scaling(1/4096f),new Matrix4f());
var crown=new AABB(-32,1152,-32,32,1328,32);var root=new AABB(-16,624,-16,16,672,16);
var cube=new BoundingBox(-32,1152,-32,32,1328,32);
frustum.prepare(0,250,0);
if(frustum.isVisible(crown)||frustum.cubeInFrustum(cube)<0||!frustum.isVisible(root))throw new IllegalStateException("Ascent ground reveal regression");
frustum.prepare(0,640,0);
if(!frustum.isVisible(crown)||frustum.cubeInFrustum(cube)>=0)throw new IllegalStateException("Ascent crown remains hidden at rosette");
SanctuaryMod.LOGGER.info("ASCENT_VIEW_VERIFIED groundHidden=true rosetteVisible=true crownRevealedAt640=true");
});}
}
@@ -0,0 +1,84 @@
package fr.koka.sanctuarytest;
import java.util.*;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.block.*;
import net.minecraft.world.level.chunk.*;
import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator;
import net.minecraft.world.level.levelgen.densityfunction.SamplerContext;
/** Two bounded, terrain-selected lake systems. Each broad upper pool spills into a lower pool. */
public final class Basins180 {
public record Basin(int x,int y,int z,int dx,int dz,int score){}
private static final Map<AdventureBiomes180,List<Basin>> PLANS=Collections.synchronizedMap(new WeakHashMap<>());
private Basins180(){}
private static boolean solid(AdventureBiomes180 s,int x,int y,int z){return s.field().sampleValue(SamplerContext.EMPTY_UNCACHED,x,y,z)>0;}
public static List<Basin> sites(AdventureBiomes180 source){synchronized(PLANS){return PLANS.computeIfAbsent(source,Basins180::plan);}}
private static List<Basin> plan(AdventureBiomes180 s){
var candidates=new ArrayList<Basin>();
for(int x=-192;x<=192;x+=32)for(int z=-192;z<=192;z+=32){
if(Math.hypot(x,z)>240||Dungeon180.near(s,x,z,48))continue;
for(int y=Math.min(212,s.terrain(x,z).top()-22);y>=104;y-=4){
if(!solid(s,x,y,z)||solid(s,x,y+3,z)||solid(s,x,y+10,z))continue;
for(int axis=0;axis<4;axis++){
int dx=axis==0?1:axis==1?-1:0,dz=axis==2?1:axis==3?-1:0,score=0;
if(Dungeon180.near(s,x+dx*30,z+dz*30,30))continue;
for(int u=-24;u<=48;u+=8)for(int v=-16;v<=16;v+=8){
int bx=x+dx*u-dz*v,bz=z+dz*u+dx*v;
if(solid(s,bx,y-8,bz)&&!solid(s,bx,y+6,bz)&&s.terrain(bx,bz).top()>y+14)score++;
}
if(s.field().coherent()&&galleryOverlap(s,x,y+2,z,dx,dz))continue;
candidates.add(new Basin(x,y+2,z,dx,dz,score));
}
break;
}
}
candidates.sort(Comparator.comparingInt(Basin::score).reversed());var chosen=new ArrayList<Basin>();
for(var b:candidates)if(b.score()>=24&&chosen.stream().allMatch(a->Math.hypot(a.x()-b.x(),a.z()-b.z())>145)){
chosen.add(b);if(chosen.size()==2)break;
}
if(chosen.isEmpty()||(!s.field().coherent()&&chosen.size()!=2))throw new IllegalStateException("No two retained cave lake sites: "+chosen+" candidates="+candidates.size());
return List.copyOf(chosen);
}
private static boolean galleryOverlap(AdventureBiomes180 s,int x,int y,int z,int dx,int dz){
if(Math.hypot(x,z)>135)return false;
for(var c:Cavern183.plan(s).cells().values()){
if(c.y()>y+4||c.ceiling()<y-16)continue;
int u=(c.x()-x)*dx+(c.z()-z)*dz,v=-(c.x()-x)*dz+(c.z()-z)*dx;
if(u>=-34&&u<=60&&Math.abs(v)<=26)return true;
}
return false;
}
public static void place(WorldGenLevel level,ChunkGenerator generator,ChunkAccess c){
if(!WorldgenLab.ecology180(generator))return;var source=(AdventureBiomes180)((NoiseBasedChunkGenerator)generator).getBiomeSource();
for(var b:sites(source)){
if(Math.abs(c.getPos().getMiddleBlockX()-b.x())>76||Math.abs(c.getPos().getMiddleBlockZ()-b.z())>76)continue;
for(int x=c.getPos().getMinBlockX();x<=c.getPos().getMaxBlockX();x++)for(int z=c.getPos().getMinBlockZ();z<=c.getPos().getMaxBlockZ();z++){
double u=(x-b.x())*b.dx()+(z-b.z())*b.dz(),v=-(x-b.x())*b.dz()+(z-b.z())*b.dx();
double wobble=.10*EcologyBiomes176.noise(level.getSeed()^0x180BA5L,x/12.0,z/12.0);
double upper=u*u/(29*29.0)+v*v/(21*21.0)+wobble;
double lower=(u-37)*(u-37)/(18*18.0)+v*v/(14*14.0)+wobble;
if(upper>1.08&&lower>1.08)continue;
boolean low=upper>1.0||(u>26&&Math.abs(v)<=2&&lower<=1);
double shape=low?lower:upper;if(shape>1.08)continue;
int waterY=b.y()-(low?4:0);
boolean spill=Math.abs(v)<=2&&u>22&&u<38;
boolean shore=shape>.88&&!spill;
if(Dungeon180.protectedAt(source,x,waterY,z))continue;
int nativeFloor=waterY-8;
for(int probe=waterY+3;probe>=waterY-10;probe--)if(AdventureCaves180.natural(c.getBlockState(new BlockPos(x,probe,z)))){nativeFloor=probe;break;}
// Keep protruding rock and existing high banks. The lake bends around those reliefs.
if(!spill && nativeFloor>waterY+1)continue;
if(shore && nativeFloor>=waterY)continue;
int depth=shore?1:Math.clamp(waterY-nativeFloor+1,2,8);
// Native clay/rock form a continuous bed and bank; no water can escape underneath.
for(int y=waterY-depth-2;y<=waterY;y++){
var material=y<waterY-depth?Blocks.TUFF:y==waterY-depth?Blocks.CLAY:shore?(y==waterY?Blocks.MOSS_BLOCK:Blocks.CLAY):Blocks.WATER;
var p=new BlockPos(x,y,z);c.setBlockState(p,material.defaultBlockState());
if(material==Blocks.WATER)c.markPosForPostProcessing(p);
}
for(int y=waterY+1;y<=b.y()+3;y++)c.setBlockState(new BlockPos(x,y,z),Blocks.AIR.defaultBlockState());
}
}
}
}
@@ -0,0 +1,113 @@
package fr.koka.sanctuarytest;
import java.util.*;
import net.minecraft.core.*;
import net.minecraft.util.RandomSource;
import net.minecraft.world.entity.EntityTypes;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.block.*;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.RailShape;
import net.minecraft.world.level.chunk.*;
import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator;
import net.minecraft.world.level.levelgen.densityfunction.SamplerContext;
/** Small seed-owned complexes, clipped per chunk; no global hydrology or existing-world writes. */
public final class CaveComplex179 {
public record Site(int x,int y,int z,int dx,int dz,int length,boolean hut){}
private static final Map<LivingBiomes179,List<Site>> PLANS=Collections.synchronizedMap(new WeakHashMap<>());
private CaveComplex179(){}
public static List<Site> sites(LivingBiomes179 source){synchronized(PLANS){return PLANS.computeIfAbsent(source,CaveComplex179::plan);}}
private static boolean rock(LivingBiomes179 source,int x,int y,int z){return source.field().sampleValue(SamplerContext.EMPTY_UNCACHED,x,y,z)>0;}
private static List<Site> plan(LivingBiomes179 source){
var sites=new ArrayList<Site>();
for(int side=0;side<3;side++){
int dx=side==0?1:side==1?-1:0,dz=side==2?1:0;Site best=null;int score=-1;
for(int r=112;r<=208;r+=24)for(int cross=-72;cross<=72;cross+=24){
int x=dx*r-dz*cross,z=dz*r+dx*cross,top=source.terrain(x,z).top();
for(int y=Math.min(208,top-20);y>=112;y-=2){
if(!rock(source,x,y,z)||rock(source,x,y+3,z)||rock(source,x,y+9,z))continue;
int n=0;for(int a=-6;a<=6;a+=3)for(int b=-6;b<=6;b+=3)if(!rock(source,x+a,y+4,z+b))n++;
if(n>score){score=n;best=new Site(x,y,z,dx,dz,0,false);}break;
}
}
if(best!=null){
int length=36;
// Stop just outside the island skin: a real daylight opening, not an endless bridge.
while(length<180 && source.terrain(best.x()+dx*length,best.z()+dz*length).top()>best.y()+4)length+=4;
sites.add(new Site(best.x(),best.y(),best.z(),dx,dz,length+3,false));
}
}
Site hut=null;int best=-1;
for(int x=-224;x<=224;x+=16)for(int z=-224;z<=224;z+=16){
int top=source.terrain(x,z).top();
for(int y=Math.min(216,top-20);y>=96;y-=2){
if(!LivingCaves179.biome(source,x,y,z).equals("living_marsh")||!rock(source,x,y,z)||rock(source,x,y+1,z)||rock(source,x,y+8,z))continue;
int n=0;for(int a=-4;a<=4;a+=2)for(int b=-4;b<=4;b+=2)if(!rock(source,x+a,y+5,z+b)&&rock(source,x+a,y-3,z+b))n++;
if(n>best){best=n;hut=new Site(x,y,z,1,0,0,true);}break;
}
}
if(hut!=null)sites.add(hut);
return List.copyOf(sites);
}
public static void place(WorldGenLevel level,ChunkGenerator generator,ChunkAccess c){
if(!WorldgenLab.ecology179(generator))return;
var source=(LivingBiomes179)((NoiseBasedChunkGenerator)generator).getBiomeSource();
for(var site:sites(source)){
int radius=site.hut()?7:site.length()+30;
if(c.getPos().getMaxBlockX()<site.x()-radius||c.getPos().getMinBlockX()>site.x()+radius||c.getPos().getMaxBlockZ()<site.z()-radius||c.getPos().getMinBlockZ()>site.z()+radius)continue;
for(int x=c.getPos().getMinBlockX();x<=c.getPos().getMaxBlockX();x++)for(int z=c.getPos().getMinBlockZ();z<=c.getPos().getMaxBlockZ();z++){
int u=(x-site.x())*site.dx()+(z-site.z())*site.dz(),v=-(x-site.x())*site.dz()+(z-site.z())*site.dx();
if(site.hut())hut(c,site,x,z,u,v);else mine(c,site,x,z,u,v);
}
if(site.hut() && c.getPos().contains(new BlockPos(site.x(),site.y(),site.z()))){
var random=RandomSource.create(level.getSeed()^0x179A11L);
LivingCaves179.spawn(level,new BlockPos(site.x(),site.y()+2,site.z()),EntityTypes.WITCH,random);
LivingCaves179.spawn(level,new BlockPos(site.x()+1,site.y()+2,site.z()),EntityTypes.CAT,random);
}
}
}
private static void set(ChunkAccess c,int x,int y,int z,Block b){c.setBlockState(new BlockPos(x,y,z),b.defaultBlockState());}
private static void mine(ChunkAccess c,Site s,int x,int z,int u,int v){
boolean hall=Math.abs(u)<=7&&Math.abs(v)<=6;
boolean trunk=u>=-30&&u<=s.length()&&Math.abs(v)<=2;
boolean branch=(Math.abs(u+22)<=2||Math.abs(u-22)<=2)&&Math.abs(v)<=26;
if(!hall&&!trunk&&!branch)return;
int height=hall?7:4,y=s.y();
for(int dy=1;dy<=height;dy++)set(c,x,y+dy,z,Blocks.AIR);
set(c,x,y,z,Blocks.DARK_OAK_PLANKS);
boolean pillar=hall?Math.abs(u)==7&&Math.abs(v)==6:trunk?Math.abs(v)==2&&Math.floorMod(u,6)==0:Math.abs(v)%6==0&&(Math.abs(u+22)==2||Math.abs(u-22)==2);
if(pillar){
for(int dy=1;dy<=height;dy++)set(c,x,y+dy,z,Blocks.DARK_OAK_LOG);
// Suspended sections attach to a natural ceiling where one exists.
int anchor=0;
for(int dy=height+1;dy<=40;dy++)if(LivingCaves179.natural(c.getBlockState(new BlockPos(x,y+dy,z)))){anchor=dy;break;}
if(anchor>0)for(int dy=height+1;dy<anchor;dy++)set(c,x,y+dy,z,Blocks.IRON_CHAIN);
}
boolean beam=hall?(Math.abs(u)%7==0):(trunk?Math.floorMod(u,6)==0:Math.floorMod(v,6)==0);
if(beam)set(c,x,y+height,z,Blocks.DARK_OAK_LOG);
if(beam && !pillar && (hall?u==0&&Math.abs(v)==5:trunk?v==0:true))
c.setBlockState(new BlockPos(x,y+height-1,z),Blocks.LANTERN.defaultBlockState().setValue(LanternBlock.HANGING,true));
if(trunk&&v==0&&!hall)c.setBlockState(new BlockPos(x,y+1,z),Blocks.RAIL.defaultBlockState().setValue(RailBlock.SHAPE,s.dx()!=0?RailShape.EAST_WEST:RailShape.NORTH_SOUTH));
if(hall&&Math.abs(u)==5&&Math.abs(v)==4)set(c,x,y+1,z,Blocks.BARREL);
}
private static void hut(ChunkAccess c,Site s,int x,int z,int u,int v){
if(Math.abs(u)>4||Math.abs(v)>5)return;
int y=s.y()+1;
boolean corner=Math.abs(u)==3&&Math.abs(v)==4;
if(corner){for(int dy=-4;dy<=4;dy++)set(c,x,y+dy,z,Blocks.SPRUCE_LOG);}
if(Math.abs(u)<=3&&Math.abs(v)<=4){
set(c,x,y,z,Blocks.SPRUCE_PLANKS);
for(int dy=1;dy<=4;dy++)set(c,x,y+dy,z,Blocks.AIR);
boolean wall=Math.abs(u)==3||Math.abs(v)==4;
if(wall&&!(v==-4&&u==0))for(int dy=1;dy<=3;dy++)set(c,x,y+dy,z,corner?Blocks.SPRUCE_LOG:dy==2&&((Math.abs(u)==3&&v==0)||(Math.abs(v)==4&&Math.abs(u)==2))?Blocks.GLASS_PANE:Blocks.SPRUCE_PLANKS);
}
// Stepped overhanging roof, with a clear entrance and a porch.
int roof=y+4+(4-Math.abs(u))/2;set(c,x,roof,z,Blocks.DARK_OAK_SLAB);
if(v==-5&&Math.abs(u)<=2)set(c,x,y,z,Blocks.SPRUCE_PLANKS);
if(u==2&&v==2)set(c,x,y+1,z,Blocks.CAULDRON);
if(u==-2&&v==2)set(c,x,y+1,z,Blocks.CRAFTING_TABLE);
if(u==0&&v==3)set(c,x,y+1,z,Blocks.LANTERN);
if(u==-2&&v==3)set(c,x,y+1,z,Blocks.POTTED_RED_MUSHROOM);
}
}
@@ -0,0 +1,157 @@
package fr.koka.sanctuarytest;
import java.util.*;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.block.*;
import net.minecraft.world.level.chunk.*;
import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator;
import net.minecraft.world.level.levelgen.densityfunction.SamplerContext;
/** Local, seed-bound rock gallery. All carving belongs to new gallery_v1 chunks. */
public final class Cavern182 {
record Room(int x,int y,int z,int rx,int rz) {}
record Cell(int x,int y,int z,int ceiling,boolean path) {}
record Plan(List<Room> rooms,Map<Long,Cell> cells,List<BlockPos> route) {}
private static final Map<AdventureBiomes180,Plan> PLANS=Collections.synchronizedMap(new WeakHashMap<>());
private Cavern182() {}
private static long key(int x,int z){return ((long)x<<32)^(z&0xffffffffL);}
private static boolean rock(AdventureBiomes180 s,int x,int y,int z){return s.field().sampleValue(SamplerContext.EMPTY_UNCACHED,x,y,z)>0;}
static Plan plan(AdventureBiomes180 source){synchronized(PLANS){return PLANS.computeIfAbsent(source,Cavern182::create);}}
private static int floor(AdventureBiomes180 s,int x,int z,int low,int high,int fallback) {
int best=-1,bestScore=Integer.MIN_VALUE;
for(int y=high;y>=low;y--) {
if(!rock(s,x,y,z)||!rock(s,x,y-3,z)||rock(s,x,y+1,z))continue;
boolean roof=false;for(int h=12;h<=40;h+=4)if(rock(s,x,y+h,z)){roof=true;break;}
if(!roof)continue;
int score=0;
for(int dx=-8;dx<=8;dx+=4)for(int dz=-8;dz<=8;dz+=4)
if(!rock(s,x+dx,y+4,z+dz)&&rock(s,x+dx,y-4,z+dz))score+=3;
score-=Math.abs(y-fallback)/3;
if(score>bestScore){best=y;bestScore=score;}
}
if(best>=0)return best;
if(fallback>0&&rock(s,x,fallback-3,z))return fallback;
throw new IllegalStateException("No supported central gallery floor at "+x+","+z);
}
private static Plan create(AdventureBiomes180 s) {
int y=floor(s,0,0,148,212,-1);
var rooms=List.of(new Room(0,y,0,14,11),
new Room(-34,floor(s,-34,-26,y-18,y+8,y-6),-26,10,8),
new Room(-38,floor(s,-38,24,y-30,y+8,y-16),24,12,10));
var cells=new LinkedHashMap<Long,Cell>();
for(var r:rooms)for(int x=r.x-r.rx-1;x<=r.x+r.rx+1;x++)for(int z=r.z-r.rz-1;z<=r.z+r.rz+1;z++) {
double u=(x-r.x)/(double)r.rx,v=(z-r.z)/(double)r.rz;
double d=u*u+v*v+.07*Math.sin(x*.6)*Math.cos(z*.4);
if(d>1)continue;
double amplitude=Math.clamp((Math.hypot(x-r.x,z-r.z)-5)/4,0,1);
int ground=r.y+(int)Math.round(amplitude*(Math.sin(x*.19)*1.1+Math.sin(z*.23)));
int ceiling=r.y+5+(int)(9*Math.sqrt(Math.max(0,1-d)));
cells.put(key(x,z),new Cell(x,ground,z,ceiling,false));
}
var route=new ArrayList<BlockPos>();
link(cells,route,rooms.get(0),rooms.get(1),new int[][]{{0,0},{-8,-12},{-23,-17},{-34,-26}});
link(cells,route,rooms.get(1),rooms.get(2),new int[][]{{-34,-26},{-49,-16},{-48,5},{-38,24}});
link(cells,route,rooms.get(2),rooms.get(0),new int[][]{{-38,24},{-25,20},{-12,8},{0,0}});
return new Plan(rooms,Map.copyOf(cells),List.copyOf(route));
}
private static void link(Map<Long,Cell> cells,List<BlockPos> route,Room a,Room b,int[][] turns) {
var line=new ArrayList<int[]>();
for(int i=1;i<turns.length;i++) {
int x=turns[i-1][0],z=turns[i-1][1],tx=turns[i][0],tz=turns[i][1];
int dx=Math.abs(tx-x),dz=Math.abs(tz-z),sx=Integer.signum(tx-x),sz=Integer.signum(tz-z),err=dx-dz;
while(x!=tx||z!=tz){line.add(new int[]{x,z});int twice=2*err;if(twice> -dz){err-=dz;x+=sx;}if(twice<dx){err+=dx;z+=sz;}}
}
line.add(new int[]{b.x,b.z});
for(int i=0;i<line.size();i++) {
var p=line.get(i);int y=(int)Math.round(a.y+(b.y-a.y)*i/(double)(line.size()-1));
route.add(new BlockPos(p[0],y+1,p[1]));
for(int dx=-2;dx<=2;dx++)for(int dz=-2;dz<=2;dz++) {
if(dx*dx+dz*dz>5)continue;
int x=p[0]+dx,z=p[1]+dz;var old=cells.get(key(x,z));
// Keep the first corridor floor at self-overlaps, guaranteeing a stable shared seam.
if(old!=null&&old.path)continue;
int roof=Math.max(old==null?y+6:old.ceiling,y+5+(dx==0||dz==0?2:0));
cells.put(key(x,z),new Cell(x,y,z,roof,true));
}
}
}
private static void set(ChunkAccess c,BlockPos p,Block b){c.setBlockState(p,b.defaultBlockState());}
public static void place(WorldGenLevel level,ChunkGenerator generator,ChunkAccess chunk) {
if(!WorldgenLab.gallery182(generator))return;
if(chunk.getPos().getMaxBlockX()< -54||chunk.getPos().getMinBlockX()>15
||chunk.getPos().getMaxBlockZ()< -36||chunk.getPos().getMinBlockZ()>35)return;
var source=(AdventureBiomes180)((NoiseBasedChunkGenerator)generator).getBiomeSource();var p=plan(source);
for(var c:p.cells.values()) {
var at=new BlockPos(c.x,c.y,c.z);if(!chunk.getPos().contains(at))continue;
for(int y=c.y-2;y<=c.ceiling;y++) {
var pos=new BlockPos(c.x,y,c.z);
if(y>c.y){set(chunk,pos,Blocks.AIR);continue;}
// Sculpt only this floor's local foundation, retaining minerals elsewhere.
Block stone=y<c.y?Blocks.TUFF:Math.floorMod(c.x*11+c.z*7,19)<3?Blocks.MOSS_BLOCK:Blocks.ANDESITE;
if(Math.abs(c.x)<=2&&Math.abs(c.z)<=2)stone=Blocks.POLISHED_TUFF;
set(chunk,pos,stone);
}
// Vertical seams colour existing stone; there is no box-shaped lining.
for(int[] d:new int[][]{{1,0},{-1,0},{0,1},{0,-1}}) {
int wx=c.x+d[0],wz=c.z+d[1];if(p.cells.containsKey(key(wx,wz)))continue;
for(int y=c.y+1;y<c.ceiling;y++) {
var wall=new BlockPos(wx,y,wz);if(!chunk.getPos().contains(wall))continue;
if(!AdventureCaves180.natural(chunk.getBlockState(wall)))continue;
if(Math.floorMod(wx*3+wz*5+y/4,13)<2)set(chunk,wall,Blocks.CALCITE);
else if(y==c.y+1&&Math.floorMod(wx+wz,4)==0)set(chunk,wall,Blocks.MOSS_BLOCK);
else if(y==c.y+3&&Math.floorMod(wx+wz,7)==0)set(chunk,wall,Blocks.CHISELED_TUFF);
}
}
}
int y=p.rooms.getFirst().y;
// The dark core is a visual placeholder; the broken stone arch is not a Nether frame.
putIfOwned(chunk,0,y+1,0,Blocks.CHISELED_POLISHED_BLACKSTONE);
putIfOwned(chunk,0,y+2,0,Blocks.OBSIDIAN);
for(int side:new int[]{-1,1})for(int h=0;h<7;h++) {
int x=side*(4-(h>=5?1:0));
putIfOwned(chunk,x,y+1+h,-5,h%3==0?Blocks.OBSIDIAN:Blocks.POLISHED_BASALT);
}
for(int[] b:new int[][]{{-6,-4},{6,-4},{-5,5},{5,5}}) {
var cell=p.cells.get(key(b[0],b[1]));
putIfOwned(chunk,b[0],cell.y-1,b[1],Blocks.SEA_LANTERN);
putIfOwned(chunk,b[0],cell.y,b[1],Blocks.COPPER_GRATE.waxed().oxidized());
}
}
private static void putIfOwned(ChunkAccess chunk,int x,int y,int z,Block b) {
var p=new BlockPos(x,y,z);if(chunk.getPos().contains(p))set(chunk,p,b);
}
static Map<String,Object> check(ServerLevel level) {
var source=(AdventureBiomes180)((NoiseBasedChunkGenerator)level.getChunkSource().getGenerator()).getBiomeSource();var p=plan(source);
for(int x=-4;x<=0;x++)for(int z=-3;z<=2;z++)level.getChunk(x,z);
int cy=p.rooms.getFirst().y;
if(!level.getBlockState(new BlockPos(0,cy+2,0)).is(Blocks.OBSIDIAN))throw new IllegalStateException("Missing lower core");
// Follow the actual saved surfaces across chunk boundaries, not just the planned centreline.
var floors=new HashMap<Long,Integer>();
for(var c:p.cells.values()) {
if(Math.abs(c.x)<=1&&Math.abs(c.z)<=1)continue;
int actual=Integer.MIN_VALUE;
for(int y=c.y+2;y>=c.y-2;y--) {
var at=new BlockPos(c.x,y,c.z);
if(!level.getBlockState(at).getCollisionShape(level,at).isEmpty()&&level.getBlockState(at.above()).isAir()&&level.getBlockState(at.above(2)).isAir()){actual=y;break;}
}
if(actual!=Integer.MIN_VALUE)floors.put(key(c.x,c.z),actual);
}
long start=key(2,0);if(!floors.containsKey(start))throw new IllegalStateException("No gallery approach");
var visited=new HashSet<Long>();var queue=new ArrayDeque<Long>();queue.add(start);
while(!queue.isEmpty()) {
long k=queue.removeFirst();if(!visited.add(k))continue;int x=(int)(k>>32),z=(int)k;
for(int[] d:new int[][]{{1,0},{-1,0},{0,1},{0,-1}}) {
long next=key(x+d[0],z+d[1]);var y=floors.get(next);
if(y!=null&&Math.abs(y-floors.get(k))<=1&&!visited.contains(next))queue.add(next);
}
}
for(var r:p.rooms)if(!visited.contains(key(r.x+2,r.z)))throw new IllegalStateException("Disconnected gallery room: "+r);
if(visited.size()<floors.size()*.9)throw new IllegalStateException("Too many isolated gallery surfaces: "+visited.size()+"/"+floors.size());
return Map.of("lowerCore",new BlockPos(0,cy+2,0).toShortString(),"rooms",p.rooms,"walkableCells",visited.size(),"plannedColumns",p.cells.size());
}
}
@@ -0,0 +1,214 @@
package fr.koka.sanctuarytest;
import java.util.*;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.block.*;
import net.minecraft.world.level.chunk.*;
import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator;
import net.minecraft.world.level.levelgen.densityfunction.SamplerContext;
/** Local, seed-bound rock gallery. All carving belongs to new discovery_v1 chunks. */
public final class Cavern183 {
record Room(int x,int y,int z,int rx,int rz) {}
record Cell(int x,int y,int z,int ceiling,boolean path) {}
record Plan(List<Room> rooms,Map<Long,Cell> cells,List<BlockPos> route) {}
private static final Map<AdventureBiomes180,Plan> PLANS=Collections.synchronizedMap(new WeakHashMap<>());
private Cavern183() {}
private static long key(int x,int z){return ((long)x<<32)^(z&0xffffffffL);}
private static boolean rock(AdventureBiomes180 s,int x,int y,int z){return s.field().sampleValue(SamplerContext.EMPTY_UNCACHED,x,y,z)>0;}
static Plan plan(AdventureBiomes180 source){synchronized(PLANS){return PLANS.computeIfAbsent(source,Cavern183::create);}}
private static int floor(AdventureBiomes180 s,int x,int z,int low,int high,int fallback) {
int best=-1,bestScore=Integer.MIN_VALUE;
for(int y=high;y>=low;y--) {
if(!rock(s,x,y,z)||!rock(s,x,y-3,z)||rock(s,x,y+1,z))continue;
boolean roof=false;for(int h=12;h<=40;h+=4)if(rock(s,x,y+h,z)){roof=true;break;}
if(!roof)continue;
int score=0;
for(int dx=-8;dx<=8;dx+=4)for(int dz=-8;dz<=8;dz+=4)
if(!rock(s,x+dx,y+4,z+dz)&&rock(s,x+dx,y-4,z+dz))score+=3;
score-=Math.abs(y-fallback)/3;
if(score>bestScore){best=y;bestScore=score;}
}
if(best>=0)return best;
if(fallback>0&&rock(s,x,fallback-3,z))return fallback;
return -1;
}
private static Room satellite(AdventureBiomes180 s,int x,int z,int centre,int preferred,int rx,int rz) {
Room best=null;int score=Integer.MAX_VALUE;
// Search only nearby existing cave floors, rather than forcing a floating room at a fixed coordinate.
for(int dx=-28;dx<=28;dx+=4)for(int dz=-28;dz<=28;dz+=4){
if(Math.hypot(x+dx,z+dz)>58||(z<0?z+dz> -12:z+dz<12))continue;
double separation=Math.pow((x+dx)/(double)(19+rx+3),2)+Math.pow((z+dz)/(double)(16+rz+3),2);
if(separation<1)continue; // Separate floor levels must meet through a ramp, never overlapping rooms.
int y=floor(s,x+dx,z+dz,centre-18,centre+8,-1);if(y<0)continue;
int cost=dx*dx+dz*dz+2*Math.abs(y-preferred);
if(cost<score){score=cost;best=new Room(x+dx,y,z+dz,rx,rz);}
}
return best; // Some seeds have no separate pocket here; retain the central chamber instead of forcing a room.
}
private static Plan create(AdventureBiomes180 s) {
int y=floor(s,0,0,148,212,-1);
// Some seeds have their central cave below the historical search band.
// Preserve every previously admitted floor; widen only a failed search.
if(y<0&&s.field().coherent())y=floor(s,0,0,48,240,-1);
// A seed can have solid rock at the origin without an existing cave floor.
// Keep admitted layouts identical; only sculpt a local chamber when needed.
if(y<0&&s.polish197())y=supportedChamber(s);
if(y<0&&s.stableGallery198())y=GalleryFloor198.choose((x,h,z)->rock(s,x,h,z));
if(y<0)throw new IllegalStateException("No supported central gallery floor");
var rooms=new ArrayList<Room>();rooms.add(new Room(0,y,0,19,16));
if(s.field().coherent()){
var north=satellite(s,-34,-26,y,y-6,10,8);if(north!=null)rooms.add(north);
var south=satellite(s,-38,24,y,y-16,12,10);if(south!=null)rooms.add(south);
}else{
rooms.add(new Room(-34,floor(s,-34,-26,y-18,y+8,y-6),-26,10,8));
rooms.add(new Room(-38,floor(s,-38,24,y-30,y+8,y-16),24,12,10));
}
for(var room:rooms)if(room.y<0)throw new IllegalStateException("No supported central gallery floor at "+room.x+","+room.z);
var cells=new LinkedHashMap<Long,Cell>();
for(var r:rooms)for(int x=r.x-r.rx-1;x<=r.x+r.rx+1;x++)for(int z=r.z-r.rz-1;z<=r.z+r.rz+1;z++) {
double u=(x-r.x)/(double)r.rx,v=(z-r.z)/(double)r.rz;
double d=u*u+v*v+.07*Math.sin(x*.6)*Math.cos(z*.4);
if(d>1)continue;
double amplitude=Math.clamp((Math.hypot(x-r.x,z-r.z)-5)/4,0,1);
int ground=r.y+(int)Math.round(amplitude*(Math.sin(x*.19)*1.1+Math.sin(z*.23)));
int ceiling=r.y+5+(int)(13*Math.sqrt(Math.max(0,1-d)));
cells.put(key(x,z),new Cell(x,ground,z,ceiling,false));
}
var route=new ArrayList<BlockPos>();
if(rooms.size()==3){
var a=rooms.get(1);var b=rooms.get(2);
link(cells,route,rooms.get(0),a,new int[][]{{0,0},{-8,-12},{-23,-17},{a.x,a.z}});
link(cells,route,a,b,new int[][]{{a.x,a.z},{-49,-16},{-48,5},{b.x,b.z}});
link(cells,route,b,rooms.get(0),new int[][]{{b.x,b.z},{-25,20},{-12,8},{0,0}});
}else if(rooms.size()==2){
var a=rooms.get(1);link(cells,route,rooms.getFirst(),a,new int[][]{{0,0},{a.x/2,a.z/2},{a.x,a.z}});
}
return new Plan(List.copyOf(rooms),Map.copyOf(cells),List.copyOf(route));
}
private static int supportedChamber(AdventureBiomes180 s) {
int best=-1,bestScore=Integer.MIN_VALUE,top=s.field().surface(0,0);
for(int y=32;y<=Math.min(240,top-22);y++) {
if(!rock(s,0,y,0)||!rock(s,0,y-3,0))continue;
int score=-Math.abs(y-164)/3;
for(int x=-16;x<=16;x+=8)for(int z=-12;z<=12;z+=6) {
if(rock(s,x,y-3,z))score+=4;
if(rock(s,x,y+20,z))score+=2;
if(!rock(s,x,y+5,z))score++;
}
if(score>bestScore){bestScore=score;best=y;}
}
return best;
}
private static void link(Map<Long,Cell> cells,List<BlockPos> route,Room a,Room b,int[][] turns) {
var line=new ArrayList<int[]>();
for(int i=1;i<turns.length;i++) {
int x=turns[i-1][0],z=turns[i-1][1],tx=turns[i][0],tz=turns[i][1];
int dx=Math.abs(tx-x),dz=Math.abs(tz-z),sx=Integer.signum(tx-x),sz=Integer.signum(tz-z),err=dx-dz;
while(x!=tx||z!=tz){line.add(new int[]{x,z});int twice=2*err;if(twice> -dz){err-=dz;x+=sx;}if(twice<dx){err+=dx;z+=sz;}}
}
line.add(new int[]{b.x,b.z});
for(int i=0;i<line.size();i++) {
var p=line.get(i);int y=(int)Math.round(a.y+(b.y-a.y)*i/(double)(line.size()-1));
route.add(new BlockPos(p[0],y+1,p[1]));
for(int dx=-2;dx<=2;dx++)for(int dz=-2;dz<=2;dz++) {
if(dx*dx+dz*dz>5)continue;
int x=p[0]+dx,z=p[1]+dz;var old=cells.get(key(x,z));
// Keep the first corridor floor at self-overlaps, guaranteeing a stable shared seam.
if(old!=null&&old.path)continue;
int roof=Math.max(old==null?y+6:old.ceiling,y+5+(dx==0||dz==0?2:0));
cells.put(key(x,z),new Cell(x,y,z,roof,true));
}
}
}
private static void set(ChunkAccess c,BlockPos p,Block b){c.setBlockState(p,b.defaultBlockState());}
public static void place(WorldGenLevel level,ChunkGenerator generator,ChunkAccess chunk) {
if(!WorldgenLab.discovery183(generator)&&!WorldgenLab.ascent184(generator)&&!WorldgenLab.stem185(generator)&&!WorldgenLab.anchors186(generator)&&!WorldgenLab.natural187(generator)&&!WorldgenLab.details188(generator)&&!WorldgenLab.finishedTerrain(generator))return;
var source=(AdventureBiomes180)((NoiseBasedChunkGenerator)generator).getBiomeSource();
int margin=source.field().coherent()?32:0;
if(chunk.getPos().getMaxBlockX()< -54-margin||chunk.getPos().getMinBlockX()>20
||chunk.getPos().getMaxBlockZ()< -36-margin||chunk.getPos().getMinBlockZ()>35+margin)return;
var p=plan(source);
for(var c:p.cells.values()) {
var at=new BlockPos(c.x,c.y,c.z);if(!chunk.getPos().contains(at))continue;
for(int y=c.y-2;y<=c.ceiling;y++) {
var pos=new BlockPos(c.x,y,c.z);
if(y>c.y){set(chunk,pos,Blocks.AIR);continue;}
// Sculpt only this floor's local foundation, retaining minerals elsewhere.
Block stone=y<c.y?Blocks.TUFF:Math.floorMod(c.x*11+c.z*7,19)<3?Blocks.MOSS_BLOCK:Blocks.ANDESITE;
if(Math.abs(c.x)<=2&&Math.abs(c.z)<=2)stone=Blocks.POLISHED_TUFF;
set(chunk,pos,stone);
}
// Vertical seams colour existing stone; there is no box-shaped lining.
for(int[] d:new int[][]{{1,0},{-1,0},{0,1},{0,-1}}) {
int wx=c.x+d[0],wz=c.z+d[1];if(p.cells.containsKey(key(wx,wz)))continue;
for(int y=c.y+1;y<c.ceiling;y++) {
var wall=new BlockPos(wx,y,wz);if(!chunk.getPos().contains(wall))continue;
if(!AdventureCaves180.natural(chunk.getBlockState(wall)))continue;
if(Math.floorMod(wx*3+wz*5+y/4,13)<2)set(chunk,wall,Blocks.CALCITE);
else if(y==c.y+1&&Math.floorMod(wx+wz,4)==0)set(chunk,wall,Blocks.MOSS_BLOCK);
else if(y==c.y+3&&Math.floorMod(wx+wz,7)==0)set(chunk,wall,Blocks.CHISELED_TUFF);
}
}
}
int y=p.rooms.getFirst().y;
// Horizontal 5 x 5 rim, dry 3 x 3 recess exactly one block below the walking plane.
for(int x=-2;x<=2;x++)for(int z=-2;z<=2;z++) {
boolean rim=Math.abs(x)==2||Math.abs(z)==2;
putIfOwned(chunk,x,y,z,rim?Blocks.OBSIDIAN:Blocks.AIR);
putIfOwned(chunk,x,y-1,z,Blocks.POLISHED_DEEPSLATE);
putIfOwned(chunk,x,y+1,z,Blocks.AIR);
putIfOwned(chunk,x,y+2,z,Blocks.AIR);
}
for(int[] b:new int[][]{{-6,-4},{6,-4},{-5,5},{5,5}}) {
var cell=p.cells.get(key(b[0],b[1]));
putIfOwned(chunk,b[0],cell.y-1,b[1],Blocks.SEA_LANTERN);
putIfOwned(chunk,b[0],cell.y,b[1],Blocks.COPPER_GRATE.waxed().oxidized());
}
}
private static void putIfOwned(ChunkAccess chunk,int x,int y,int z,Block b) {
var p=new BlockPos(x,y,z);if(chunk.getPos().contains(p))set(chunk,p,b);
}
static Map<String,Object> check(ServerLevel level) {
var source=(AdventureBiomes180)((NoiseBasedChunkGenerator)level.getChunkSource().getGenerator()).getBiomeSource();var p=plan(source);
var chunks=new HashSet<net.minecraft.world.level.ChunkPos>();
for(var c:p.cells.values())chunks.add(new net.minecraft.world.level.ChunkPos(c.x>>4,c.z>>4));
for(var cp:chunks)level.getChunk(cp.x(),cp.z());
int cy=p.rooms.getFirst().y;
for(int x=-2;x<=2;x++)for(int z=-2;z<=2;z++) {
boolean rim=Math.abs(x)==2||Math.abs(z)==2;var at=new BlockPos(x,cy,z);
if(!level.getBlockState(at).is(rim?Blocks.OBSIDIAN:Blocks.AIR)
||!level.getBlockState(at.below()).is(Blocks.POLISHED_DEEPSLATE)
||!level.getBlockState(at.above()).isAir()||!level.getBlockState(at.above(2)).isAir())
throw new IllegalStateException("Incorrect horizontal portal at "+at);
}
// Follow the actual saved surfaces across chunk boundaries, not just the planned centreline.
var floors=new HashMap<Long,Integer>();
for(var c:p.cells.values()) {
if(Math.abs(c.x)<=1&&Math.abs(c.z)<=1)continue;
int actual=Integer.MIN_VALUE;
for(int y=c.y+2;y>=c.y-2;y--) {
var at=new BlockPos(c.x,y,c.z);
if(!level.getBlockState(at).getCollisionShape(level,at).isEmpty()&&level.getBlockState(at.above()).isAir()&&level.getBlockState(at.above(2)).isAir()){actual=y;break;}
}
if(actual!=Integer.MIN_VALUE)floors.put(key(c.x,c.z),actual);
}
long start=key(2,0);if(!floors.containsKey(start))throw new IllegalStateException("No gallery approach");
var visited=new HashSet<Long>();var queue=new ArrayDeque<Long>();queue.add(start);
while(!queue.isEmpty()) {
long k=queue.removeFirst();if(!visited.add(k))continue;int x=(int)(k>>32),z=(int)k;
for(int[] d:new int[][]{{1,0},{-1,0},{0,1},{0,-1}}) {
long next=key(x+d[0],z+d[1]);var y=floors.get(next);
if(y!=null&&Math.abs(y-floors.get(k))<=1&&!visited.contains(next))queue.add(next);
}
}
for(var r:p.rooms)if(!visited.contains(key(r.x+2,r.z)))throw new IllegalStateException("Disconnected gallery room: "+r);
if(visited.size()<floors.size()*.9)throw new IllegalStateException("Too many isolated gallery surfaces: "+visited.size()+"/"+floors.size());
return Map.of("horizontalPortal",new BlockPos(0,cy,0).toShortString(),"rooms",p.rooms,"walkableCells",visited.size(),"plannedColumns",p.cells.size());
}
}
@@ -0,0 +1,79 @@
package fr.koka.sanctuarytest;
import java.util.*;
import net.minecraft.core.*;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.Identifier;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.chunk.*;
import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator;
/** Three seed-selected summit chunks, each owning exactly one successful native cherry tree. */
public final class Cherry180 {
private static final Map<AdventureBiomes180,List<BlockPos>> PLANS=Collections.synchronizedMap(new WeakHashMap<>());
private static final Map<AdventureBiomes180,Map<Long,BlockPos>> PLACED=Collections.synchronizedMap(new WeakHashMap<>());
private Cherry180(){}
public static List<BlockPos> sites(AdventureBiomes180 source){synchronized(PLANS){return PLANS.computeIfAbsent(source,s->{
if(s.field().coherent())return coherentSites(s);
if(s.field().eroded())return s.field().erosion().crowns().stream()
.map(c->new BlockPos(c.x(),s.field().erosion().surface(c.x(),c.z()),c.z())).toList();
var candidates=new ArrayList<BlockPos>();int cx=s.field().peakX()>>4,cz=s.field().peakZ()>>4;
for(int dx=-4;dx<=4;dx++)for(int dz=-4;dz<=4;dz++){
int x=(cx+dx)*16+8,z=(cz+dz)*16+8,y=s.terrain(x,z).top();if(y>=270)candidates.add(new BlockPos(x,y,z));
}
candidates.sort(Comparator.<BlockPos>comparingInt(BlockPos::getY).reversed());var chosen=new ArrayList<BlockPos>();
for(var p:candidates)if(chosen.stream().allMatch(q->q.distSqr(p)>24*24)){chosen.add(p);if(chosen.size()==3)break;}
if(chosen.size()!=3)throw new IllegalStateException("No three spacious summit sites");return List.copyOf(chosen);
});}}
private static List<BlockPos> coherentSites(AdventureBiomes180 s){
var chosen=new ArrayList<BlockPos>();var water=Water189.plan(s).columns();
for(var crown:s.field().erosion().crowns()){
var candidates=new ArrayList<BlockPos>();
for(int dx=-40;dx<=40;dx+=2)for(int dz=-40;dz<=40;dz+=2){
int x=crown.x()+dx,z=crown.z()+dz,y=s.field().erosion().surface(x,z);
if(y<250||s.field().erosion().slope(x,z)>.75||Math.abs(y-crown.height())>14)continue;
var p=new BlockPos(x,y,z);
if(chosen.stream().anyMatch(q->(q.getX()>>4)==(x>>4)&&(q.getZ()>>4)==(z>>4)||q.distSqr(p)<24*24))continue;
if(Anchors189.treeBlocked(s,p.above()))continue;
boolean clear=true;
for(int ox=-5;ox<=5&&clear;ox++)for(int oz=-5;oz<=5&&clear;oz++){
int top=water.getOrDefault(((long)(x+ox)<<32)^((z+oz)&0xffffffffL),-1);
if(top>=0&&y>=top-30&&y<=top+28)clear=false;
}
for(int ox=-4;ox<=4&&clear;ox+=4)for(int oz=-4;oz<=4&&clear;oz+=4)
for(int h=3;h<=11;h+=2)if(s.field().sampleValue(net.minecraft.world.level.levelgen.densityfunction.SamplerContext.EMPTY_UNCACHED,x+ox,y+h,z+oz)>0)clear=false;
if(clear)candidates.add(p);
}
candidates.sort(Comparator.comparingDouble(p->p.distSqr(new BlockPos(crown.x(),(int)crown.height(),crown.z()))));
if(candidates.isEmpty())throw new IllegalStateException("No dry, open summit tree site near "+crown);
chosen.add(candidates.getFirst());
}
return List.copyOf(chosen);
}
public static List<BlockPos> trees(AdventureBiomes180 source){synchronized(PLACED){return List.copyOf(PLACED.getOrDefault(source,Map.of()).values());}}
public static void place(WorldGenLevel level,ChunkGenerator generator,ChunkAccess c){
if(!WorldgenLab.ecology180(generator))return;var source=(AdventureBiomes180)((NoiseBasedChunkGenerator)generator).getBiomeSource();
if(sites(source).stream().noneMatch(c.getPos()::contains))return;
var random=RandomSource.create(level.getSeed()^c.getPos().pack()^0x180C4EL);
var feature=level.registryAccess().lookupOrThrow(Registries.FEATURE).getValue(Identifier.withDefaultNamespace("cherry_bees_005"));
var positions=new ArrayList<BlockPos>();
if(source.field().eroded()){
var center=sites(source).stream().filter(c.getPos()::contains).findFirst().orElseThrow();
for(int dx=0;dx<16;dx++)for(int dz=0;dz<16;dz++)
positions.add(new BlockPos(c.getPos().getMinBlockX()+dx,center.getY(),c.getPos().getMinBlockZ()+dz));
positions.sort(Comparator.comparingDouble(p->p.distSqr(center)));
}else for(int attempt=0;attempt<49;attempt++)
positions.add(new BlockPos(c.getPos().getMinBlockX()+5+attempt%7,0,c.getPos().getMinBlockZ()+5+attempt/7));
for(var candidate:positions){
int x=candidate.getX(),z=candidate.getZ();
if(source.field().eroded() && source.field().erosion().slope(x,z)>.75)continue;
for(int y=315;y>=(source.field().coherent()?250:270);y--){
var p=new BlockPos(x,y,z);if(!AdventureCaves180.natural(c.getBlockState(p))||!c.getBlockState(p.above()).isAir())continue;
c.setBlockState(p,Blocks.GRASS_BLOCK.defaultBlockState());
if(feature.place(level,generator,random,p.above())){synchronized(PLACED){PLACED.computeIfAbsent(source,s->new HashMap<>()).put(c.getPos().pack(),p.above());}return;}break;
}
}
throw new IllegalStateException("Unable to grow the reserved summit cherry in "+c.getPos());
}
}
@@ -0,0 +1,56 @@
package fr.koka.sanctuarytest;
import java.util.*;
import net.minecraft.core.*;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.levelgen.densityfunction.SamplerContext;
/** Shared spatial checks for independent decorators; diagnostics run only in the laboratory. */
final class Continuity193 {
static boolean touches(Set<BlockPos> a,Set<BlockPos> b){
if(a.size()>b.size())return touches(b,a);
for(var p:a){if(b.contains(p))return true;for(var d:Direction.values())if(b.contains(p.relative(d)))return true;}
return false;
}
static boolean covered(ReliefEcology177.Field f,int x,int y,int z){
int roof=0;
for(int[] d:new int[][]{{0,0},{4,0},{-4,0},{0,4},{0,-4}}){
for(int dy=4;dy<=40;dy+=4)if(f.sampleValue(SamplerContext.EMPTY_UNCACHED,x+d[0],y+dy,z+d[1])>0){roof++;break;}
}
return roof>=3;
}
record Water(String kind,BlockPos seed,int surface,Set<BlockPos> cells){}
static Map<String,Object> check(ServerLevel level,AdventureBiomes180 s){
var pools=new ArrayList<Water>();
for(var p:NaturalWater184.plan(s).pools())pools.add(new Water("cavity",p.seed(),p.surface(),p.cells()));
for(var p:SurfaceWater187.plan(s).pools())pools.add(new Water("surface",p.seed(),p.surface(),p.cells()));
int pairs=0,cells=0;var details=new ArrayList<Map<String,Object>>();
for(int i=0;i<pools.size();i++){
var p=pools.get(i);
for(int j=i+1;j<pools.size();j++){pairs++;if(touches(p.cells,pools.get(j).cells))throw new IllegalStateException("Independent water bodies touch: "+p.seed+" / "+pools.get(j).seed);}
var chunks=new HashSet<net.minecraft.world.level.ChunkPos>();
for(var q:p.cells)chunks.add(new net.minecraft.world.level.ChunkPos(q.getX()>>4,q.getZ()>>4));
for(var cp:chunks)level.getChunk(cp.x(),cp.z());
int empty=0;var missing=new TreeMap<String,Integer>();
for(var q:p.cells){cells++;if(level.getFluidState(q).isEmpty()){empty++;missing.merge(level.getBlockState(q).toString(),1,Integer::sum);}}
if(empty!=0)throw new IllegalStateException("Planned lake lost "+empty+" water cells at "+p.seed+" "+missing);
details.add(Map.of("kind",p.kind,"seed",p.seed.toShortString(),"surface",p.surface,"cells",p.cells.size()));
}
int naturalFloors=0,aerial=0;var anchors=Anchors189.plan(s,s.seed());
for(var site:anchors.sites())if(site.kind()!=Anchors189.Kind.CAVE){
if(site.kind()==Anchors189.Kind.SKY)aerial++;
var centre=site.floor();
for(var chunk:anchors.chunks().values())for(var e:chunk.entrySet()){
var p=e.getKey();if(Math.abs(p.getX()-centre.getX())>10||Math.abs(p.getZ()-centre.getZ())>10||Math.abs(p.getY()-centre.getY())>16)continue;
if(e.getValue().is(Blocks.MOSS_BLOCK))throw new IllegalStateException("Moss checkerboard in exposed anchor "+p);
if(e.getValue().is(Blocks.GRASS_BLOCK)){
var f=s.field();if(f.sampleValue(SamplerContext.EMPTY_UNCACHED,p.getX(),p.getY(),p.getZ())<=0||f.sampleValue(SamplerContext.EMPTY_UNCACHED,p.getX(),p.getY()+1,p.getZ())>0)throw new IllegalStateException("Anchor floor detached from density "+p);
naturalFloors++;
}
}
}
if(aerial==0||naturalFloors==0)throw new IllegalStateException("Missing supported aerial refuge");
return Map.of("waterBodies",details,"disjointPairsChecked",pairs,"waterCellsChecked",cells,"overlappingWaterBodies",0,"exposedAnchorMoss",0,"anchorFloorsOnNaturalSurface",naturalFloors,"aerialAnchors",aerial);
}
}
@@ -0,0 +1,110 @@
package fr.koka.sanctuarytest;
import java.util.*;
import net.minecraft.core.*;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.tags.BlockTags;
import net.minecraft.world.level.block.*;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.*;
import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator;
import net.minecraft.world.level.levelgen.densityfunction.SamplerContext;
/** Local repairs for new beta.197 worlds. No mutation of historical profiles. */
public final class Details197 {
private Details197() {}
private static boolean rock(AdventureBiomes180 s,BlockPos p) {
return s.field().sampleValue(SamplerContext.EMPTY_UNCACHED,p.getX(),p.getY(),p.getZ())>0;
}
static boolean openCliff(AdventureBiomes180 s,BlockPos lip,Direction outward) {
// A void below a bank can be a cave, with soil above it. Require open sky
// and a clear outward face for the first eight blocks of the curtain.
for(int y=lip.getY()+3;y<=319;y++)
if(rock(s,new BlockPos(lip.getX(),y,lip.getZ())))return false;
for(int down=1;down<=8;down++)for(int forward=0;forward<=2;forward++)
if(rock(s,lip.below(down).relative(outward,forward)))return false;
return true;
}
public static BlockPos islandSpawn(ServerLevel level,int x,int z) {
for(int y=316;y>=198;y--) {
var floor=new BlockPos(x,y,z);var state=level.getBlockState(floor);
if(state.is(BlockTags.LEAVES)||state.is(BlockTags.LOGS)||!state.isSolidRender())continue;
if(level.getBlockState(floor.above()).isAir()&&level.getBlockState(floor.above(2)).isAir())return floor.above();
}
return null;
}
private static int roofHeight(int x) {return 8-Math.abs(x);}
static boolean hutFits(AdventureBiomes180 s,BlockPos p) {
for(int x=-4;x<=4;x++)for(int z=-5;z<=5;z++) {
int roof=roofHeight(x);
for(int y=2;y<=roof+2;y++)if(rock(s,p.offset(x,y,z)))return false;
}
for(int x:new int[]{-3,3})for(int z:new int[]{-4,4}) {
boolean support=false;
for(int depth=0;depth<=8;depth++)if(rock(s,p.offset(x,-depth,z))){support=true;break;}
if(!support)return false;
}
return true;
}
private static BlockState roof(int x) {
if(x==0)return Blocks.DARK_OAK_SLAB.defaultBlockState();
return Blocks.DARK_OAK_STAIRS.defaultBlockState().setValue(StairBlock.FACING,x<0?Direction.EAST:Direction.WEST);
}
private static void put(ChunkAccess c,BlockPos p,BlockState state){c.setBlockState(p,state);}
static void hut(ChunkAccess c,BlockPos origin) {
var p=origin.above();
for(int x=-4;x<=4;x++)for(int z=-5;z<=5;z++) {
var floor=p.offset(x,0,z);if(!c.getPos().contains(floor))continue;
int h=roofHeight(x);boolean corner=Math.abs(x)==3&&Math.abs(z)==4;
for(int y=1;y<h;y++)put(c,floor.above(y),Blocks.AIR.defaultBlockState());
if(Math.abs(x)<=3&&Math.abs(z)<=4) {
put(c,floor,Blocks.SPRUCE_PLANKS.defaultBlockState());
if(Math.abs(x)==3||Math.abs(z)==4)for(int y=1;y<h;y++) {
if(z==-4&&x==0&&y<=2)continue;
put(c,floor.above(y),(corner?Blocks.SPRUCE_LOG:y==2&&z==0?Blocks.GLASS:Blocks.SPRUCE_PLANKS).defaultBlockState());
}
}
if(corner)for(int depth=1;depth<=9;depth++) {
var at=floor.below(depth);if(AdventureCaves180.natural(c.getBlockState(at)))break;
put(c,at,Blocks.SPRUCE_LOG.defaultBlockState());
}
put(c,floor.above(h),roof(x));
if(z==-5&&Math.abs(x)<=2)put(c,floor,Blocks.SPRUCE_PLANKS.defaultBlockState());
if(x==2&&z==2)put(c,floor.above(),Blocks.CAULDRON.defaultBlockState());
if(x==-2&&z==2)put(c,floor.above(),Blocks.CRAFTING_TABLE.defaultBlockState());
if(x==0&&z==3)put(c,floor.above(),Blocks.LANTERN.defaultBlockState());
}
}
public static boolean protectsHut(ChunkGenerator g,BlockPos p) {
if(!SharedIsland196.current(g)||p.getY()<80||p.getY()>240||Math.abs(p.getX())>230||Math.abs(p.getZ())>230)return false;
var s=(AdventureBiomes180)((NoiseBasedChunkGenerator)g).getBiomeSource();var hut=Dungeon180.plan(s).hut();
return hut!=null&&Math.abs(p.getX()-hut.getX())<=4&&Math.abs(p.getZ()-hut.getZ())<=5
&&p.getY()>=hut.getY()-8&&p.getY()<=hut.getY()+10;
}
static Map<String,Object> check(ServerLevel level) {
var s=(AdventureBiomes180)level.getChunkSource().getGenerator().getBiomeSource();
var result=new LinkedHashMap<String,Object>();var hut=Dungeon180.plan(s).hut();
if(hut!=null) {
int roofBlocks=0;
for(int x=-4;x<=4;x++)for(int z=-5;z<=5;z++) {
var p=hut.offset(x,1+roofHeight(x),z);level.getChunk(p.getX()>>4,p.getZ()>>4);
if(!level.getBlockState(p).equals(roof(x)))throw new IllegalStateException("Broken hut roof at "+p+": "+level.getBlockState(p));
roofBlocks++;
}
for(int y=2;y<=3;y++)if(!level.getBlockState(hut.offset(0,y,-4)).isAir())throw new IllegalStateException("Blocked hut entrance");
result.put("hut",hut.toShortString());result.put("intactRoofBlocks",roofBlocks);
}else result.put("hut","no supported clear marsh site");
int curtains=0;
for(var outlet:Water189.plan(s).outlets()) {
var d=Direction.getApproximateNearest(outlet.lip().getX()-outlet.start().getX(),0,outlet.lip().getZ()-outlet.start().getZ());
if(!openCliff(s,outlet.lip(),d))throw new IllegalStateException("Buried waterfall mouth "+outlet);
for(int h=1;h<=8;h++) {
var p=outlet.lip().below(h);
if(level.getFluidState(p).isEmpty())throw new IllegalStateException("Obstructed waterfall "+p);
if(level.getBlockState(p.relative(d)).isSolidRender())throw new IllegalStateException("Waterfall inside cliff "+p);
}
curtains++;
}
result.put("exteriorWaterfallLanes",curtains);return result;
}
}
@@ -0,0 +1,36 @@
package fr.koka.sanctuarytest;
import java.util.*;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.*;
import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator;
/** Entirely opt-in, chunk-owned discovery revision; no save migration or runtime rebuild. */
public final class Discovery183 {
private Discovery183() {}
private static final Map<Station183.Layout,Map<BlockPos,BlockState>> STATIONS=new java.util.concurrent.ConcurrentHashMap<>();
public static void place(WorldGenLevel level,ChunkGenerator generator,ChunkAccess chunk){
if(!WorldgenLab.discovery183(generator))return;
var source=(AdventureBiomes180)((NoiseBasedChunkGenerator)generator).getBiomeSource();
Cavern183.place(level,generator,chunk);
Sulfur183.place(source,chunk);
Water183.place(source,chunk);
var p=chunk.getPos();
if(p.x()>=-1&&p.x()<=0&&p.z()>=-1&&p.z()<=0)
Rosette183.plan(level.getSeed()).forEach((at,b)->{if(p.contains(at))chunk.setBlockState(at,b);});
if(p.x()>=-3&&p.x()<=2&&p.z()>=-2&&p.z()<=1)
STATIONS.computeIfAbsent(Station183.layout(generator),Station183::plan).forEach((at,b)->{if(p.contains(at))chunk.setBlockState(at,b);});
StationAtlas183.populate(level,generator,chunk);
}
static Map<String,Object> check(ServerLevel level){
var source=(AdventureBiomes180)((NoiseBasedChunkGenerator)level.getChunkSource().getGenerator()).getBiomeSource();
var result=new LinkedHashMap<String,Object>();
result.put("gallery",Cavern183.check(level));result.put("rosette",Rosette183.check(level));
result.put("sulfur",Sulfur183.check(level,source));result.put("upperWater",Water183.check(level,source));
result.put("gemDistricts",Gems183.check(level));result.put("ISS",Station183.check(level));result.put("atlas",StationAtlas183.check(level));
return result;
}
}
@@ -0,0 +1,176 @@
package fr.koka.sanctuarytest;
import java.util.*;
import java.nio.charset.StandardCharsets;
import net.minecraft.core.*;
import net.minecraft.core.registries.Registries;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.*;
import net.minecraft.util.RandomSource;
import net.minecraft.world.entity.*;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.block.*;
import net.minecraft.world.level.block.entity.SpawnerBlockEntity;
import net.minecraft.world.level.block.state.properties.RailShape;
import net.minecraft.world.level.chunk.*;
import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator;
import net.minecraft.world.level.levelgen.densityfunction.SamplerContext;
import net.minecraft.world.level.storage.loot.LootTable;
/** Connected, seed-owned mine dungeon; native saved spawners and deferred minecart loot. */
public final class Dungeon180 {
public record Room(int id,int x,int y,int z,int rx,int rz,int kind){} // 0 hall, 1 combat, 2 cache
public record Cell(int x,int y,int z,int dx,int dz,int step){}
public record Plan(List<Room> rooms,Map<Long,Cell> paths,BlockPos hut,int exitX,int exitZ){}
private static final Map<AdventureBiomes180,Plan> PLANS=Collections.synchronizedMap(new WeakHashMap<>());
public static final ResourceKey<LootTable> LOOT=ResourceKey.create(Registries.LOOT_TABLE,Identifier.fromNamespaceAndPath("sanctuary_test","chests/mine_cache_180"));
private Dungeon180(){}
private static final Map<AdventureBiomes180,Map<Long,Integer>> FLOORS=Collections.synchronizedMap(new WeakHashMap<>());
/** Native dripstone pools can replace a floor after this chunk's corridors were placed. */
public static boolean protectsFloor(ChunkGenerator generator,BlockPos pos){
if(SharedIsland196.size(generator)==null||pos.getY()<80||pos.getY()>230||Math.abs(pos.getZ())>180)return false;
var source=(AdventureBiomes180)((NoiseBasedChunkGenerator)generator).getBiomeSource();
Map<Long,Integer> floors;
synchronized(FLOORS){floors=FLOORS.computeIfAbsent(source,s->{
var p=plan(s);var m=new HashMap<Long,Integer>();
for(var cell:p.paths().values())for(int offset=-1;offset<=1;offset++){
int x=cell.x()+(cell.dz()!=0?offset:0),z=cell.z()+(cell.dx()!=0?offset:0);
m.putIfAbsent(key(x,z),cell.y());
}
// Centre lines and rooms own their floor even where the widened corridors overlap.
for(var cell:p.paths().values())m.put(key(cell.x(),cell.z()),cell.y());
for(var room:p.rooms())for(int x=room.x()-room.rx()-1;x<=room.x()+room.rx()+1;x++)
for(int z=room.z()-room.rz()-1;z<=room.z()+room.rz()+1;z++){
var owner=roomAt(p,x,z);if(owner!=null)m.put(key(x,z),owner.y());
}
return Map.copyOf(m);
});}
var y=floors.get(key(pos.getX(),pos.getZ()));return y!=null&&y==pos.getY();
}
static long key(int x,int z){return ((long)x<<32)^(z&0xffffffffL);}
private static boolean rock(AdventureBiomes180 s,int x,int y,int z){return s.field().sampleValue(SamplerContext.EMPTY_UNCACHED,x,y,z)>0;}
public static Plan plan(AdventureBiomes180 source){synchronized(PLANS){return PLANS.computeIfAbsent(source,Dungeon180::create);}}
private static Plan create(AdventureBiomes180 s){
BlockPos origin=null;int best=-1;
for(int x=96;x<=192;x+=24)for(int z=-72;z<=72;z+=24)for(int y=Math.min(208,s.terrain(x,z).top()-20);y>=112;y-=2){
if(!rock(s,x,y,z)||rock(s,x,y+3,z)||rock(s,x,y+9,z))continue;
int score=0;for(int a=-6;a<=6;a+=3)for(int b=-6;b<=6;b+=3)if(!rock(s,x+a,y+4,z+b))score++;
if(score>best){best=score;origin=new BlockPos(x,y,z);}break;
}
if(origin==null)throw new IllegalStateException("No covered dungeon entrance chamber");
int[][] layout={{0,0,0,0},{-28,8,0,0},{-52,-16,-2,1},{-76,-12,-2,2},{-24,-36,3,1},{4,-52,3,2},{32,-28,0,0},{44,4,-2,1},{16,36,0,1},{-16,52,-3,2},{-44,28,0,0},{-72,52,2,1},{-96,20,2,2}};
var rooms=new ArrayList<Room>();
int mirror=((s.field().peakZ()&8)==0)?1:-1;
for(int i=0;i<layout.length;i++){var a=layout[i];rooms.add(new Room(i,origin.getX()+a[0],origin.getY()+a[2],origin.getZ()+mirror*a[1],a[3]==0?8:6,a[3]==0?7:5,a[3]));}
int[][] edges={{0,1},{1,2},{2,3},{1,4},{4,5},{5,6},{0,6},{6,7},{0,8},{8,9},{9,10},{10,1},{10,11},{11,12},{12,3},{8,7}};
var paths=new LinkedHashMap<Long,Cell>();
for(int i=0;i<edges.length;i++){var a=rooms.get(edges[i][0]);var b=rooms.get(edges[i][1]);link(paths,a.x(),a.y(),a.z(),b.x(),b.y(),b.z(),i%2==0);}
int exitX=origin.getX()+36,exitZ=origin.getZ()+20*mirror;
while(exitX<(int)(360*s.field().base().spread())&&s.terrain(exitX,exitZ).top()>origin.getY()+4)exitX+=4;
// Daylight access bends twice instead of forming one unbroken tunnel.
link(paths,origin.getX(),origin.getY(),origin.getZ(),origin.getX()+28,origin.getY(),exitZ,false);
link(paths,origin.getX()+28,origin.getY(),exitZ,exitX+3,origin.getY(),exitZ,true);
BlockPos hut=null;best=-1;
for(int x=-224;x<=224;x+=16)for(int z=-224;z<=224;z+=16)for(int y=Math.min(216,s.terrain(x,z).top()-20);y>=96;y-=2){
if(!AdventureCaves180.biome(s,x,y,z).equals("adventure_marsh")||!rock(s,x,y,z)||rock(s,x,y+1,z)||rock(s,x,y+8,z))continue;
int n=0;for(int a=-4;a<=4;a+=2)for(int b=-4;b<=4;b+=2)if(!rock(s,x+a,y+5,z+b)&&rock(s,x+a,y-3,z+b))n++;
if(n>best && (!s.polish197() || Details197.hutFits(s,new BlockPos(x,y,z)))){best=n;hut=new BlockPos(x,y,z);}break;
}
return new Plan(List.copyOf(rooms),Map.copyOf(paths),hut,exitX+3,exitZ);
}
private static void link(Map<Long,Cell> map,int x,int y,int z,int tx,int ty,int tz,boolean xFirst){
int length=Math.abs(tx-x)+Math.abs(tz-z),startY=y;
for(int step=0;step<=length;step++){
int dx=0,dz=0;
if(xFirst?x!=tx:z==tz){dx=Integer.signum(tx-x);}else dz=Integer.signum(tz-z);
if(dx==0&&dz==0){if(x!=tx)dx=Integer.signum(tx-x);else dz=Integer.signum(tz-z);}
int floor=startY+(int)Math.round((ty-startY)*step/(double)Math.max(1,length));
map.putIfAbsent(key(x,z),new Cell(x,floor,z,dx,dz,step));x+=dx;z+=dz;
}
}
public static boolean near(AdventureBiomes180 s,int x,int z,int margin){
var p=plan(s);if(p.hut()!=null&&Math.hypot(x-p.hut().getX(),z-p.hut().getZ())<margin+9)return true;
for(var r:p.rooms())if(Math.hypot(x-r.x(),z-r.z())<margin+10)return true;
for(var c:p.paths().values())if(Math.abs(c.x()-x)<margin&&Math.abs(c.z()-z)<margin)return true;
return false;
}
public static boolean protectedAt(AdventureBiomes180 s,int x,int y,int z){
var p=plan(s);
if(p.hut()!=null&&Math.abs(x-p.hut().getX())<=7&&Math.abs(z-p.hut().getZ())<=7&&Math.abs(y-p.hut().getY())<10)return true;
for(var r:p.rooms())if(Math.abs(y-r.y())<13&&Math.abs(x-r.x())<=r.rx()+3&&Math.abs(z-r.z())<=r.rz()+3)return true;
for(int dx=-3;dx<=3;dx++)for(int dz=-3;dz<=3;dz++){var c=p.paths().get(key(x+dx,z+dz));if(c!=null&&y>=c.y()-2&&y<=c.y()+6)return true;}
return false;
}
static Room roomAt(Plan p,int x,int z){
for(var r:p.rooms())if((x-r.x())*(double)(x-r.x())/(r.rx()*r.rx())+(z-r.z())*(double)(z-r.z())/(r.rz()*r.rz())<=1.08)return r;return null;
}
private static void set(ChunkAccess c,int x,int y,int z,Block b){c.setBlockState(new BlockPos(x,y,z),b.defaultBlockState());}
public static void place(WorldGenLevel level,ChunkGenerator generator,ChunkAccess c){
if(!WorldgenLab.ecology180(generator))return;var source=(AdventureBiomes180)((NoiseBasedChunkGenerator)generator).getBiomeSource();var p=plan(source);
for(int x=c.getPos().getMinBlockX();x<=c.getPos().getMaxBlockX();x++)for(int z=c.getPos().getMinBlockZ();z<=c.getPos().getMaxBlockZ();z++){
var r=roomAt(p,x,z);
if(r!=null){
double dist=Math.pow((x-r.x())/(double)r.rx(),2)+Math.pow((z-r.z())/(double)r.rz(),2);
int h=4+(int)(4*Math.sqrt(Math.max(0,1-dist)));
boolean ground=AdventureCaves180.natural(c.getBlockState(new BlockPos(x,r.y(),z)));
set(c,x,r.y(),z,ground?(source.polish197()
? (EcologyBiomes176.noise(source.seed()^0x197D17L,x/9.0,z/9.0)>.32?Blocks.COARSE_DIRT:Blocks.STONE)
: (Math.floorMod(x*31+z*17,7)==0?Blocks.COARSE_DIRT:Blocks.STONE)):Blocks.DARK_OAK_PLANKS);
for(int dy=1;dy<=h;dy++)set(c,x,r.y()+dy,z,Blocks.AIR);
if(r.kind()==1&&!c.getBlockState(new BlockPos(x,r.y()+h+1,z)).isSolidRender())set(c,x,r.y()+h+1,z,Blocks.MOSSY_COBBLESTONE);
if(r.kind()==2&&x==r.x()+3&&z==r.z()){
set(c,x,r.y()+1,z,Blocks.BARREL);set(c,x,r.y()+2,z,Blocks.LANTERN);
}
continue;
}
Cell cell=p.paths().get(key(x,z));int offset=0;
for(int a=-2;a<=2&&cell==null;a++)for(int b=-2;b<=2;b++){
var q=p.paths().get(key(x+a,z+b));if(q!=null&&((q.dx()!=0&&a==0)||(q.dz()!=0&&b==0))){cell=q;offset=Math.abs(a)+Math.abs(b);break;}
}
if(cell==null)continue;
int y=cell.y();boolean border=offset==2,frame=cell.step()%7==0;
if(!border){
if(!AdventureCaves180.natural(c.getBlockState(new BlockPos(x,y,z))))set(c,x,y,z,Blocks.DARK_OAK_PLANKS);
for(int dy=1;dy<=4;dy++)set(c,x,y+dy,z,Blocks.AIR);
if(frame)set(c,x,y+4,z,Blocks.DARK_OAK_LOG);
if(offset==0&&cell.step()%28==0)c.setBlockState(new BlockPos(x,y+3,z),Blocks.LANTERN.defaultBlockState().setValue(LanternBlock.HANGING,true));
if(offset==0&&cell.step()%7!=0&&cell.dx()!=0)c.setBlockState(new BlockPos(x,y+1,z),Blocks.RAIL.defaultBlockState().setValue(RailBlock.SHAPE,RailShape.EAST_WEST));
}else if(frame){for(int dy=0;dy<=4;dy++)set(c,x,y+dy,z,Blocks.DARK_OAK_LOG);}
}
if(p.hut()!=null){if(source.polish197())Details197.hut(c,p.hut());else hut(c,p.hut());}
}
private static void hut(ChunkAccess c,BlockPos p){
for(int x=Math.max(c.getPos().getMinBlockX(),p.getX()-4);x<=Math.min(c.getPos().getMaxBlockX(),p.getX()+4);x++)for(int z=Math.max(c.getPos().getMinBlockZ(),p.getZ()-5);z<=Math.min(c.getPos().getMaxBlockZ(),p.getZ()+5);z++){
int u=x-p.getX(),v=z-p.getZ(),y=p.getY()+1;boolean corner=Math.abs(u)==3&&Math.abs(v)==4;
if(corner)for(int dy=-4;dy<=3;dy++)set(c,x,y+dy,z,Blocks.SPRUCE_LOG);
if(Math.abs(u)<=3&&Math.abs(v)<=4){set(c,x,y,z,Blocks.SPRUCE_PLANKS);for(int dy=1;dy<=4;dy++)set(c,x,y+dy,z,Blocks.AIR);
if((Math.abs(u)==3||Math.abs(v)==4)&&!(v==-4&&u==0))for(int dy=1;dy<=3;dy++)set(c,x,y+dy,z,corner?Blocks.SPRUCE_LOG:dy==2&&u!=0&&v==0?Blocks.GLASS_PANE:Blocks.SPRUCE_PLANKS);
}
set(c,x,y+4+(4-Math.abs(u))/2,z,Blocks.DARK_OAK_SLAB);
if(v==-5&&Math.abs(u)<=2)set(c,x,y,z,Blocks.SPRUCE_PLANKS);
if(u==2&&v==2)set(c,x,y+1,z,Blocks.CAULDRON);if(u==-2&&v==2)set(c,x,y+1,z,Blocks.CRAFTING_TABLE);if(u==0&&v==3)set(c,x,y+1,z,Blocks.LANTERN);
}
}
public static void populate(WorldGenLevel level,ChunkGenerator generator,ChunkAccess c){
if(!WorldgenLab.ecology180(generator))return;var source=(AdventureBiomes180)((NoiseBasedChunkGenerator)generator).getBiomeSource();var p=plan(source);
for(var r:p.rooms()){
var at=new BlockPos(r.x(),r.y()+1,r.z());if(!c.getPos().contains(at))continue;
var random=RandomSource.create(level.getSeed()^at.asLong()^0x1801007L);
if(r.kind()==1){
level.setBlock(at,Blocks.SPAWNER.defaultBlockState(),2);
if(!(level.getBlockEntity(at) instanceof SpawnerBlockEntity spawner))throw new IllegalStateException("Missing native spawner block entity");
spawner.setEntityId(switch(r.id()%3){case 0->EntityTypes.ZOMBIE;case 1->EntityTypes.SKELETON;default->EntityTypes.CAVE_SPIDER;},random);
}else if(r.kind()==2){
for(int dx=-1;dx<=1;dx++)if(c.getPos().contains(at.offset(dx,0,0)))c.setBlockState(at.offset(dx,0,0),Blocks.RAIL.defaultBlockState().setValue(RailBlock.SHAPE,RailShape.EAST_WEST));
var cart=EntityTypes.CHEST_MINECART.create(level.getLevel(),EntitySpawnReason.STRUCTURE);
if(cart==null)throw new IllegalStateException("Missing chest minecart");
cart.snapTo(r.x()+.5,r.y()+1.0625,r.z()+.5,90,0);cart.setLootTable((WorldgenLab.details188(generator)||WorldgenLab.finishedTerrain(generator))?ResourceKey.create(Registries.LOOT_TABLE,Identifier.fromNamespaceAndPath("sanctuary_test","chests/mine_cache_188")):LOOT,level.getSeed()^at.asLong());
cart.setCustomName(Component.translatable("entity.sanctuary_test.treasure_cart"));
cart.setUUID(UUID.nameUUIDFromBytes(("sanctuary_test:dungeon180:"+level.getSeed()+":"+r.id()).getBytes(StandardCharsets.UTF_8)));
level.addFreshEntity(cart);
}
}
if(p.hut()!=null&&c.getPos().contains(p.hut())){
var random=RandomSource.create(level.getSeed()^0x179A11L);AdventureCaves180.spawn(level,p.hut().above(2),EntityTypes.WITCH,random);AdventureCaves180.spawn(level,p.hut().offset(1,2,0),EntityTypes.CAT,random);
}
}
}
@@ -0,0 +1,136 @@
package fr.koka.sanctuarytest;
import com.mojang.authlib.GameProfile;
import com.mojang.brigadier.arguments.StringArgumentType;
import fr.koka.sanctuary.operator.OperatorService;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.network.Connection;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.PacketFlow;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ClientInformation;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.network.CommonListenerCookie;
import net.minecraft.world.level.GameType;
/** Optional development module only. No sockets, renderer or additional JVM per actor. */
final class DuoLab {
private static final Map<String, ServerPlayer> ACTORS = new LinkedHashMap<>();
private static java.io.BufferedWriter trace;
private static int ticks;
private static long traceStarted;
private DuoLab() { }
static void register() {
// An explicit JVM opt-in keeps the ordinary quick-test pack unchanged.
if (!Boolean.getBoolean("sanctuary.duo")) return;
CommandRegistrationCallback.EVENT.register((dispatcher, registries, environment) ->
dispatcher.register(Commands.literal("duo").requires(OperatorService::canCommand)
.then(Commands.literal("trace")
.then(Commands.literal("start").executes(c -> trace(c.getSource(), true)))
.then(Commands.literal("stop").executes(c -> trace(c.getSource(), false))))
.then(Commands.literal("spawn").then(Commands.argument("name", StringArgumentType.word())
.executes(c -> spawn(c.getSource(), StringArgumentType.getString(c, "name")))))
.then(Commands.literal("remove").then(Commands.argument("name", StringArgumentType.word())
.executes(c -> remove(c.getSource(), StringArgumentType.getString(c, "name")))))
.then(Commands.literal("list").executes(c -> {
c.getSource().sendSuccess(() -> Component.translatable("sanctuary_test.duo.list", String.join(", ", ACTORS.keySet())), false);
return ACTORS.size();
}))));
ServerTickEvents.END_SERVER_TICK.register(server -> {
if (trace != null && System.nanoTime() - traceStarted >= 300_000_000_000L) closeTrace();
if (trace == null || ++ticks % 40 != 0) return;
try {
for (var player : server.getPlayerList().getPlayers()) {
var row = new com.google.gson.JsonObject();
row.addProperty("time", java.time.Instant.now().toString());
row.addProperty("player", player.getGameProfile().name());
row.addProperty("actor", ACTORS.containsValue(player));
row.addProperty("dimension", player.level().dimension().identifier().toString());
row.addProperty("x", player.getX()); row.addProperty("y", player.getY()); row.addProperty("z", player.getZ());
row.addProperty("yaw", player.getYRot()); row.addProperty("pitch", player.getXRot());
trace.write(row.toString()); trace.newLine();
}
trace.flush();
} catch (java.io.IOException error) {
org.slf4j.LoggerFactory.getLogger("sanctuary-duo").error("Trace stopped: cannot write session", error);
closeTrace();
}
});
ServerLifecycleEvents.SERVER_STOPPED.register(server -> { ACTORS.clear(); closeTrace(); });
}
private static int trace(CommandSourceStack source, boolean start) {
closeTrace();
if (start) try {
var folder = java.nio.file.Path.of("duo-sessions");
java.nio.file.Files.createDirectories(folder);
var path = folder.resolve("positions-" + System.currentTimeMillis() + ".jsonl");
trace = java.nio.file.Files.newBufferedWriter(path, StandardCharsets.UTF_8, java.nio.file.StandardOpenOption.CREATE_NEW);
traceStarted = System.nanoTime(); ticks = 0;
} catch (java.io.IOException error) {
source.sendFailure(Component.translatable("sanctuary_test.duo.trace_failed")); return 0;
}
source.sendSuccess(() -> Component.translatable("sanctuary_test.duo.trace_" + (start ? "started" : "stopped")), false);
return 1;
}
private static void closeTrace() {
if (trace != null) try { trace.close(); } catch (java.io.IOException ignored) { }
trace = null;
}
private static int spawn(CommandSourceStack source, String name) {
if (!name.matches("[A-Za-z0-9_]{1,12}") || ACTORS.size() >= 4
|| source.getServer().getPlayerList().getPlayerByName("Lab_" + name) != null) {
source.sendFailure(Component.translatable("sanctuary_test.duo.invalid"));
return 0;
}
String fullName = "Lab_" + name;
var profile = new GameProfile(UUID.nameUUIDFromBytes(("SanctuaryDuo:" + fullName).getBytes(StandardCharsets.UTF_8)), fullName);
var player = new ServerPlayer(source.getServer(), source.getLevel(), profile, ClientInformation.createDefault());
source.getServer().getPlayerList().placeNewPlayer(new SinkConnection(), player, CommonListenerCookie.createInitial(profile, false));
var position = source.getPosition();
player.teleportTo(source.getLevel(), position.x + 2, position.y, position.z, java.util.Set.of(), 0, 0, false);
player.setGameMode(GameType.CREATIVE);
ACTORS.put(name, player);
source.sendSuccess(() -> Component.translatable("sanctuary_test.duo.spawned", fullName), false);
return 1;
}
private static int remove(CommandSourceStack source, String name) {
var player = ACTORS.remove(name);
if (player == null) {
source.sendFailure(Component.translatable("sanctuary_test.duo.missing", name));
return 0;
}
source.getServer().getPlayerList().remove(player);
source.sendSuccess(() -> Component.translatable("sanctuary_test.duo.removed", name), false);
return 1;
}
private static final class SinkConnection extends Connection {
private net.minecraft.network.PacketListener listener;
SinkConnection() { super(PacketFlow.SERVERBOUND); }
@Override public <T extends net.minecraft.network.PacketListener> void setupInboundProtocol(net.minecraft.network.ProtocolInfo<T> protocol, T listener) { this.listener = listener; }
@Override public net.minecraft.network.PacketListener getPacketListener() { return listener; }
@Override public void flushChannel() { }
@Override public void send(Packet<?> packet) { }
@Override public void send(Packet<?> packet, io.netty.channel.ChannelFutureListener listener) { }
@Override public void send(Packet<?> packet, io.netty.channel.ChannelFutureListener listener, boolean flush) { }
@Override public boolean isConnected() { return true; }
@Override public boolean isConnecting() { return false; }
@Override public boolean isMemoryConnection() { return true; }
@Override public SocketAddress getRemoteAddress() { return new InetSocketAddress("127.0.0.1", 0); }
}
}
@@ -0,0 +1,71 @@
package fr.koka.sanctuarytest;
import com.mojang.serialization.MapCodec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import java.util.*;
import java.util.stream.Stream;
import net.minecraft.core.Holder;
import net.minecraft.world.level.biome.*;
/** New lab-only ecology. All returned holders belong to this palette, including the cave variants. */
public final class EcologyBiomes176 extends BiomeSource {
public static final MapCodec<EcologyBiomes176> CODEC = RecordCodecBuilder.mapCodec(i -> i.group(
Biome.CODEC.listOf().fieldOf("biomes").forGetter(s -> s.biomes)
).apply(i, EcologyBiomes176::new));
private final List<Holder<Biome>> biomes;
private final Map<String,Holder<Biome>> named;
private record State(long seed, List<SkyFragments175.Reef> reefs, double cos, double sin) {}
private volatile State state;
public EcologyBiomes176(List<Holder<Biome>> biomes) {
this.biomes=List.copyOf(biomes);
var values=new HashMap<String,Holder<Biome>>();
for(var b:biomes) values.put(b.unwrapKey().orElseThrow().identifier().getPath(),b);
named=Map.copyOf(values);
for(String name:List.of("forest","birch","flowers","meadow","autumn","cherry","jungle",
"swamp","mangrove","autumn_scrub","cherry_scrub","lush","dripstone","void"))
if(!named.containsKey("ecology_"+name))throw new IllegalArgumentException("Missing ecology biome "+name);
}
public void bind(long seed,SkyFragments175.Field field) {
double angle=(mix(seed^0x176B10L)>>>11)*0x1.0p-53*Math.PI*2;
state=new State(seed,field.reefs(),Math.cos(angle),Math.sin(angle));
}
@Override protected MapCodec<EcologyBiomes176> codec(){return CODEC;}
@Override protected Stream<Holder<Biome>> collectPossibleBiomes(){return biomes.stream();}
@Override public BiomeResolver createResolver(Climate.Sampler ignored){
return (qx,qy,qz)->resolve(qx*4,qy*4,qz*4);
}
public Holder<Biome> resolve(int x,int y,int z) {
var s=state;
if(s==null)throw new IllegalStateException("Ecology palette must be bound before chunk generation");
if(y<0 || y>=512 || Math.hypot(x,z)>398)return get("void");
if(y>=320) {
int nearest=-1;double best=Double.POSITIVE_INFINITY;
for(int i=0;i<s.reefs().size();i++) {
var r=s.reefs().get(i);
double distance=Math.pow((x-r.x())/(double)(r.length()+20),2)
+Math.pow((z-r.z())/(double)(r.length()+20),2)+Math.pow((y-r.y())/35.0,2);
if(distance<best){best=distance;nearest=i;}
}
return get(switch(nearest){case 0->"jungle";case 1->"mangrove";case 2->"swamp";
case 3->"autumn_scrub";case 4->"cherry_scrub";default->"void";});
}
double humidity=noise(s.seed()^0xCAE176L,x/110.0,z/110.0);
if(y<174)return get(humidity>-.1?"lush":"dripstone");
double across=x*s.cos()+z*s.sin()+65*noise(s.seed(),x/170.0,z/170.0);
if(across<-105)return get("autumn");
if(across<-65)return get("birch");
if(across>105)return get("cherry");
if(across>65)return get("flowers");
return get(noise(s.seed()^0x6ADE176L,x/130.0,z/130.0)>.05?"meadow":"forest");
}
private Holder<Biome> get(String name){return named.get("ecology_"+name);}
public static long mix(long n){n=(n^(n>>>30))*0xbf58476d1ce4e5b9L;n=(n^(n>>>27))*0x94d049bb133111ebL;return n^(n>>>31);}
private static double at(long seed,int x,int z){return (mix(seed^x*0x9e3779b97f4a7c15L^z*0xc2b2ae3d27d4eb4fL)>>>11)*0x1.0p-52-1;}
public static double noise(long seed,double x,double z){
int ix=(int)Math.floor(x),iz=(int)Math.floor(z);double u=x-ix,v=z-iz;
u=u*u*(3-2*u);v=v*v*(3-2*v);
return lerp(lerp(at(seed,ix,iz),at(seed,ix+1,iz),u),lerp(at(seed,ix,iz+1),at(seed,ix+1,iz+1),u),v);
}
private static double lerp(double a,double b,double t){return a+(b-a)*t;}
}
@@ -0,0 +1,48 @@
package fr.koka.sanctuarytest;
import com.mojang.serialization.MapCodec;
import net.minecraft.resources.Identifier;
import net.minecraft.world.level.block.*;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.levelgen.RandomState;
import net.minecraft.world.level.levelgen.material.MaterialRuleContext;
import net.minecraft.world.level.levelgen.material.rule.*;
/** Continuous warped beds, evaluated natively during material generation. No second volume scan. */
public record EcologyStrata176() implements MaterialRule {
public static final MapCodec<EcologyStrata176> CODEC=MapCodec.unit(EcologyStrata176::new);
private static final Identifier SALT=Identifier.fromNamespaceAndPath("sanctuary_test","ecology_strata_v1");
public static long seed(RandomState random){return random.getOrCreateRandomFactory(SALT).at(0,0,0).nextLong();}
public static double warp(long seed,int x,int z){
return 25*EcologyBiomes176.noise(seed,x/135.0,z/135.0)
+7*EcologyBiomes176.noise(seed^0x57A7AL,x/43.0,z/43.0);
}
public static BlockState rock(double depth,int y){
if(y>=320){double bed=depth-Math.floor(depth/24)*24;
return (bed<3?Blocks.CALCITE:bed<9?Blocks.ANDESITE:Blocks.STONE).defaultBlockState();}
double bed=depth-Math.floor(depth/31)*31;
Block block;
if(depth<112)block=bed<3?Blocks.TUFF:Blocks.DEEPSLATE;
else if(depth<144)block=bed<12?Blocks.TUFF:Blocks.DEEPSLATE;
else if(depth<174)block=bed<18?Blocks.TUFF:Blocks.ANDESITE;
else block=bed<3?Blocks.CALCITE:bed<11?Blocks.ANDESITE:Blocks.STONE;
return block.defaultBlockState();
}
@Override public RuleEvaluator compile(MaterialRuleContext context){
long seed=context.getOrCreateRandomFactory(SALT).at(0,0,0).nextLong();
// Each evaluator belongs to a material context; the column cache has no shared mutable state.
return new RuleEvaluator(){
int lastX=Integer.MIN_VALUE,lastZ=Integer.MIN_VALUE;double displacement;
@Override public BlockState tryApply(int x,int y,int z){
if(x!=lastX || z!=lastZ){displacement=warp(seed,x,z);lastX=x;lastZ=z;}
if(y>=174 && context.stoneDepthAbove()<=3){
String biome=context.getBiome().unwrapKey().orElseThrow().identifier().getPath();
if(biome.equals("ecology_mangrove"))return Blocks.MUD.defaultBlockState();
return (context.stoneDepthAbove()==1?Blocks.GRASS_BLOCK:Blocks.DIRT).defaultBlockState();
}
return rock(y+displacement,y);
}
};
}
@Override public MapCodec<EcologyStrata176> codec(){return CODEC;}
}

Some files were not shown because too many files have changed in this diff Show More